attention-manifest-protocol 0.6.1 → 0.6.2

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.
@@ -0,0 +1,581 @@
1
+ /**
2
+ * Bounded, literal LaTeX structure and project adapter. Never expands macros,
3
+ * executes TeX or resolves files outside the caller-provided inventory.
4
+ */
5
+ import { posix } from "node:path";
6
+ import { encodeDocumentDetail } from "../context/document.js";
7
+ import { workspaceTerms } from "../context/workspace.js";
8
+ const Levels = { part: 0, chapter: 1, section: 2, subsection: 3, subsubsection: 4, paragraph: 5, subparagraph: 6 };
9
+ const Literals = new Set(["verbatim", "verbatim*", "Verbatim", "lstlisting", "minted", "comment", "filecontents", "filecontents*"]);
10
+ const Dependencies = new Set([
11
+ "input",
12
+ "include",
13
+ "subfile",
14
+ "bibliography",
15
+ "addbibresource",
16
+ "includegraphics",
17
+ "documentclass",
18
+ "usepackage",
19
+ "RequirePackage",
20
+ "LoadClass",
21
+ ]);
22
+ const References = new Set(["ref", "eqref", "pageref", "autoref", "cref", "Cref", "vref", "nameref"]);
23
+ const Definitions = new Set(["newcommand", "renewcommand", "providecommand", "DeclareRobustCommand", "newenvironment", "renewenvironment"]);
24
+ const short = (value) => value.replace(/\s+/g, " ").trim().slice(0, 180);
25
+ const skipSpace = (text, start) => {
26
+ let offset = start;
27
+ while (offset < text.length) {
28
+ if (/\s/.test(text[offset])) {
29
+ offset += 1;
30
+ }
31
+ else if (text[offset] === "%") {
32
+ offset = text.indexOf("\n", offset);
33
+ if (offset < 0) {
34
+ return text.length;
35
+ }
36
+ }
37
+ else {
38
+ break;
39
+ }
40
+ }
41
+ return offset;
42
+ };
43
+ const group = (text, start, open = "{") => {
44
+ const begin = skipSpace(text, start);
45
+ if (text[begin] !== open) {
46
+ return (void 0);
47
+ }
48
+ const close = open === "{" ? "}" : open === "[" ? "]" : ")";
49
+ let depth = 1;
50
+ let braces = 0;
51
+ let value = "";
52
+ for (let offset = begin + 1; offset < text.length; offset += 1) {
53
+ const char = text[offset];
54
+ if (char === "\\") {
55
+ value += text.slice(offset, offset + 2);
56
+ offset += 1;
57
+ continue;
58
+ }
59
+ if (char === "%") {
60
+ const newline = text.indexOf("\n", offset);
61
+ offset = newline < 0 ? text.length : newline;
62
+ value += "\n";
63
+ continue;
64
+ }
65
+ if (open === "[" && char === "{") {
66
+ braces += 1;
67
+ }
68
+ else if (open === "[" && char === "}") {
69
+ braces -= 1;
70
+ }
71
+ if (braces === 0 && char === open) {
72
+ depth += 1;
73
+ }
74
+ else if (braces === 0 && char === close) {
75
+ depth -= 1;
76
+ if (depth === 0) {
77
+ return { value, end: offset + 1 };
78
+ }
79
+ }
80
+ if (depth + braces > 128 || braces < 0) {
81
+ throw new Error("TeX group nesting exceeds bounds");
82
+ }
83
+ value += char;
84
+ }
85
+ throw new Error("Unclosed TeX argument group");
86
+ };
87
+ const tokens = (text) => {
88
+ const result = [];
89
+ let offset = 0;
90
+ while (offset < text.length) {
91
+ if (text[offset] === "%") {
92
+ offset = text.indexOf("\n", offset);
93
+ if (offset < 0) {
94
+ break;
95
+ }
96
+ }
97
+ if (text[offset] !== "\\") {
98
+ offset += 1;
99
+ continue;
100
+ }
101
+ const start = offset;
102
+ const match = /^\\([A-Za-z@]+|[^\r\n])/.exec(text.slice(offset));
103
+ if (!match) {
104
+ offset += 1;
105
+ continue;
106
+ }
107
+ const name = match[1];
108
+ offset += match[0].length;
109
+ if (text[offset] === "*") {
110
+ offset += 1;
111
+ }
112
+ if (name === "verb") {
113
+ const delimiter = text[offset];
114
+ const end = delimiter ? text.indexOf(delimiter, offset + 1) : -1;
115
+ const newline = text.indexOf("\n", offset);
116
+ offset = end >= 0 && (newline < 0 || end < newline) ? end + 1 : newline < 0 ? text.length : newline;
117
+ continue;
118
+ }
119
+ if (["def", "gdef", "edef", "xdef"].includes(name)) {
120
+ const body = text.indexOf("{", offset);
121
+ offset = body < 0 ? text.length : group(text, body).end;
122
+ continue;
123
+ }
124
+ if (Definitions.has(name)) {
125
+ const macro = group(text, offset);
126
+ offset = macro?.end ?? skipSpace(text, offset);
127
+ if (!macro) {
128
+ offset += /^\\[A-Za-z@]+/.exec(text.slice(offset))?.[0].length ?? 0;
129
+ }
130
+ let option = group(text, offset, "[");
131
+ while (option) {
132
+ offset = option.end;
133
+ option = group(text, offset, "[");
134
+ }
135
+ const body = group(text, offset);
136
+ offset = body?.end ?? offset;
137
+ if (name.endsWith("environment")) {
138
+ offset = group(text, offset)?.end ?? offset;
139
+ }
140
+ continue;
141
+ }
142
+ if (!Object.hasOwn(Levels, name) &&
143
+ !["begin", "end", "label", "title"].includes(name) &&
144
+ !Dependencies.has(name) &&
145
+ !References.has(name) &&
146
+ !/^(?:[A-Za-z]*cite[A-Za-z]*|nocite)$/.test(name)) {
147
+ continue;
148
+ }
149
+ let option = group(text, offset, "[");
150
+ while (option) {
151
+ offset = option.end;
152
+ option = group(text, offset, "[");
153
+ }
154
+ let argument = group(text, offset);
155
+ if (!argument && name === "input") {
156
+ const begin = skipSpace(text, offset);
157
+ const filename = /^[^\s%{}\\]+/.exec(text.slice(begin))?.[0] ?? "";
158
+ if (filename) {
159
+ argument = { value: filename, end: begin + filename.length };
160
+ }
161
+ }
162
+ if (!argument) {
163
+ result.push({ name, value: "", start, end: offset });
164
+ continue;
165
+ }
166
+ offset = argument.end;
167
+ result.push({ name, value: argument.value.trim(), start, end: offset });
168
+ if (name === "begin" && Literals.has(argument.value.trim())) {
169
+ const closing = `\\end{${argument.value.trim()}}`;
170
+ const end = text.indexOf(closing, offset);
171
+ if (end < 0) {
172
+ throw new Error(`Unclosed literal environment ${argument.value}`);
173
+ }
174
+ result.push({ name: "end", value: argument.value.trim(), start: end, end: end + closing.length });
175
+ offset = end + closing.length;
176
+ }
177
+ }
178
+ return result;
179
+ };
180
+ const bibBlocks = (text) => {
181
+ const blocks = [];
182
+ let offset = 0;
183
+ while (offset < text.length) {
184
+ if (text[offset] === "%") {
185
+ const end = text.indexOf("\n", offset);
186
+ offset = end < 0 ? text.length : end + 1;
187
+ continue;
188
+ }
189
+ const match = /^@([A-Za-z]+)\s*([{(])/.exec(text.slice(offset));
190
+ if (!match) {
191
+ offset += 1;
192
+ continue;
193
+ }
194
+ const start = offset;
195
+ const open = match[2];
196
+ offset += match[0].length;
197
+ const bodyStart = offset;
198
+ let braces = open === "{" ? 1 : 0;
199
+ let quoted = false;
200
+ while (offset < text.length) {
201
+ const char = text[offset];
202
+ if (char === "\\") {
203
+ offset += 2;
204
+ continue;
205
+ }
206
+ if (char === '"' && (braces === 0 || (open === "{" && braces === 1))) {
207
+ quoted = !quoted;
208
+ }
209
+ if (!quoted && char === "{") {
210
+ braces += 1;
211
+ }
212
+ else if (!quoted && char === "}") {
213
+ braces -= 1;
214
+ if (open === "{" && braces === 0) {
215
+ break;
216
+ }
217
+ }
218
+ else if (!quoted && open === "(" && char === ")" && braces === 0) {
219
+ break;
220
+ }
221
+ if (braces > 128) {
222
+ throw new Error("BibTeX nesting exceeds 128");
223
+ }
224
+ offset += 1;
225
+ }
226
+ if (offset >= text.length) {
227
+ throw new Error("Unclosed BibTeX entry");
228
+ }
229
+ const type = match[1].toLowerCase();
230
+ const key = text.slice(bodyStart, offset).split(",")[0].trim();
231
+ offset += 1;
232
+ if (!["comment", "preamble", "string"].includes(type) && key && !/[\s{}]/.test(key)) {
233
+ blocks.push({ start, end: offset, label: key, type: "paragraph", tag: `bibtex:${type}`, key });
234
+ }
235
+ }
236
+ return blocks;
237
+ };
238
+ /**
239
+ * Adds format facts to the shared snapshot. LaTeX build projects are knowledge
240
+ * views under their root files, not replacements for workspace ownership.
241
+ */
242
+ export const addTexStructure = (snapshot, inputs, store) => {
243
+ const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]));
244
+ const parsed = new Map();
245
+ const roots = new Set();
246
+ const requests = new Map();
247
+ const keys = new Set();
248
+ const covered = new Set();
249
+ const edge = (from, to, kind, source) => {
250
+ const key = JSON.stringify([from, to, kind, source.uri, source.startLine]);
251
+ if (!keys.has(key)) {
252
+ keys.add(key);
253
+ snapshot.relations.push({ from, to, kind, source, method: "explicit" });
254
+ }
255
+ };
256
+ const diagnostic = (path, code, message) => {
257
+ snapshot.diagnostics.push({ uri: path, code, message });
258
+ };
259
+ const line = (file, offset) => {
260
+ let low = 0;
261
+ let high = file.lines.length;
262
+ while (low < high) {
263
+ const middle = (low + high) >>> 1;
264
+ if (file.lines[middle] <= offset) {
265
+ low = middle + 1;
266
+ }
267
+ else {
268
+ high = middle;
269
+ }
270
+ }
271
+ return low;
272
+ };
273
+ const source = (file, start, end) => ({
274
+ ...file.file.source,
275
+ startLine: line(file, start),
276
+ endLine: line(file, Math.max(start, end - 1)),
277
+ locator: { kind: "part", part: file.path, element: `chars:${start}:${end}` },
278
+ });
279
+ inputs
280
+ .filter((input) => /\.(?:tex|ltx|sty|cls|bib)$/i.test(input.path))
281
+ .forEach((input) => {
282
+ const file = {
283
+ path: input.path,
284
+ text: input.content,
285
+ tokens: [],
286
+ blocks: [],
287
+ lines: [0, ...[...input.content.matchAll(/\n/g)].map((match) => match.index + 1)],
288
+ file: entities.get(`file:${input.path}`),
289
+ };
290
+ try {
291
+ if (Buffer.byteLength(file.text) > 16 * 1024 * 1024) {
292
+ throw new Error("TeX source exceeds 16 MiB");
293
+ }
294
+ if (/\.bib$/i.test(file.path)) {
295
+ file.blocks = bibBlocks(file.text);
296
+ }
297
+ else {
298
+ file.tokens = tokens(file.text);
299
+ const sections = [];
300
+ const environments = [];
301
+ file.tokens.forEach((token) => {
302
+ if (Object.hasOwn(Levels, token.name) && token.value) {
303
+ const level = Levels[token.name];
304
+ while (sections.length > 0 && sections.at(-1).level >= level) {
305
+ sections.pop().end = token.start;
306
+ }
307
+ const block = { start: token.start, end: file.text.length, label: token.value, type: "section", tag: token.name, level };
308
+ sections.push(block);
309
+ file.blocks.push(block);
310
+ }
311
+ else if (token.name === "begin" && token.value && token.value !== "document") {
312
+ const block = { start: token.start, end: file.text.length, label: token.value, type: "environment", tag: token.value };
313
+ environments.push(block);
314
+ file.blocks.push(block);
315
+ }
316
+ else if (token.name === "end" && token.value !== "document") {
317
+ if (environments.at(-1)?.tag === token.value) {
318
+ environments.pop().end = token.end;
319
+ }
320
+ else {
321
+ diagnostic(file.path, "tex_environment_mismatch", `Unmatched \\end{${token.value}} at line ${line(file, token.start)}`);
322
+ }
323
+ }
324
+ });
325
+ environments.forEach((block) => diagnostic(file.path, "tex_environment_mismatch", `Unclosed ${block.tag} at line ${line(file, block.start)}`));
326
+ const list = [];
327
+ file.tokens
328
+ .filter((token) => Dependencies.has(token.name))
329
+ .forEach((token) => {
330
+ const values = ["bibliography", "usepackage", "RequirePackage"].includes(token.name)
331
+ ? token.value.split(",")
332
+ : [token.value];
333
+ values.forEach((value) => {
334
+ list.push({ path: file.path, token, value: value.trim() });
335
+ });
336
+ });
337
+ requests.set(file.path, list);
338
+ }
339
+ }
340
+ catch (error) {
341
+ diagnostic(file.path, "invalid_tex", error instanceof Error ? error.message : "TeX parsing failed");
342
+ return;
343
+ }
344
+ file.blocks.sort((left, right) => left.start - right.start || right.end - left.end);
345
+ const active = [];
346
+ const children = new Map();
347
+ file.blocks.forEach((block) => {
348
+ while (active.length > 0 && (active.at(-1).end < block.end || active.at(-1).end <= block.start)) {
349
+ active.pop();
350
+ }
351
+ const parentId = active.at(-1)?.node?.id ?? file.file.id;
352
+ block.parent = active.at(-1);
353
+ const body = file.text.slice(block.start, block.end);
354
+ const node = {
355
+ id: `knowledge:${file.path}#tex:${block.start}`,
356
+ kind: "knowledge",
357
+ projectId: file.file.projectId,
358
+ parentId,
359
+ label: block.label,
360
+ summary: short(`${block.tag} ${block.label}: ${body}`),
361
+ source: source(file, block.start, block.end),
362
+ structureKind: block.type,
363
+ tags: ["latex", block.tag],
364
+ searchTerms: workspaceTerms(body),
365
+ detailRef: store.put(encodeDocumentDetail({ kind: "text", text: body })),
366
+ summaryProvenance: { method: "extractive-v1", sources: [{ uri: file.path, revision: file.file.source.revision }], children: [] },
367
+ };
368
+ block.node = node;
369
+ entities.set(node.id, node);
370
+ const siblings = children.get(parentId) ?? [];
371
+ siblings.push(node);
372
+ children.set(parentId, siblings);
373
+ active.push(block);
374
+ });
375
+ file.blocks.forEach((block) => {
376
+ const node = block.node;
377
+ const nested = children.get(node.id) ?? [];
378
+ node.summaryProvenance.children = nested.map((child) => child.id);
379
+ node.summary = short(`${block.tag} ${block.label}; ${nested.map((child) => child.label).join("; ")} ${file.text.slice(block.start, block.end)}`);
380
+ });
381
+ file.file.structureKind = "document";
382
+ file.file.tags = [...(file.file.tags ?? []), /\.bib$/i.test(file.path) ? "bibtex" : "latex"];
383
+ file.file.summary = short(`${file.path}: ${file.blocks.map((block) => block.label).join("; ")} ${file.text}`);
384
+ file.file.searchTerms = workspaceTerms(file.text);
385
+ file.file.summaryProvenance = {
386
+ method: "extractive-v1",
387
+ sources: [{ uri: file.path, revision: file.file.source.revision }],
388
+ children: (children.get(file.file.id) ?? []).map((child) => child.id),
389
+ };
390
+ parsed.set(file.path, file);
391
+ if (file.tokens.some((token) => token.name === "documentclass" || (token.name === "begin" && token.value === "document"))) {
392
+ roots.add(file.path);
393
+ }
394
+ diagnostic(file.path, "tex_static_partial", "Literal structure only: no macro expansion, catcode/conditional evaluation, compilation, TeX search paths or BibTeX string expansion");
395
+ });
396
+ const owner = (file, offset) => {
397
+ let low = 0;
398
+ let high = file.blocks.length;
399
+ while (low < high) {
400
+ const middle = (low + high) >>> 1;
401
+ if (file.blocks[middle].start <= offset) {
402
+ low = middle + 1;
403
+ }
404
+ else {
405
+ high = middle;
406
+ }
407
+ }
408
+ let block = low > 0 ? file.blocks[low - 1] : (void 0);
409
+ while (block && block.end <= offset) {
410
+ block = block.parent;
411
+ }
412
+ return block?.node?.id ?? file.file.id;
413
+ };
414
+ const resolve = (request, root) => {
415
+ const value = request.value;
416
+ if (!value || /[\\{}#$\x00-\x1f]/.test(value) || posix.isAbsolute(value) || /^[a-z][a-z0-9+.-]*:/i.test(value)) {
417
+ return (void 0);
418
+ }
419
+ const command = request.token.name;
420
+ const extensions = ["bibliography", "addbibresource"].includes(command)
421
+ ? [".bib"]
422
+ : command === "includegraphics"
423
+ ? [".pdf", ".png", ".jpg", ".jpeg", ".eps", ".svg"]
424
+ : ["usepackage", "RequirePackage"].includes(command)
425
+ ? [".sty"]
426
+ : ["documentclass", "LoadClass"].includes(command)
427
+ ? [".cls"]
428
+ : [".tex", ".ltx"];
429
+ const names = posix.extname(value) ? [value] : extensions.map((extension) => `${value}${extension}`);
430
+ const bases = [...new Set([posix.dirname(root), posix.dirname(request.path)])];
431
+ for (let baseIndex = 0; baseIndex < bases.length; baseIndex += 1) {
432
+ const base = bases[baseIndex];
433
+ for (let index = 0; index < names.length; index += 1) {
434
+ const path = posix.normalize(posix.join(base, names[index]));
435
+ if (!path.startsWith("../") && entities.has(`file:${path}`)) {
436
+ return path;
437
+ }
438
+ }
439
+ }
440
+ return (void 0);
441
+ };
442
+ parsed.forEach((file) => {
443
+ const hint = /^\s*%\s*!\s*TeX\s+root\s*=\s*(.+?)\s*$/im.exec(file.text)?.[1];
444
+ if (hint) {
445
+ const path = posix.normalize(posix.join(posix.dirname(file.path), hint));
446
+ if (parsed.has(path)) {
447
+ roots.add(path);
448
+ }
449
+ else {
450
+ diagnostic(file.path, "tex_unresolved_root", hint);
451
+ }
452
+ }
453
+ });
454
+ const visit = (root, project) => {
455
+ const pending = [root];
456
+ const seen = new Set();
457
+ const dependencies = [];
458
+ const projectId = `knowledge:${root}#latex-project`;
459
+ while (pending.length > 0) {
460
+ const path = pending.pop();
461
+ if (seen.has(path)) {
462
+ continue;
463
+ }
464
+ seen.add(path);
465
+ covered.add(path);
466
+ const file = parsed.get(path);
467
+ if (!file) {
468
+ continue;
469
+ }
470
+ (requests.get(path) ?? []).forEach((request) => {
471
+ const target = resolve(request, root);
472
+ const origin = source(file, request.token.start, request.token.end);
473
+ if (target) {
474
+ dependencies.push({ from: path, to: target, command: request.token.name });
475
+ edge(file.file.id, `file:${target}`, "imports", origin);
476
+ edge(owner(file, request.token.start), `file:${target}`, "imports", origin);
477
+ if (!seen.has(target)) {
478
+ pending.push(target);
479
+ }
480
+ }
481
+ else if (["documentclass", "usepackage", "RequirePackage", "LoadClass"].includes(request.token.name) && /^[\w.-]+$/.test(request.value)) {
482
+ const id = `package:${JSON.stringify(["latex", file.file.projectId, request.token.name, request.value])}`;
483
+ if (!entities.has(id)) {
484
+ entities.set(id, {
485
+ id,
486
+ kind: "package",
487
+ projectId: file.file.projectId,
488
+ parentId: file.file.projectId,
489
+ label: request.value,
490
+ summary: `Declared TeX ${request.token.name}: ${request.value}; installation/version unverified`,
491
+ source: origin,
492
+ tags: ["latex", request.token.name],
493
+ });
494
+ }
495
+ edge(file.file.id, id, "imports", origin);
496
+ }
497
+ else {
498
+ diagnostic(path, "tex_unresolved_dependency", `${request.token.name}{${request.value}} from root ${root}`);
499
+ }
500
+ });
501
+ }
502
+ const labels = new Map();
503
+ const citations = new Map();
504
+ const addKey = (map, key, id) => {
505
+ const ids = map.get(key) ?? [];
506
+ ids.push(id);
507
+ map.set(key, ids);
508
+ };
509
+ seen.forEach((path) => {
510
+ const file = parsed.get(path);
511
+ file?.tokens
512
+ .filter((token) => token.name === "label")
513
+ .forEach((token) => addKey(labels, token.value, owner(file, token.start)));
514
+ file?.blocks.filter((block) => Boolean(block.key)).forEach((block) => addKey(citations, block.key, block.node.id));
515
+ });
516
+ seen.forEach((path) => {
517
+ const file = parsed.get(path);
518
+ file?.tokens.forEach((token) => {
519
+ const cite = /^(?:[A-Za-z]*cite[A-Za-z]*|nocite)$/.test(token.name);
520
+ if (!cite && !References.has(token.name)) {
521
+ return;
522
+ }
523
+ token.value
524
+ .split(",")
525
+ .map((value) => value.trim())
526
+ .forEach((key) => {
527
+ if (token.name === "nocite" && key === "*") {
528
+ citations.forEach((ids) => ids.forEach((id) => edge(owner(file, token.start), id, "references", source(file, token.start, token.end))));
529
+ return;
530
+ }
531
+ const targets = (cite ? citations : labels).get(key) ?? [];
532
+ if (targets.length === 1) {
533
+ edge(owner(file, token.start), targets[0], "references", source(file, token.start, token.end));
534
+ }
535
+ else {
536
+ diagnostic(path, targets.length > 1 ? "tex_ambiguous_reference" : "tex_unresolved_reference", `${token.name}{${key}} in ${root}`);
537
+ }
538
+ });
539
+ });
540
+ });
541
+ if (!project) {
542
+ return;
543
+ }
544
+ const file = parsed.get(root);
545
+ const paths = [...seen].sort();
546
+ const names = paths
547
+ .flatMap((path) => parsed
548
+ .get(path)
549
+ ?.blocks.filter((block) => block.type === "section")
550
+ .map((block) => block.label) ?? [])
551
+ .join("; ");
552
+ entities.set(projectId, {
553
+ id: projectId,
554
+ kind: "knowledge",
555
+ projectId: file.file.projectId,
556
+ parentId: file.file.id,
557
+ label: `LaTeX project: ${root}`,
558
+ summary: short(`LaTeX project ${root}, ${paths.length} supplied files: ${names}`),
559
+ structureKind: "document",
560
+ tags: ["latex", "latex-project"],
561
+ source: { ...file.file.source },
562
+ searchTerms: workspaceTerms(`${root} ${paths.join(" ")} ${names}`),
563
+ detailRef: store.put(encodeDocumentDetail({ kind: "tex-project", root, files: paths, dependencies })),
564
+ summaryProvenance: {
565
+ method: "extractive-v1",
566
+ sources: paths.map((path) => ({ uri: path, revision: entities.get(`file:${path}`).source.revision })),
567
+ children: [],
568
+ },
569
+ });
570
+ paths.forEach((path) => edge(projectId, `file:${path}`, "documents", file.file.source));
571
+ };
572
+ [...roots]
573
+ .sort()
574
+ .filter((path) => parsed.has(path))
575
+ .forEach((path) => visit(path, true));
576
+ [...parsed.keys()]
577
+ .sort()
578
+ .filter((path) => /\.(?:tex|ltx)$/i.test(path) && !covered.has(path))
579
+ .forEach((path) => visit(path, false));
580
+ snapshot.entities = [...entities.values()];
581
+ };
@@ -0,0 +1,9 @@
1
+ import type { ContentStore } from "../context/contentStore.js";
2
+ import type { WorkspaceSnapshot } from "../context/workspace.js";
3
+ export interface WorkspaceFile {
4
+ path: string;
5
+ content: string;
6
+ }
7
+ type WorkspaceScanner = (inputs: readonly WorkspaceFile[], store: ContentStore) => WorkspaceSnapshot;
8
+ export declare const scanWorkspace: WorkspaceScanner;
9
+ export {};