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
@@ -1,279 +0,0 @@
1
- /**
2
- * Diagnostic Event Types for pi-lens
3
- *
4
- * Standardized events for the bus system.
5
- * These events flow through the system:
6
- * - Runners publish DiagnosticFound events
7
- * - LSP clients publish LspDiagnostic events
8
- * - Aggregators subscribe and build reports
9
- * - UI subscribes for real-time display
10
- */
11
-
12
- import { z } from "zod";
13
- import { BusEvent } from "./bus.js";
14
-
15
- // --- Shared Schemas ---
16
-
17
- export const DiagnosticSeverity = z.enum(["error", "warning", "info", "hint"]);
18
- export type DiagnosticSeverity = z.infer<typeof DiagnosticSeverity>;
19
-
20
- export const OutputSemantic = z.enum([
21
- "blocking", // Hard stop - must fix
22
- "warning", // Soft stop - should fix
23
- "fixed", // Auto-fix applied
24
- "silent", // Track but don't display
25
- "none", // No action needed
26
- ]);
27
- export type OutputSemantic = z.infer<typeof OutputSemantic>;
28
-
29
- export const DiagnosticSchema = z.object({
30
- id: z.string(), // Unique for deduplication
31
- message: z.string(),
32
- filePath: z.string(),
33
- line: z.number().optional(),
34
- column: z.number().optional(),
35
- severity: DiagnosticSeverity,
36
- semantic: OutputSemantic,
37
- tool: z.string(), // Which tool produced this
38
- rule: z.string().optional(),
39
- fixable: z.boolean().optional(),
40
- fixSuggestion: z.string().optional(),
41
- });
42
- export type Diagnostic = z.infer<typeof DiagnosticSchema>;
43
-
44
- export const RunnerStatus = z.enum([
45
- "starting",
46
- "running",
47
- "completed",
48
- "failed",
49
- "skipped",
50
- ]);
51
- export type RunnerStatus = z.infer<typeof RunnerStatus>;
52
-
53
- // --- Event Definitions ---
54
-
55
- /**
56
- * Fired when a runner discovers diagnostics
57
- * Published by: dispatch runners
58
- * Subscribed by: aggregators, delta tracker, UI
59
- */
60
- export const DiagnosticFound = BusEvent.define(
61
- "diagnostic.found",
62
- z.object({
63
- runnerId: z.string(),
64
- filePath: z.string(),
65
- diagnostics: z.array(DiagnosticSchema),
66
- durationMs: z.number(),
67
- }),
68
- );
69
-
70
- /**
71
- * Fired when a file is modified
72
- * Published by: tool_result handler, file watcher
73
- * Subscribed by: runner scheduler, cache invalidator
74
- */
75
- export const FileModified = BusEvent.define(
76
- "file.modified",
77
- z.object({
78
- filePath: z.string(),
79
- content: z.string().optional(),
80
- changeType: z.enum(["write", "edit", "external"]),
81
- }),
82
- );
83
-
84
- /**
85
- * Fired when a file is created
86
- * Published by: file watcher
87
- * Subscribed by: runner scheduler
88
- */
89
- export const FileCreated = BusEvent.define(
90
- "file.created",
91
- z.object({
92
- filePath: z.string(),
93
- }),
94
- );
95
-
96
- /**
97
- * Fired when a file is deleted
98
- * Published by: file watcher
99
- * Subscribed by: cache invalidator
100
- */
101
- export const FileDeleted = BusEvent.define(
102
- "file.deleted",
103
- z.object({
104
- filePath: z.string(),
105
- }),
106
- );
107
-
108
- /**
109
- * Fired when a runner starts execution
110
- * Published by: dispatcher
111
- * Subscribed by: progress UI, metrics
112
- */
113
- export const RunnerStarted = BusEvent.define(
114
- "runner.started",
115
- z.object({
116
- runnerId: z.string(),
117
- filePath: z.string(),
118
- timestamp: z.number(),
119
- }),
120
- );
121
-
122
- /**
123
- * Fired when a runner completes
124
- * Published by: dispatcher
125
- * Subscribed by: progress UI, metrics aggregator
126
- */
127
- export const RunnerCompleted = BusEvent.define(
128
- "runner.completed",
129
- z.object({
130
- runnerId: z.string(),
131
- filePath: z.string(),
132
- status: RunnerStatus,
133
- durationMs: z.number(),
134
- diagnosticCount: z.number(),
135
- }),
136
- );
137
-
138
- /**
139
- * Fired when LSP publishes diagnostics
140
- * Published by: LSPClient (via textDocument/publishDiagnostics)
141
- * Subscribed by: diagnostic aggregator
142
- */
143
- export const LspDiagnostic = BusEvent.define(
144
- "lsp.diagnostic",
145
- z.object({
146
- serverId: z.string(), // e.g., "typescript", "pyright"
147
- filePath: z.string(),
148
- diagnostics: z.array(z.object({
149
- severity: z.number(), // 1=error, 2=warn, 3=info, 4=hint
150
- message: z.string(),
151
- range: z.object({
152
- start: z.object({ line: z.number(), character: z.number() }),
153
- end: z.object({ line: z.number(), character: z.number() }),
154
- }),
155
- code: z.union([z.string(), z.number()]).optional(),
156
- source: z.string().optional(),
157
- })),
158
- version: z.number().optional(), // Document version for debouncing
159
- }),
160
- );
161
-
162
- /**
163
- * Fired when baseline is updated (for delta mode)
164
- * Published by: delta tracker
165
- * Subscribed by: diagnostic filter
166
- */
167
- export const BaselineUpdated = BusEvent.define(
168
- "baseline.updated",
169
- z.object({
170
- filePath: z.string(),
171
- diagnosticIds: z.array(z.string()),
172
- timestamp: z.number(),
173
- }),
174
- );
175
-
176
- /**
177
- * Fired when aggregated report is ready
178
- * Published by: report aggregator
179
- * Subscribed by: UI, commands
180
- */
181
- export const ReportReady = BusEvent.define(
182
- "report.ready",
183
- z.object({
184
- filePath: z.string(),
185
- report: z.object({
186
- blockers: z.array(DiagnosticSchema),
187
- warnings: z.array(DiagnosticSchema),
188
- fixed: z.array(DiagnosticSchema),
189
- silent: z.array(DiagnosticSchema),
190
- }),
191
- durationMs: z.number(),
192
- }),
193
- );
194
-
195
- /**
196
- * Fired when auto-fix is applied
197
- * Published by: autofix runner
198
- * Subscribed by: UI, file watcher
199
- */
200
- export const AutoFixApplied = BusEvent.define(
201
- "autofix.applied",
202
- z.object({
203
- filePath: z.string(),
204
- runnerId: z.string(),
205
- fixesApplied: z.number(),
206
- fixes: z.array(z.object({
207
- line: z.number(),
208
- message: z.string(),
209
- })),
210
- }),
211
- );
212
-
213
- /**
214
- * Fired when session starts
215
- * Published by: session_start handler
216
- * Subscribed by: cache manager, file watcher
217
- */
218
- export const SessionStarted = BusEvent.define(
219
- "session.started",
220
- z.object({
221
- cwd: z.string(),
222
- timestamp: z.number(),
223
- }),
224
- );
225
-
226
- /**
227
- * Fired when turn ends
228
- * Published by: turn_end handler
229
- * Subscribed by: batch processor, metrics
230
- */
231
- export const TurnEnded = BusEvent.define(
232
- "turn.ended",
233
- z.object({
234
- cwd: z.string(),
235
- modifiedFiles: z.array(z.string()),
236
- timestamp: z.number(),
237
- }),
238
- );
239
-
240
- // --- Event Aggregator Helper ---
241
-
242
- export class DiagnosticAggregator {
243
- private diagnostics = new Map<string, Diagnostic[]>();
244
- private unsubscribe: (() => void) | null = null;
245
-
246
- start() {
247
- this.unsubscribe = DiagnosticFound.subscribe((event) => {
248
- const { filePath, diagnostics } = event.properties;
249
- const existing = this.diagnostics.get(filePath) ?? [];
250
-
251
- // Merge and dedupe by id
252
- const merged = [...existing, ...diagnostics];
253
- const unique = new Map(merged.map(d => [d.id, d]));
254
-
255
- this.diagnostics.set(filePath, Array.from(unique.values()));
256
- });
257
- }
258
-
259
- stop() {
260
- this.unsubscribe?.();
261
- this.unsubscribe = null;
262
- }
263
-
264
- getForFile(filePath: string): Diagnostic[] {
265
- return this.diagnostics.get(filePath) ?? [];
266
- }
267
-
268
- getAll(): Map<string, Diagnostic[]> {
269
- return new Map(this.diagnostics);
270
- }
271
-
272
- clear(filePath?: string) {
273
- if (filePath) {
274
- this.diagnostics.delete(filePath);
275
- } else {
276
- this.diagnostics.clear();
277
- }
278
- }
279
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Bus Module - Event-driven architecture for pi-lens
3
- *
4
- * Export all bus-related functionality.
5
- */
6
- export * from "./bus.js";
7
- export * from "./events.js";
8
- export * from "./integration.js";
@@ -1,9 +0,0 @@
1
- /**
2
- * Bus Module - Event-driven architecture for pi-lens
3
- *
4
- * Export all bus-related functionality.
5
- */
6
-
7
- export * from "./bus.js";
8
- export * from "./events.js";
9
- export * from "./integration.js";
@@ -1,158 +0,0 @@
1
- /**
2
- * Bus Integration for pi-lens
3
- *
4
- * Connects the event bus system to the existing pi-lens architecture.
5
- * This provides:
6
- * - Event aggregation for diagnostic collection
7
- * - Real-time progress tracking
8
- * - Hook integration for tool_result handler
9
- */
10
- import { enableDebug } from "./bus.js";
11
- import { DiagnosticAggregator, FileModified, ReportReady, RunnerCompleted, RunnerStarted, } from "./events.js";
12
- const state = {
13
- aggregator: new DiagnosticAggregator(),
14
- runnerInProgress: new Set(),
15
- lastReport: new Map(),
16
- isEnabled: false,
17
- };
18
- // --- Event Subscribers ---
19
- let unsubscribers = [];
20
- /**
21
- * Initialize the bus integration
22
- * Call this from session_start handler
23
- */
24
- export function initBusIntegration(_pi, options) {
25
- if (state.isEnabled)
26
- return; // Already initialized
27
- if (options?.debug) {
28
- enableDebug(true);
29
- }
30
- // Start the diagnostic aggregator
31
- state.aggregator.start();
32
- // Subscribe to runner progress events for UI feedback
33
- const unsubRunnerStarted = RunnerStarted.subscribe((event) => {
34
- const { runnerId, filePath } = event.properties;
35
- state.runnerInProgress.add(`${runnerId}:${filePath}`);
36
- });
37
- const unsubRunnerCompleted = RunnerCompleted.subscribe((event) => {
38
- const { runnerId, filePath, durationMs, diagnosticCount } = event.properties;
39
- state.runnerInProgress.delete(`${runnerId}:${filePath}`);
40
- // Log slow runners in debug mode only
41
- if (options?.debug && durationMs > 5000) {
42
- console.error(`[bus] Slow runner: ${runnerId} took ${durationMs}ms for ${filePath}`);
43
- }
44
- // Log runners that found excessive issues (indicates rule misconfiguration)
45
- if (diagnosticCount > 100) {
46
- console.error(`[bus] ${runnerId} found ${diagnosticCount} issues in ${filePath} (rules may be too broad)`);
47
- }
48
- });
49
- // Cache reports for quick retrieval
50
- const unsubReportReady = ReportReady.subscribe((event) => {
51
- const { filePath, report, durationMs } = event.properties;
52
- // Store the report
53
- state.lastReport.set(filePath, {
54
- output: formatReport(report, durationMs),
55
- timestamp: Date.now(),
56
- });
57
- });
58
- // Track file modifications to clear stale data
59
- const unsubFileModified = FileModified.subscribe((event) => {
60
- const { filePath } = event.properties;
61
- // Clear cached report for modified file
62
- state.lastReport.delete(filePath);
63
- // Clear diagnostics aggregator for this file (will be repopulated)
64
- state.aggregator.clear(filePath);
65
- });
66
- // Store unsubscribers for cleanup
67
- unsubscribers = [
68
- unsubRunnerStarted,
69
- unsubRunnerCompleted,
70
- unsubReportReady,
71
- unsubFileModified,
72
- ];
73
- state.isEnabled = true;
74
- console.error("[pi-lens] Bus integration initialized");
75
- }
76
- /**
77
- * Shutdown the bus integration
78
- * Call this when the extension is disabled
79
- */
80
- export function shutdownBusIntegration() {
81
- if (!state.isEnabled)
82
- return;
83
- // Stop all subscribers
84
- for (const unsub of unsubscribers) {
85
- unsub();
86
- }
87
- unsubscribers = [];
88
- // Stop the aggregator
89
- state.aggregator.stop();
90
- // Clear all state
91
- state.runnerInProgress.clear();
92
- state.lastReport.clear();
93
- state.aggregator.clear();
94
- state.isEnabled = false;
95
- }
96
- // --- Helper Functions ---
97
- function formatReport(report, durationMs) {
98
- const lines = [];
99
- if (report.blockers.length > 0) {
100
- lines.push(`🔴 STOP — ${report.blockers.length} issue(s) must be fixed:`);
101
- for (const d of report.blockers.slice(0, 5)) {
102
- const line = d.line ? `L${d.line}: ` : "";
103
- lines.push(` ${line}${d.message.split("\n")[0]}`);
104
- }
105
- if (report.blockers.length > 5) {
106
- lines.push(` ... and ${report.blockers.length - 5} more`);
107
- }
108
- }
109
- if (report.fixed.length > 0) {
110
- lines.push(`✅ Auto-fixed ${report.fixed.length} issue(s)`);
111
- }
112
- if (lines.length > 0) {
113
- lines.push(`(completed in ${durationMs}ms)`);
114
- }
115
- return lines.join("\n");
116
- }
117
- // --- API for index.ts ---
118
- /**
119
- * Get aggregated diagnostics for a file
120
- */
121
- export function getDiagnosticsForFile(filePath) {
122
- return state.aggregator.getForFile(filePath);
123
- }
124
- /**
125
- * Get the last report output for a file
126
- */
127
- export function getLastReport(filePath) {
128
- return state.lastReport.get(filePath)?.output;
129
- }
130
- /**
131
- * Check if any runners are currently in progress for a file
132
- */
133
- export function hasRunnersInProgress(filePath) {
134
- if (filePath) {
135
- for (const key of state.runnerInProgress) {
136
- if (key.endsWith(`:${filePath}`))
137
- return true;
138
- }
139
- return false;
140
- }
141
- return state.runnerInProgress.size > 0;
142
- }
143
- /**
144
- * Get list of runners in progress
145
- */
146
- export function getRunnersInProgress() {
147
- return Array.from(state.runnerInProgress).map((key) => {
148
- const [runnerId, filePath] = key.split(":");
149
- return { runnerId, filePath };
150
- });
151
- }
152
- /**
153
- * Clear all cached data
154
- */
155
- export function clearBusCache() {
156
- state.lastReport.clear();
157
- state.aggregator.clear();
158
- }
@@ -1,227 +0,0 @@
1
- /**
2
- * Bus Integration for pi-lens
3
- *
4
- * Connects the event bus system to the existing pi-lens architecture.
5
- * This provides:
6
- * - Event aggregation for diagnostic collection
7
- * - Real-time progress tracking
8
- * - Hook integration for tool_result handler
9
- */
10
-
11
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
- import { enableDebug } from "./bus.js";
13
- import {
14
- type Diagnostic,
15
- DiagnosticAggregator,
16
- FileModified,
17
- ReportReady,
18
- RunnerCompleted,
19
- RunnerStarted,
20
- } from "./events.js";
21
-
22
- // --- Integration State ---
23
-
24
- interface IntegrationState {
25
- aggregator: DiagnosticAggregator;
26
- runnerInProgress: Set<string>; // runnerId:filePath
27
- lastReport: Map<string, { output: string; timestamp: number }>;
28
- isEnabled: boolean;
29
- }
30
-
31
- const state: IntegrationState = {
32
- aggregator: new DiagnosticAggregator(),
33
- runnerInProgress: new Set(),
34
- lastReport: new Map(),
35
- isEnabled: false,
36
- };
37
-
38
- // --- Event Subscribers ---
39
-
40
- let unsubscribers: Array<() => void> = [];
41
-
42
- /**
43
- * Initialize the bus integration
44
- * Call this from session_start handler
45
- */
46
- export function initBusIntegration(
47
- _pi: ExtensionAPI,
48
- options?: { debug?: boolean },
49
- ): void {
50
- if (state.isEnabled) return; // Already initialized
51
-
52
- if (options?.debug) {
53
- enableDebug(true);
54
- }
55
-
56
- // Start the diagnostic aggregator
57
- state.aggregator.start();
58
-
59
- // Subscribe to runner progress events for UI feedback
60
- const unsubRunnerStarted = RunnerStarted.subscribe((event) => {
61
- const { runnerId, filePath } = event.properties;
62
- state.runnerInProgress.add(`${runnerId}:${filePath}`);
63
- });
64
-
65
- const unsubRunnerCompleted = RunnerCompleted.subscribe((event) => {
66
- const { runnerId, filePath, durationMs, diagnosticCount } =
67
- event.properties;
68
- state.runnerInProgress.delete(`${runnerId}:${filePath}`);
69
-
70
- // Log slow runners in debug mode only
71
- if (options?.debug && durationMs > 5000) {
72
- console.error(
73
- `[bus] Slow runner: ${runnerId} took ${durationMs}ms for ${filePath}`,
74
- );
75
- }
76
-
77
- // Log runners that found excessive issues (indicates rule misconfiguration)
78
- if (diagnosticCount > 100) {
79
- console.error(
80
- `[bus] ${runnerId} found ${diagnosticCount} issues in ${filePath} (rules may be too broad)`,
81
- );
82
- }
83
- });
84
-
85
- // Cache reports for quick retrieval
86
- const unsubReportReady = ReportReady.subscribe((event) => {
87
- const { filePath, report, durationMs } = event.properties;
88
-
89
- // Store the report
90
- state.lastReport.set(filePath, {
91
- output: formatReport(report, durationMs),
92
- timestamp: Date.now(),
93
- });
94
- });
95
-
96
- // Track file modifications to clear stale data
97
- const unsubFileModified = FileModified.subscribe((event) => {
98
- const { filePath } = event.properties;
99
-
100
- // Clear cached report for modified file
101
- state.lastReport.delete(filePath);
102
-
103
- // Clear diagnostics aggregator for this file (will be repopulated)
104
- state.aggregator.clear(filePath);
105
- });
106
-
107
- // Store unsubscribers for cleanup
108
- unsubscribers = [
109
- unsubRunnerStarted,
110
- unsubRunnerCompleted,
111
- unsubReportReady,
112
- unsubFileModified,
113
- ];
114
-
115
- state.isEnabled = true;
116
-
117
- console.error("[pi-lens] Bus integration initialized");
118
- }
119
-
120
- /**
121
- * Shutdown the bus integration
122
- * Call this when the extension is disabled
123
- */
124
- export function shutdownBusIntegration(): void {
125
- if (!state.isEnabled) return;
126
-
127
- // Stop all subscribers
128
- for (const unsub of unsubscribers) {
129
- unsub();
130
- }
131
- unsubscribers = [];
132
-
133
- // Stop the aggregator
134
- state.aggregator.stop();
135
-
136
- // Clear all state
137
- state.runnerInProgress.clear();
138
- state.lastReport.clear();
139
- state.aggregator.clear();
140
-
141
- state.isEnabled = false;
142
- }
143
-
144
- // --- Helper Functions ---
145
-
146
- function formatReport(
147
- report: {
148
- blockers: Diagnostic[];
149
- warnings: Diagnostic[];
150
- fixed: Diagnostic[];
151
- silent: Diagnostic[];
152
- },
153
- durationMs: number,
154
- ): string {
155
- const lines: string[] = [];
156
-
157
- if (report.blockers.length > 0) {
158
- lines.push(`🔴 STOP — ${report.blockers.length} issue(s) must be fixed:`);
159
- for (const d of report.blockers.slice(0, 5)) {
160
- const line = d.line ? `L${d.line}: ` : "";
161
- lines.push(` ${line}${d.message.split("\n")[0]}`);
162
- }
163
- if (report.blockers.length > 5) {
164
- lines.push(` ... and ${report.blockers.length - 5} more`);
165
- }
166
- }
167
-
168
- if (report.fixed.length > 0) {
169
- lines.push(`✅ Auto-fixed ${report.fixed.length} issue(s)`);
170
- }
171
-
172
- if (lines.length > 0) {
173
- lines.push(`(completed in ${durationMs}ms)`);
174
- }
175
-
176
- return lines.join("\n");
177
- }
178
-
179
- // --- API for index.ts ---
180
-
181
- /**
182
- * Get aggregated diagnostics for a file
183
- */
184
- export function getDiagnosticsForFile(filePath: string): Diagnostic[] {
185
- return state.aggregator.getForFile(filePath);
186
- }
187
-
188
- /**
189
- * Get the last report output for a file
190
- */
191
- export function getLastReport(filePath: string): string | undefined {
192
- return state.lastReport.get(filePath)?.output;
193
- }
194
-
195
- /**
196
- * Check if any runners are currently in progress for a file
197
- */
198
- export function hasRunnersInProgress(filePath?: string): boolean {
199
- if (filePath) {
200
- for (const key of state.runnerInProgress) {
201
- if (key.endsWith(`:${filePath}`)) return true;
202
- }
203
- return false;
204
- }
205
- return state.runnerInProgress.size > 0;
206
- }
207
-
208
- /**
209
- * Get list of runners in progress
210
- */
211
- export function getRunnersInProgress(): Array<{
212
- runnerId: string;
213
- filePath: string;
214
- }> {
215
- return Array.from(state.runnerInProgress).map((key) => {
216
- const [runnerId, filePath] = key.split(":");
217
- return { runnerId, filePath };
218
- });
219
- }
220
-
221
- /**
222
- * Clear all cached data
223
- */
224
- export function clearBusCache(): void {
225
- state.lastReport.clear();
226
- state.aggregator.clear();
227
- }