@shanepadgett/tau-agent 0.27.0 → 0.28.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.
Files changed (113) hide show
  1. package/extensions/context/sync.ts +1 -9
  2. package/extensions/context-pruning/prune.ts +2 -2
  3. package/extensions/explore/README.md +13 -21
  4. package/extensions/explore/ast/adapter.ts +97 -0
  5. package/extensions/explore/ast/engine.ts +470 -0
  6. package/extensions/explore/ast/format/ast-search.ts +97 -0
  7. package/extensions/explore/ast/format/composite.ts +26 -0
  8. package/extensions/explore/ast/format/context.ts +66 -0
  9. package/extensions/explore/ast/format/deps.ts +86 -0
  10. package/extensions/explore/ast/format/discover.ts +85 -0
  11. package/extensions/explore/ast/format/impact.ts +105 -0
  12. package/extensions/explore/ast/format/outline.ts +66 -0
  13. package/extensions/explore/ast/format/relationships.ts +81 -0
  14. package/extensions/explore/ast/format/show.ts +38 -0
  15. package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
  16. package/extensions/explore/ast/grammars/manifest.json +41 -0
  17. package/extensions/explore/ast/grammars/manifest.ts +99 -0
  18. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  19. package/extensions/explore/ast/grammars/swift.wasm +0 -0
  20. package/extensions/explore/ast/graph/file-graph.ts +360 -0
  21. package/extensions/explore/ast/graph/relationships.ts +597 -0
  22. package/extensions/explore/ast/identity.ts +119 -0
  23. package/extensions/explore/ast/ir.ts +130 -0
  24. package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
  25. package/extensions/explore/ast/languages/csharp.ts +493 -0
  26. package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
  27. package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
  28. package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
  29. package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
  30. package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
  31. package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
  32. package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
  33. package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
  34. package/extensions/explore/ast/languages/go.ts +545 -0
  35. package/extensions/explore/ast/languages/java.ts +412 -0
  36. package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
  37. package/extensions/explore/ast/languages/kotlin.ts +611 -0
  38. package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
  39. package/extensions/explore/ast/languages/odin.ts +407 -0
  40. package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
  41. package/extensions/explore/ast/languages/rust.ts +506 -0
  42. package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
  43. package/extensions/explore/ast/languages/swift.ts +466 -0
  44. package/extensions/explore/ast/languages/tree.ts +220 -0
  45. package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
  46. package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
  47. package/extensions/explore/ast/languages/typescript.ts +716 -0
  48. package/extensions/explore/ast/markdown.ts +145 -0
  49. package/extensions/explore/ast/package-surface.ts +42 -0
  50. package/extensions/explore/ast/queries/ast-search.ts +306 -0
  51. package/extensions/explore/ast/queries/composite-target.ts +77 -0
  52. package/extensions/explore/ast/queries/context.ts +406 -0
  53. package/extensions/explore/ast/queries/discover.ts +427 -0
  54. package/extensions/explore/ast/queries/impact.ts +250 -0
  55. package/extensions/explore/ast/queries/outline.ts +203 -0
  56. package/extensions/explore/ast/queries/show.ts +262 -0
  57. package/extensions/explore/ast/query.ts +48 -0
  58. package/extensions/explore/ast/read/hook.ts +125 -0
  59. package/extensions/explore/ast/read/policy.ts +55 -0
  60. package/extensions/explore/ast/registry.ts +120 -0
  61. package/extensions/explore/ast/scan.ts +76 -0
  62. package/extensions/explore/ast/slice.ts +31 -0
  63. package/extensions/explore/ast/tools/ast-search.ts +138 -0
  64. package/extensions/explore/ast/tools/context.ts +126 -0
  65. package/extensions/explore/ast/tools/deps.ts +72 -0
  66. package/extensions/explore/ast/tools/discover.ts +208 -0
  67. package/extensions/explore/ast/tools/impact.ts +135 -0
  68. package/extensions/explore/ast/tools/outline.ts +200 -0
  69. package/extensions/explore/ast/tools/relationships.ts +160 -0
  70. package/extensions/explore/ast/tools/render.ts +121 -0
  71. package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
  72. package/extensions/explore/ast/tools/show.ts +116 -0
  73. package/extensions/explore/guidance.ts +52 -0
  74. package/extensions/explore/index.ts +82 -92
  75. package/extensions/explore/read/autoread.ts +118 -0
  76. package/extensions/explore/settings.ts +49 -31
  77. package/extensions/explore/traverse.ts +254 -91
  78. package/extensions/handoff/index.ts +1 -1
  79. package/extensions/publish/index.ts +18 -85
  80. package/extensions/publish/publish-ui.ts +0 -4
  81. package/extensions/soul/README.md +7 -4
  82. package/extensions/soul/index.ts +11 -5
  83. package/extensions/soul/prompt.ts +31 -13
  84. package/extensions/soul/settings.ts +7 -2
  85. package/extensions/subagent/agents/context-sync.md +6 -10
  86. package/extensions/subagent/agents/review.md +24 -55
  87. package/extensions/subagent/agents/scout.md +38 -115
  88. package/extensions/subagent/run.ts +1 -1
  89. package/extensions/tau-help/help.md +2 -4
  90. package/package.json +6 -4
  91. package/schemas/tau.schema.json +35 -30
  92. package/{extensions/explore → shared}/autoread.ts +21 -7
  93. package/shared/events.ts +1 -1
  94. package/shared/full-file-knowledge.ts +49 -0
  95. package/extensions/explore/ast-guidance.ts +0 -67
  96. package/extensions/explore/ast-languages.ts +0 -61
  97. package/extensions/explore/ast-tools.ts +0 -1994
  98. package/extensions/explore/ast-worker.ts +0 -1025
  99. package/extensions/explore/find.ts +0 -147
  100. package/extensions/explore/full-file-knowledge.ts +0 -234
  101. package/extensions/explore/grep.ts +0 -749
  102. package/extensions/explore/limits.ts +0 -22
  103. package/extensions/explore/ls.ts +0 -113
  104. package/extensions/explore/orientation-state.ts +0 -280
  105. package/extensions/explore/path-display.ts +0 -41
  106. package/extensions/explore/path-tree.ts +0 -150
  107. package/extensions/explore/read-cache.ts +0 -273
  108. package/extensions/explore/read-snapshots.ts +0 -57
  109. package/extensions/explore/read-stats-panel.ts +0 -176
  110. package/extensions/explore/read-stats.ts +0 -138
  111. package/extensions/explore/read.ts +0 -372
  112. package/extensions/explore/result.ts +0 -29
  113. package/native-bin/darwin-arm64/tau-ast +0 -0
