@danypops/pi-lector 0.15.0 → 0.17.0

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.
@@ -1,4 +1,4 @@
1
- import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeCandidate } from "@danypops/lector";
1
+ import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeSearchResult } from "@danypops/lector";
2
2
  import { invokeLectorVehicleOperation, type LectorVehicleCall } from "../vehicle-client.ts";
3
3
 
4
4
  /** Matches EXTERNAL_SEARCH_PERMISSIONS' own declared value server-side (external-search/operation-registration.ts). */
@@ -12,7 +12,7 @@ const EXTERNAL_SEARCH_PERMISSIONS = ["external-search:read"];
12
12
  export interface ExternalSearchOperations {
13
13
  githubRepos(query: string, maxResults: number, call: LectorVehicleCall): Promise<GithubRepoSearchResult>;
14
14
  npmPackages(query: string, maxResults: number, call: LectorVehicleCall): Promise<{ candidates: readonly NpmPackageCandidate[] }>;
15
- sourcegraphCode(query: string, maxResults: number, call: LectorVehicleCall): Promise<{ candidates: readonly SourcegraphCodeCandidate[] }>;
15
+ sourcegraphCode(query: string, maxResults: number, call: LectorVehicleCall): Promise<SourcegraphCodeSearchResult>;
16
16
  }
17
17
 
18
18
  export function createExternalSearchOperations(): ExternalSearchOperations {
@@ -29,12 +29,7 @@ export function createExternalSearchOperations(): ExternalSearchOperations {
29
29
  );
30
30
  },
31
31
  sourcegraphCode(query, maxResults, call) {
32
- return invokeLectorVehicleOperation<{ candidates: readonly SourcegraphCodeCandidate[] }>(
33
- "search.sourcegraphCode",
34
- { query, maxResults },
35
- EXTERNAL_SEARCH_PERMISSIONS,
36
- call,
37
- );
32
+ return invokeLectorVehicleOperation<SourcegraphCodeSearchResult>("search.sourcegraphCode", { query, maxResults }, EXTERNAL_SEARCH_PERMISSIONS, call);
38
33
  },
39
34
  };
40
35
  }
@@ -1,4 +1,4 @@
1
- import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeCandidate } from "@danypops/lector";
1
+ import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeSearchResult } from "@danypops/lector";
2
2
  import type { LectorTheme } from "../lector-tui-theme.ts";
3
3
  import { presentationTitle } from "../presentation/tool-presentation.ts";
4
4
 
@@ -45,17 +45,15 @@ export function formatNpmPackageSearchResult(
45
45
  return lines.join("\n");
46
46
  }
47
47
 
