@mjasnikovs/pi-task 0.39.5 → 0.40.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.
Files changed (35) hide show
  1. package/README.md +24 -9
  2. package/dist/task/context-attribution.js +18 -6
  3. package/dist/task/external-context.d.ts +8 -1
  4. package/dist/task/external-context.js +52 -4
  5. package/dist/task/phases.js +2 -1
  6. package/dist/task/prompts.d.ts +7 -1
  7. package/dist/task/prompts.js +14 -4
  8. package/dist/workers/docs-cache.js +50 -3
  9. package/dist/workers/docs-chunk.d.ts +6 -3
  10. package/dist/workers/docs-chunk.js +8 -5
  11. package/dist/workers/docs-core.d.ts +27 -3
  12. package/dist/workers/docs-core.js +104 -41
  13. package/dist/workers/docs-ecosystems.d.ts +173 -0
  14. package/dist/workers/docs-ecosystems.js +449 -0
  15. package/dist/workers/docs-index.d.ts +2 -1
  16. package/dist/workers/docs-index.js +55 -27
  17. package/dist/workers/docs-project.d.ts +10 -0
  18. package/dist/workers/docs-project.js +86 -24
  19. package/dist/workers/docs-resolve.d.ts +6 -1
  20. package/dist/workers/docs-resolve.js +4 -3
  21. package/dist/workers/docs-retrieve.d.ts +2 -0
  22. package/dist/workers/docs-retrieve.js +11 -11
  23. package/dist/workers/eco-cargo.d.ts +115 -0
  24. package/dist/workers/eco-cargo.js +793 -0
  25. package/dist/workers/eco-hackage.d.ts +93 -0
  26. package/dist/workers/eco-hackage.js +508 -0
  27. package/dist/workers/npm-version.d.ts +5 -3
  28. package/dist/workers/npm-version.js +6 -4
  29. package/dist/workers/pi-worker-docs.d.ts +18 -4
  30. package/dist/workers/pi-worker-docs.js +57 -19
  31. package/dist/workers/research-cache.d.ts +2 -13
  32. package/dist/workers/research-cache.js +22 -46
  33. package/dist/workers/shared.d.ts +16 -5
  34. package/dist/workers/shared.js +0 -0
  35. package/package.json +1 -1
@@ -5,17 +5,49 @@ import * as path from 'node:path';
5
5
  import { retrieveChunks as defaultRetrieveChunks, PROJECT_RETRIEVE_LIMIT, RETRIEVE_CONTENT_BUDGET } from './docs-retrieve.js';
6
6
  import { buildExtractionPrompt } from './abstention.js';
7
7
  import { chunkDeclarations } from './docs-chunk.js';
8
+ import { ECOSYSTEMS, detectEcosystems } from './docs-ecosystems.js';
8
9
  const DEFAULT_LIMIT = PROJECT_RETRIEVE_LIMIT;
9
10
  const DEFAULT_BUDGET = RETRIEVE_CONTENT_BUDGET;
