autonomous-flow-daemon 1.6.0 → 1.9.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 (61) hide show
  1. package/CHANGELOG.md +85 -85
  2. package/LICENSE +21 -21
  3. package/README-ko.md +282 -0
  4. package/README.md +282 -266
  5. package/mcp-config.json +10 -10
  6. package/package.json +4 -2
  7. package/src/adapters/index.ts +370 -370
  8. package/src/cli.ts +162 -127
  9. package/src/commands/benchmark.ts +187 -187
  10. package/src/commands/correlate.ts +180 -0
  11. package/src/commands/dashboard.ts +404 -0
  12. package/src/commands/evolution.ts +84 -1
  13. package/src/commands/fix.ts +158 -158
  14. package/src/commands/lang.ts +41 -41
  15. package/src/commands/plugin.ts +110 -0
  16. package/src/commands/restart.ts +14 -14
  17. package/src/commands/score.ts +276 -276
  18. package/src/commands/start.ts +155 -155
  19. package/src/commands/status.ts +157 -157
  20. package/src/commands/stop.ts +68 -68
  21. package/src/commands/suggest.ts +211 -0
  22. package/src/commands/sync.ts +329 -16
  23. package/src/constants.ts +32 -32
  24. package/src/core/boast.ts +280 -280
  25. package/src/core/config.ts +49 -49
  26. package/src/core/correlation-engine.ts +265 -0
  27. package/src/core/db.ts +145 -117
  28. package/src/core/discovery.ts +65 -65
  29. package/src/core/federation.ts +129 -0
  30. package/src/core/hologram/engine.ts +71 -71
  31. package/src/core/hologram/fallback.ts +11 -11
  32. package/src/core/hologram/go-extractor.ts +203 -0
  33. package/src/core/hologram/incremental.ts +227 -227
  34. package/src/core/hologram/py-extractor.ts +132 -132
  35. package/src/core/hologram/rust-extractor.ts +244 -0
  36. package/src/core/hologram/ts-extractor.ts +406 -320
  37. package/src/core/hologram/types.ts +27 -25
  38. package/src/core/hologram.ts +73 -71
  39. package/src/core/i18n/messages.ts +309 -309
  40. package/src/core/locale.ts +88 -88
  41. package/src/core/log-rotate.ts +33 -33
  42. package/src/core/log-utils.ts +38 -38
  43. package/src/core/lru-map.ts +61 -61
  44. package/src/core/notify.ts +74 -74
  45. package/src/core/plugin-manager.ts +225 -0
  46. package/src/core/rule-suggestion.ts +127 -0
  47. package/src/core/validator-generator.ts +224 -0
  48. package/src/core/workspace.ts +28 -28
  49. package/src/daemon/client.ts +78 -65
  50. package/src/daemon/event-batcher.ts +108 -108
  51. package/src/daemon/guards.ts +13 -13
  52. package/src/daemon/http-routes.ts +376 -293
  53. package/src/daemon/mcp-handler.ts +575 -270
  54. package/src/daemon/mcp-subscriptions.ts +81 -0
  55. package/src/daemon/mesh.ts +51 -0
  56. package/src/daemon/server.ts +655 -590
  57. package/src/daemon/types.ts +121 -100
  58. package/src/daemon/workspace-map.ts +104 -92
  59. package/src/platform.ts +60 -60
  60. package/src/version.ts +15 -15
  61. package/README.ko.md +0 -266