48
- export function formatSourcegraphCodeSearchResult(
49
- result: { candidates: readonly SourcegraphCodeCandidate[] } | undefined,
50
- expanded: boolean,
51
- theme: LectorTheme,
52
- ): string {
53
- if (!result || result.candidates.length === 0) return theme.fg("dim", "no code matches");
48
+ export function formatSourcegraphCodeSearchResult(result: SourcegraphCodeSearchResult | undefined, expanded: boolean, theme: LectorTheme): string {
49
+ if (!result) return theme.fg("dim", "no code matches");
54
50
  const { visible, more } = boundedCandidates(result.candidates, expanded);
55
51
  const lines = visible.map((candidate) => {
56
52
  const matches = candidate.lineMatches.slice(0, expanded ? candidate.lineMatches.length : 3);
57
53
  return `${theme.fg("accent", `${candidate.repository}/${candidate.path}`)}\n${matches.map((match) => ` ${match.line}: ${match.preview}`).join("\n")}`;
58
54
  });
59
55
  if (more > 0) lines.push(theme.fg("muted", `… ${more} more (expand to show)`));
56
+ if (result.truncated) lines.push(theme.fg("warning", `partial results · ${result.stopReason ?? "bounded"}`));
57
+ if (lines.length === 0) return theme.fg("dim", "no code matches");
60
58
  return lines.join("\n");
61
59
  }
@@ -21,7 +21,7 @@ import type {
21
21
  PackageSourceOperationResult,
22
22
  PopulateSymbolGraphResult,
23
23
  RepoFetchResult,
24
- SourcegraphCodeCandidate,
24
+ SourcegraphCodeSearchResult,
25
25
  SymbolAnnotation,
26
26
  SymbolNode,
27
27
  SymbolSearchResult,
@@ -2457,7 +2457,7 @@ export default function (pi: ExtensionAPI) {
2457
2457
  type ExternalSearchToolDetails =
2458
2458
  | { readonly action: "github_repos"; readonly result: GithubRepoSearchResult }
2459
2459
  | { readonly action: "npm_packages"; readonly result: { candidates: readonly NpmPackageCandidate[] } }
2460
- | { readonly action: "sourcegraph_code"; readonly result: { candidates: readonly SourcegraphCodeCandidate[] } };
2460
+ | { readonly action: "sourcegraph_code"; readonly result: SourcegraphCodeSearchResult };
2461
2461
 
2462
2462
  const externalSearchOperations = createExternalSearchOperations();
2463
2463
  registerLectorTool({
@@ -1,14 +1,18 @@
1
- import type { MutationHistoryEntry } from "@danypops/lector";
1
+ import type { MutationHistoryEntry, MutationTransactionLookupOutcome } from "@danypops/lector";
2
2
  import { withWorkspace, workspaceForPath } from "../lector-client.ts";
3
3
  import { invokeLectorVehicleOperation, type LectorVehicleCall } from "../vehicle-client.ts";
4
4
  import { toWorkspaceRelativePath } from "../workspace-relative-path.ts";
5
5
 
6
6
  const MAX_INTERNAL_HISTORY_LOOKUP_RESULTS = 2_000;
7
7
 
8
- export interface MutationTransactionRevertOutcome {
9
- readonly transactionId: string;
10
- readonly reverted: readonly { readonly path: string; readonly newHash: string | null }[];
11
- }
8
+ export type MutationTransactionRevertOutcome =
9
+ | {
10
+ readonly status: "reverted";
11
+ readonly transactionId: string;
12
+ readonly reverted: readonly { readonly path: string; readonly newHash: string | null }[];
13
+ }
14
+ | { readonly status: "stale"; readonly transactionId: string; readonly stalePaths: readonly string[] }
15
+ | Extract<MutationTransactionLookupOutcome, { readonly status: "evicted" | "wrong-workspace" | "unknown" }>;
12
16
 
13
17
  /** Match MUTATION_HISTORY_READ_PERMISSIONS/MUTATION_HISTORY_WRITE_PERMISSIONS' own declared values server-side (mutation-history/operation-registration.ts). */
14
18
  const MUTATION_HISTORY_READ_PERMISSIONS = ["workspace:read"];
@@ -32,24 +36,14 @@ async function listResolvedHistory(
32
36
  maxResults: number,
33
37
  call: LectorVehicleCall,
34
38
  ): Promise<readonly MutationHistoryEntry[]> {
35
- const relativePath = toWorkspaceRelativePath(root, absolutePath);
36
- // Single-file edits historically record the caller's workspace-relative path, while LSP
37
- // WorkspaceEdits record canonical absolute paths. Query both identities until the daemon's
38
- // stored-history migration can normalize old entries, then deduplicate by immutable entry id.
39
- const paths = relativePath === absolutePath ? [absolutePath] : [relativePath, absolutePath];
40
- const pages = await Promise.all(
41
- paths.map((path) =>
42
- invokeLectorVehicleOperation<{ entries: readonly MutationHistoryEntry[] }>(
43
- "workspace.mutationHistory",
44
- { workspaceId, path, maxResults },
45
- MUTATION_HISTORY_READ_PERMISSIONS,
46
- call,
47
- ),
48
- ),
39
+ const path = toWorkspaceRelativePath(root, absolutePath);
40
+ const page = await invokeLectorVehicleOperation<{ entries: readonly MutationHistoryEntry[] }>(
41
+ "workspace.mutationHistory",
42
+ { workspaceId, path, maxResults },
43
+ MUTATION_HISTORY_READ_PERMISSIONS,
44
+ call,
49
45
  );
50
- const byId = new Map<string, MutationHistoryEntry>();
51
- for (const page of pages) for (const entry of page.entries) byId.set(entry.id, entry);
52
- return [...byId.values()].sort((a, b) => b.timestamp - a.timestamp).slice(0, maxResults);
46
+ return page.entries;
53
47
  }
54
48
 
55
49
  export function createMutationHistoryOperations(): MutationHistoryOperations {
@@ -84,13 +78,23 @@ export function createMutationHistoryOperations(): MutationHistoryOperations {
84
78
  revertTransaction(absolutePath, transactionId, call) {
85
79
  return withWorkspace(
86
80
  () => workspaceForPath(absolutePath),
87
- ({ workspaceId }) =>
88
- invokeLectorVehicleOperation<MutationTransactionRevertOutcome>(
81
+ async ({ workspaceId }) => {
82
+ const lookup = await invokeLectorVehicleOperation<MutationTransactionLookupOutcome>(
83
+ "workspace.mutationTransaction",
84
+ { workspaceId, transactionId },
85
+ MUTATION_HISTORY_READ_PERMISSIONS,
86
+ call,
87
+ );
88
+ if (lookup.status === "stale") return { status: "stale", transactionId, stalePaths: lookup.stalePaths };
89
+ if (lookup.status !== "ready") return lookup;
90
+ const reverted = await invokeLectorVehicleOperation<Omit<Extract<MutationTransactionRevertOutcome, { status: "reverted" }>, "status">>(
89
91
  "workspace.revertMutationTransaction",
90
92
  { workspaceId, transactionId },
91
93
  MUTATION_HISTORY_WRITE_PERMISSIONS,
92
94
  call,
93
- ),
95
+ );
96
+ return { status: "reverted", ...reverted };
97
+ },
94
98
  );
95
99
  },
96
100
  };
@@ -12,9 +12,23 @@ export function formatMutationHistoryList(entries: readonly MutationHistoryEntry
12
12
  }
13
13
 
14
14
  export function formatMutationTransactionRevert(originalTransactionId: string, outcome: MutationTransactionRevertOutcome): string {
15
- const lines = [
16
- `${originalTransactionId} reverted atomically; revert recorded as transaction ${outcome.transactionId}`,
17
- ...outcome.reverted.map((entry) => `${entry.path} -> ${entry.newHash ?? "(deleted)"}`),
18
- ];
19
- return lines.join("\n");
15
+ switch (outcome.status) {
16
+ case "reverted":
17
+ return [
18
+ `${originalTransactionId} reverted atomically; revert recorded as transaction ${outcome.transactionId}`,
19
+ ...outcome.reverted.map((entry) => `${entry.path} -> ${entry.newHash ?? "(deleted)"}`),
20
+ ].join("\n");
21
+ case "stale":
22
+ return `${originalTransactionId} is stale at ${outcome.stalePaths.length} path(s); no files were reverted\n${outcome.stalePaths.join("\n")}`;
23
+ case "evicted":
24
+ return `${originalTransactionId} cannot be reverted because its bounded process-local history was evicted`;
25
+ case "wrong-workspace":
26
+ return `${originalTransactionId} belongs to a different registered workspace; use a path from that workspace`;
27
+ case "unknown":
28
+ return `${originalTransactionId} is unknown; mutation history is process-local and is lost after daemon restart`;
29
+ default: {
30
+ const exhaustive: never = outcome;
31
+ return exhaustive;
32
+ }
33
+ }
20
34
  }
@@ -26,7 +26,16 @@ export function formatWorkspaceCacheCall(
26
26
 
27
27
  function formatResultCounts(result: CacheResultCounts): string {
28
28
  const failed = result.filesFailed > 0 ? `, ${result.filesFailed} failed` : "";
29
- return `${result.filesProcessed}/${result.filesAttempted} files${failed}, ${result.symbolsProcessed} symbols, ${result.nodesAdded} nodes, ${result.edgesAdded} edges`;
29
+ const reuse = result.filesReused === undefined ? "" : `, ${result.filesReused} reused, ${result.filesReprocessed ?? 0} reprocessed`;
30
+ const retries = result.staleRetries ? `, ${result.staleRetries} stale ${result.staleRetries === 1 ? "retry" : "retries"}` : "";
31
+ const coverage = result.sourceCoverage;
32
+ const scopes = coverage?.scopes
33
+ .slice(0, 3)
34
+ .map((entry) => `${entry.scope}:${entry.files}`)
35
+ .join(", ");
36
+ const omitted = coverage ? coverage.scopeOmittedCount + Math.max(0, coverage.scopes.length - 3) : 0;
37
+ const scopeSummary = scopes ? `; coverage ${scopes}${omitted > 0 ? ` (+${omitted} scopes)` : ""}${coverage?.truncated ? " [bounded]" : ""}` : "";
38
+ return `${result.filesProcessed}/${result.filesAttempted} files${failed}${reuse}${retries}, ${result.symbolsProcessed} symbols, ${result.nodesAdded} nodes, ${result.edgesAdded} edges${scopeSummary}`;
30
39
  }
31
40
 
32
41
  export function formatWorkspaceReleaseModelContent(outcome: OperationOutputs["workspace.release"]): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-lector",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
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",
@@ -22,7 +22,7 @@
22
22
  "@danypops/vehicle-client-pi": "^0.45.0"
23
23
  },
24
24
  "dependencies": {
25
- "@danypops/lector": "^0.21.0",
25
+ "@danypops/lector": "^0.23.0",
26
26
  "@danypops/vehicle-client": "^0.10.8",
27
27
  "@danypops/vehicle-core": "^0.19.1",
28
28
  "malevich-tui-components": "^0.32.1",