pi-lens 3.2.0 → 3.3.1

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 (77) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -10
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/format-service.test.js +245 -0
  5. package/clients/__tests__/formatters.test.js +271 -0
  6. package/clients/agent-behavior-client.test.js +94 -0
  7. package/clients/biome-client.test.js +144 -0
  8. package/clients/cache-manager.test.js +197 -0
  9. package/clients/complexity-client.test.js +234 -0
  10. package/clients/dependency-checker.test.js +60 -0
  11. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  12. package/clients/dispatch/__tests__/runner-registration.test.js +234 -0
  13. package/clients/dispatch/__tests__/runner-registration.test.ts +2 -2
  14. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  15. package/clients/dispatch/dispatcher.format.test.js +46 -0
  16. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  17. package/clients/dispatch/dispatcher.test.js +116 -0
  18. package/clients/dispatch/runners/architect.test.js +138 -0
  19. package/clients/dispatch/runners/ast-grep-napi.test.js +106 -0
  20. package/clients/dispatch/runners/lsp.js +42 -5
  21. package/clients/dispatch/runners/oxlint.test.js +230 -0
  22. package/clients/dispatch/runners/pyright.test.js +98 -0
  23. package/clients/dispatch/runners/python-slop.test.js +203 -0
  24. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  25. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  26. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  27. package/clients/dispatch/utils/format-utils.js +1 -6
  28. package/clients/dispatch/utils/format-utils.ts +1 -6
  29. package/clients/dogfood.test.js +201 -0
  30. package/clients/file-kinds.test.js +169 -0
  31. package/clients/formatters.js +1 -1
  32. package/clients/go-client.test.js +127 -0
  33. package/clients/jscpd-client.test.js +127 -0
  34. package/clients/knip-client.test.js +112 -0
  35. package/clients/lsp/__tests__/client.test.js +310 -0
  36. package/clients/lsp/__tests__/client.test.ts +1 -46
  37. package/clients/lsp/__tests__/config.test.js +167 -0
  38. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  39. package/clients/lsp/__tests__/integration.test.js +127 -0
  40. package/clients/lsp/__tests__/launch.test.js +313 -0
  41. package/clients/lsp/__tests__/server.test.js +259 -0
  42. package/clients/lsp/__tests__/service.test.js +435 -0
  43. package/clients/lsp/client.js +32 -44
  44. package/clients/lsp/client.ts +36 -45
  45. package/clients/lsp/launch.js +11 -6
  46. package/clients/lsp/launch.ts +11 -6
  47. package/clients/lsp/server.js +27 -2
  48. package/clients/metrics-client.test.js +141 -0
  49. package/clients/ruff-client.test.js +132 -0
  50. package/clients/rust-client.test.js +108 -0
  51. package/clients/sanitize.test.js +177 -0
  52. package/clients/secrets-scanner.test.js +100 -0
  53. package/clients/test-runner-client.test.js +192 -0
  54. package/clients/todo-scanner.test.js +301 -0
  55. package/clients/type-coverage-client.test.js +105 -0
  56. package/clients/typescript-client.codefix.test.js +157 -0
  57. package/clients/typescript-client.test.js +105 -0
  58. package/commands/rate.test.js +119 -0
  59. package/index.ts +66 -72
  60. package/package.json +1 -1
  61. package/clients/bus/bus.js +0 -191
  62. package/clients/bus/bus.ts +0 -251
  63. package/clients/bus/events.js +0 -214
  64. package/clients/bus/events.ts +0 -279
  65. package/clients/bus/index.js +0 -8
  66. package/clients/bus/index.ts +0 -9
  67. package/clients/bus/integration.js +0 -158
  68. package/clients/bus/integration.ts +0 -227
  69. package/clients/dispatch/bus-dispatcher.js +0 -178
  70. package/clients/dispatch/bus-dispatcher.ts +0 -258
  71. package/clients/services/__tests__/effect-integration.test.ts +0 -111
  72. package/clients/services/effect-integration.js +0 -198
  73. package/clients/services/effect-integration.ts +0 -276
  74. package/clients/services/index.js +0 -7
  75. package/clients/services/index.ts +0 -8
  76. package/clients/services/runner-service.js +0 -134
  77. package/clients/services/runner-service.ts +0 -225
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Autofix Integration Tests
3
+ *
4
+ * Tests for auto-format and auto-fix behavior.
5
+ * Validates that autofix runs by default and can be disabled.
6
+ */
7
+ import { describe, expect, it, vi } from "vitest";
8
+ // --- Mock Runners ---
9
+ const createAutofixRunner = (id) => ({
10
+ id,
11
+ appliesTo: ["jsts"],
12
+ priority: 10,
13
+ enabledByDefault: true,
14
+ when: async (ctx) => ctx.autofix,
15
+ async run(ctx) {
16
+ return {
17
+ status: "succeeded",
18
+ diagnostics: [
19
+ {
20
+ id: `${id}:fixed`,
21
+ message: `Fixed by ${id}`,
22
+ filePath: ctx.filePath,
23
+ line: 1,
24
+ column: 1,
25
+ severity: "info",
26
+ semantic: "fixed",
27
+ tool: id,
28
+ },
29
+ ],
30
+ semantic: "fixed",
31
+ };
32
+ },
33
+ });
34
+ const createNonAutofixRunner = (id) => ({
35
+ id,
36
+ appliesTo: ["jsts"],
37
+ priority: 20,
38
+ enabledByDefault: true,
39
+ async run(ctx) {
40
+ return {
41
+ status: "succeeded",
42
+ diagnostics: [
43
+ {
44
+ id: `${id}:info`,
45
+ message: `Info from ${id}`,
46
+ filePath: ctx.filePath,
47
+ line: 1,
48
+ column: 1,
49
+ severity: "info",
50
+ semantic: "silent",
51
+ tool: id,
52
+ },
53
+ ],
54
+ semantic: "none",
55
+ };
56
+ },
57
+ });
58
+ const createMockContext = (filePath = "test.ts", autofix = true) => ({
59
+ filePath,
60
+ cwd: "/test",
61
+ kind: "jsts",
62
+ pi: {
63
+ getFlag: vi.fn((flag) => {
64
+ if (flag === "no-autofix")
65
+ return !autofix;
66
+ return autofix;
67
+ }),
68
+ },
69
+ autofix,
70
+ deltaMode: false,
71
+ baselines: new Map(),
72
+ hasTool: vi.fn(() => Promise.resolve(false)),
73
+ log: vi.fn(),
74
+ });
75
+ const _createMockGroup = (runners) => ({
76
+ runnerIds: runners.map((r) => r.id),
77
+ mode: "all",
78
+ });
79
+ // --- Tests ---
80
+ describe("Autofix Integration", () => {
81
+ describe("Autofix by Default", () => {
82
+ it("should run autofix runners when enabled", async () => {
83
+ const autofixRunner = createAutofixRunner("biome-fix");
84
+ const ctx = createMockContext("test.ts", true);
85
+ // The runner should apply (autofix is true)
86
+ const shouldRun = await autofixRunner.when?.(ctx);
87
+ expect(shouldRun).toBe(true);
88
+ });
89
+ it("should skip autofix runners when disabled", async () => {
90
+ const autofixRunner = createAutofixRunner("biome-fix");
91
+ const ctx = createMockContext("test.ts", false);
92
+ // The runner should not apply (autofix is false)
93
+ const shouldRun = await autofixRunner.when?.(ctx);
94
+ expect(shouldRun).toBe(false);
95
+ });
96
+ });
97
+ describe("--no-autofix Flag", () => {
98
+ it("should disable autofix with --no-autofix flag", () => {
99
+ const mockPi = {
100
+ getFlag: vi.fn((flag) => flag === "no-autofix"),
101
+ };
102
+ // Simulate flag check in dispatch context
103
+ const autofixEnabled = !mockPi.getFlag("no-autofix");
104
+ expect(autofixEnabled).toBe(false);
105
+ });
106
+ it("should enable autofix without --no-autofix flag", () => {
107
+ const mockPi = {
108
+ getFlag: vi.fn((_flag) => false),
109
+ };
110
+ const autofixEnabled = !mockPi.getFlag("no-autofix");
111
+ expect(autofixEnabled).toBe(true);
112
+ });
113
+ });
114
+ describe("File Modification Warnings", () => {
115
+ it("should track when format changes files", async () => {
116
+ // Simulate format result
117
+ const formatResult = {
118
+ formatChanged: true,
119
+ fixedCount: 0,
120
+ };
121
+ // Should show warning
122
+ const shouldWarn = formatResult.formatChanged || formatResult.fixedCount > 0;
123
+ expect(shouldWarn).toBe(true);
124
+ });
125
+ it("should track when autofix changes files", async () => {
126
+ // Simulate autofix result
127
+ const fixResult = {
128
+ formatChanged: false,
129
+ fixedCount: 3,
130
+ };
131
+ // Should show warning
132
+ const shouldWarn = fixResult.formatChanged || fixResult.fixedCount > 0;
133
+ expect(shouldWarn).toBe(true);
134
+ });
135
+ it("should show warning message format", () => {
136
+ const warningMessage = "āš ļø **File modified by auto-format/fix. Re-read before next edit.**";
137
+ expect(warningMessage).toContain("File modified");
138
+ expect(warningMessage).toContain("auto-format");
139
+ expect(warningMessage).toContain("fix");
140
+ });
141
+ });
142
+ describe("Runner Categories", () => {
143
+ it("should have autofix runners with when conditions", () => {
144
+ const autofixRunner = createAutofixRunner("ruff-fix");
145
+ expect(autofixRunner.when).toBeDefined();
146
+ expect(typeof autofixRunner.when).toBe("function");
147
+ });
148
+ it("should have non-autofix runners without when conditions", () => {
149
+ const regularRunner = createNonAutofixRunner("ts-lsp");
150
+ // Regular runners run regardless of autofix setting
151
+ expect(regularRunner.when).toBeUndefined();
152
+ });
153
+ it("should categorize fixed diagnostics correctly", async () => {
154
+ const autofixRunner = createAutofixRunner("biome-fix");
155
+ const ctx = createMockContext("test.ts", true);
156
+ const result = await autofixRunner.run(ctx);
157
+ // Should have semantic: "fixed"
158
+ expect(result.semantic).toBe("fixed");
159
+ // Diagnostics should also be fixed
160
+ for (const diag of result.diagnostics || []) {
161
+ expect(diag.semantic).toBe("fixed");
162
+ }
163
+ });
164
+ });
165
+ describe("Format vs Autofix Coordination", () => {
166
+ it("should run format before lint", async () => {
167
+ const order = [];
168
+ const formatRunner = {
169
+ id: "format",
170
+ appliesTo: ["jsts"],
171
+ priority: 5, // Lower = runs first
172
+ enabledByDefault: true,
173
+ async run() {
174
+ order.push("format");
175
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
176
+ },
177
+ };
178
+ const lintRunner = {
179
+ id: "lint",
180
+ appliesTo: ["jsts"],
181
+ priority: 10, // Higher = runs after
182
+ enabledByDefault: true,
183
+ async run() {
184
+ order.push("lint");
185
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
186
+ },
187
+ };
188
+ // Verify priority ordering
189
+ expect(formatRunner.priority).toBeLessThan(lintRunner.priority);
190
+ });
191
+ it("should handle format and lint race conditions", async () => {
192
+ // Simulate format changing file while lint is running
193
+ const _ctx = createMockContext("test.ts", true);
194
+ // Format result
195
+ const formatChanged = true;
196
+ // Lint should still work
197
+ const lintResult = {
198
+ status: "succeeded",
199
+ diagnostics: [],
200
+ semantic: "none",
201
+ };
202
+ // Both should complete without error
203
+ expect(formatChanged).toBe(true);
204
+ expect(lintResult.status).toBe("succeeded");
205
+ });
206
+ });
207
+ describe("Real-world Scenarios", () => {
208
+ it("should handle ruff autofix flow", async () => {
209
+ const ruffRunner = createAutofixRunner("ruff");
210
+ const ctxWithAutofix = createMockContext("test.py", true);
211
+ const ctxNoAutofix = createMockContext("test.py", false);
212
+ // With autofix
213
+ const shouldRunWith = await ruffRunner.when?.(ctxWithAutofix);
214
+ expect(shouldRunWith).toBe(true);
215
+ // Without autofix
216
+ const shouldRunWithout = await ruffRunner.when?.(ctxNoAutofix);
217
+ expect(shouldRunWithout).toBe(false);
218
+ });
219
+ it("should handle biome autofix flow", async () => {
220
+ const biomeRunner = createAutofixRunner("biome-fix");
221
+ const ctxWithAutofix = createMockContext("test.ts", true);
222
+ const ctxNoAutofix = createMockContext("test.ts", false);
223
+ // With autofix
224
+ const shouldRunWith = await biomeRunner.when?.(ctxWithAutofix);
225
+ expect(shouldRunWith).toBe(true);
226
+ // Without autofix
227
+ const shouldRunWithout = await biomeRunner.when?.(ctxNoAutofix);
228
+ expect(shouldRunWithout).toBe(false);
229
+ });
230
+ it("should show file modification warning only when changes made", async () => {
231
+ // No changes
232
+ const noChanges = { formatChanged: false, fixedCount: 0 };
233
+ expect(noChanges.formatChanged || noChanges.fixedCount > 0).toBe(false);
234
+ // Format only
235
+ const formatOnly = { formatChanged: true, fixedCount: 0 };
236
+ expect(formatOnly.formatChanged || formatOnly.fixedCount > 0).toBe(true);
237
+ // Fix only
238
+ const fixOnly = { formatChanged: false, fixedCount: 5 };
239
+ expect(fixOnly.formatChanged || fixOnly.fixedCount > 0).toBe(true);
240
+ // Both
241
+ const both = { formatChanged: true, fixedCount: 3 };
242
+ expect(both.formatChanged || both.fixedCount > 0).toBe(true);
243
+ });
244
+ });
245
+ });
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Runner Registration Verification Tests
3
+ *
4
+ * Ensures all runners are properly registered and unique.
5
+ * Catches issues like missing runner imports.
6
+ */
7
+ import { beforeAll, describe, expect, it } from "vitest";
8
+ import { clearRunnerRegistry, getRunner, getRunnersForKind, listRunners, } from "../dispatcher.js";
9
+ describe("Runner Registration", () => {
10
+ let allRunners;
11
+ beforeAll(async () => {
12
+ // Clear any existing registrations for clean slate
13
+ clearRunnerRegistry();
14
+ // Import runners to trigger registration
15
+ // This is the critical import that ensures runners are registered before dispatch
16
+ await import("../runners/index.js");
17
+ // Get all registered runners
18
+ allRunners = listRunners();
19
+ });
20
+ describe("Basic Registration", () => {
21
+ it("should have runners registered", () => {
22
+ expect(allRunners.length).toBeGreaterThan(0);
23
+ });
24
+ it("should have unique runner IDs", () => {
25
+ const ids = allRunners.map((r) => r.id);
26
+ const uniqueIds = new Set(ids);
27
+ // All IDs should be unique
28
+ expect(uniqueIds.size).toBe(ids.length);
29
+ });
30
+ it("should be able to retrieve any registered runner by ID", () => {
31
+ for (const runner of allRunners) {
32
+ // Skip disabled runners (those with no appliesTo)
33
+ if (!(runner.appliesTo?.length ?? 0))
34
+ continue;
35
+ const retrieved = getRunner(runner.id);
36
+ expect(retrieved).toBeDefined();
37
+ expect(retrieved?.id).toBe(runner.id);
38
+ }
39
+ });
40
+ it("should return undefined for unknown runner IDs", () => {
41
+ const unknown = getRunner("definitely-not-a-real-runner-id");
42
+ expect(unknown).toBeUndefined();
43
+ });
44
+ });
45
+ describe("Runner Properties", () => {
46
+ it("should have valid appliesTo for all runners", () => {
47
+ const validKinds = [
48
+ "jsts",
49
+ "python",
50
+ "rust",
51
+ "go",
52
+ "shell",
53
+ "json",
54
+ "markdown",
55
+ "cmake",
56
+ "cxx",
57
+ ];
58
+ for (const runner of allRunners) {
59
+ // Skip disabled runners (those with no appliesTo)
60
+ if (!(runner.appliesTo?.length ?? 0))
61
+ continue;
62
+ // Each runner should have at least one appliesTo
63
+ if (!(runner.appliesTo?.length ?? 0)) {
64
+ console.error(`Runner ${runner.id} has no appliesTo`);
65
+ }
66
+ expect(runner.appliesTo?.length ?? 0, `Runner ${runner.id} should have appliesTo`).toBeGreaterThan(0);
67
+ // All appliesTo should be valid kinds
68
+ for (const kind of runner.appliesTo) {
69
+ expect(validKinds).toContain(kind);
70
+ }
71
+ }
72
+ });
73
+ it("should have priority defined", () => {
74
+ for (const runner of allRunners) {
75
+ // Skip disabled runners (those with no appliesTo)
76
+ if (!(runner.appliesTo?.length ?? 0))
77
+ continue;
78
+ // Priority should be a number (or undefined, which defaults to 100)
79
+ if (runner.priority !== undefined) {
80
+ expect(typeof runner.priority).toBe("number");
81
+ expect(runner.priority).toBeGreaterThanOrEqual(0);
82
+ }
83
+ }
84
+ });
85
+ it("should have enabledByDefault boolean", () => {
86
+ for (const runner of allRunners) {
87
+ // Skip disabled runners (those with no appliesTo)
88
+ if (!(runner.appliesTo?.length ?? 0))
89
+ continue;
90
+ expect(typeof runner.enabledByDefault).toBe("boolean");
91
+ }
92
+ });
93
+ it("should have a run function", () => {
94
+ for (const runner of allRunners) {
95
+ // Skip disabled runners (those with no appliesTo)
96
+ if (!(runner.appliesTo?.length ?? 0))
97
+ continue;
98
+ expect(typeof runner.run).toBe("function");
99
+ }
100
+ });
101
+ });
102
+ describe("Expected Runners", () => {
103
+ const expectedRunners = [
104
+ "ts-lsp",
105
+ "ts-slop",
106
+ "pyright",
107
+ "python-slop",
108
+ "biome-lint",
109
+ "oxlint",
110
+ "ruff-lint",
111
+ "shellcheck",
112
+ "spellcheck",
113
+ "ast-grep-napi",
114
+ "architect",
115
+ "config-validation",
116
+ ];
117
+ it("should have all expected critical runners", () => {
118
+ const registeredIds = allRunners.map((r) => r.id);
119
+ for (const expectedId of expectedRunners) {
120
+ expect(registeredIds).toContain(expectedId);
121
+ }
122
+ });
123
+ it("should have TypeScript-related runners", () => {
124
+ const tsRunners = getRunnersForKind("jsts");
125
+ const tsIds = tsRunners.map((r) => r.id);
126
+ // Should have at least ts-lsp
127
+ expect(tsIds).toContain("ts-lsp");
128
+ // Should have ts-slop
129
+ expect(tsIds).toContain("ts-slop");
130
+ });
131
+ it("should have Python-related runners", () => {
132
+ const pyRunners = getRunnersForKind("python");
133
+ const pyIds = pyRunners.map((r) => r.id);
134
+ // Should have pyright
135
+ expect(pyIds).toContain("pyright");
136
+ // Should have python-slop
137
+ expect(pyIds).toContain("python-slop");
138
+ });
139
+ it("should have lint runners", () => {
140
+ const jstsRunners = getRunnersForKind("jsts");
141
+ const lintIds = ["biome-lint", "oxlint", "ts-slop"];
142
+ for (const lintId of lintIds) {
143
+ // At least one should be present
144
+ const hasLintRunner = jstsRunners.some((r) => r.id === lintId);
145
+ if (hasLintRunner) {
146
+ // Found at least one
147
+ expect(hasLintRunner).toBe(true);
148
+ break;
149
+ }
150
+ }
151
+ });
152
+ it("should have format runners", () => {
153
+ const jstsRunners = getRunnersForKind("jsts");
154
+ const formatIds = ["biome-lint"];
155
+ for (const formatId of formatIds) {
156
+ const hasFormatRunner = jstsRunners.some((r) => r.id === formatId);
157
+ if (hasFormatRunner) {
158
+ expect(hasFormatRunner).toBe(true);
159
+ break;
160
+ }
161
+ }
162
+ });
163
+ });
164
+ describe("Runner Import Verification", () => {
165
+ it("should load runner index without errors", async () => {
166
+ // This catches the bug where runners weren't imported
167
+ // in the dispatch system
168
+ expect(async () => {
169
+ await import("../runners/index.js");
170
+ }).not.toThrow();
171
+ });
172
+ it("should have runners available after import", async () => {
173
+ // Clear and re-import to verify fresh load
174
+ const initialCount = listRunners().length;
175
+ // Import again - should not duplicate due to id check
176
+ await import("../runners/index.js");
177
+ const finalCount = listRunners().length;
178
+ // Should be same count (no duplicates)
179
+ expect(finalCount).toBe(initialCount);
180
+ });
181
+ });
182
+ describe("Runner Condition Functions", () => {
183
+ it("should handle runners with when conditions", () => {
184
+ const runnersWithWhen = allRunners.filter((r) => r.when !== undefined);
185
+ for (const runner of runnersWithWhen) {
186
+ // when should be a function
187
+ expect(typeof runner.when).toBe("function");
188
+ }
189
+ });
190
+ it("should evaluate when conditions correctly", async () => {
191
+ // Find a runner with a when condition (e.g., autofix runners)
192
+ const conditionalRunner = allRunners.find((r) => r.when !== undefined);
193
+ if (conditionalRunner) {
194
+ // Create mock contexts
195
+ const ctxWithAutofix = {
196
+ autofix: true,
197
+ filePath: "test.ts",
198
+ cwd: "/test",
199
+ kind: "jsts",
200
+ pi: { getFlag: () => false },
201
+ deltaMode: false,
202
+ baselines: new Map(),
203
+ hasTool: async () => false,
204
+ log: () => { },
205
+ };
206
+ const ctxWithoutAutofix = {
207
+ ...ctxWithAutofix,
208
+ autofix: false,
209
+ };
210
+ // Evaluate condition
211
+ const shouldRunWith = await conditionalRunner.when?.(ctxWithAutofix);
212
+ const shouldRunWithout = await conditionalRunner.when?.(ctxWithoutAutofix);
213
+ // Results should be boolean
214
+ expect(typeof shouldRunWith).toBe("boolean");
215
+ expect(typeof shouldRunWithout).toBe("boolean");
216
+ }
217
+ });
218
+ });
219
+ describe("Priority Ordering", () => {
220
+ it("should return runners sorted by priority", () => {
221
+ const kinds = ["jsts", "python", "rust", "go"];
222
+ for (const kind of kinds) {
223
+ const runners = getRunnersForKind(kind);
224
+ if (runners.length > 1) {
225
+ const priorities = runners.map((r) => r.priority ?? 100);
226
+ // Should be sorted ascending
227
+ for (let i = 1; i < priorities.length; i++) {
228
+ expect(priorities[i - 1]).toBeLessThanOrEqual(priorities[i]);
229
+ }
230
+ }
231
+ }
232
+ });
233
+ });
234
+ });
@@ -23,7 +23,7 @@ describe("Runner Registration", () => {
23
23
  clearRunnerRegistry();
24
24
 
25
25
  // Import runners to trigger registration
26
- // This is the critical import that was missing in the effect-integration bug
26
+ // This is the critical import that ensures runners are registered before dispatch
27
27
  await import("../runners/index.js");
28
28
 
29
29
  // Get all registered runners
@@ -200,7 +200,7 @@ describe("Runner Registration", () => {
200
200
  describe("Runner Import Verification", () => {
201
201
  it("should load runner index without errors", async () => {
202
202
  // This catches the bug where runners weren't imported
203
- // in effect-integration.ts and bus-dispatcher.ts
203
+ // in the dispatch system
204
204
  expect(async () => {
205
205
  await import("../runners/index.js");
206
206
  }).not.toThrow();
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatDiagnostic } from "./utils/format-utils.js";
3
+ describe("formatDiagnostic edge cases", () => {
4
+ it("should handle messages with colons correctly", () => {
5
+ // This tests the actual format of inline code fix messages
6
+ const diagnostic = {
7
+ id: "architect-1",
8
+ // This is the format of architect warnings - starts with "No "
9
+ message: "No absolute Windows paths — breaks CI and cross-platform builds.",
10
+ filePath: "/test.ts",
11
+ line: 10,
12
+ severity: "warning",
13
+ semantic: "warning",
14
+ tool: "architect",
15
+ rule: "no-absolute-paths",
16
+ };
17
+ const formatted = formatDiagnostic(diagnostic);
18
+ // Should show the complete message, not cut off at "No "
19
+ expect(formatted).toContain("No absolute Windows paths");
20
+ expect(formatted).toContain("breaks CI and cross-platform builds");
21
+ expect(formatted).not.toBe(" L10: No "); // Should NOT be truncated
22
+ });
23
+ it("should handle code fix messages with newlines", () => {
24
+ const diagnostic = {
25
+ id: "ts-12-2345",
26
+ // This is the actual format from ts-lsp runner
27
+ message: "Property 'debug' is missing in type 'Config'\nšŸ’” Quick fix: Add missing property 'debug'",
28
+ filePath: "/test.ts",
29
+ line: 12,
30
+ severity: "error",
31
+ semantic: "blocking",
32
+ tool: "ts-lsp",
33
+ rule: "TS2345",
34
+ };
35
+ const formatted = formatDiagnostic(diagnostic);
36
+ // Should have both lines properly indented
37
+ expect(formatted).toBe(" L12: Property 'debug' is missing in type 'Config'\n šŸ’” Quick fix: Add missing property 'debug'");
38
+ });
39
+ it("should handle messages with em-dashes (—)", () => {
40
+ const diagnostic = {
41
+ id: "architect-2",
42
+ message: "No hardcoded secrets — use environment variables or a secrets manager.",
43
+ filePath: "/test.ts",
44
+ line: 5,
45
+ severity: "warning",
46
+ semantic: "warning",
47
+ tool: "architect",
48
+ rule: "no-secrets",
49
+ };
50
+ const formatted = formatDiagnostic(diagnostic);
51
+ // Should preserve the full message with em-dash
52
+ expect(formatted).toContain("No hardcoded secrets");
53
+ expect(formatted).toContain("use environment variables");
54
+ });
55
+ it("should not truncate architect 'No ' messages", () => {
56
+ // Testing all the "No " patterns from default-architect.yaml
57
+ const testMessages = [
58
+ "No absolute Windows paths — breaks CI and cross-platform builds.",
59
+ "No hardcoded localhost URLs — use environment variables or a config service.",
60
+ "No empty catch/except blocks. Swallowing errors makes debugging impossible — at least log the error.",
61
+ "No hardcoded secrets — use environment variables or a secrets manager.",
62
+ "No 'any' types — use 'unknown' or define a proper interface to maintain type safety.",
63
+ ];
64
+ for (const message of testMessages) {
65
+ const diagnostic = {
66
+ id: "test-1",
67
+ message,
68
+ filePath: "/test.ts",
69
+ line: 1,
70
+ severity: "warning",
71
+ semantic: "warning",
72
+ tool: "architect",
73
+ rule: "test",
74
+ };
75
+ const formatted = formatDiagnostic(diagnostic);
76
+ // Each formatted message should be more than just " L1: No "
77
+ expect(formatted.length).toBeGreaterThan(10);
78
+ expect(formatted).toContain("No ");
79
+ expect(formatted).toContain("—"); // Should have the em-dash
80
+ }
81
+ });
82
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatDiagnostic } from "./utils/format-utils.js";
3
+ describe("formatDiagnostic with code fixes", () => {
4
+ it("should format multi-line messages with proper indentation", () => {
5
+ const diagnostic = {
6
+ id: "ts-12-2345",
7
+ message: "Property 'debug' is missing in type 'Config'\nšŸ’” Quick fix: Add missing property 'debug'",
8
+ filePath: "/test.ts",
9
+ line: 12,
10
+ severity: "error",
11
+ semantic: "blocking",
12
+ tool: "ts-lsp",
13
+ rule: "TS2345",
14
+ };
15
+ const formatted = formatDiagnostic(diagnostic);
16
+ // Should have proper indentation on both lines
17
+ expect(formatted).toBe(" L12: Property 'debug' is missing in type 'Config'\n šŸ’” Quick fix: Add missing property 'debug'");
18
+ });
19
+ it("should format single-line messages", () => {
20
+ const diagnostic = {
21
+ id: "ts-5-1234",
22
+ message: "Cannot find name 'foo'",
23
+ filePath: "/test.ts",
24
+ line: 5,
25
+ severity: "error",
26
+ semantic: "blocking",
27
+ tool: "ts-lsp",
28
+ rule: "TS1234",
29
+ };
30
+ const formatted = formatDiagnostic(diagnostic);
31
+ expect(formatted).toBe(" L5: Cannot find name 'foo'");
32
+ });
33
+ it("should handle diagnostics without line numbers", () => {
34
+ const diagnostic = {
35
+ id: "test-1",
36
+ message: "General error\nšŸ’” Fix: Do something",
37
+ filePath: "/test.ts",
38
+ severity: "warning",
39
+ semantic: "warning",
40
+ tool: "test",
41
+ rule: "test",
42
+ };
43
+ const formatted = formatDiagnostic(diagnostic);
44
+ expect(formatted).toBe(" General error\n šŸ’” Fix: Do something");
45
+ });
46
+ });