@danypops/pi-lector 0.12.13 → 0.12.15

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.
@@ -3,9 +3,15 @@ import { lectorClient, withWorkspace, workspaceForDirectory } from "../lector-cl
3
3
  import { invokeLectorVehicleOperation, type LectorVehicleCall } from "../vehicle-client.ts";
4
4
 
5
5
  type SymbolComparison = OperationOutputs["workspace.compareSymbolAcrossVersions"];
6
+ type GitWorktreeAddResult = OperationOutputs["workspace.gitWorktreeAdd"];
7
+ type GitWorktreeRemoveResult = OperationOutputs["workspace.gitWorktreeRemove"];
8
+ type GitGrepResult = OperationOutputs["workspace.gitGrep"];
9
+ type GitListFilesResult = OperationOutputs["workspace.gitListFiles"];
6
10
 
7
11
  /** Matches GIT_READ_PERMISSIONS' own declared value server-side (git/operation-registration.ts). */
8
12
  const GIT_READ_PERMISSIONS = ["workspace:read"];
13
+ /** Matches GIT_WORKTREE_WRITE_PERMISSIONS' own declared value server-side (git/operation-registration.ts). */
14
+ const GIT_WORKTREE_WRITE_PERMISSIONS = ["workspace:write"];
9
15
 
10
16
  /**
11
17
  * Thin wrappers over Lector's read-only git operations. `directory` is
@@ -24,6 +30,31 @@ export interface GitOperations {
24
30
  log(directory: string, maxCount: number, call: LectorVehicleCall): Promise<readonly GitLogEntry[]>;
25
31
  diff(directory: string, ref: string | undefined, maxBytes: number, call: LectorVehicleCall): Promise<GitDiffResult>;
26
32
  compareSymbol(directory: string, path: string, symbolName: string, fromRef: string, toRef: string | undefined, maxBytes: number): Promise<SymbolComparison>;
33
+ /** A path's exact blob content at `ref`, without checking anything out -- Tier 1's own showFile, undefined content meaning the path did not exist there. */
34
+ showFile(directory: string, ref: string, path: string, call: LectorVehicleCall): Promise<string | undefined>;
35
+ /** Text search across `ref`'s own tree, no checkout -- the ref-scoped equivalent of search_code. pathspecs narrows the search (glob-based, e.g. "*.go"). */
36
+ grep(
37
+ directory: string,
38
+ ref: string,
39
+ pattern: string,
40
+ pathspecs: readonly string[] | undefined,
41
+ maxMatches: number,
42
+ maxBytes: number,
43
+ call: LectorVehicleCall,
44
+ ): Promise<GitGrepResult>;
45
+ /** Every file path in `ref`'s own tree, no checkout -- pathspecs narrows the listing (prefix-based, not glob-based like grep's). */
46
+ listFiles(directory: string, ref: string, pathspecs: readonly string[] | undefined, maxResults: number, call: LectorVehicleCall): Promise<GitListFilesResult>;
47
+ /** True iff ancestorRef is a real ancestor of (or the exact same commit as) ref -- the backport/reachability check "was this fix ported to this branch" actually needs. */
48
+ isAncestor(directory: string, ancestorRef: string, ref: string, call: LectorVehicleCall): Promise<boolean>;
49
+ /**
50
+ * Materializes a real, disposable, read-only project at `ref` via a detached git worktree.
51
+ * The returned `path` is a real directory every other pi-lector tool already accepts as its
52
+ * own `directory` argument (find_symbols, search_code, git itself, ...) -- no separate
53
+ * workspaceId ever needs to reach the agent.
54
+ */
55
+ worktreeAdd(directory: string, ref: string, forceRefresh: boolean | undefined, call: LectorVehicleCall): Promise<GitWorktreeAddResult>;
56
+ /** `directory` is the worktree's own path (worktreeAdd's returned `path`), not the source repo's -- it resolves to the exact same workspace via the same git-root walk every other tool already uses. */
57
+ worktreeRemove(directory: string, call: LectorVehicleCall): Promise<GitWorktreeRemoveResult>;
27
58
  }
28
59
 
29
60
  export function createLectorGitOperations(): GitOperations {
@@ -63,5 +94,71 @@ export function createLectorGitOperations(): GitOperations {
63
94
  },
64
95
  );
65
96
  },
