pi-lens 3.8.18 → 3.8.21
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 +31 -0
- package/README.md +26 -17
- package/clients/dispatch/dispatcher.ts +53 -1
- package/clients/dispatch/integration.ts +37 -26
- package/clients/dispatch/plan.ts +26 -15
- package/clients/dispatch/runners/lsp.ts +6 -1
- package/clients/dispatch/runners/pyright.ts +4 -6
- package/clients/dispatch/runners/ruff.ts +52 -7
- package/clients/dispatch/runners/sqlfluff.ts +48 -1
- package/clients/dispatch/runners/yamllint.ts +50 -0
- package/clients/file-utils.ts +13 -2
- package/clients/formatters.ts +8 -4
- package/clients/installer/index.ts +371 -49
- package/clients/language-policy.ts +154 -0
- package/clients/language-profile.ts +167 -0
- package/clients/lsp/index.ts +81 -11
- package/clients/lsp/interactive-install.ts +35 -16
- package/clients/lsp/server.ts +357 -267
- package/clients/pipeline.ts +71 -40
- package/clients/runtime-context.ts +26 -0
- package/clients/runtime-coordinator.ts +30 -2
- package/clients/runtime-session.ts +293 -103
- package/clients/runtime-tool-result.ts +8 -10
- package/clients/runtime-turn.ts +21 -4
- package/clients/todo-scanner.ts +6 -1
- package/clients/type-coverage-client.ts +1 -1
- package/commands/booboo.ts +3 -1
- package/index.ts +15 -3
- package/package.json +1 -1
package/clients/pipeline.ts
CHANGED
|
@@ -18,24 +18,30 @@ import type { BiomeClient } from "./biome-client.js";
|
|
|
18
18
|
import { getDiagnosticLogger } from "./diagnostic-logger.js";
|
|
19
19
|
import { getDiagnosticTracker } from "./diagnostic-tracker.js";
|
|
20
20
|
import { dispatchLintWithResult } from "./dispatch/integration.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveRunnerPath,
|
|
23
|
+
toRunnerDisplayPath,
|
|
24
|
+
} from "./dispatch/runner-context.js";
|
|
21
25
|
import type { PiAgentAPI } from "./dispatch/types.js";
|
|
22
26
|
import { detectFileKind, getFileKindLabel } from "./file-kinds.js";
|
|
23
27
|
import type { FormatService } from "./format-service.js";
|
|
24
28
|
import { logLatency } from "./latency-logger.js";
|
|
25
29
|
import { getLSPService } from "./lsp/index.js";
|
|
26
30
|
import type { MetricsClient } from "./metrics-client.js";
|
|
31
|
+
import { normalizeMapKey } from "./path-utils.js";
|
|
27
32
|
import type { RuffClient } from "./ruff-client.js";
|
|
28
33
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
29
34
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
30
35
|
import { formatSecrets, scanForSecrets } from "./secrets-scanner.js";
|
|
31
36
|
import type { TestRunnerClient } from "./test-runner-client.js";
|
|
32
|
-
import { normalizeMapKey } from "./path-utils.js";
|
|
33
|
-
import { resolveRunnerPath, toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
34
37
|
|
|
35
38
|
const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
36
39
|
const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
|
|
37
40
|
|
|
38
|
-
function exceedsLspSyncLimits(
|
|
41
|
+
function exceedsLspSyncLimits(
|
|
42
|
+
filePath: string,
|
|
43
|
+
content: string,
|
|
44
|
+
): {
|
|
39
45
|
tooLarge: boolean;
|
|
40
46
|
reason: string;
|
|
41
47
|
} {
|
|
@@ -140,6 +146,8 @@ function createPhaseTracker(toolName: string, filePath: string): PhaseTracker {
|
|
|
140
146
|
|
|
141
147
|
// --- ESLint autofix helpers ---
|
|
142
148
|
|
|
149
|
+
const BIOME_CONFIGS = ["biome.json", "biome.jsonc"];
|
|
150
|
+
|
|
143
151
|
const ESLINT_CONFIGS = [
|
|
144
152
|
".eslintrc",
|
|
145
153
|
".eslintrc.js",
|
|
@@ -158,6 +166,19 @@ function isJsTs(filePath: string): boolean {
|
|
|
158
166
|
return JSTS_EXTS.has(path.extname(filePath).toLowerCase());
|
|
159
167
|
}
|
|
160
168
|
|
|
169
|
+
function hasBiomeConfig(cwd: string): boolean {
|
|
170
|
+
for (const cfg of BIOME_CONFIGS) {
|
|
171
|
+
if (nodeFs.existsSync(path.join(cwd, cfg))) return true;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const pkg = JSON.parse(
|
|
175
|
+
nodeFs.readFileSync(path.join(cwd, "package.json"), "utf-8"),
|
|
176
|
+
);
|
|
177
|
+
if (pkg.devDependencies?.["@biomejs/biome"]) return true;
|
|
178
|
+
} catch {}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
161
182
|
function hasEslintConfig(cwd: string): boolean {
|
|
162
183
|
for (const cfg of ESLINT_CONFIGS) {
|
|
163
184
|
if (nodeFs.existsSync(path.join(cwd, cfg))) return true;
|
|
@@ -171,7 +192,10 @@ function hasEslintConfig(cwd: string): boolean {
|
|
|
171
192
|
return false;
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
const _eslintCache = new Map<
|
|
195
|
+
const _eslintCache = new Map<
|
|
196
|
+
string,
|
|
197
|
+
{ available: boolean; bin: string | null }
|
|
198
|
+
>();
|
|
175
199
|
|
|
176
200
|
function findEslintBin(cwd: string): string {
|
|
177
201
|
const isWin = process.platform === "win32";
|
|
@@ -331,7 +355,7 @@ export async function runPipeline(
|
|
|
331
355
|
const deferLspSync =
|
|
332
356
|
!getFlag("no-autofix") &&
|
|
333
357
|
(ruffClient.isPythonFile(filePath) ||
|
|
334
|
-
biomeClient.isSupportedFile(filePath) ||
|
|
358
|
+
(biomeClient.isSupportedFile(filePath) && hasBiomeConfig(cwd)) ||
|
|
335
359
|
isJsTs(filePath));
|
|
336
360
|
|
|
337
361
|
if (deferLspSync) {
|
|
@@ -386,7 +410,9 @@ export async function runPipeline(
|
|
|
386
410
|
!noAutofixRuff && ruffClient.isPythonFile(filePath)
|
|
387
411
|
? ruffClient.ensureAvailable()
|
|
388
412
|
: Promise.resolve(false),
|
|
389
|
-
!noAutofixBiome &&
|
|
413
|
+
!noAutofixBiome &&
|
|
414
|
+
biomeClient.isSupportedFile(filePath) &&
|
|
415
|
+
hasBiomeConfig(cwd)
|
|
390
416
|
? biomeClient.ensureAvailable()
|
|
391
417
|
: Promise.resolve(false),
|
|
392
418
|
]);
|
|
@@ -494,12 +520,16 @@ export async function runPipeline(
|
|
|
494
520
|
);
|
|
495
521
|
for (const d of dispatchResult.diagnostics) {
|
|
496
522
|
const shownInline = inlineKeys.has(toKey(d));
|
|
497
|
-
logger.logCaught(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
523
|
+
logger.logCaught(
|
|
524
|
+
d,
|
|
525
|
+
{
|
|
526
|
+
model: ctx.telemetry?.model ?? "unknown",
|
|
527
|
+
sessionId: ctx.telemetry?.sessionId ?? "unknown",
|
|
528
|
+
turnIndex: ctx.telemetry?.turnIndex ?? 0,
|
|
529
|
+
writeIndex: ctx.telemetry?.writeIndex ?? 0,
|
|
530
|
+
},
|
|
531
|
+
shownInline,
|
|
532
|
+
);
|
|
503
533
|
}
|
|
504
534
|
}
|
|
505
535
|
|
|
@@ -552,34 +582,34 @@ export async function runPipeline(
|
|
|
552
582
|
target.runner,
|
|
553
583
|
target.config,
|
|
554
584
|
);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
585
|
+
const testDuration = Date.now() - testStart;
|
|
586
|
+
logLatency({
|
|
587
|
+
type: "phase",
|
|
588
|
+
toolName,
|
|
589
|
+
filePath,
|
|
590
|
+
phase: "test_runner",
|
|
591
|
+
durationMs: testDuration,
|
|
592
|
+
metadata: {
|
|
593
|
+
testFile: target.testFile,
|
|
594
|
+
runner: target.runner,
|
|
595
|
+
strategy: target.strategy,
|
|
596
|
+
success: !testResult?.error,
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
if (testResult && !testResult.error) {
|
|
600
|
+
testSummary = {
|
|
601
|
+
passed: testResult.passed,
|
|
602
|
+
total: testResult.passed + testResult.failed + testResult.skipped,
|
|
603
|
+
failed: testResult.failed,
|
|
604
|
+
};
|
|
605
|
+
if (testSummary.failed > 0) {
|
|
606
|
+
hasBlockers = true;
|
|
607
|
+
}
|
|
608
|
+
const testOutput = testRunnerClient.formatResult(testResult);
|
|
609
|
+
if (testOutput) {
|
|
610
|
+
output += `\n\n${testOutput}`;
|
|
582
611
|
}
|
|
612
|
+
}
|
|
583
613
|
}
|
|
584
614
|
}
|
|
585
615
|
phase.end("test_runner", { found: testInfoFound, ran: testRunnerRan });
|
|
@@ -608,7 +638,8 @@ export async function runPipeline(
|
|
|
608
638
|
|
|
609
639
|
for (const [diagPath, diags] of allDiags) {
|
|
610
640
|
const normalizedDiagPath = resolveRunnerPath(cwd, diagPath);
|
|
611
|
-
if (normalizeMapKey(normalizedDiagPath) === normalizedEditedPath)
|
|
641
|
+
if (normalizeMapKey(normalizedDiagPath) === normalizedEditedPath)
|
|
642
|
+
continue;
|
|
612
643
|
|
|
613
644
|
if (!nodeFs.existsSync(normalizedDiagPath)) {
|
|
614
645
|
stalePathsSkipped++;
|
|
@@ -25,3 +25,29 @@ export function consumeTurnEndFindings(
|
|
|
25
25
|
],
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export function consumeSessionStartGuidance(
|
|
30
|
+
cacheManager: CacheManager,
|
|
31
|
+
cwd: string,
|
|
32
|
+
): { messages: Array<{ role: "system"; content: string }> } | undefined {
|
|
33
|
+
const guidance = cacheManager.readCache<{ content: string }>(
|
|
34
|
+
"session-start-guidance",
|
|
35
|
+
cwd,
|
|
36
|
+
);
|
|
37
|
+
if (!guidance?.data?.content) return;
|
|
38
|
+
|
|
39
|
+
cacheManager.writeCache(
|
|
40
|
+
"session-start-guidance",
|
|
41
|
+
null as unknown as { content: string },
|
|
42
|
+
cwd,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
messages: [
|
|
47
|
+
{
|
|
48
|
+
role: "system",
|
|
49
|
+
content: `[pi-lens] Session guidance:\n\n${guidance.data.content}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { FileComplexity } from "./complexity-client.js";
|
|
|
3
3
|
import type { RuleScanResult } from "./rules-scanner.js";
|
|
4
4
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
5
5
|
import type { ProjectIndex } from "./project-index.js";
|
|
6
|
+
import { normalizeMapKey } from "./path-utils.js";
|
|
6
7
|
|
|
7
8
|
export interface ErrorDebtBaseline {
|
|
8
9
|
testsPassed: boolean;
|
|
@@ -10,11 +11,13 @@ export interface ErrorDebtBaseline {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export class RuntimeCoordinator {
|
|
13
|
-
private _projectRoot = process.cwd();
|
|
14
|
+
private _projectRoot = normalizeMapKey(process.cwd());
|
|
15
|
+
private _sessionGeneration = 0;
|
|
14
16
|
private _errorDebtBaseline: ErrorDebtBaseline | null = null;
|
|
15
17
|
private _pipelineCrashCounts = new Map<string, number>();
|
|
16
18
|
private _cachedExports = new Map<string, string>();
|
|
17
19
|
private _cachedProjectIndex: ProjectIndex | null = null;
|
|
20
|
+
private _startupScansInFlight = new Map<string, number>();
|
|
18
21
|
private _lastCascadeOutput = "";
|
|
19
22
|
private _complexityBaselines = new Map<string, FileComplexity>();
|
|
20
23
|
private _fixedThisTurn = new Set<string>();
|
|
@@ -30,10 +33,12 @@ export class RuntimeCoordinator {
|
|
|
30
33
|
private _gitGuardSummary = "";
|
|
31
34
|
|
|
32
35
|
resetForSession(): void {
|
|
36
|
+
this._sessionGeneration += 1;
|
|
33
37
|
this._complexityBaselines.clear();
|
|
34
38
|
this._pipelineCrashCounts.clear();
|
|
35
39
|
this._cachedExports.clear();
|
|
36
40
|
this._cachedProjectIndex = null;
|
|
41
|
+
this._startupScansInFlight.clear();
|
|
37
42
|
this._lastCascadeOutput = "";
|
|
38
43
|
this._fixedThisTurn.clear();
|
|
39
44
|
this._telemetrySessionId =
|
|
@@ -111,6 +116,29 @@ export class RuntimeCoordinator {
|
|
|
111
116
|
return this._turnIndex;
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
get sessionGeneration(): number {
|
|
120
|
+
return this._sessionGeneration;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isCurrentSession(generation: number): boolean {
|
|
124
|
+
return this._sessionGeneration === generation;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
markStartupScanInFlight(name: string, generation: number): void {
|
|
128
|
+
this._startupScansInFlight.set(name, generation);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
clearStartupScanInFlight(name: string, generation: number): void {
|
|
132
|
+
const owner = this._startupScansInFlight.get(name);
|
|
133
|
+
if (owner === generation) {
|
|
134
|
+
this._startupScansInFlight.delete(name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
isStartupScanInFlight(name: string): boolean {
|
|
139
|
+
return this._startupScansInFlight.has(name);
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
formatPipelineCrashNotice(filePath: string, err: unknown): string {
|
|
115
143
|
const key = path.resolve(filePath);
|
|
116
144
|
const count = (this._pipelineCrashCounts.get(key) ?? 0) + 1;
|
|
@@ -140,7 +168,7 @@ export class RuntimeCoordinator {
|
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
set projectRoot(value: string) {
|
|
143
|
-
this._projectRoot = value;
|
|
171
|
+
this._projectRoot = normalizeMapKey(value);
|
|
144
172
|
}
|
|
145
173
|
|
|
146
174
|
get errorDebtBaseline(): ErrorDebtBaseline | null {
|