@danypops/pi-lector 0.1.9 → 0.1.11

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.
@@ -140,7 +140,7 @@ export function formatDiagnosticsResult(diagnostics: readonly Diagnostic[] | und
140
140
  const lines = [theme.fg("muted", `${diagnostics.length} diagnostic${diagnostics.length === 1 ? "" : "s"}:`)];
141
141
 
142
142
  for (const diagnostic of diagnostics.slice(0, displayCount)) {
143
- const severity = theme.fg(DIAGNOSTIC_SEVERITY_COLOR[diagnostic.severity] ?? "muted", theme.bold(diagnostic.severity));
143
+ const severity = theme.fg(DIAGNOSTIC_SEVERITY_COLOR[diagnostic.severity], theme.bold(diagnostic.severity));
144
144
  const location = formatLocation(theme, diagnostic.range.path, diagnostic.range.start.line, diagnostic.range.start.character);
145
145
  const origin = diagnostic.source ? theme.fg("dim", ` (${diagnostic.source}${diagnostic.code !== undefined ? ` ${diagnostic.code}` : ""})`) : "";
146
146
  lines.push(` ${severity} ${location} -- ${diagnostic.message}${origin}`);
@@ -209,7 +209,11 @@ export function describePopulateSymbolGraphJob(job: JobSnapshot<PopulateSymbolGr
209
209
  if (job.status === "running") return `Source workspace is registered; symbol graph is still loading (job ${job.id}). Poll job_status.`;
210
210
  if (job.status === "failed") return `Job ${job.id} failed [${job.error.code}] -- ${job.error.message}`;
211
211
  const result = job.result;
212
- return `Job ${job.id} cached ${result.filesProcessed} file${result.filesProcessed === 1 ? "" : "s"}, ${result.symbolsProcessed} symbol${result.symbolsProcessed === 1 ? "" : "s"}, ${result.nodesAdded} node${result.nodesAdded === 1 ? "" : "s"}, ${result.edgesAdded} edge${result.edgesAdded === 1 ? "" : "s"}`;
212
+ const counts = `${result.filesProcessed}/${result.filesAttempted} files, ${result.symbolsProcessed} symbol${result.symbolsProcessed === 1 ? "" : "s"}, ${result.nodesAdded} node${result.nodesAdded === 1 ? "" : "s"}, ${result.edgesAdded} edge${result.edgesAdded === 1 ? "" : "s"}`;
213
+ if (result.completeness === "complete") return `Job ${job.id} cached ${counts}`;
214
+ const first = result.failures[0];
215
+ const failure = first ? ` First failure: ${first.path} [${first.code} via ${first.provenance.backend}] ${first.message}` : "";
216
+ return `Job ${job.id} partially cached ${counts}; ${result.filesFailed} failed file${result.filesFailed === 1 ? "" : "s"} (${result.failureCount} failed operations).${failure}`;
213
217
  }
214
218
 
215
219
  export function formatPopulateSymbolGraphResult(job: JobSnapshot<PopulateSymbolGraphResult> | undefined, theme: LectorTheme): string {
@@ -1,5 +1,6 @@
1
1
  import type { SymbolSearchResult, TextSearchResult, WorkspaceQueryOutcome } from "@danypops/lector";
2
2
  import { keyHint } from "@earendil-works/pi-coding-agent";
3
+ import { describeFindSymbolSources } from "./find-symbols-rendering.ts";
3
4
  import type { LectorTheme } from "./lector-tui-theme.ts";
4
5
 
5
6
  const DEFAULT_VISIBLE_PER_WORKSPACE = 10;
@@ -29,6 +30,7 @@ export function formatFindSymbolsAcrossProjectsResult(
29
30
  lines.push(
30
31
  theme.fg("muted", ` ${outcome.result.provenance.fidelity} via ${outcome.result.provenance.backend}${outcome.result.truncated ? " (truncated)" : ""}`),
31
32
  );
33
+ for (const source of describeFindSymbolSources(outcome.result)) lines.push(theme.fg("muted", ` ${source}`));
32
34
  if (outcome.result.symbols.length === 0) {
33
35
  lines.push(theme.fg("dim", " no symbols matched"));
34
36
  continue;
@@ -24,6 +24,16 @@ export function formatFindSymbolsCall(args: { query?: unknown; directory?: unkno
24
24
  return content;
25
25
  }
26
26
 
27
+ export function describeFindSymbolSources(result: SymbolSearchResult): readonly string[] {
28
+ return (result.sources ?? []).map((source) => {
29
+ const identity = `${source.provenance.languageId}: ${source.status} via ${source.provenance.backend}`;
30
+ if (source.status === "failed") {
31
+ return source.error ? `${identity} [${source.error.code}] ${source.error.message}` : identity;
32
+ }
33
+ return `${identity} (${source.symbolCount} symbol${source.symbolCount === 1 ? "" : "s"}${source.truncated ? ", truncated" : ""})`;
34
+ });
35
+ }
36
+
27
37
  /** One result line, kind-padded to the widest kind actually present so the name column lines up. */
28
38
  function formatSymbolLine(symbol: WorkspaceSymbol, theme: FindSymbolsTheme, kindColumnWidth: number): string {
29
39
  const kind = theme.fg(colorForKind(symbol.kind), symbol.kind.padEnd(kindColumnWidth));
@@ -36,13 +46,18 @@ export function formatFindSymbolsResult(result: SymbolSearchResult | undefined,
36
46
  if (!result) return theme.fg("dim", `No symbols found matching "${query}".`);
37
47
  const { symbols, provenance, truncated } = result;
38
48
  const source = `${provenance.fidelity} via ${provenance.backend}${truncated ? " (truncated)" : ""}`;
49
+ const sourceLines = describeFindSymbolSources(result).map((line) => theme.fg("muted", line));
39
50
  if (symbols.length === 0) {
40
- return `${theme.fg("muted", source)}\n${theme.fg("dim", `No symbols found matching "${query}".`)}`;
51
+ return [theme.fg("muted", source), ...sourceLines, theme.fg("dim", `No symbols found matching "${query}".`)].join("\n");
41
52
  }
42
53
 
43
54
  const kindColumnWidth = Math.max(...symbols.map((symbol) => symbol.kind.length));
44
55
  const displayCount = expanded ? symbols.length : Math.min(symbols.length, DEFAULT_VISIBLE_RESULTS);
45
- const lines = [theme.fg("muted", source), theme.fg("muted", `${symbols.length} symbol${symbols.length === 1 ? "" : "s"} matching "${query}":`)];
56
+ const lines = [
57
+ theme.fg("muted", source),
58
+ ...sourceLines,
59
+ theme.fg("muted", `${symbols.length} symbol${symbols.length === 1 ? "" : "s"} matching "${query}":`),
60
+ ];
46
61
 
47
62
  for (const symbol of symbols.slice(0, displayCount)) {
48
63
  lines.push(formatSymbolLine(symbol, theme, kindColumnWidth));
@@ -53,7 +53,7 @@ import { createLectorCrossWorkspaceSearchOperations } from "./cross-workspace-se
53
53
  import { formatCrossWorkspaceCall, formatFindSymbolsAcrossProjectsResult, formatSearchTextAcrossProjectsResult } from "./cross-workspace-search-rendering.ts";
54
54
  import { createLectorEditOperations } from "./edit-operations.ts";
55
55
  import { createLectorFindSymbolsOperations } from "./find-symbols-operations.ts";
56
- import { formatFindSymbolsCall, formatFindSymbolsResult } from "./find-symbols-rendering.ts";
56
+ import { describeFindSymbolSources, formatFindSymbolsCall, formatFindSymbolsResult } from "./find-symbols-rendering.ts";
57
57
  import { createLectorGitOperations } from "./git-operations.ts";
58
58
  import { formatGitDiffCall, formatGitDiffResult, formatGitLogCall, formatGitLogResult, formatGitStatusCall, formatGitStatusResult } from "./git-rendering.ts";
59
59
  import { nearestGitRoot } from "./nearest-workspace-root.ts";
@@ -186,10 +186,12 @@ export default function (pi: ExtensionAPI) {
186
186
  const result = await findSymbolsOperations.findSymbols(params.query, directory);
187
187
  const { symbols, provenance, truncated } = result;
188
188
  const source = `${provenance.fidelity} via ${provenance.backend}${truncated ? " (truncated)" : ""}`;
189
+ const sourceDetails = describeFindSymbolSources(result);
190
+ const heading = [source, ...sourceDetails].join("\n");
189
191
  const text =
190
192
  symbols.length === 0
191
- ? `${source}\nNo symbols found matching "${params.query}".`
192
- : `${source}\n${symbols
193
+ ? `${heading}\nNo symbols found matching "${params.query}".`
194
+ : `${heading}\n${symbols
193
195
  .map((symbol) => `${symbol.kind} ${symbol.name} -- ${symbol.location.path}:${symbol.location.line}:${symbol.location.character}`)
194
196
  .join("\n")}`;
195
197
  return { content: [{ type: "text", text }], details: result };
@@ -44,12 +44,14 @@ export type CachePresentationState =
44
44
  | { readonly status: "not-cached"; readonly reason: string }
45
45
  | { readonly status: "caching"; readonly jobId: string }
46
46
  | { readonly status: "finished-caching"; readonly job: JobSnapshot<PopulateSymbolGraphResult> & { readonly status: "succeeded" } }
47
+ | { readonly status: "partial"; readonly result: PopulateSymbolGraphResult }
47
48
  | { readonly status: "cached" };
48
49
 
49
50
  export function describeCacheState(state: CachePresentationState): string {
50
51
  if (state.status === "not-cached") return `not cached (${state.reason})`;
51
52
  if (state.status === "caching") return `caching (job ${state.jobId})`;
52
53
  if (state.status === "finished-caching") return `finished caching (job ${state.job.id})`;
54
+ if (state.status === "partial") return `partially cached (${state.result.filesFailed} failed file${state.result.filesFailed === 1 ? "" : "s"})`;
53
55
  return "cached";
54
56
  }
55
57
 
@@ -57,6 +59,7 @@ export function cacheContextMessage(state: CachePresentationState): string {
57
59
  const prefix = `Lector workspace cache: ${describeCacheState(state)}.`;
58
60
  if (state.status === "not-cached") return `${prefix} Live code-intelligence operations remain available.`;
59
61
  if (state.status === "caching") return `${prefix} The cached graph is still building; use live code-intelligence operations until it is ready.`;
62
+ if (state.status === "partial") return `${prefix} The graph is usable, but live code-intelligence operations are required for failed files.`;
60
63
  return `${prefix} The cached graph is ready.`;
61
64
  }
62
65
 
@@ -80,6 +83,15 @@ export async function monitorWorkspaceCache(operations: WorkspaceCacheOperations
80
83
  options.onState({ status: "cached" });
81
84
  return;
82
85
  }
86
+ if (initial.status === "partial") {
87
+ options.onState({ status: "partial", result: initial.generation.result });
88
+ return;
89
+ }
90
+
91
+ function reportCompleted(job: JobSnapshot<PopulateSymbolGraphResult> & { readonly status: "succeeded" }): void {
92
+ options.onState({ status: "finished-caching", job });
93
+ options.onState(job.result.completeness === "partial" ? { status: "partial", result: job.result } : { status: "cached" });
94
+ }
83
95
 
84
96
  let jobId: string;
85
97
  if (initial.status === "caching") {
@@ -89,8 +101,7 @@ export async function monitorWorkspaceCache(operations: WorkspaceCacheOperations
89
101
  const submitted = await operations.submit(options.directory, options.maxFiles, options.maxSymbolsPerFile);
90
102
  if (submitted.status === "failed") throw new Error(`${submitted.error.code}: ${submitted.error.message}`);
91
103
  if (submitted.status === "succeeded") {
92
- options.onState({ status: "finished-caching", job: submitted });
93
- options.onState({ status: "cached" });
104
+ reportCompleted(submitted);
94
105
  return;
95
106
  }
96
107
  jobId = submitted.id;
@@ -103,8 +114,7 @@ export async function monitorWorkspaceCache(operations: WorkspaceCacheOperations
103
114
  const job = await operations.jobStatus(jobId);
104
115
  if (job.status === "failed") throw new Error(`${job.error.code}: ${job.error.message}`);
105
116
  if (job.status === "succeeded") {
106
- options.onState({ status: "finished-caching", job });
107
- options.onState({ status: "cached" });
117
+ reportCompleted(job);
108
118
  return;
109
119
  }
110
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-lector",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Pi host adapter for Lector: overrides read/write/edit with a daemon-backed, hash-guarded filesystem",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "typebox": "*"
19
19
  },
20
20
  "dependencies": {
21
- "@danypops/lector": "^0.1.8"
21
+ "@danypops/lector": "^0.1.10"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@earendil-works/pi-ai": "^0.81.1",