11
+ /**
12
+ * Scope value for project-source rows. It sits in the same column as a registry
13
+ * id because these rows share the tables, but it is NOT one: a project is keyed
14
+ * by a hash of its cwd, so no registry could name it.
15
+ */
16
+ export const PROJECT_SCOPE = 'project';
17
+ /**
18
+ * The rows whose manifest is present, or npm alone when none is. The npm
19
+ * fallback is what keeps a bare directory indexing its `.ts` exactly as before.
20
+ */
21
+ function projectProfiles(cwd) {
22
+ const detected = detectEcosystems(cwd);
23
+ return detected.length ? detected.map(id => ECOSYSTEMS[id]) : [ECOSYSTEMS.npm];
24
+ }
10
25
  export function getProjectName(cwd) {
11
- try {
12
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
13
- if (pkg.name)
14
- return pkg.name;
26
+ for (const profile of projectProfiles(cwd)) {
27
+ const name = profile.projectName(cwd);
28
+ if (name)
29
+ return name;
15
30
  }
16
- catch { }
17
31
  return path.basename(cwd);
18
32
  }
33
+ /** The file extension a glob like `*.rs` selects. */
34
+ function extensionOf(glob) {
35
+ return glob.replace(/^\*/, '');
36
+ }
37
+ /** The extensions a given project is indexed for — `.ts/.tsx`, `.rs`, `.hs`. */
38
+ export function projectSourceLabel(cwd) {
39
+ return [...new Set(projectProfiles(cwd).flatMap(p => p.projectGlobs))]
40
+ .map(extensionOf)
41
+ .join('/');
42
+ }
43
+ /** Which row chunks a given project file, by its extension. */
44
+ function profileForFile(file, profiles) {
45
+ for (const profile of profiles) {
46
+ if (profile.projectGlobs.some(g => file.endsWith(extensionOf(g))))
47
+ return profile;
48
+ }
49
+ return profiles[0];
50
+ }
19
51
  export function cwdKey(cwd) {
20
52
  return createHash('sha256').update(cwd).digest('hex').slice(0, 8);
21
53
  }
@@ -38,8 +70,9 @@ export function cwdKey(cwd) {
38
70
  * and a temp dir that is not itself inside a repo.
39
71
  */
40
72
  export function getProjectFiles(cwd) {
73
+ const globs = [...new Set(projectProfiles(cwd).flatMap(p => p.projectGlobs))];
41
74
  try {
42
- const result = spawnSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '*.ts', '*.tsx'], { cwd, encoding: 'utf8', timeout: 5000 });
75
+ const result = spawnSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', ...globs], { cwd, encoding: 'utf8', timeout: 5000 });
43
76
  if (result.status === 0 && result.stdout?.trim()) {
44
77
  return result.stdout
45
78
  .trim()
@@ -49,10 +82,25 @@ export function getProjectFiles(cwd) {
49
82
  }
50
83
  }
51
84
  catch { }
52
- return walkTsFiles(cwd);
85
+ return walkSourceFiles(cwd, globs.map(extensionOf));
53
86
  }
54
- function walkTsFiles(root) {
55
- const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage']);
87
+ /**
88
+ * Build output, per ecosystem. NOT `profile.skipDirs` — that list says which
89
+ * directories of a DOWNLOADED package are not its published API, and `tests/`
90
+ * and `examples/` of the project's OWN repo are exactly what a question about
91
+ * the project may be asking. `git ls-files` filters none of them either.
92
+ */
93
+ const BUILD_OUTPUT_DIRS = [
94
+ 'node_modules',
95
+ '.git',
96
+ 'dist',
97
+ 'build',
98
+ 'coverage',
99
+ 'target',
100
+ 'dist-newstyle'
101
+ ];
102
+ function walkSourceFiles(root, extensions) {
103
+ const SKIP = new Set(BUILD_OUTPUT_DIRS);
56
104
  const out = [];
57
105
  const stack = [root];
58
106
  while (stack.length) {
@@ -70,8 +118,7 @@ function walkTsFiles(root) {
70
118
  const full = path.join(dir, entry.name);
71
119
  if (entry.isDirectory())
72
120
  stack.push(full);
73
- else if (entry.isFile()
74
- && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
121
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
75
122
  out.push(full);
76
123
  }
77
124
  }
@@ -91,9 +138,10 @@ export function getMaxMtime(files) {
91
138
  return String(Math.floor(max));
92
139
  }
93
140
  export function ensureProjectIndexed(cache, name, version, files, cwd) {
141
+ const profiles = projectProfiles(cwd);
94
142
  const existing = cache.db
95
- .prepare('SELECT content_hash FROM packages WHERE name = ? AND version = ?')
96
- .get(name, version);
143
+ .prepare('SELECT content_hash FROM packages WHERE ecosystem = ? AND name = ? AND version = ?')
144
+ .get(PROJECT_SCOPE, name, version);
97
145
  if (existing)
98
146
  return { hitCache: true, filesIngested: 0, chunksWritten: 0 };
99
147
  const t0 = Date.now();
@@ -101,9 +149,13 @@ export function ensureProjectIndexed(cache, name, version, files, cwd) {
101
149
  try {
102
150
  // Delete by NAME with no version: unlike the package index, a project
103
151
  // keeps only its newest max-mtime version, so every older one goes.
104
- cache.db.prepare('DELETE FROM chunks WHERE name = ?').run(name);
105
- cache.db.prepare('DELETE FROM packages WHERE name = ?').run(name);
106
- const insertChunk = cache.db.prepare('INSERT INTO chunks (name, version, file_path, kind, content) VALUES (?, ?, ?, ?, ?)');
152
+ cache.db
153
+ .prepare('DELETE FROM chunks WHERE ecosystem = ? AND name = ?')
154
+ .run(PROJECT_SCOPE, name);
155
+ cache.db
156
+ .prepare('DELETE FROM packages WHERE ecosystem = ? AND name = ?')
157
+ .run(PROJECT_SCOPE, name);
158
+ const insertChunk = cache.db.prepare('INSERT INTO chunks (ecosystem, name, version, file_path, kind, content) VALUES (?, ?, ?, ?, ?, ?)');
107
159
  let filesIngested = 0;
108
160
  let chunksWritten = 0;
109
161
  for (const abs of files) {
@@ -115,18 +167,25 @@ export function ensureProjectIndexed(cache, name, version, files, cwd) {
115
167
  continue;
116
168
  }
117
169
  const rel = path.relative(cwd, abs);
118
- const chunks = chunkDeclarations(raw, rel);
170
+ // The project's own source goes in WHOLE. `profile.surface` strips a
171
+ // dependency's implementation down to its published API, which is the
172
+ // opposite of what is wanted here: a binary crate's `main.rs` has no
173
+ // `pub` item in it at all, so surfacing it indexes to nothing and
174
+ // `pi-worker-docs(".", …)` answers "no chunks" for the whole project.
175
+ // Only the CHUNK BOUNDARY is language-specific.
176
+ const profile = profileForFile(abs, profiles);
177
+ const chunks = chunkDeclarations(raw, rel, profile.declSplitRe, profile.commentPrefix);
119
178
  if (!chunks.length)
120
179
  continue;
121
180
  filesIngested++;
122
181
  for (const c of chunks) {
123
- insertChunk.run(name, version, rel, 'dts', c);
182
+ insertChunk.run(PROJECT_SCOPE, name, version, rel, 'dts', c);
124
183
  chunksWritten++;
125
184
  }
126
185
  }
127
186
  cache.db
128
- .prepare('INSERT OR REPLACE INTO packages (name, version, content_hash, indexed_at) VALUES (?, ?, ?, ?)')
129
- .run(name, version, version, Date.now());
187
+ .prepare('INSERT OR REPLACE INTO packages (ecosystem, name, version, content_hash, indexed_at) VALUES (?, ?, ?, ?, ?)')
188
+ .run(PROJECT_SCOPE, name, version, version, Date.now());
130
189
  cache.db.exec('COMMIT');
131
190
  return { hitCache: false, filesIngested, chunksWritten, indexingMs: Date.now() - t0 };
132
191
  }
@@ -154,8 +213,8 @@ listFiles = getProjectFiles) {
154
213
  };
155
214
  }
156
215
  const chunkCount = cache.db
157
- .prepare('SELECT count(*) AS c FROM chunks WHERE name = ? AND version = ?')
158
- .get(cacheKey, version)?.c ?? 0;
216
+ .prepare('SELECT count(*) AS c FROM chunks WHERE ecosystem = ? AND name = ? AND version = ?')
217
+ .get(PROJECT_SCOPE, cacheKey, version)?.c ?? 0;
159
218
  if (chunkCount === 0) {
160
219
  return {
161
220
  kind: 'no_chunks',
@@ -163,12 +222,14 @@ listFiles = getProjectFiles) {
163
222
  cacheKey,
164
223
  version,
165
224
  hitCache: indexResult.hitCache,
166
- filesIngested: indexResult.filesIngested
225
+ filesIngested: indexResult.filesIngested,
226
+ sourceLabel: projectSourceLabel(cwd)
167
227
  };
168
228
  }
169
229
  let chunks;
170
230
  try {
171
231
  chunks = retrieveChunksFn(cache, {
232
+ ecosystem: PROJECT_SCOPE,
172
233
  name: cacheKey,
173
234
  version,
174
235
  query,
@@ -190,7 +251,8 @@ listFiles = getProjectFiles) {
190
251
  cacheKey,
191
252
  version,
192
253
  hitCache: indexResult.hitCache,
193
- filesIngested: indexResult.filesIngested
254
+ filesIngested: indexResult.filesIngested,
255
+ sourceLabel: projectSourceLabel(cwd)
194
256
  };
195
257
  }
196
258
  return {
@@ -1,16 +1,21 @@
1
+ import type { EcosystemId } from './docs-ecosystems.js';
1
2
  /** True for TypeScript declaration files: .d.ts, .d.mts, .d.cts. */
2
3
  export declare function isDtsFile(name: string): boolean;
3
4
  export interface ResolvedPackage {
5
+ /** Which registry this package came from. Scopes it in the docs cache. */
6
+ ecosystem: EcosystemId;
4
7
  name: string;
5
8
  version: string;
6
9
  root: string;
7
- entryDts: string | null;
10
+ /** The file holding the package's public API surface, if it has one. */
11
+ entry: string | null;
8
12
  readme: string | null;
9
13
  }
10
14
  export declare class ResolveError extends Error {
11
15
  readonly kind: 'not_installed' | 'invalid_name';
12
16
  constructor(kind: 'not_installed' | 'invalid_name', message: string);
13
17
  }
18
+ export declare function isValidModuleName(name: string): boolean;
14
19
  /**
15
20
  * Split a runtime builtin specifier (`bun:sql`, `node:fs/promises`) into its
16
21
  * runtime and submodule. Returns null for ordinary specifiers (including scoped
@@ -15,7 +15,7 @@ export class ResolveError extends Error {
15
15
  }
16
16
  }
17
17
  const MODULE_NAME_RE = /^(?:@[a-z0-9-_.]+\/)?[a-z0-9-_.]+(?:\/[a-z0-9-_./]+)?$/i;
18
- function isValidModuleName(name) {
18
+ export function isValidModuleName(name) {
19
19
  if (!name || name.includes('..') || name.startsWith('/'))
20
20
  return false;
21
21
  return MODULE_NAME_RE.test(name);
@@ -147,10 +147,11 @@ export function resolvePackage(moduleName, cwd) {
147
147
  const root = path.dirname(pkgJsonPath);
148
148
  const pkg = readPackageJson(pkgJsonPath);
149
149
  return {
150
+ ecosystem: 'npm',
150
151
  name: pkg.name ?? parent,
151
152
  version: pkg.version ?? '0.0.0',
152
153
  root,
153
- entryDts: resolveEntryDts(moduleName, parent, root, pkg),
154
+ entry: resolveEntryDts(moduleName, parent, root, pkg),
154
155
  readme: findReadme(root)
155
156
  };
156
157
  }
@@ -282,7 +283,7 @@ export function detectTypesRedirect(pkg) {
282
283
  // redirect stub — use its own types.
283
284
  if (countTypeFiles(pkg.root, 2) > 1)
284
285
  return null;
285
- const entry = pkg.entryDts ?? findIndexDts(pkg.root);
286
+ const entry = pkg.entry ?? findIndexDts(pkg.root);
286
287
  if (!entry)
287
288
  return null;
288
289
  let content;
@@ -6,6 +6,8 @@ export interface RetrievedChunk {
6
6
  rank: number;
7
7
  }
8
8
  export interface RetrieveOptions {
9
+ /** Registry scope of the rows to search, or the project-source scope. */
10
+ ecosystem: string;
9
11
  name: string;
10
12
  version: string;
11
13
  query: string;
@@ -37,13 +37,13 @@ function tokenize(query) {
37
37
  function buildFtsQuery(tokens) {
38
38
  return tokens.map(t => `"${t}"`).join(' OR ');
39
39
  }
40
- function fallbackChunks(cache, name, version) {
40
+ function fallbackChunks(cache, ecosystem, name, version) {
41
41
  const dts = cache.db
42
- .prepare("SELECT file_path, kind, content, 0 AS rank FROM chunks WHERE name = ? AND version = ? AND kind = 'dts' ORDER BY file_path, id LIMIT 1")
43
- .all(name, version);
42
+ .prepare("SELECT file_path, kind, content, 0 AS rank FROM chunks WHERE ecosystem = ? AND name = ? AND version = ? AND kind = 'dts' ORDER BY file_path, id LIMIT 1")
43
+ .all(ecosystem, name, version);
44
44
  const readme = cache.db
45
- .prepare("SELECT file_path, kind, content, 0 AS rank FROM chunks WHERE name = ? AND version = ? AND kind = 'readme' ORDER BY id LIMIT 1")
46
- .all(name, version);
45
+ .prepare("SELECT file_path, kind, content, 0 AS rank FROM chunks WHERE ecosystem = ? AND name = ? AND version = ? AND kind = 'readme' ORDER BY id LIMIT 1")
46
+ .all(ecosystem, name, version);
47
47
  const out = [];
48
48
  for (const r of dts) {
49
49
  out.push({
@@ -91,7 +91,7 @@ export function retrieveChunks(cache, opts) {
91
91
  const budget = opts.contentBudget ?? DEFAULT_BUDGET;
92
92
  const tokens = tokenize(opts.query);
93
93
  if (tokens.length === 0) {
94
- return enforceBudget(fallbackChunks(cache, opts.name, opts.version), budget);
94
+ return enforceBudget(fallbackChunks(cache, opts.ecosystem, opts.name, opts.version), budget);
95
95
  }
96
96
  const ftsQuery = buildFtsQuery(tokens);
97
97
  let rows;
@@ -100,16 +100,16 @@ export function retrieveChunks(cache, opts) {
100
100
  .prepare(`SELECT c.file_path, c.kind, c.content, bm25(chunks_fts) AS rank
101
101
  FROM chunks_fts
102
102
  JOIN chunks c ON c.id = chunks_fts.rowid
103
- WHERE c.name = ?1 AND c.version = ?2 AND chunks_fts MATCH ?3
103
+ WHERE c.ecosystem = ?1 AND c.name = ?2 AND c.version = ?3 AND chunks_fts MATCH ?4
104
104
  ORDER BY rank
105
- LIMIT ?4`)
106
- .all(opts.name, opts.version, ftsQuery, limit);
105
+ LIMIT ?5`)
106
+ .all(opts.ecosystem, opts.name, opts.version, ftsQuery, limit);
107
107
  }
108
108
  catch {
109
- return enforceBudget(fallbackChunks(cache, opts.name, opts.version), budget);
109
+ return enforceBudget(fallbackChunks(cache, opts.ecosystem, opts.name, opts.version), budget);
110
110
  }
111
111
  if (rows.length === 0) {
112
- return enforceBudget(fallbackChunks(cache, opts.name, opts.version), budget);
112
+ return enforceBudget(fallbackChunks(cache, opts.ecosystem, opts.name, opts.version), budget);
113
113
  }
114
114
  const mapped = rows.map(r => ({
115
115
  filePath: r.file_path,
@@ -0,0 +1,115 @@
1
+ /**
2
+ * eco-cargo — reading Rust crates for the docs Worker tool.
3
+ *
4
+ * Rust ships no declarations file, so the documented surface has to be cut out
5
+ * of `.rs` source: doc comments, attributes and public item heads kept, function
6
+ * bodies and private items dropped. That is what `surface` below does, and it is
7
+ * why this row needs code where the npm row needed none.
8
+ *
9
+ * Nothing here parses TOML. `Cargo.lock` is a generated file with a fixed
10
+ * `[[package]]` shape, and a line reader over it costs one small function where
11
+ * a TOML dependency would cost a dependency.
12
+ */
13
+ import { type ResolvedPackage } from './docs-resolve.js';
14
+ import type { NpmVersionInfo } from './npm-version.js';
15
+ export declare function isValidCrateName(name: string): boolean;
16
+ /** `serde_json::Value` is a path into `serde_json`; the crate is what installs. */
17
+ export declare function crateOf(name: string): string;
18
+ /** Every version of `name` recorded in a `Cargo.lock`, oldest first. */
19
+ export declare function lockVersions(lockText: string, name: string): string[];
20
+ /** Directories that never hold a project's own manifest. */
21
+ export declare const SKIP_DIRS: Set<string>;
22
+ /**
23
+ * Immediate child directories of `cwd`, for the one-level-down manifest scan —
24
+ * and EMPTY unless `cwd` is itself a project root.
25
+ *
26
+ * Without that guard the scan reaches into any directory that merely happens to
27
+ * contain projects. `/tmp` is the case that bites: a single unrelated checkout
28
+ * under it would make every lookup run from `/tmp` believe it was in that
29
+ * ecosystem.
30
+ */
31
+ export declare function childDirs(cwd: string): string[];
32
+ /**
33
+ * The `Cargo.lock` governing `cwd`: cargo's own upward walk first, then ONE level
34
+ * down. The step down is the Tauri shape — `package.json` at the repo root and
35
+ * the crate under `src-tauri/` — where the upward walk from the root finds
36
+ * nothing at all.
37
+ */
38
+ export declare function findLock(cwd: string): string | null;
39
+ /**
40
+ * The first `<dir>/<name>` at or above `cwd`, checking each ancestor's immediate
41
+ * children too.
42
+ *
43
+ * The sideways step is the Tauri shape: `package.json` at the repo root and the
44
+ * crate under `src-tauri/`. Checking children of `cwd` alone is not enough —
45
+ * from the frontend directory `src/`, the crate is in a SIBLING, so the scan has
46
+ * to happen at every level on the way up or the whole project reads as npm-only.
47
+ */
48
+ export declare function findAtOrAbove(cwd: string, ...rel: string[]): string | null;
49
+ /**
50
+ * The version the project pins `name` to. Several is not an error — a workspace
51
+ * legitimately holds two majors of one crate — and the NEWEST is taken, which
52
+ * the answer then states in its `Per <name>@<version>:` header.
53
+ */
54
+ export declare function lockedVersion(name: string, cwd: string): string | null;
55
+ /**
56
+ * Every crate the lock pins, name to version. Cargo has already resolved these,
57
+ * so unlike an npm range these are exact — and a `cargo update` that moves one
58
+ * is what a cached answer about it has to be dropped on.
59
+ */
60
+ export declare function lockedDeps(cwd: string): Record<string, string> | undefined;
61
+ export interface CargoResolveDirs {
62
+ cargoHome: string;
63
+ /** Where a crate fetched by this tool was extracted. */
64
+ modulesDir: string;
65
+ }
66
+ /**
67
+ * Find a crate's source on disk.
68
+ *
69
+ * The lock is consulted first, so a project reading `tokio` gets the `tokio` it
70
+ * builds against rather than the newest copy the machine happens to hold. With
71
+ * no lock in sight — which is where the post-fetch re-resolve arrives, since it
72
+ * is handed the download directory — the newest extracted copy wins.
73
+ */
74
+ export declare function resolveCrate(name: string, cwd: string, dirs: CargoResolveDirs): ResolvedPackage;
75
+ /** The newest published version of a crate, or null on any failure. */
76
+ export declare function cratesLatest(name: string, fetchFn: typeof fetch, signal?: AbortSignal): Promise<NpmVersionInfo | null>;
77
+ export declare function crateTarballUrl(name: string, version: string): string;
78
+ /**
79
+ * Where a Rust declaration begins, so a chunk never splits a signature. Column 0
80
+ * only: `rustSurface` INDENTS the members of an impl or a trait, so an `^\s*`
81
+ * anchor cut every method into its own chunk — an orphan signature with no
82
+ * receiver type, carrying the next method's doc comment.
83
+ */
84
+ export declare const CARGO_DECL_SPLIT_RE: RegExp;
85
+ interface Item {
86
+ /** Doc comments and attributes immediately above the item. */
87
+ pending: string;
88
+ /** The item text up to its `{` or `;`. */
89
+ head: string;
90
+ /** The brace body, without the braces. Null for a `;`-terminated item. */
91
+ body: string | null;
92
+ }
93
+ /**
94
+ * Split one nesting level of Rust source into items.
95
+ *
96
+ * Braces are counted with strings, chars, comments and lifetimes skipped: `"{"`
97
+ * and `'{'` both appear in real source, and a scanner that counts them never
98
+ * finds the end of the item it is in.
99
+ */
100
+ export declare function splitRustItems(src: string): Item[];
101
+ /**
102
+ * Reduce Rust source to its public API surface.
103
+ *
104
+ * Function bodies go — they are the bulk of the file and answer no question the
105
+ * docs tool is asked. Everything a caller can name stays: the item head, its doc
106
+ * comment, its attributes, and for a struct or enum its fields and variants.
107
+ *
108
+ * Inside a `trait` every member is public by definition, so the `pub` test is
109
+ * suspended there; anywhere else a bare or `pub(crate)` item is dropped.
110
+ */
111
+ export declare function rustSurface(src: string, insideTrait?: boolean, topLevel?: boolean): string;
112
+ export declare function isRustFile(name: string): boolean;
113
+ /** The `[package] name` of a cargo project, for labelling its own source. */
114
+ export declare function cargoProjectName(cwd: string): string | null;
115
+ export {};