97
+ async worktreeAdd(directory, ref, forceRefresh, call) {
98
+ return withWorkspace(
99
+ () => workspaceForDirectory(directory),
100
+ ({ workspaceId }) =>
101
+ invokeLectorVehicleOperation<GitWorktreeAddResult>(
102
+ "workspace.gitWorktreeAdd",
103
+ { workspaceId, ref, forceRefresh },
104
+ GIT_WORKTREE_WRITE_PERMISSIONS,
105
+ call,
106
+ ),
107
+ );
108
+ },
109
+ async worktreeRemove(directory, call) {
110
+ return withWorkspace(
111
+ () => workspaceForDirectory(directory),
112
+ ({ workspaceId }) =>
113
+ invokeLectorVehicleOperation<GitWorktreeRemoveResult>("workspace.gitWorktreeRemove", { workspaceId }, GIT_WORKTREE_WRITE_PERMISSIONS, call),
114
+ );
115
+ },
116
+ async showFile(directory, ref, path, call) {
117
+ return withWorkspace(
118
+ () => workspaceForDirectory(directory),
119
+ async ({ workspaceId }) => {
120
+ const { content } = await invokeLectorVehicleOperation<{ content: string | undefined }>(
121
+ "workspace.gitShowFile",
122
+ { workspaceId, ref, path },
123
+ GIT_READ_PERMISSIONS,
124
+ call,
125
+ );
126
+ return content;
127
+ },
128
+ );
129
+ },
130
+ async grep(directory, ref, pattern, pathspecs, maxMatches, maxBytes, call) {
131
+ return withWorkspace(
132
+ () => workspaceForDirectory(directory),
133
+ ({ workspaceId }) =>
134
+ invokeLectorVehicleOperation<GitGrepResult>(
135
+ "workspace.gitGrep",
136
+ { workspaceId, ref, pattern, pathspecs, maxMatches, maxBytes },
137
+ GIT_READ_PERMISSIONS,
138
+ call,
139
+ ),
140
+ );
141
+ },
142
+ async listFiles(directory, ref, pathspecs, maxResults, call) {
143
+ return withWorkspace(
144
+ () => workspaceForDirectory(directory),
145
+ ({ workspaceId }) =>
146
+ invokeLectorVehicleOperation<GitListFilesResult>("workspace.gitListFiles", { workspaceId, ref, pathspecs, maxResults }, GIT_READ_PERMISSIONS, call),
147
+ );
148
+ },
149
+ async isAncestor(directory, ancestorRef, ref, call) {
150
+ return withWorkspace(
151
+ () => workspaceForDirectory(directory),
152
+ async ({ workspaceId }) => {
153
+ const { isAncestor } = await invokeLectorVehicleOperation<{ isAncestor: boolean }>(
154
+ "workspace.gitIsAncestor",
155
+ { workspaceId, ancestorRef, ref },
156
+ GIT_READ_PERMISSIONS,
157
+ call,
158
+ );
159
+ return isAncestor;
160
+ },
161
+ );
162
+ },
66
163
  };
67
164
  }
@@ -11,9 +11,13 @@ const DEFAULT_VISIBLE_FILES = 20;
11
11
  const DEFAULT_VISIBLE_COMMITS = 10;
12
12
  const DEFAULT_VISIBLE_DIFF_LINES = 60;
13
13
 
14
- export type GitAction = "status" | "log" | "diff" | "compare-symbol";
14
+ export type GitAction = "status" | "log" | "diff" | "compare-symbol" | "worktree-add" | "worktree-remove" | "show" | "grep-ref" | "ls-ref" | "is-ancestor";
15
15
 
16
16
  type SymbolComparison = OperationOutputs["workspace.compareSymbolAcrossVersions"];
17
+ type GitWorktreeAddResult = OperationOutputs["workspace.gitWorktreeAdd"];
18
+ type GitWorktreeRemoveResult = OperationOutputs["workspace.gitWorktreeRemove"];
19
+ type GitGrepResult = OperationOutputs["workspace.gitGrep"];
20
+ type GitListFilesResult = OperationOutputs["workspace.gitListFiles"];
17
21
 
