@danypops/pi-lector 0.6.0 → 0.7.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
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
WorkspaceMapResult,
|
|
28
28
|
WorkspaceQueryOutcome,
|
|
29
29
|
} from "@danypops/lector";
|
|
30
|
+
import { DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS } from "@danypops/lector";
|
|
30
31
|
import {
|
|
31
32
|
type AgentToolResult,
|
|
32
33
|
createEditToolDefinition,
|
|
@@ -83,7 +84,7 @@ import { setNewWorkspaceObserver } from "./lector-client.ts";
|
|
|
83
84
|
import { createLectorLineEditOperations } from "./line-edit-operations.ts";
|
|
84
85
|
import { formatLineEditCall, formatLineEditResult } from "./line-edit-rendering.ts";
|
|
85
86
|
import { createMutationHistoryOperations } from "./mutation-history-operations.ts";
|
|
86
|
-
import { nearestGitRoot } from "./nearest-workspace-root.ts";
|
|
87
|
+
import { isFilesystemRoot, nearestGitRoot } from "./nearest-workspace-root.ts";
|
|
87
88
|
import { createLectorPackageSourceOperations } from "./package-source-operations.ts";
|
|
88
89
|
import { formatPackageSourceCall, formatPackageSourceResult } from "./package-source-rendering.ts";
|
|
89
90
|
import { createLectorReadOperations } from "./read-operations.ts";
|
|
@@ -176,8 +177,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
176
177
|
uiContext.ui.setStatus("lector-cache", uiContext.ui.theme.fg(worst, `Lector: ${summary}`));
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Starts (or restarts, on a stale generation) monitoring one workspace root's cache
|
|
182
|
+
* lifecycle -- shared by session_start's own cwd root and every later root a tool call
|
|
183
|
+
* first touches. Refuses a bare filesystem root outright: workspaceForPath's own
|
|
184
|
+
* intentional fallback for a raw read/write of a file outside any git repo can register
|
|
185
|
+
* exactly this as a "new workspace", and auto-populating it would attempt a full
|
|
186
|
+
* filesystem-wide symbol-graph scan -- confirmed live as a real, previously-shipped bug.
|
|
187
|
+
*/
|
|
180
188
|
function startMonitoringRoot(root: string, ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1]): void {
|
|
189
|
+
if (isFilesystemRoot(root)) return;
|
|
181
190
|
if (monitoringRoots.has(root)) return;
|
|
182
191
|
monitoringRoots.add(root);
|
|
183
192
|
const thisGeneration = sessionGeneration;
|
|
@@ -1550,7 +1559,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1550
1559
|
maxResults: Type.Optional(Type.Number({ description: "Maximum candidates to return (default 20)" })),
|
|
1551
1560
|
}),
|
|
1552
1561
|
async execute(_toolCallId, params): Promise<AgentToolResult<ExternalSearchToolDetails>> {
|
|
1553
|
-
const maxResults = params.maxResults ??
|
|
1562
|
+
const maxResults = params.maxResults ?? DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS;
|
|
1554
1563
|
if (params.action === "github_repos") {
|
|
1555
1564
|
const result = await externalSearchOperations.githubRepos(params.query, maxResults);
|
|
1556
1565
|
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "github_repos", result } };
|
|
@@ -2,39 +2,60 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join, parse } from "node:path";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
* a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* This -- not a Pi session's original cwd -- is Lector's real workspace
|
|
15
|
-
* granularity. A session routinely touches many unrelated repos, sibling
|
|
16
|
-
* projects, and scratch paths in one run; pi's built-in read/write/edit
|
|
17
|
-
* tools have never restricted which absolute path can be touched, and
|
|
18
|
-
* Lector must not either. (Real, shipped bug this fixes: read/write/edit
|
|
19
|
-
* hard-locked to whatever directory the session happened to start in,
|
|
20
|
-
* refusing every legitimate path outside it with a "Lector-registered
|
|
21
|
-
* workspace root" error -- discovered live, in a separate session, working
|
|
22
|
-
* against a completely different, unrelated repository.)
|
|
5
|
+
* The bare filesystem root is never a legitimate discovered project root, even if it happens
|
|
6
|
+
* to contain a marker file (a stray `git init /`, a leftover `package.json`) -- matches the
|
|
7
|
+
* same convention already established elsewhere in this house (oculus/survey/rust_scanner.go's
|
|
8
|
+
* findCrateRoot, oculus/locator/match.go's effectiveParent: reaching "/" during a walk-up means
|
|
9
|
+
* "not found", never "found here"). Confirmed live: without this, a Lector daemon registered
|
|
10
|
+
* "/" as a workspace and a background job attempted to symbol-graph the entire filesystem.
|
|
11
|
+
* `exists` is injectable so a test can simulate "a marker exists at the filesystem root"
|
|
12
|
+
* without ever touching the real one.
|
|
23
13
|
*/
|
|
24
|
-
function walkUpForMarkers(startDirectory: string, markers: readonly string[]): string | undefined {
|
|
14
|
+
function walkUpForMarkers(startDirectory: string, markers: readonly string[], exists: (path: string) => boolean = existsSync): string | undefined {
|
|
25
15
|
let dir = startDirectory;
|
|
26
16
|
const fsRoot = parse(dir).root;
|
|
27
17
|
while (dir !== fsRoot) {
|
|
28
|
-
if (markers.some((marker) =>
|
|
18
|
+
if (markers.some((marker) => exists(join(dir, marker)))) return dir;
|
|
29
19
|
const parent = dirname(dir);
|
|
30
20
|
if (parent === dir) break; // defensive: dirname must be strictly ascending
|
|
31
21
|
dir = parent;
|
|
32
22
|
}
|
|
33
|
-
return
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The nearest enclosing git repository root starting from (and including) a given directory,
|
|
28
|
+
* or undefined if none is found (e.g. /tmp scratch files, dotfiles outside any repo, or the
|
|
29
|
+
* walk reaching the filesystem root without a match). Callers choose their own fallback -- see
|
|
30
|
+
* lector-client.ts's workspaceForPath (falls back to the filesystem root: any absolute path is
|
|
31
|
+
* fair game for read/write/edit, exactly as Pi's built-in tools already allow) vs.
|
|
32
|
+
* workspaceForDirectory (falls back to the directory itself: widening a symbol-search scope all
|
|
33
|
+
* the way to the entire filesystem when a project isn't a git repo would be absurd).
|
|
34
|
+
*
|
|
35
|
+
* This -- not a Pi session's original cwd -- is Lector's real workspace granularity. A session
|
|
36
|
+
* routinely touches many unrelated repos, sibling projects, and scratch paths in one run; Pi's
|
|
37
|
+
* built-in read/write/edit tools have never restricted which absolute path can be touched, and
|
|
38
|
+
* Lector must not either. (Real, shipped bug this fixes: read/write/edit hard-locked to
|
|
39
|
+
* whatever directory the session happened to start in, refusing every legitimate path outside
|
|
40
|
+
* it with a "Lector-registered workspace root" error -- discovered live, in a separate session,
|
|
41
|
+
* working against a completely different, unrelated repository.)
|
|
42
|
+
*
|
|
43
|
+
* `exists` is injectable for tests -- see walkUpForMarkers.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* True for the bare filesystem root itself ("/" on Linux/macOS, "C:\\" on Windows) -- the one
|
|
47
|
+
* path a caller must never treat as a real project to auto-index. workspaceForPath's own
|
|
48
|
+
* intentional fallback for a raw read/write of a file outside any git repo can still produce
|
|
49
|
+
* this value; callers that trigger background work (auto-population, cache monitoring) off a
|
|
50
|
+
* newly-registered workspace must check this explicitly rather than assuming
|
|
51
|
+
* nearestGitRoot/nearestProjectRoot are the only paths that can hand them a workspace root.
|
|
52
|
+
*/
|
|
53
|
+
export function isFilesystemRoot(path: string): boolean {
|
|
54
|
+
return parse(path).root === path;
|
|
34
55
|
}
|
|
35
56
|
|
|
36
|
-
export function nearestGitRoot(startDirectory: string): string | undefined {
|
|
37
|
-
return walkUpForMarkers(startDirectory, [".git"]);
|
|
57
|
+
export function nearestGitRoot(startDirectory: string, exists: (path: string) => boolean = existsSync): string | undefined {
|
|
58
|
+
return walkUpForMarkers(startDirectory, [".git"], exists);
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
/**
|
|
@@ -45,6 +66,6 @@ export function nearestGitRoot(startDirectory: string): string | undefined {
|
|
|
45
66
|
* misattributes its whole project to the repo root, handing the language server the wrong
|
|
46
67
|
* rootUri (and, for TypeScript, the wrong tsconfig.json) even though a closer one exists.
|
|
47
68
|
*/
|
|
48
|
-
export function nearestProjectRoot(startDirectory: string, rootMarkers: readonly string[]): string | undefined {
|
|
49
|
-
return walkUpForMarkers(startDirectory, [...rootMarkers, ".git"]);
|
|
69
|
+
export function nearestProjectRoot(startDirectory: string, rootMarkers: readonly string[], exists: (path: string) => boolean = existsSync): string | undefined {
|
|
70
|
+
return walkUpForMarkers(startDirectory, [...rootMarkers, ".git"], exists);
|
|
50
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.8.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@earendil-works/pi-ai": "^0.81.1",
|