@@ -0,0 +1,440 @@
1
+ import { lstat, readFile } from "node:fs/promises";
2
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import type { Decl } from "../ir.ts";
4
+ import type { FileSource } from "../engine.ts";
5
+ import { packageDeclKey, type PackageSurfaceHost, type PackageSurfaceResolver } from "../package-surface.ts";
6
+ import { formatPathForDisplay } from "../../traverse.ts";
7
+
8
+ const RE_EXPORT_DEPTH = 4;
9
+ const TS_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".mtsx"] as const;
10
+
11
+ type PackageEntry = {
12
+ subpath: string;
13
+ file: string;
14
+ };
15
+
16
+ type SurfaceMember = {
17
+ path: string;
18
+ decl: Decl;
19
+ moduleSpecifier: string;
20
+ importName: string;
21
+ };
22
+
23
+ type ReExport =
24
+ | { kind: "star"; specifier: string }
25
+ | { kind: "named"; specifier: string; names: { exported: string; local: string }[] };
26
+
27
+ function isWithin(parent: string, child: string): boolean {
28
+ const rel = relative(resolve(parent), resolve(child));
29
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
30
+ }
31
+
32
+ async function fileExists(path: string): Promise<boolean> {
33
+ try {
34
+ return (await lstat(path)).isFile();
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ async function findPackageJson(startDir: string, stopDir: string): Promise<string | undefined> {
41
+ let dir = resolve(startDir);
42
+ const stop = resolve(stopDir);
43
+ for (;;) {
44
+ const candidate = join(dir, "package.json");
45
+ try {
46
+ if ((await lstat(candidate)).isFile()) return candidate;
47
+ } catch {
48
+ // walk up
49
+ }
50
+ if (dir === stop) return undefined;
51
+ const parent = dirname(dir);
52
+ if (parent === dir) return undefined;
53
+ if (!isWithin(stop, parent) && parent !== stop) return undefined;
54
+ dir = parent;
55
+ }
56
+ }
57
+
58
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
59
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
60
+ return value as Record<string, unknown>;
61
+ }
62
+
63
+ function pushTarget(out: string[], value: unknown): void {
64
+ if (typeof value === "string" && value.length > 0) {
65
+ out.push(value);
66
+ return;
67
+ }
68
+ if (Array.isArray(value)) {
69
+ for (const entry of value) pushTarget(out, entry);
70
+ return;
71
+ }
72
+ const obj = asRecord(value);
73
+ if (obj === undefined) return;
74
+ for (const key of ["types", "import", "default", "require", "node", "browser"]) {
75
+ if (key in obj) pushTarget(out, obj[key]);
76
+ }
77
+ for (const [key, nested] of Object.entries(obj)) {
78
+ if (key.startsWith(".")) continue;
79
+ if (
80
+ key === "types" ||
81
+ key === "import" ||
82
+ key === "default" ||
83
+ key === "require" ||
84
+ key === "node" ||
85
+ key === "browser"
86
+ ) {
87
+ continue;
88
+ }
89
+ pushTarget(out, nested);
90
+ }
91
+ }
92
+
93
+ function exportSubpaths(exportsField: unknown): { subpath: string; targets: string[] }[] {
94
+ if (typeof exportsField === "string" || Array.isArray(exportsField)) {
95
+ const targets: string[] = [];
96
+ pushTarget(targets, exportsField);
97
+ return [{ subpath: ".", targets }];
98
+ }
99
+ const obj = asRecord(exportsField);
100
+ if (obj === undefined) return [];
101
+ const keys = Object.keys(obj);
102
+ const hasSubpaths = keys.some((key) => key === "." || key.startsWith("./"));
103
+ if (!hasSubpaths) {
104
+ const targets: string[] = [];
105
+ pushTarget(targets, exportsField);
106
+ return [{ subpath: ".", targets }];
107
+ }
108
+ const out: { subpath: string; targets: string[] }[] = [];
109
+ for (const [subpath, value] of Object.entries(obj)) {
110
+ if (subpath !== "." && !subpath.startsWith("./")) continue;
111
+ const targets: string[] = [];
112
+ pushTarget(targets, value);
113
+ out.push({ subpath, targets });
114
+ }
115
+ return out;
116
+ }
117
+
118
+ function moduleSpecifierFor(packageName: string, subpath: string): string {
119
+ if (subpath === "." || subpath === "") return packageName;
120
+ if (subpath.startsWith("./")) return `${packageName}/${subpath.slice(2)}`;
121
+ return `${packageName}/${subpath}`;
122
+ }
123
+
124
+ function distToSrcPath(absolute: string): string | undefined {
125
+ const parts = resolve(absolute).split(sep);
126
+ const distAt = parts.lastIndexOf("dist");
127
+ if (distAt <= 0) return undefined;
128
+ const rewritten = parts.slice();
129
+ rewritten[distAt] = "src";
130
+ return rewritten.join(sep);
131
+ }
132
+
133
+ function pushTsStems(out: string[], stem: string): void {
134
+ for (const sourceExt of TS_SOURCE_EXTENSIONS) out.push(`${stem}${sourceExt}`);
135
+ }
136
+
137
+ function sourceCandidatesFromSeed(seed: string): string[] {
138
+ const out = [seed];
139
+ const ext = extname(seed);
140
+ if (ext === ".js" || ext === ".mjs" || ext === ".cjs" || ext === ".jsx") {
141
+ pushTsStems(out, seed.slice(0, -ext.length));
142
+ } else if (seed.endsWith(".d.ts")) {
143
+ pushTsStems(out, seed.slice(0, -".d.ts".length));
144
+ } else if (ext === "") {
145
+ for (const sourceExt of TS_SOURCE_EXTENSIONS) {
146
+ out.push(`${seed}${sourceExt}`, join(seed, `index${sourceExt}`));
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+
152
+ async function firstExistingTs(candidates: readonly string[]): Promise<string | undefined> {
153
+ for (const candidate of candidates) {
154
+ if (!(await fileExists(candidate))) continue;
155
+ const candidateExt = extname(candidate);
156
+ if (candidate.endsWith(".d.ts")) continue;
157
+ if (candidateExt === ".js" || candidateExt === ".mjs" || candidateExt === ".cjs" || candidateExt === ".jsx") {
158
+ continue;
159
+ }
160
+ if (TS_SOURCE_EXTENSIONS.includes(candidateExt as (typeof TS_SOURCE_EXTENSIONS)[number])) {
161
+ return candidate;
162
+ }
163
+ }
164
+ return undefined;
165
+ }
166
+
167
+ /** Relative module resolve for re-exports. */
168
+ async function resolveRelativeSource(baseDir: string, target: string): Promise<string | undefined> {
169
+ if (target.startsWith("node:") || target.startsWith("data:")) return undefined;
170
+ if (!target.startsWith("./") && !target.startsWith("../")) return undefined;
171
+ const absolute = resolve(baseDir, target);
172
+ const seeds = [absolute];
173
+ const fromDist = distToSrcPath(absolute);
174
+ if (fromDist !== undefined) seeds.push(fromDist);
175
+ return firstExistingTs(seeds.flatMap((seed) => sourceCandidatesFromSeed(seed)));
176
+ }
177
+
178
+ async function resolvePackageEntrySource(packageRoot: string, target: string): Promise<string | undefined> {
179
+ if (target.startsWith("node:") || target.startsWith("data:")) return undefined;
180
+ const absolute = resolve(packageRoot, target);
181
+ const seeds = [absolute];
182
+ const fromDist = distToSrcPath(absolute);
183
+ if (fromDist !== undefined) seeds.push(fromDist);
184
+ const slashPath = absolute.split(sep).join("/");
185
+ const srcMarker = "/src/";
186
+ const srcAt = slashPath.lastIndexOf(srcMarker);
187
+ if (srcAt >= 0) {
188
+ const tail = slashPath.slice(srcAt + srcMarker.length);
189
+ seeds.push(resolve(packageRoot, "src", ...tail.split("/").filter((p) => p.length > 0)));
190
+ }
191
+ for (const sourceExt of TS_SOURCE_EXTENSIONS) {
192
+ seeds.push(join(packageRoot, `src/index${sourceExt}`));
193
+ seeds.push(join(packageRoot, `index${sourceExt}`));
194
+ }
195
+ return firstExistingTs(seeds.flatMap((seed) => sourceCandidatesFromSeed(seed)));
196
+ }
197
+
198
+ async function resolvePackageEntries(packageRoot: string, manifest: Record<string, unknown>): Promise<PackageEntry[]> {
199
+ const out: PackageEntry[] = [];
200
+ const seen = new Set<string>();
201
+ const add = async (subpath: string, target: string): Promise<void> => {
202
+ const file = await resolvePackageEntrySource(packageRoot, target);
203
+ if (file === undefined) return;
204
+ const key = `${subpath}\0${file}`;
205
+ if (seen.has(key)) return;
206
+ seen.add(key);
207
+ out.push({ subpath, file });
208
+ };
209
+ if (manifest.exports !== undefined) {
210
+ for (const entry of exportSubpaths(manifest.exports)) {
211
+ for (const target of entry.targets) await add(entry.subpath, target);
212
+ }
213
+ }
214
+ if (out.length === 0) {
215
+ for (const field of ["types", "module", "main"] as const) {
216
+ const value = manifest[field];
217
+ if (typeof value === "string") await add(".", value);
218
+ }
219
+ }
220
+ return out;
221
+ }
222
+
223
+ function parseReExports(source: string): ReExport[] {
224
+ const out: ReExport[] = [];
225
+ const text = source.replace(/\/\*[\s\S]*?\*\//gu, " ").replace(/(^|[^:])\/\/.*$/gmu, "$1");
226
+ for (const match of text.matchAll(
227
+ /\bexport\s+(?:type\s+)?\*\s+(?:as\s+[A-Za-z_$][\w$]*\s+)?from\s+["']([^"']+)["']/gu,
228
+ )) {
229
+ const specifier = match[1];
230
+ if (specifier !== undefined) out.push({ kind: "star", specifier });
231
+ }
232
+ for (const match of text.matchAll(/\bexport\s+(?:type\s+)?\{([^}]+)\}\s*from\s+["']([^"']+)["']/gu)) {
233
+ const body = match[1];
234
+ const specifier = match[2];
235
+ if (body === undefined || specifier === undefined) continue;
236
+ const names: { exported: string; local: string }[] = [];
237
+ for (const part of body.split(",")) {
238
+ const cleaned = part
239
+ .trim()
240
+ .replace(/^type\s+/u, "")
241
+ .trim();
242
+ if (cleaned.length === 0) continue;
243
+ const asMatch = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/u.exec(cleaned);
244
+ if (asMatch !== null) {
245
+ const local = asMatch[1];
246
+ const exported = asMatch[2];
247
+ if (local !== undefined && exported !== undefined) names.push({ local, exported });
248
+ continue;
249
+ }
250
+ const id = /^([A-Za-z_$][\w$]*)$/u.exec(cleaned);
251
+ const name = id?.[1];
252
+ if (name !== undefined) names.push({ local: name, exported: name });
253
+ }
254
+ if (names.length > 0) out.push({ kind: "named", specifier, names });
255
+ }
256
+ return out;
257
+ }
258
+
259
+ function formatImportAccess(moduleSpecifier: string, importName: string): string {
260
+ if (importName === "default") return `import defaultExport from "${moduleSpecifier}"`;
261
+ return `import { ${importName} } from "${moduleSpecifier}"`;
262
+ }
263
+
264
+ function addSurfaceMember(into: Map<string, SurfaceMember>, member: SurfaceMember): void {
265
+ const key = packageDeclKey(member.path, member.decl);
266
+ if (!into.has(key)) into.set(key, member);
267
+ }
268
+
269
+ /** Exported roots + public members under exported owners (IR-level; used after TS entry resolve). */
270
+ function collectExportedTree(
271
+ into: Map<string, SurfaceMember>,
272
+ file: FileSource,
273
+ moduleSpecifier: string,
274
+ importNameFor: (decl: Decl) => string | undefined,
275
+ ): void {
276
+ const go = (decls: readonly Decl[], rootImportName: string | undefined): void => {
277
+ for (const decl of decls) {
278
+ let nextRoot = rootImportName;
279
+ if (rootImportName === undefined && decl.exported) {
280
+ const importName = importNameFor(decl);
281
+ if (importName !== undefined) {
282
+ addSurfaceMember(into, {
283
+ path: file.ir.path,
284
+ decl,
285
+ moduleSpecifier,
286
+ importName,
287
+ });
288
+ nextRoot = importName;
289
+ }
290
+ } else if (rootImportName !== undefined && decl.visibility === "public") {
291
+ addSurfaceMember(into, {
292
+ path: file.ir.path,
293
+ decl,
294
+ moduleSpecifier,
295
+ importName: rootImportName,
296
+ });
297
+ }
298
+ go(decl.children, nextRoot);
299
+ }
300
+ };
301
+ go(file.ir.decls, undefined);
302
+ }
303
+
304
+ async function collectFromEntries(
305
+ host: PackageSurfaceHost,
306
+ scopeRoot: string,
307
+ packageName: string,
308
+ entries: readonly PackageEntry[],
309
+ signal: AbortSignal,
310
+ ): Promise<Map<string, SurfaceMember>> {
311
+ const members = new Map<string, SurfaceMember>();
312
+ type QueueItem = {
313
+ file: string;
314
+ depth: number;
315
+ moduleSpecifier: string;
316
+ nameFilter: ReadonlyMap<string, string> | undefined;
317
+ };
318
+ const queue: QueueItem[] = entries.map((entry) => ({
319
+ file: entry.file,
320
+ depth: 0,
321
+ moduleSpecifier: moduleSpecifierFor(packageName, entry.subpath),
322
+ nameFilter: undefined,
323
+ }));
324
+ const seen = new Set<string>();
325
+
326
+ while (queue.length > 0) {
327
+ signal.throwIfAborted();
328
+ const item = queue.shift();
329
+ if (item === undefined) break;
330
+ const filterKey =
331
+ item.nameFilter === undefined
332
+ ? "*"
333
+ : [...item.nameFilter.entries()]
334
+ .map(([local, exported]) => `${local}>${exported}`)
335
+ .sort()
336
+ .join(",");
337
+ const visitKey = `${item.file}\0${item.moduleSpecifier}\0${filterKey}`;
338
+ if (seen.has(visitKey)) continue;
339
+ seen.add(visitKey);
340
+
341
+ if (!host.ownsPath(item.file)) continue;
342
+
343
+ let file: FileSource;
344
+ try {
345
+ file = await host.sourceForFile(item.file);
346
+ } catch {
347
+ continue;
348
+ }
349
+
350
+ if (isWithin(scopeRoot, file.ir.path)) {
351
+ collectExportedTree(members, file, item.moduleSpecifier, (decl) =>
352
+ item.nameFilter === undefined ? decl.name : item.nameFilter.get(decl.name),
353
+ );
354
+ }
355
+
356
+ if (item.depth >= RE_EXPORT_DEPTH) continue;
357
+ for (const re of parseReExports(file.source)) {
358
+ const target = await resolveRelativeSource(dirname(file.ir.path), re.specifier);
359
+ if (target === undefined) continue;
360
+ if (re.kind === "star") {
361
+ queue.push({
362
+ file: target,
363
+ depth: item.depth + 1,
364
+ moduleSpecifier: item.moduleSpecifier,
365
+ nameFilter: item.nameFilter,
366
+ });
367
+ continue;
368
+ }
369
+ const names = new Map<string, string>();
370
+ for (const name of re.names) {
371
+ if (item.nameFilter === undefined) {
372
+ names.set(name.local, name.exported);
373
+ continue;
374
+ }
375
+ const exposedAs = item.nameFilter.get(name.exported);
376
+ if (exposedAs === undefined) continue;
377
+ names.set(name.local, exposedAs);
378
+ }
379
+ if (names.size === 0) continue;
380
+ queue.push({
381
+ file: target,
382
+ depth: item.depth + 1,
383
+ moduleSpecifier: item.moduleSpecifier,
384
+ nameFilter: names,
385
+ });
386
+ }
387
+ }
388
+
389
+ return members;
390
+ }
391
+
392
+ /**
393
+ * Node/TS package surface: nearest package.json exports/main, dist→src, cheap re-export walk.
394
+ * Shared by typescript + tsx adapters (same resolver function identity).
395
+ */
396
+ export const resolveTypescriptPackageSurface: PackageSurfaceResolver = async (scopeRoot, host, signal) => {
397
+ const packageJsonPath = await findPackageJson(scopeRoot, host.cwd);
398
+ if (packageJsonPath === undefined) return undefined;
399
+
400
+ let manifest: Record<string, unknown>;
401
+ try {
402
+ manifest = JSON.parse(await readFile(packageJsonPath, "utf8")) as Record<string, unknown>;
403
+ } catch {
404
+ throw new Error(`packageSurface could not parse ${formatPathForDisplay(packageJsonPath, host.cwd)}`);
405
+ }
406
+ const packageName = typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : undefined;
407
+ if (packageName === undefined) {
408
+ throw new Error("packageSurface requires package.json name");
409
+ }
410
+
411
+ const packageRoot = dirname(packageJsonPath);
412
+ const entries = await resolvePackageEntries(packageRoot, manifest);
413
+ const owned = entries.filter((entry) => host.ownsPath(entry.file));
414
+ if (owned.length === 0) {
415
+ // package.json exists but no resolvable TS/TSX entry in scope ownership.
416
+ return {
417
+ declKeys: new Set(),
418
+ accessByDecl: new Map(),
419
+ paths: [],
420
+ filesVisited: 0,
421
+ };
422
+ }
423
+
424
+ const members = await collectFromEntries(host, scopeRoot, packageName, owned, signal);
425
+ const declKeys = new Set<string>();
426
+ const accessByDecl = new Map<string, string>();
427
+ const paths = new Set<string>();
428
+ for (const [key, member] of members) {
429
+ declKeys.add(key);
430
+ accessByDecl.set(key, formatImportAccess(member.moduleSpecifier, member.importName));
431
+ paths.add(member.path);
432
+ }
433
+
434
+ return {
435
+ declKeys,
436
+ accessByDecl,
437
+ paths: [...paths],
438
+ filesVisited: paths.size,
439
+ };
440
+ };