@danypops/pi-lector 0.4.0 → 0.5.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.
package/extension/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import type {
|
|
3
|
+
CachedRepositoryPage,
|
|
3
4
|
ContentHash,
|
|
4
5
|
Diagnostic,
|
|
5
6
|
DocumentSymbolEntry,
|
|
@@ -78,9 +79,10 @@ import { formatPackageSourceCall, formatPackageSourceResult } from "./package-so
|
|
|
78
79
|
import { createLectorReadOperations } from "./read-operations.ts";
|
|
79
80
|
import { createReferenceBasedRenameOperations } from "./reference-based-rename-operations.ts";
|
|
80
81
|
import { createRenameOperations } from "./rename-operations.ts";
|
|
82
|
+
import { createRepoCacheEvictOperations } from "./repo-cache-evict-operations.ts";
|
|
81
83
|
import { createRepoCacheListOperations } from "./repo-cache-list-operations.ts";
|
|
84
|
+
import { formatRepoCacheCall, formatRepoCacheEvictResult, formatRepoCacheListResult, formatRepoFetchResult } from "./repo-cache-rendering.ts";
|
|
82
85
|
import { createLectorRepoFetchOperations } from "./repo-fetch-operations.ts";
|
|
83
|
-
import { formatRepoFetchCall, formatRepoFetchResult } from "./repo-fetch-rendering.ts";
|
|
84
86
|
import { createLectorSearchOperations } from "./search-operations.ts";
|
|
85
87
|
import { formatSearchCall, formatSearchResult } from "./search-rendering.ts";
|
|
86
88
|
import { type AnnotationAnchorInput, createLectorSymbolAnnotationOperations } from "./symbol-annotation-operations.ts";
|
|
@@ -1445,88 +1447,75 @@ export default function (pi: ExtensionAPI) {
|
|
|
1445
1447
|
},
|
|
1446
1448
|
});
|
|
1447
1449
|
|
|
1450
|
+
type RepoCacheToolDetails =
|
|
1451
|
+
| { readonly action: "fetch"; readonly result: RepoFetchResult & { workspaceId: string } }
|
|
1452
|
+
| { readonly action: "list"; readonly page: CachedRepositoryPage }
|
|
1453
|
+
| { readonly action: "evict"; readonly result: { evicted: boolean } };
|
|
1454
|
+
|
|
1448
1455
|
const repoFetchOperations = createLectorRepoFetchOperations();
|
|
1449
1456
|
const repoCacheListOperations = createRepoCacheListOperations();
|
|
1457
|
+
const repoCacheEvictOperations = createRepoCacheEvictOperations();
|
|
1450
1458
|
pi.registerTool({
|
|
1451
|
-
name: "
|
|
1452
|
-
label: "Repo
|
|
1459
|
+
name: "repo_cache",
|
|
1460
|
+
label: "Repo Cache",
|
|
1453
1461
|
description:
|
|
1454
|
-
"
|
|
1455
|
-
promptSnippet: "Fetch
|
|
1462
|
+
"Fetch, list/query, or evict entries in Lector's external-repo cache. action=fetch shallow-clones an external repository into a disk-bounded cache and registers it as a read-only project -- every other tool (search_code, find_symbols, go_to_definition, ...) then works on it unchanged; explicit owner/repo[@ref] only, no discovery/search (use web_fetch to find candidates first); forceRefresh reclones even when an unexpired cache entry already exists, for a caller that has already positively confirmed the remote moved. action=list queries the cache -- no network call, no mutation -- filtering by any combination of host/owner/repo/ref (exact match) and text (case-insensitive substring), bounded and paginated via cursor; each entry reports whether it's currently a registered workspace or just present on disk. action=evict removes one cache entry from disk by its exact host/owner/repo/ref identity; refuses with a clear error if it is still a currently-registered workspace.",
|
|
1463
|
+
promptSnippet: "Fetch, list, or evict entries in the external-repo cache",
|
|
1456
1464
|
parameters: Type.Object({
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1465
|
+
action: Type.Union([Type.Literal("fetch"), Type.Literal("list"), Type.Literal("evict")]),
|
|
1466
|
+
owner: Type.Optional(Type.String({ description: "Required for action=fetch/evict -- repository owner or organization" })),
|
|
1467
|
+
repo: Type.Optional(Type.String({ description: "Required for action=fetch/evict -- repository name" })),
|
|
1468
|
+
ref: Type.Optional(
|
|
1469
|
+
Type.String({
|
|
1470
|
+
description:
|
|
1471
|
+
"fetch/evict: branch, tag, or commit; defaults to the repository's default branch. list: matches either the requested or the resolved ref",
|
|
1472
|
+
}),
|
|
1473
|
+
),
|
|
1474
|
+
host: Type.Optional(Type.String({ description: "Git host; defaults to github.com for fetch/evict, unfiltered for list" })),
|
|
1475
|
+
forceRefresh: Type.Optional(Type.Boolean({ description: "action=fetch only -- reclone even if an unexpired cache entry already exists" })),
|
|
1476
|
+
text: Type.Optional(Type.String({ description: "action=list only -- case-insensitive substring match across host/owner/repo/refs" })),
|
|
1477
|
+
maxResults: Type.Optional(Type.Number({ description: "Required for action=list -- maximum entries to return in this page" })),
|
|
1478
|
+
cursor: Type.Optional(Type.String({ description: "action=list only -- opaque cursor from a prior call's nextCursor, to fetch the next page" })),
|
|
1461
1479
|
}),
|
|
1462
|
-
async execute(_toolCallId, params) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1468
|
-
text.setText(formatRepoFetchCall(args, theme));
|
|
1469
|
-
return text;
|
|
1470
|
-
},
|
|
1471
|
-
renderResult(result, { isPartial }, theme, context) {
|
|
1472
|
-
if (isPartial) return new Text(theme.fg("warning", "Fetching repository..."), 0, 0);
|
|
1473
|
-
if (context.isError) {
|
|
1474
|
-
const errorText = result.content
|
|
1475
|
-
.filter((block) => block.type === "text")
|
|
1476
|
-
.map((block) => block.text)
|
|
1477
|
-
.join("\n");
|
|
1478
|
-
return new Text(theme.fg("error", errorText || "repo_fetch failed"), 0, 0);
|
|
1480
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<RepoCacheToolDetails>> {
|
|
1481
|
+
if (params.action === "fetch") {
|
|
1482
|
+
if (!params.owner || !params.repo) throw new Error("repo_cache action=fetch requires owner and repo");
|
|
1483
|
+
const result = await repoFetchOperations.fetch(params.host ?? "github.com", params.owner, params.repo, params.ref ?? null, params.forceRefresh);
|
|
1484
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "fetch", result } };
|
|
1479
1485
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
pi.registerTool({
|
|
1488
|
-
name: "repo_cache_list",
|
|
1489
|
-
label: "Repo Cache List",
|
|
1490
|
-
description:
|
|
1491
|
-
"Lists and queries repo_fetch's own on-disk cache -- no network call, no cache mutation. Filter by any combination of host/owner/repo/ref (exact match) and text (case-insensitive substring across host/owner/repo/refs). Each entry reports whether it's currently a registered workspace (usable directly by other tools) or just present on disk. Bounded and paginated via cursor.",
|
|
1492
|
-
promptSnippet: "List or search previously-fetched external repositories",
|
|
1493
|
-
parameters: Type.Object({
|
|
1494
|
-
text: Type.Optional(Type.String({ description: "Case-insensitive substring match across host/owner/repo/refs" })),
|
|
1495
|
-
host: Type.Optional(Type.String({ description: "Exact host match, e.g. github.com" })),
|
|
1496
|
-
owner: Type.Optional(Type.String()),
|
|
1497
|
-
repo: Type.Optional(Type.String()),
|
|
1498
|
-
ref: Type.Optional(Type.String({ description: "Matches either the requested or the resolved ref" })),
|
|
1499
|
-
maxResults: Type.Number({ description: "Maximum entries to return in this page" }),
|
|
1500
|
-
cursor: Type.Optional(Type.String({ description: "Opaque cursor from a prior call's nextCursor, to fetch the next page" })),
|
|
1501
|
-
}),
|
|
1502
|
-
async execute(_toolCallId, params) {
|
|
1486
|
+
if (params.action === "evict") {
|
|
1487
|
+
if (!params.owner || !params.repo) throw new Error("repo_cache action=evict requires owner and repo");
|
|
1488
|
+
const result = await repoCacheEvictOperations.evict(params.host ?? "github.com", params.owner, params.repo, params.ref ?? null);
|
|
1489
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "evict", result } };
|
|
1490
|
+
}
|
|
1491
|
+
if (params.maxResults === undefined) throw new Error("repo_cache action=list requires maxResults");
|
|
1503
1492
|
const page = await repoCacheListOperations.list(
|
|
1504
1493
|
{ text: params.text, host: params.host, owner: params.owner, repo: params.repo, ref: params.ref },
|
|
1505
1494
|
params.maxResults,
|
|
1506
1495
|
params.cursor,
|
|
1507
1496
|
);
|
|
1508
|
-
return { content: [{ type: "text", text: JSON.stringify(page) }], details: { page } };
|
|
1497
|
+
return { content: [{ type: "text", text: JSON.stringify(page) }], details: { action: "list", page } };
|
|
1509
1498
|
},
|
|
1510
|
-
renderCall(
|
|
1499
|
+
renderCall(args, theme, context) {
|
|
1500
|
+
const action = args.action === "list" || args.action === "evict" ? args.action : "fetch";
|
|
1511
1501
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1512
|
-
text.setText(
|
|
1502
|
+
text.setText(formatRepoCacheCall(action, args, theme));
|
|
1513
1503
|
return text;
|
|
1514
1504
|
},
|
|
1515
1505
|
renderResult(result, { isPartial }, theme, context) {
|
|
1516
|
-
if (isPartial) return new Text(theme.fg("warning", "
|
|
1506
|
+
if (isPartial) return new Text(theme.fg("warning", "Working on repo cache..."), 0, 0);
|
|
1517
1507
|
if (context.isError) {
|
|
1518
1508
|
const errorText = result.content
|
|
1519
1509
|
.filter((block) => block.type === "text")
|
|
1520
1510
|
.map((block) => block.text)
|
|
1521
1511
|
.join("\n");
|
|
1522
|
-
return new Text(theme.fg("error", errorText || "
|
|
1512
|
+
return new Text(theme.fg("error", errorText || "repo_cache failed"), 0, 0);
|
|
1523
1513
|
}
|
|
1524
|
-
const details = result.details as
|
|
1525
|
-
| { page?: { entries: readonly { host: string; owner: string; repo: string }[]; nextCursor: string | null } }
|
|
1526
|
-
| undefined;
|
|
1514
|
+
const details = result.details as RepoCacheToolDetails | undefined;
|
|
1527
1515
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1528
|
-
|
|
1529
|
-
|
|
1516
|
+
if (details?.action === "list") text.setText(formatRepoCacheListResult(details.page, theme));
|
|
1517
|
+
else if (details?.action === "evict") text.setText(formatRepoCacheEvictResult(details.result, theme));
|
|
1518
|
+
else text.setText(formatRepoFetchResult(details?.action === "fetch" ? details.result : undefined, theme));
|
|
1530
1519
|
return text;
|
|
1531
1520
|
},
|
|
1532
1521
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { lectorClient } from "./lector-client.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thin wrapper over repo.evictCache -- no `directory`/workspaceForDirectory resolution (matching
|
|
5
|
+
* repo-fetch-operations.ts and repo-cache-list-operations.ts: this targets the daemon-wide fetch
|
|
6
|
+
* cache, not a workspace-scoped concept).
|
|
7
|
+
*/
|
|
8
|
+
export interface RepoCacheEvictOperations {
|
|
9
|
+
evict(host: string, owner: string, repo: string, ref: string | null): Promise<{ evicted: boolean }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createRepoCacheEvictOperations(): RepoCacheEvictOperations {
|
|
13
|
+
return {
|
|
14
|
+
async evict(host, owner, repo, ref) {
|
|
15
|
+
const client = await lectorClient();
|
|
16
|
+
return client.call("repo.evictCache", { host, owner, repo, ref });
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CachedRepositoryPage, RepoFetchResult } from "@danypops/lector";
|
|
2
|
+
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
+
|
|
4
|
+
type RepoCacheAction = "fetch" | "list" | "evict";
|
|
5
|
+
|
|
6
|
+
export function formatRepoCacheCall(
|
|
7
|
+
action: RepoCacheAction,
|
|
8
|
+
args: { owner?: unknown; repo?: unknown; ref?: unknown; host?: unknown; text?: unknown },
|
|
9
|
+
theme: LectorTheme,
|
|
10
|
+
): string {
|
|
11
|
+
const label = theme.fg("toolTitle", theme.bold("repo_cache"));
|
|
12
|
+
if (action === "list") {
|
|
13
|
+
const filter = typeof args.text === "string" && args.text.length > 0 ? args.text : typeof args.repo === "string" ? args.repo : "";
|
|
14
|
+
return `${label} ${theme.fg("accent", "list")}${filter ? ` ${theme.fg("dim", filter)}` : ""}`;
|
|
15
|
+
}
|
|
16
|
+
const host = typeof args.host === "string" && args.host.length > 0 ? args.host : "github.com";
|
|
17
|
+
const owner = typeof args.owner === "string" ? args.owner : "";
|
|
18
|
+
const repo = typeof args.repo === "string" ? args.repo : "";
|
|
19
|
+
const ref = typeof args.ref === "string" ? `@${args.ref}` : "";
|
|
20
|
+
return `${label} ${theme.fg("accent", action)} ${theme.fg("dim", `${host}/${owner}/${repo}${ref}`)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatRepoFetchResult(result: (RepoFetchResult & { workspaceId: string }) | undefined, theme: LectorTheme): string {
|
|
24
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
25
|
+
const lines = [
|
|
26
|
+
`${theme.fg("accent", result.workspaceId)} ${result.fromCache ? theme.fg("dim", "(from cache)") : theme.fg("toolTitle", "(fetched)")} -- ${result.path}`,
|
|
27
|
+
];
|
|
28
|
+
if (result.refFallbackOccurred) {
|
|
29
|
+
lines.push(theme.fg("warning", `requested ref not found; fell back to the default branch (resolved: ${result.resolvedRef})`));
|
|
30
|
+
}
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatRepoCacheListResult(page: CachedRepositoryPage | undefined, theme: LectorTheme): string {
|
|
35
|
+
const count = page?.entries.length ?? 0;
|
|
36
|
+
return count === 0 ? theme.fg("dim", "no cached repositories") : theme.fg("success", `${count} cached repositor${count === 1 ? "y" : "ies"}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatRepoCacheEvictResult(result: { evicted: boolean } | undefined, theme: LectorTheme): string {
|
|
40
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
41
|
+
return result.evicted ? theme.fg("success", "evicted") : theme.fg("dim", "nothing cached for that reference");
|
|
42
|
+
}
|
|
@@ -7,14 +7,14 @@ import { lectorClient } from "./lector-client.ts";
|
|
|
7
7
|
* creates a new registered workspace from a fetched external repo.
|
|
8
8
|
*/
|
|
9
9
|
export interface RepoFetchOperations {
|
|
10
|
-
fetch(host: string, owner: string, repo: string, ref: string | null): Promise<RepoFetchResult & { workspaceId: string }>;
|
|
10
|
+
fetch(host: string, owner: string, repo: string, ref: string | null, forceRefresh?: boolean): Promise<RepoFetchResult & { workspaceId: string }>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function createLectorRepoFetchOperations(): RepoFetchOperations {
|
|
14
14
|
return {
|
|
15
|
-
async fetch(host, owner, repo, ref) {
|
|
15
|
+
async fetch(host, owner, repo, ref, forceRefresh) {
|
|
16
16
|
const client = await lectorClient();
|
|
17
|
-
return client.call("repo.fetch", { host, owner, repo, ref });
|
|
17
|
+
return client.call("repo.fetch", { host, owner, repo, ref, forceRefresh });
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@danypops/daemon-kit": "^0.22.1",
|
|
22
|
-
"@danypops/lector": "^0.
|
|
22
|
+
"@danypops/lector": "^0.6.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@earendil-works/pi-ai": "^0.81.1",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { RepoFetchResult } from "@danypops/lector";
|
|
2
|
-
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
-
|
|
4
|
-
export function formatRepoFetchCall(args: { owner?: unknown; repo?: unknown; ref?: unknown; host?: unknown }, theme: LectorTheme): string {
|
|
5
|
-
const host = typeof args.host === "string" && args.host.length > 0 ? args.host : "github.com";
|
|
6
|
-
const owner = typeof args.owner === "string" ? args.owner : "";
|
|
7
|
-
const repo = typeof args.repo === "string" ? args.repo : "";
|
|
8
|
-
const ref = typeof args.ref === "string" ? `@${args.ref}` : "";
|
|
9
|
-
return `${theme.fg("toolTitle", theme.bold("repo_fetch"))} ${theme.fg("accent", `${host}/${owner}/${repo}${ref}`)}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function formatRepoFetchResult(result: (RepoFetchResult & { workspaceId: string }) | undefined, theme: LectorTheme): string {
|
|
13
|
-
if (!result) return theme.fg("dim", "No result.");
|
|
14
|
-
const lines = [
|
|
15
|
-
`${theme.fg("accent", result.workspaceId)} ${result.fromCache ? theme.fg("dim", "(from cache)") : theme.fg("toolTitle", "(fetched)")} -- ${result.path}`,
|
|
16
|
-
];
|
|
17
|
-
if (result.refFallbackOccurred) {
|
|
18
|
-
lines.push(theme.fg("warning", `requested ref not found; fell back to the default branch (resolved: ${result.resolvedRef})`));
|
|
19
|
-
}
|
|
20
|
-
return lines.join("\n");
|
|
21
|
-
}
|