@danypops/pi-lector 0.2.0 → 0.2.1

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,5 +1,5 @@
1
1
  import type { JobSnapshot, OperationInputs, OperationOutputs, PopulateSymbolGraphResult, SymbolEdgeKind, SymbolNode } from "@danypops/lector";
2
- import { lectorClient, withWorkspace, workspaceForCodeIntelligencePath } from "./lector-client.ts";
2
+ import { lectorClient, withWorkspace, workspaceForCodeIntelligencePath, workspaceForPathOrDirectory } from "./lector-client.ts";
3
3
 
4
4
  /**
5
5
  * Thin wrappers over Lector's code-intelligence operations: goToDefinition,
@@ -13,6 +13,13 @@ import { lectorClient, withWorkspace, workspaceForCodeIntelligencePath } from ".
13
13
  * falling back to the file's own containing directory, never the filesystem
14
14
  * root -- every one of these operations spawns a real language server,
15
15
  * unlike read/write/edit) -- never a value captured once at session start.
16
+ *
17
+ * Exception: populateSymbolGraph, workspaceMap, and hasWarmIndex are not file-anchored --
18
+ * their `path` genuinely means "the project itself", so they resolve via
19
+ * workspaceForPathOrDirectory instead, which does not blindly take dirname() first. A real,
20
+ * confirmed live bug: passing a project's own root directory through workspaceForCodeIntelligencePath
21
+ * silently resolved to that directory's *parent* (dirname strips the final segment even when the
22
+ * given path was already a directory with its own .git), mixing in every sibling project's graph.
16
23
  */
17
24
  export interface CodeIntelligenceOperations {
18
25
  goToDefinition(path: string, line: number, character: number): Promise<OperationOutputs["workspace.goToDefinition"]>;
@@ -123,7 +130,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
123
130
  },
124
131
  async populateSymbolGraph(path, maxFiles, maxSymbolsPerFile, waitMs = 500) {
125
132
  return withWorkspace(
126
- () => workspaceForCodeIntelligencePath(path),
133
+ () => workspaceForPathOrDirectory(path),
127
134
  async ({ workspaceId }) => {
128
135
  const client = await lectorClient();
129
136
  const { job } = await client.call("job.submit", {
@@ -152,7 +159,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
152
159
  },
153
160
  async hasWarmIndex(path) {
154
161
  return withWorkspace(
155
- () => workspaceForCodeIntelligencePath(path),
162
+ () => workspaceForPathOrDirectory(path),
156
163
  async ({ workspaceId }) => {
157
164
  const client = await lectorClient();
158
165
  const { warm } = await client.call("workspace.hasWarmIndex", { workspaceId });
@@ -162,7 +169,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
162
169
  },
163
170
  async workspaceMap(path, maxNodes, maxEdges, maxEntries, maxBytes) {
164
171
  return withWorkspace(
165
- () => workspaceForCodeIntelligencePath(path),
172
+ () => workspaceForPathOrDirectory(path),
166
173
  async ({ workspaceId }) => {
167
174
  const client = await lectorClient();
168
175
  return client.call("workspace.map", { workspaceId, maxNodes, maxEdges, maxEntries, maxBytes });
@@ -1,3 +1,4 @@
1
+ import { existsSync, statSync } from "node:fs";
1
2
  import { dirname, parse } from "node:path";
2
3
  import { createRetryingClient, type RetryingClient } from "@danypops/daemon-kit/pi-client";
3
4
  import {
@@ -111,6 +112,24 @@ export function workspaceForCodeIntelligencePath(absolutePath: string): Promise<
111
112
  return workspaceForDirectory(dirname(absolutePath));
112
113
  }
113
114
 
115
+ /**
116
+ * For an operation whose `path` genuinely means "the project/workspace itself"
117
+ * (populateSymbolGraph, workspaceMap, hasWarmIndex) rather than one specific file
118
+ * to act on -- unlike workspaceForCodeIntelligencePath, does NOT blindly take
119
+ * dirname() first. A real, confirmed live bug: passing a project's own root
120
+ * directory (e.g. "/repo", which has its own .git right there) through
121
+ * dirname() strips its final segment, silently resolving to the *parent*
122
+ * directory's own nearest git root instead -- for a project nested one level
123
+ * under a broader already-registered workspace, this mixes in every sibling
124
+ * project's own graph, with no error at all. Checks whether the path is
125
+ * itself a real, existing directory first; only takes dirname() when it is
126
+ * not (a file, or a not-yet-existing path).
127
+ */
128
+ export function workspaceForPathOrDirectory(path: string): Promise<ResolvedWorkspace> {
129
+ const isRealDirectory = existsSync(path) && statSync(path).isDirectory();
130
+ return workspaceForDirectory(isRealDirectory ? path : dirname(path));
131
+ }
132
+
114
133
  /**
115
134
  * Resolves a workspace via `resolve`, then calls `perform` with it. A daemon
116
135
  * restart wipes its in-memory workspace registry (workspace ids are not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-lector",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",