@@ -1,320 +1,406 @@
1
- import type { Node, Tree } from "web-tree-sitter";
2
- import type { LanguageExtractor, HologramOptions } from "./types";
3
- import { readFileSync } from "fs";
4
-
5
- /** Extract imported symbols from a context file using regex (L1 filtering) */
6
- function extractImportedSymbols(contextSource: string, targetPath: string): Set<string> | "all" {
7
- const symbols = new Set<string>();
8
- const targetBase = targetPath.replace(/\.[tj]sx?$/, "").replace(/\\/g, "/");
9
- const targetName = targetBase.split("/").pop() ?? targetBase;
10
-
11
- function matchesTarget(from: string): boolean {
12
- const normalized = from.replace(/\.[tj]sx?$/, "").replace(/\\/g, "/");
13
- return normalized.endsWith(targetName) || normalized.endsWith(targetBase);
14
- }
15
-
16
- // Namespace import
17
- const nsRe = /import\s+\*\s+as\s+\w+\s+from\s+["']([^"']+)["']/g;
18
- for (const m of contextSource.matchAll(nsRe)) {
19
- if (matchesTarget(m[1])) return "all";
20
- }
21
-
22
- // Named imports
23
- const namedRe = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
24
- for (const m of contextSource.matchAll(namedRe)) {
25
- if (matchesTarget(m[2])) {
26
- m[1].split(",").forEach(s => {
27
- const name = s.trim().split(/\s+as\s+/)[0].trim();
28
- if (name) symbols.add(name);
29
- });
30
- }
31
- }
32
-
33
- // Default import
34
- const defaultRe = /import\s+(\w+)\s+from\s+["']([^"']+)["']/g;
35
- for (const m of contextSource.matchAll(defaultRe)) {
36
- if (matchesTarget(m[2])) symbols.add("default");
37
- }
38
-
39
- // Combined: import X, { A, B } from "./target"
40
- const combinedRe = /import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
41
- for (const m of contextSource.matchAll(combinedRe)) {
42
- if (matchesTarget(m[3])) {
43
- symbols.add("default");
44
- m[2].split(",").forEach(s => {
45
- const name = s.trim().split(/\s+as\s+/)[0].trim();
46
- if (name) symbols.add(name);
47
- });
48
- }
49
- }
50
-
51
- return symbols;
52
- }
53
-
54
- /** Get the exported name from a top-level declaration node */
55
- function getExportedName(node: Node): string | null {
56
- // Check if wrapped in export_statement
57
- const parent = node.parent;
58
- const isExported = parent?.type === "export_statement" || node.type === "export_statement";
59
- if (!isExported && parent?.type !== "program") return null;
60
- if (!isExported) return null;
61
-
62
- const decl = node.type === "export_statement"
63
- ? node.namedChildren.find(c =>
64
- c.type === "function_declaration" ||
65
- c.type === "class_declaration" ||
66
- c.type === "interface_declaration" ||
67
- c.type === "type_alias_declaration" ||
68
- c.type === "enum_declaration" ||
69
- c.type === "lexical_declaration")
70
- : node;
71
-
72
- if (!decl) return null;
73
-
74
- const nameNode = decl.childForFieldName("name");
75
- if (nameNode) return nameNode.text;
76
-
77
- // Variable declarations
78
- if (decl.type === "lexical_declaration") {
79
- const declarator = decl.namedChildren.find(c => c.type === "variable_declarator");
80
- return declarator?.childForFieldName("name")?.text ?? null;
81
- }
82
-
83
- return null;
84
- }
85
-
86
- /** Stub a node's body, keeping only the signature */
87
- function stubBody(node: Node, source: string): string {
88
- const body = node.childForFieldName("body");
89
- if (!body) return collapseWhitespace(node.text);
90
- return collapseWhitespace(source.slice(node.startIndex, body.startIndex).trimEnd()) + " {…}";
91
- }
92
-
93
- /** Extract function signature */
94
- function extractFunction(node: Node, source: string): string {
95
- return stubBody(node, source);
96
- }
97
-
98
- /** Extract class with member signatures */
99
- function extractClass(node: Node, source: string): string {
100
- const nameNode = node.childForFieldName("name");
101
- const name = nameNode?.text ?? "Anonymous";
102
- const body = node.childForFieldName("body");
103
-
104
- // Heritage (extends/implements)
105
- let heritage = "";
106
- const heritageNodes = node.children.filter(c =>
107
- c.type === "extends_clause" || c.type === "implements_clause" ||
108
- c.type === "class_heritage");
109
- if (heritageNodes.length > 0) {
110
- heritage = " " + heritageNodes.map(h => collapseWhitespace(h.text)).join(" ");
111
- }
112
-
113
- // Prefix (export, abstract, etc.)
114
- const prefix = collapseWhitespace(
115
- source.slice(node.startIndex, (nameNode ?? body ?? node).startIndex).trimEnd()
116
- ).replace(name, "").trimEnd();
117
- const classPrefix = prefix ? `${prefix} ${name}` : `class ${name}`;
118
-
119
- if (!body) return `${classPrefix}${heritage} {}`;
120
-
121
- const members: string[] = [];
122
- for (const member of body.namedChildren) {
123
- switch (member.type) {
124
- case "public_field_definition":
125
- case "property_definition": {
126
- members.push(" " + collapseWhitespace(member.text).replace(/;$/, "") + ";");
127
- break;
128
- }
129
- case "method_definition": {
130
- const methodBody = member.childForFieldName("body");
131
- if (methodBody) {
132
- const sig = collapseWhitespace(source.slice(member.startIndex, methodBody.startIndex).trimEnd());
133
- members.push(" " + sig + ";");
134
- } else {
135
- members.push(" " + collapseWhitespace(member.text) + ";");
136
- }
137
- break;
138
- }
139
- }
140
- }
141
-
142
- return `${classPrefix}${heritage} {\n${members.join("\n")}\n}`;
143
- }
144
-
145
- /** Extract interface with all members */
146
- function extractInterface(node: Node, source: string): string {
147
- const nameNode = node.childForFieldName("name");
148
- const name = nameNode?.text ?? "Anonymous";
149
- const body = node.childForFieldName("body");
150
-
151
- // Heritage (extends)
152
- const extendsClause = node.children.find(c => c.type === "extends_type_clause");
153
- const ext = extendsClause ? " " + collapseWhitespace(extendsClause.text) : "";
154
-
155
- // Prefix
156
- const prefixEnd = (nameNode ?? body ?? node).startIndex;
157
- const rawPrefix = source.slice(node.startIndex, prefixEnd).trimEnd();
158
- const prefix = collapseWhitespace(rawPrefix).replace(name, "").trimEnd();
159
- const ifacePrefix = prefix ? `${prefix} ${name}` : `interface ${name}`;
160
-
161
- if (!body) return `${ifacePrefix}${ext} {}`;
162
-
163
- const members = body.namedChildren.map(m => {
164
- const text = collapseWhitespace(m.text).replace(/;$/, "");
165
- return " " + text + ";";
166
- });
167
-
168
- return `${ifacePrefix}${ext} {\n${members.join("\n")}\n}`;
169
- }
170
-
171
- /** Extract enum */
172
- function extractEnum(node: Node): string {
173
- const nameNode = node.childForFieldName("name");
174
- const name = nameNode?.text ?? "Anonymous";
175
- const body = node.childForFieldName("body");
176
-
177
- const isExport = node.parent?.type === "export_statement" ? "export " : "";
178
- const isConst = node.children.some(c => c.text === "const") ? "const " : "";
179
-
180
- if (!body) return `${isExport}${isConst}enum ${name} {}`;
181
-
182
- const members = body.namedChildren
183
- .filter(m => m.type === "enum_member" || m.type === "property_identifier")
184
- .map(m => collapseWhitespace(m.text));
185
-
186
- return `${isExport}${isConst}enum ${name} { ${members.join(", ")} }`;
187
- }
188
-
189
- /** Extract variable statement (const/let/var with possible arrow functions) */
190
- function extractVariable(node: Node, source: string): string {
191
- const keyword = node.children[0]?.text ?? "const";
192
- const isExport = node.parent?.type === "export_statement" ? "export " : "";
193
-
194
- const declarators = node.namedChildren.filter(c => c.type === "variable_declarator");
195
- const parts = declarators.map(d => {
196
- const name = d.childForFieldName("name")?.text ?? "?";
197
- const typeAnn = d.childForFieldName("type")
198
- ? ": " + collapseWhitespace(d.childForFieldName("type")!.text)
199
- : "";
200
- const value = d.childForFieldName("value");
201
-
202
- if (value && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function")) {
203
- return `${name} = ${stubBody(value, source)}`;
204
- }
205
- if (typeAnn) return `${name}${typeAnn}`;
206
- if (value) return `${name} = …`;
207
- return name;
208
- });
209
-
210
- return `${isExport}${keyword} ${parts.join(", ")};`;
211
- }
212
-
213
- /** Extract type alias */
214
- function extractTypeAlias(node: Node): string {
215
- const isExport = node.parent?.type === "export_statement" ? "export " : "";
216
- return isExport + collapseWhitespace(node.text);
217
- }
218
-
219
- function collapseWhitespace(s: string): string {
220
- return s.replace(/\s+/g, " ").trim();
221
- }
222
-
223
- /** Process a single top-level statement */
224
- function extractTopLevel(node: Node, source: string): string | null {
225
- // Unwrap export_statement to get the inner declaration
226
- if (node.type === "export_statement") {
227
- // Re-export: export { ... } from "..."
228
- const exportClause = node.namedChildren.find(c => c.type === "export_clause");
229
- if (exportClause) return collapseWhitespace(node.text);
230
-
231
- // export default expression
232
- const defaultKw = node.children.find(c => c.text === "default");
233
- if (defaultKw) {
234
- const inner = node.namedChildren.find(c => c.type !== "export_clause");
235
- if (inner && (inner.type === "function_declaration" || inner.type === "class_declaration")) {
236
- return "export default " + extractTopLevel(inner, source);
237
- }
238
- return `export default …;`;
239
- }
240
-
241
- // export <declaration>
242
- const inner = node.namedChildren[0];
243
- if (inner) {
244
- const result = extractTopLevel(inner, source);
245
- if (result) {
246
- const alreadyHasExport = result.startsWith("export ");
247
- return alreadyHasExport ? result : "export " + result;
248
- }
249
- }
250
- return collapseWhitespace(node.text);
251
- }
252
-
253
- switch (node.type) {
254
- case "import_statement":
255
- return collapseWhitespace(node.text);
256
- case "function_declaration":
257
- case "generator_function_declaration":
258
- return extractFunction(node, source);
259
- case "class_declaration":
260
- return extractClass(node, source);
261
- case "interface_declaration":
262
- return extractInterface(node, source);
263
- case "type_alias_declaration":
264
- return extractTypeAlias(node);
265
- case "enum_declaration":
266
- return extractEnum(node);
267
- case "lexical_declaration":
268
- case "variable_declaration":
269
- return extractVariable(node, source);
270
- case "export_statement":
271
- return collapseWhitespace(node.text);
272
- default:
273
- return null;
274
- }
275
- }
276
-
277
- export const tsExtractor: LanguageExtractor = {
278
- extensions: ["ts", "tsx", "js", "jsx", "mts", "cts"],
279
- grammarName: "typescript",
280
-
281
- extract(tree: Tree, source: string, options?: HologramOptions): string[] {
282
- const lines: string[] = [];
283
-
284
- // L1 filtering setup
285
- let importedSymbols: Set<string> | "all" | null = null;
286
- if (options?.contextFile) {
287
- try {
288
- const contextSource = readFileSync(options.contextFile, "utf-8");
289
- importedSymbols = extractImportedSymbols(contextSource, "");
290
- if (importedSymbols !== "all" && importedSymbols.size === 0) importedSymbols = null;
291
- } catch {
292
- importedSymbols = null;
293
- }
294
- }
295
-
296
- for (const stmt of tree.rootNode.namedChildren) {
297
- // L1: filter non-imported exports
298
- if (importedSymbols && importedSymbols !== "all") {
299
- const exportedName = getExportedName(stmt);
300
- if (exportedName !== null && !importedSymbols.has(exportedName)) {
301
- const line = extractTopLevel(stmt, source);
302
- if (line) {
303
- const stub = line.split("\n")[0].replace(/\{[^}]*\}?\s*$/, "").trimEnd();
304
- lines.push(`${stub} // details omitted — read directly if needed`);
305
- }
306
- continue;
307
- }
308
- }
309
-
310
- const line = extractTopLevel(stmt, source);
311
- if (line) lines.push(line);
312
- }
313
-
314
- if (importedSymbols && importedSymbols !== "all") {
315
- lines.push("\n// [afd L1] Non-imported exports are shown as stubs. Use afd_read for full details.");
316
- }
317
-
318
- return lines;
319
- },
320
- };
1
+ import type { Node, Tree } from "web-tree-sitter";
2
+ import type { LanguageExtractor, HologramOptions } from "./types";
3
+ import { readFileSync } from "fs";
4
+
5
+ /** Extract imported symbols from a context file using regex (L1 filtering) */
6
+ function extractImportedSymbols(contextSource: string, targetPath: string): Set<string> | "all" {
7
+ const symbols = new Set<string>();
8
+ const targetBase = targetPath.replace(/\.[tj]sx?$/, "").replace(/\\/g, "/");
9
+ const targetName = targetBase.split("/").pop() ?? targetBase;
10
+
11
+ function matchesTarget(from: string): boolean {
12
+ const normalized = from.replace(/\.[tj]sx?$/, "").replace(/\\/g, "/");
13
+ return normalized.endsWith(targetName) || normalized.endsWith(targetBase);
14
+ }
15
+
16
+ // Namespace import
17
+ const nsRe = /import\s+\*\s+as\s+\w+\s+from\s+["']([^"']+)["']/g;
18
+ for (const m of contextSource.matchAll(nsRe)) {
19
+ if (matchesTarget(m[1])) return "all";
20
+ }
21
+
22
+ // Named imports
23
+ const namedRe = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
24
+ for (const m of contextSource.matchAll(namedRe)) {
25
+ if (matchesTarget(m[2])) {
26
+ m[1].split(",").forEach(s => {
27
+ const name = s.trim().split(/\s+as\s+/)[0].trim();
28
+ if (name) symbols.add(name);
29
+ });
30
+ }
31
+ }
32
+
33
+ // Default import
34
+ const defaultRe = /import\s+(\w+)\s+from\s+["']([^"']+)["']/g;
35
+ for (const m of contextSource.matchAll(defaultRe)) {
36
+ if (matchesTarget(m[2])) symbols.add("default");
37
+ }
38
+
39
+ // Combined: import X, { A, B } from "./target"
40
+ const combinedRe = /import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
41
+ for (const m of contextSource.matchAll(combinedRe)) {
42
+ if (matchesTarget(m[3])) {
43
+ symbols.add("default");
44
+ m[2].split(",").forEach(s => {
45
+ const name = s.trim().split(/\s+as\s+/)[0].trim();
46
+ if (name) symbols.add(name);
47
+ });
48
+ }
49
+ }
50
+
51
+ return symbols;
52
+ }
53
+
54
+ /** Node types that contain implementation bodies (NOT pure type declarations) */
55
+ const IMPL_NODE_TYPES = new Set([
56
+ "function_declaration",
57
+ "generator_function_declaration",
58
+ "class_declaration",
59
+ "lexical_declaration",
60
+ "variable_declaration",
61
+ "expression_statement",
62
+ ]);
63
+
64
+ /**
65
+ * Fast pure-type file check: scans only root-level node types (O(n), no deep traversal).
66
+ * Returns true if every top-level statement is a pure type declaration with no implementation body.
67
+ */
68
+ function isPureTypeFile(rootChildren: Node[]): boolean {
69
+ for (const node of rootChildren) {
70
+ if (IMPL_NODE_TYPES.has(node.type)) return false;
71
+
72
+ if (node.type === "export_statement") {
73
+ // Check what's being exported — if it has an implementation node, it's not pure
74
+ const inner = node.namedChildren.find(c =>
75
+ c.type !== "export_clause" && c.type !== "string" && c.type !== "identifier"
76
+ );
77
+ if (inner && IMPL_NODE_TYPES.has(inner.type)) return false;
78
+ }
79
+ }
80
+ return true;
81
+ }
82
+
83
+ /**
84
+ * Get the declared name from any top-level node (exported or not).
85
+ * Used for L1 symbol filtering.
86
+ */
87
+ function getDeclarationName(node: Node): string | null {
88
+ let target = node;
89
+
90
+ // Unwrap export_statement
91
+ if (node.type === "export_statement") {
92
+ const inner = node.namedChildren.find(c =>
93
+ c.type === "function_declaration" ||
94
+ c.type === "generator_function_declaration" ||
95
+ c.type === "class_declaration" ||
96
+ c.type === "interface_declaration" ||
97
+ c.type === "type_alias_declaration" ||
98
+ c.type === "enum_declaration" ||
99
+ c.type === "lexical_declaration"
100
+ );
101
+ if (!inner) return null;
102
+ target = inner;
103
+ }
104
+
105
+ const nameNode = target.childForFieldName("name");
106
+ if (nameNode) return nameNode.text;
107
+
108
+ if (target.type === "lexical_declaration") {
109
+ const declarator = target.namedChildren.find(c => c.type === "variable_declarator");
110
+ return declarator?.childForFieldName("name")?.text ?? null;
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /** Get the exported name from a top-level declaration node */
117
+ function getExportedName(node: Node): string | null {
118
+ // Check if wrapped in export_statement
119
+ const parent = node.parent;
120
+ const isExported = parent?.type === "export_statement" || node.type === "export_statement";
121
+ if (!isExported && parent?.type !== "program") return null;
122
+ if (!isExported) return null;
123
+
124
+ const decl = node.type === "export_statement"
125
+ ? node.namedChildren.find(c =>
126
+ c.type === "function_declaration" ||
127
+ c.type === "class_declaration" ||
128
+ c.type === "interface_declaration" ||
129
+ c.type === "type_alias_declaration" ||
130
+ c.type === "enum_declaration" ||
131
+ c.type === "lexical_declaration")
132
+ : node;
133
+
134
+ if (!decl) return null;
135
+
136
+ const nameNode = decl.childForFieldName("name");
137
+ if (nameNode) return nameNode.text;
138
+
139
+ // Variable declarations
140
+ if (decl.type === "lexical_declaration") {
141
+ const declarator = decl.namedChildren.find(c => c.type === "variable_declarator");
142
+ return declarator?.childForFieldName("name")?.text ?? null;
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ /** Stub a node's body, keeping only the signature */
149
+ function stubBody(node: Node, source: string): string {
150
+ const body = node.childForFieldName("body");
151
+ if (!body) return collapseWhitespace(node.text);
152
+ return collapseWhitespace(source.slice(node.startIndex, body.startIndex).trimEnd()) + " {…}";
153
+ }
154
+
155
+ /** Extract function signature */
156
+ function extractFunction(node: Node, source: string): string {
157
+ return stubBody(node, source);
158
+ }
159
+
160
+ /** Extract class with member signatures */
161
+ function extractClass(node: Node, source: string): string {
162
+ const nameNode = node.childForFieldName("name");
163
+ const name = nameNode?.text ?? "Anonymous";
164
+ const body = node.childForFieldName("body");
165
+
166
+ // Heritage (extends/implements)
167
+ let heritage = "";
168
+ const heritageNodes = node.children.filter(c =>
169
+ c.type === "extends_clause" || c.type === "implements_clause" ||
170
+ c.type === "class_heritage");
171
+ if (heritageNodes.length > 0) {
172
+ heritage = " " + heritageNodes.map(h => collapseWhitespace(h.text)).join(" ");
173
+ }
174
+
175
+ // Prefix (export, abstract, etc.)
176
+ const prefix = collapseWhitespace(
177
+ source.slice(node.startIndex, (nameNode ?? body ?? node).startIndex).trimEnd()
178
+ ).replace(name, "").trimEnd();
179
+ const classPrefix = prefix ? `${prefix} ${name}` : `class ${name}`;
180
+
181
+ if (!body) return `${classPrefix}${heritage} {}`;
182
+
183
+ const members: string[] = [];
184
+ for (const member of body.namedChildren) {
185
+ switch (member.type) {
186
+ case "public_field_definition":
187
+ case "property_definition": {
188
+ members.push(" " + collapseWhitespace(member.text).replace(/;$/, "") + ";");
189
+ break;
190
+ }
191
+ case "method_definition": {
192
+ const methodBody = member.childForFieldName("body");
193
+ if (methodBody) {
194
+ const sig = collapseWhitespace(source.slice(member.startIndex, methodBody.startIndex).trimEnd());
195
+ members.push(" " + sig + ";");
196
+ } else {
197
+ members.push(" " + collapseWhitespace(member.text) + ";");
198
+ }
199
+ break;
200
+ }
201
+ }
202
+ }
203
+
204
+ return `${classPrefix}${heritage} {\n${members.join("\n")}\n}`;
205
+ }
206
+
207
+ /** Extract interface with all members */
208
+ function extractInterface(node: Node, source: string): string {
209
+ const nameNode = node.childForFieldName("name");
210
+ const name = nameNode?.text ?? "Anonymous";
211
+ const body = node.childForFieldName("body");
212
+
213
+ // Heritage (extends)
214
+ const extendsClause = node.children.find(c => c.type === "extends_type_clause");
215
+ const ext = extendsClause ? " " + collapseWhitespace(extendsClause.text) : "";
216
+
217
+ // Prefix
218
+ const prefixEnd = (nameNode ?? body ?? node).startIndex;
219
+ const rawPrefix = source.slice(node.startIndex, prefixEnd).trimEnd();
220
+ const prefix = collapseWhitespace(rawPrefix).replace(name, "").trimEnd();
221
+ const ifacePrefix = prefix ? `${prefix} ${name}` : `interface ${name}`;
222
+
223
+ if (!body) return `${ifacePrefix}${ext} {}`;
224
+
225
+ const members = body.namedChildren.map(m => {
226
+ const text = collapseWhitespace(m.text).replace(/;$/, "");
227
+ return " " + text + ";";
228
+ });
229
+
230
+ return `${ifacePrefix}${ext} {\n${members.join("\n")}\n}`;
231
+ }
232
+
233
+ /** Extract enum */
234
+ function extractEnum(node: Node): string {
235
+ const nameNode = node.childForFieldName("name");
236
+ const name = nameNode?.text ?? "Anonymous";
237
+ const body = node.childForFieldName("body");
238
+
239
+ const isExport = node.parent?.type === "export_statement" ? "export " : "";
240
+ const isConst = node.children.some(c => c.text === "const") ? "const " : "";
241
+
242
+ if (!body) return `${isExport}${isConst}enum ${name} {}`;
243
+
244
+ const members = body.namedChildren
245
+ .filter(m => m.type === "enum_member" || m.type === "property_identifier")
246
+ .map(m => collapseWhitespace(m.text));
247
+
248
+ return `${isExport}${isConst}enum ${name} { ${members.join(", ")} }`;
249
+ }
250
+
251
+ /** Extract variable statement (const/let/var with possible arrow functions) */
252
+ function extractVariable(node: Node, source: string): string {
253
+ const keyword = node.children[0]?.text ?? "const";
254
+ const isExport = node.parent?.type === "export_statement" ? "export " : "";
255
+
256
+ const declarators = node.namedChildren.filter(c => c.type === "variable_declarator");
257
+ const parts = declarators.map(d => {
258
+ const name = d.childForFieldName("name")?.text ?? "?";
259
+ const typeAnn = d.childForFieldName("type")
260
+ ? ": " + collapseWhitespace(d.childForFieldName("type")!.text)
261
+ : "";
262
+ const value = d.childForFieldName("value");
263
+
264
+ if (value && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function")) {
265
+ return `${name} = ${stubBody(value, source)}`;
266
+ }
267
+ if (typeAnn) return `${name}${typeAnn}`;
268
+ if (value) return `${name} = …`;
269
+ return name;
270
+ });
271
+
272
+ return `${isExport}${keyword} ${parts.join(", ")};`;
273
+ }
274
+
275
+ /** Extract type alias */
276
+ function extractTypeAlias(node: Node): string {
277
+ const isExport = node.parent?.type === "export_statement" ? "export " : "";
278
+ return isExport + collapseWhitespace(node.text);
279
+ }
280
+
281
+ function collapseWhitespace(s: string): string {
282
+ return s.replace(/\s+/g, " ").trim();
283
+ }
284
+
285
+ /** Process a single top-level statement */
286
+ function extractTopLevel(node: Node, source: string): string | null {
287
+ // Unwrap export_statement to get the inner declaration
288
+ if (node.type === "export_statement") {
289
+ // Re-export: export { ... } from "..."
290
+ const exportClause = node.namedChildren.find(c => c.type === "export_clause");
291
+ if (exportClause) return collapseWhitespace(node.text);
292
+
293
+ // export default expression
294
+ const defaultKw = node.children.find(c => c.text === "default");
295
+ if (defaultKw) {
296
+ const inner = node.namedChildren.find(c => c.type !== "export_clause");
297
+ if (inner && (inner.type === "function_declaration" || inner.type === "class_declaration")) {
298
+ return "export default " + extractTopLevel(inner, source);
299
+ }
300
+ return `export default …;`;
301
+ }
302
+
303
+ // export <declaration>
304
+ const inner = node.namedChildren[0];
305
+ if (inner) {
306
+ const result = extractTopLevel(inner, source);
307
+ if (result) {
308
+ const alreadyHasExport = result.startsWith("export ");
309
+ return alreadyHasExport ? result : "export " + result;
310
+ }
311
+ }
312
+ return collapseWhitespace(node.text);
313
+ }
314
+
315
+ switch (node.type) {
316
+ case "import_statement":
317
+ return collapseWhitespace(node.text);
318
+ case "function_declaration":
319
+ case "generator_function_declaration":
320
+ return extractFunction(node, source);
321
+ case "class_declaration":
322
+ return extractClass(node, source);
323
+ case "interface_declaration":
324
+ return extractInterface(node, source);
325
+ case "type_alias_declaration":
326
+ return extractTypeAlias(node);
327
+ case "enum_declaration":
328
+ return extractEnum(node);
329
+ case "lexical_declaration":
330
+ case "variable_declaration":
331
+ return extractVariable(node, source);
332
+ case "export_statement":
333
+ return collapseWhitespace(node.text);
334
+ default:
335
+ return null;
336
+ }
337
+ }
338
+
339
+ export const tsExtractor: LanguageExtractor = {
340
+ extensions: ["ts", "tsx", "js", "jsx", "mts", "cts"],
341
+ grammarName: "typescript",
342
+
343
+ extract(tree: Tree, source: string, options?: HologramOptions): string[] {
344
+ const rootChildren = tree.rootNode.namedChildren;
345
+
346
+ // === Pure Type File Bypass ===
347
+ // If all top-level nodes are type declarations (no implementation bodies),
348
+ // skip extraction entirely and return the original source as-is.
349
+ if (!options?.symbols && !options?.contextFile && isPureTypeFile(rootChildren)) {
350
+ return [source];
351
+ }
352
+
353
+ // === L1 Symbol Extraction ===
354
+ // If specific symbols are requested, pinpoint-extract only those nodes.
355
+ if (options?.symbols && options.symbols.length > 0) {
356
+ const symbolSet = new Set(options.symbols);
357
+ const matched: string[] = [];
358
+ for (const stmt of rootChildren) {
359
+ const name = getDeclarationName(stmt);
360
+ if (name && symbolSet.has(name)) {
361
+ const line = extractTopLevel(stmt, source);
362
+ if (line) matched.push(line);
363
+ }
364
+ }
365
+ return matched;
366
+ }
367
+
368
+ const lines: string[] = [];
369
+
370
+ // L1 filtering setup
371
+ let importedSymbols: Set<string> | "all" | null = null;
372
+ if (options?.contextFile) {
373
+ try {
374
+ const contextSource = readFileSync(options.contextFile, "utf-8");
375
+ importedSymbols = extractImportedSymbols(contextSource, "");
376
+ if (importedSymbols !== "all" && importedSymbols.size === 0) importedSymbols = null;
377
+ } catch {
378
+ importedSymbols = null;
379
+ }
380
+ }
381
+
382
+ for (const stmt of rootChildren) {
383
+ // L1: filter non-imported exports
384
+ if (importedSymbols && importedSymbols !== "all") {
385
+ const exportedName = getExportedName(stmt);
386
+ if (exportedName !== null && !importedSymbols.has(exportedName)) {
387
+ const line = extractTopLevel(stmt, source);
388
+ if (line) {
389
+ const stub = line.split("\n")[0].replace(/\{[^}]*\}?\s*$/, "").trimEnd();
390
+ lines.push(`${stub} // details omitted — read directly if needed`);
391
+ }
392
+ continue;
393
+ }
394
+ }
395
+
396
+ const line = extractTopLevel(stmt, source);
397
+ if (line) lines.push(line);
398
+ }
399
+
400
+ if (importedSymbols && importedSymbols !== "all") {
401
+ lines.push("\n// [afd L1] Non-imported exports are shown as stubs. Use afd_read for full details.");
402
+ }
403
+
404
+ return lines;
405
+ },
406
+ };