18
22
  export interface GitToolDetails {
19
23
  readonly action: GitAction;
@@ -21,10 +25,26 @@ export interface GitToolDetails {
21
25
  readonly entries?: readonly GitLogEntry[];
22
26
  readonly result?: GitDiffResult;
23
27
  readonly comparison?: SymbolComparison;
28
+ readonly worktreeAdd?: GitWorktreeAddResult;
29
+ readonly worktreeRemove?: GitWorktreeRemoveResult;
30
+ readonly showFile?: { readonly ref: string; readonly path: string; readonly content: string | undefined };
31
+ readonly grep?: GitGrepResult;
32
+ readonly listFiles?: GitListFilesResult;
33
+ readonly isAncestor?: { readonly ancestorRef: string; readonly ref: string; readonly result: boolean };
24
34
  }
25
35
 
26
36
  export function formatGitCall(
27
- args: { action?: unknown; directory?: unknown; ref?: unknown; path?: unknown; symbol?: unknown; fromRef?: unknown; toRef?: unknown },
37
+ args: {
38
+ action?: unknown;
39
+ directory?: unknown;
40
+ ref?: unknown;
41
+ path?: unknown;
42
+ symbol?: unknown;
43
+ fromRef?: unknown;
44
+ toRef?: unknown;
45
+ pattern?: unknown;
46
+ ancestorRef?: unknown;
47
+ },
28
48
  theme: LectorTheme,
29
49
  ): string {
30
50
  const action = typeof args.action === "string" ? args.action : "";
@@ -36,15 +56,85 @@ export function formatGitCall(
36
56
  const toRef = typeof args.toRef === "string" ? ` -> ${args.toRef}` : "";
37
57
  return `${theme.fg("toolTitle", theme.bold("git"))} ${theme.fg("muted", action)} ${theme.fg("accent", `${directory}/${path}`)} (${symbol}) ${fromRef}${toRef}`;
38
58
  }
59
+ if (action === "is-ancestor") {
60
+ const ancestorRef = typeof args.ancestorRef === "string" ? args.ancestorRef : "";
61
+ const ref = typeof args.ref === "string" ? args.ref : "";
62
+ return `${theme.fg("toolTitle", theme.bold("git"))} ${theme.fg("muted", action)} ${theme.fg("accent", directory)} ${ancestorRef} -> ${ref}`;
63
+ }
64
+ if (action === "grep-ref") {
65
+ const ref = typeof args.ref === "string" ? args.ref : "";
66
+ const pattern = typeof args.pattern === "string" ? args.pattern : "";
67
+ return `${theme.fg("toolTitle", theme.bold("git"))} ${theme.fg("muted", action)} ${theme.fg("accent", directory)} ${ref} "${pattern}"`;
68
+ }
69
+ if (action === "show") {
70
+ const ref = typeof args.ref === "string" ? args.ref : "";
71
+ const path = typeof args.path === "string" ? args.path : "";
72
+ return `${theme.fg("toolTitle", theme.bold("git"))} ${theme.fg("muted", action)} ${theme.fg("accent", `${directory}/${path}`)} @ ${ref}`;
73
+ }
39
74
  const ref = typeof args.ref === "string" ? ` ${args.ref}` : "";
40
75
  return `${theme.fg("toolTitle", theme.bold("git"))} ${theme.fg("muted", action)} ${theme.fg("accent", directory)}${ref}`;
41
76
  }
42
77
 
78
+ function formatGitWorktreeAddResult(result: GitWorktreeAddResult | undefined, theme: LectorTheme): string {
79
+ if (!result) return theme.fg("dim", "No result.");
80
+ const verb = result.created ? "created" : "reused existing";
81
+ return [theme.fg("accent", `${verb} worktree at ${result.ref} (${result.commit.slice(0, 8)})`), theme.fg("muted", result.path)].join("\n");
82
+ }
83
+
84
+ function formatGitWorktreeRemoveResult(result: GitWorktreeRemoveResult | undefined, theme: LectorTheme): string {
85
+ if (!result) return theme.fg("dim", "No result.");
86
+ return theme.fg("accent", "worktree removed");
87
+ }
88
+
89
+ function formatGitShowFileResult(details: GitToolDetails["showFile"], theme: LectorTheme): string {
90
+ if (!details) return theme.fg("dim", "No result.");
91
+ if (details.content === undefined) return theme.fg("dim", `"${details.path}" does not exist at ${details.ref}`);
92
+ return details.content;
93
+ }
94
+
95
+ function formatGitGrepResult(result: GitGrepResult | undefined, expanded: boolean, theme: LectorTheme): string {
96
+ if (!result || result.matches.length === 0) return theme.fg("dim", "No matches.");
97
+ const lines = renderTruncatedList({
98
+ items: result.matches,
99
+ expanded,
100
+ visibleCount: DEFAULT_VISIBLE_FILES,
101
+ formatItem: (match) => `${theme.fg("accent", `${match.path}:${match.line}`)}:${match.text}`,
102
+ moreLine: moreLine(theme),
103
+ truncationWarning: result.truncated ? theme.fg("warning", "(also bounded by maxMatches/maxBytes)") : undefined,
104
+ });
105
+ return lines.join("\n");
106
+ }
107
+
108
+ function formatGitListFilesResult(result: GitListFilesResult | undefined, expanded: boolean, theme: LectorTheme): string {
109
+ if (!result || result.paths.length === 0) return theme.fg("dim", "No files.");
110
+ const lines = renderTruncatedList({
111
+ items: result.paths,
112
+ expanded,
113
+ visibleCount: DEFAULT_VISIBLE_FILES,
114
+ formatItem: (path) => path,
115
+ moreLine: moreLine(theme),
116
+ truncationWarning: result.truncated ? theme.fg("warning", "(bounded by maxResults)") : undefined,
117
+ });
118
+ return lines.join("\n");
119
+ }
120
+
121
+ function formatGitIsAncestorResult(details: GitToolDetails["isAncestor"], theme: LectorTheme): string {
122
+ if (!details) return theme.fg("dim", "No result.");
123
+ const verb = details.result ? "is" : "is not";
124
+ return theme.fg("accent", `${details.ancestorRef} ${verb} an ancestor of ${details.ref}`);
125
+ }
126
+
43
127
  export function formatGitResult(details: GitToolDetails | undefined, expanded: boolean, theme: LectorTheme): string {
44
128
  if (!details) return theme.fg("dim", "No result.");
45
129
  if (details.action === "status") return formatGitStatusResult(details.summary, expanded, theme);
46
130
  if (details.action === "log") return formatGitLogResult(details.entries, expanded, theme);
47
131
  if (details.action === "compare-symbol") return formatCompareSymbolResult(details.comparison, expanded, theme);
132
+ if (details.action === "worktree-add") return formatGitWorktreeAddResult(details.worktreeAdd, theme);
133
+ if (details.action === "worktree-remove") return formatGitWorktreeRemoveResult(details.worktreeRemove, theme);
134
+ if (details.action === "show") return formatGitShowFileResult(details.showFile, theme);
135
+ if (details.action === "grep-ref") return formatGitGrepResult(details.grep, expanded, theme);
136
+ if (details.action === "ls-ref") return formatGitListFilesResult(details.listFiles, expanded, theme);
137
+ if (details.action === "is-ancestor") return formatGitIsAncestorResult(details.isAncestor, theme);
48
138
  return formatGitDiffResult(details.result, expanded, theme);
49
139
  }
50
140
 
@@ -1246,26 +1246,54 @@ export default function (pi: ExtensionAPI) {
1246
1246
  name: "git",
1247
1247
  label: "Git",
1248
1248
  description:
1249
- "Working tree status, recent commit log, unified diff, and one symbol's own declaration diff across two versions, for a real git repository, in one tool. Fails clearly if `directory` is not inside a git repository. ACTIONS: status (working tree state, ahead/behind tracking), log (recent commits, bounded by maxCount), diff (unified diff against `ref`, defaulting to HEAD, bounded by maxBytes), compare-symbol (a named symbol's own declaration text diffed between fromRef and toRef, or fromRef and the current working tree when toRef is omitted -- tree-sitter syntactic tier only, TypeScript/JavaScript files only, no project-aware cross-reference resolution).",
1250
- promptSnippet: "Show a repository's status, log, diff, or one symbol's diff across versions",
1249
+ "Working tree status, recent commit log, unified diff, one symbol's own declaration diff across two versions, ref-scoped blob/text/ancestry queries with no checkout, and a real disposable checkout at another ref, for a real git repository, in one tool. Fails clearly if `directory` is not inside a git repository. ACTIONS: status (working tree state, ahead/behind tracking), log (recent commits, bounded by maxCount), diff (unified diff against `ref`, defaulting to HEAD, bounded by maxBytes), compare-symbol (a named symbol's own declaration text diffed between fromRef and toRef, or fromRef and the current working tree when toRef is omitted -- tree-sitter syntactic tier only, TypeScript/JavaScript files only, no project-aware cross-reference resolution), show (a path's exact blob content at `ref`, no checkout), grep-ref (text search across `ref`'s own tree, no checkout -- the ref-scoped equivalent of search_code), ls-ref (every file path in `ref`'s own tree, no checkout), is-ancestor (is `ancestorRef` a real ancestor of, or the same commit as, `ref` -- the backport/reachability check \"was this fix ported to this branch\" actually needs), worktree-add (materializes `ref` as a real, read-only project via a detached git worktree and returns its own `directory` -- pass that straight to find_symbols/search_code/this tool itself for full semantic queries against another branch/commit, not just text), worktree-remove (releases and deletes a worktree-add-created checkout -- `directory` is that checkout's own returned directory, not the source repo's).",
1250
+ promptSnippet:
1251
+ "Show a repository's status, log, diff, one symbol's diff across versions, a ref-scoped blob/text/ancestry query, or a real checkout at another ref",
1251
1252
  promptGuidelines: [
1252
- "maxCount is required for action=log; maxBytes is required for action=diff/compare-symbol -- every bounded query needs its bound stated explicitly, never defaulted silently.",
1253
+ "maxCount is required for action=log; maxBytes is required for action=diff/compare-symbol/grep-ref -- every bounded query needs its bound stated explicitly, never defaulted silently.",
1253
1254
  "path, symbol, and fromRef are required for action=compare-symbol; toRef is optional and means 'the current working tree' when omitted.",
1255
+ "ref is required for action=worktree-add. A repeated worktree-add for the same (directory, ref) reuses the existing checkout unless forceRefresh is set -- use that when ref is a branch that may have moved.",
1256
+ "action=worktree-remove's directory is worktree-add's own returned directory, never the source repo's -- always call it once done with a worktree to reclaim disk.",
1257
+ "ref and path are required for action=show; ref and pattern (maxMatches, maxBytes) are required for action=grep-ref; ref (maxResults) is required for action=ls-ref; ancestorRef and ref are required for action=is-ancestor. None of the four checks anything out -- prefer them over worktree-add/find_symbols for a quick existence/text/ancestry answer.",
1254
1258
  ],
1255
1259
  parameters: Type.Object({
1256
- action: Type.String({ description: "status | log | diff | compare-symbol" }),
1260
+ action: Type.String({ description: "status | log | diff | compare-symbol | show | grep-ref | ls-ref | is-ancestor | worktree-add | worktree-remove" }),
1257
1261
  directory: Type.String({ description: "Directory inside the repository to check, absolute or relative to the current working directory" }),
1258
1262
  maxCount: Type.Optional(Type.Number({ description: "Maximum number of commits to return, most recent first -- required for action=log" })),
1259
- ref: Type.Optional(Type.String({ description: "Ref to diff against; defaults to HEAD -- only used for action=diff" })),
1263
+ ref: Type.Optional(
1264
+ Type.String({
1265
+ description:
1266
+ "Ref to diff against (defaults to HEAD) for action=diff, or to check out/query for action=worktree-add/show/grep-ref/ls-ref/is-ancestor (required for those)",
1267
+ }),
1268
+ ),
1260
1269
  maxBytes: Type.Optional(
1261
- Type.Number({ description: "Maximum diff/comparison size in bytes before truncating -- required for action=diff/compare-symbol" }),
1270
+ Type.Number({
1271
+ description: "Maximum diff/comparison/grep output size in bytes before truncating -- required for action=diff/compare-symbol/grep-ref",
1272
+ }),
1273
+ ),
1274
+ path: Type.Optional(
1275
+ Type.String({ description: "File path (relative to directory) containing the symbol, or to read -- required for action=compare-symbol/show" }),
1262
1276
  ),
1263
- path: Type.Optional(Type.String({ description: "File path (relative to directory) containing the symbol -- required for action=compare-symbol" })),
1264
1277
  symbol: Type.Optional(Type.String({ description: "Exact symbol name to compare -- required for action=compare-symbol" })),
1265
1278
  fromRef: Type.Optional(Type.String({ description: "Git ref for the 'before' version -- required for action=compare-symbol" })),
1266
1279
  toRef: Type.Optional(
1267
1280
  Type.String({ description: "Git ref for the 'after' version; omit to compare against the current working tree -- action=compare-symbol only" }),
1268
1281
  ),
1282
+ forceRefresh: Type.Optional(
1283
+ Type.Boolean({
1284
+ description: "action=worktree-add only: recreate an already-reused worktree at ref's current tip instead of returning the existing one",
1285
+ }),
1286
+ ),
1287
+ pattern: Type.Optional(Type.String({ description: "Text pattern to search for -- required for action=grep-ref" })),
1288
+ pathspecs: Type.Optional(
1289
+ Type.Array(Type.String(), {
1290
+ description:
1291
+ 'Narrows action=grep-ref (glob-based, e.g. "*.go") or action=ls-ref (prefix-based, e.g. "pkg/dpll"); omitted searches/lists the whole tree',
1292
+ }),
1293
+ ),
1294
+ maxMatches: Type.Optional(Type.Number({ description: "Maximum grep matches to return -- required for action=grep-ref" })),
1295
+ maxResults: Type.Optional(Type.Number({ description: "Maximum file paths to return -- required for action=ls-ref" })),
1296
+ ancestorRef: Type.Optional(Type.String({ description: "The candidate ancestor ref -- required for action=is-ancestor" })),
1269
1297
  }),
1270
1298
  async execute(toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<GitToolDetails>> {
1271
1299
  const directory = resolve(cwd, params.directory);
@@ -1296,6 +1324,43 @@ export default function (pi: ExtensionAPI) {
1296
1324
  const details: GitToolDetails = { action: "compare-symbol", comparison };
1297
1325
  return { content: [{ type: "text", text: JSON.stringify(comparison) }], details };
1298
1326
  }
1327
+ if (params.action === "worktree-add") {
1328
+ if (!params.ref) throw new Error("git action=worktree-add requires ref");
1329
+ const worktreeAdd = await gitOperations.worktreeAdd(directory, params.ref, params.forceRefresh, vehicleCall);
1330
+ const details: GitToolDetails = { action: "worktree-add", worktreeAdd };
1331
+ return { content: [{ type: "text", text: JSON.stringify(worktreeAdd) }], details };
1332
+ }
1333
+ if (params.action === "worktree-remove") {
1334
+ const worktreeRemove = await gitOperations.worktreeRemove(directory, vehicleCall);
1335
+ const details: GitToolDetails = { action: "worktree-remove", worktreeRemove };
1336
+ return { content: [{ type: "text", text: JSON.stringify(worktreeRemove) }], details };
1337
+ }
1338
+ if (params.action === "show") {
1339
+ if (!params.ref || !params.path) throw new Error("git action=show requires ref and path");
1340
+ const content = await gitOperations.showFile(directory, params.ref, params.path, vehicleCall);
1341
+ const details: GitToolDetails = { action: "show", showFile: { ref: params.ref, path: params.path, content } };
1342
+ return { content: [{ type: "text", text: content ?? `"${params.path}" does not exist at ${params.ref}` }], details };
1343
+ }
1344
+ if (params.action === "grep-ref") {
1345
+ if (!params.ref || !params.pattern) throw new Error("git action=grep-ref requires ref and pattern");
1346
+ if (params.maxMatches === undefined || params.maxBytes === undefined) throw new Error("git action=grep-ref requires maxMatches and maxBytes");
1347
+ const grep = await gitOperations.grep(directory, params.ref, params.pattern, params.pathspecs, params.maxMatches, params.maxBytes, vehicleCall);
1348
+ const details: GitToolDetails = { action: "grep-ref", grep };
1349
+ return { content: [{ type: "text", text: JSON.stringify(grep) }], details };
1350
+ }
1351
+ if (params.action === "ls-ref") {
1352
+ if (!params.ref) throw new Error("git action=ls-ref requires ref");
1353
+ if (params.maxResults === undefined) throw new Error("git action=ls-ref requires maxResults");
1354
+ const listFiles = await gitOperations.listFiles(directory, params.ref, params.pathspecs, params.maxResults, vehicleCall);
1355
+ const details: GitToolDetails = { action: "ls-ref", listFiles };
1356
+ return { content: [{ type: "text", text: JSON.stringify(listFiles) }], details };
1357
+ }
1358
+ if (params.action === "is-ancestor") {
1359
+ if (!params.ancestorRef || !params.ref) throw new Error("git action=is-ancestor requires ancestorRef and ref");
1360
+ const result = await gitOperations.isAncestor(directory, params.ancestorRef, params.ref, vehicleCall);
1361
+ const details: GitToolDetails = { action: "is-ancestor", isAncestor: { ancestorRef: params.ancestorRef, ref: params.ref, result } };
1362
+ return { content: [{ type: "text", text: JSON.stringify({ isAncestor: result }) }], details };
1363
+ }
1299
1364
  throw new Error(`unknown git action: ${String(params.action)}`);
1300
1365
  },
1301
1366
  renderCall(args, theme, context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-lector",
3
- "version": "0.12.13",
3
+ "version": "0.12.15",
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",