autonomous-flow-daemon 1.0.0 → 1.6.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.
- package/CHANGELOG.md +39 -0
- package/README.ko.md +142 -125
- package/README.md +119 -134
- package/package.json +11 -5
- package/src/adapters/index.ts +247 -35
- package/src/cli.ts +79 -1
- package/src/commands/benchmark.ts +187 -0
- package/src/commands/diagnose.ts +56 -14
- package/src/commands/doctor.ts +243 -0
- package/src/commands/evolution.ts +107 -0
- package/src/commands/fix.ts +22 -2
- package/src/commands/hooks.ts +136 -0
- package/src/commands/lang.ts +41 -0
- package/src/commands/mcp.ts +129 -0
- package/src/commands/restart.ts +14 -0
- package/src/commands/score.ts +192 -64
- package/src/commands/start.ts +137 -37
- package/src/commands/stats.ts +103 -0
- package/src/commands/status.ts +157 -0
- package/src/commands/stop.ts +42 -9
- package/src/commands/sync.ts +253 -20
- package/src/commands/vaccine.ts +177 -0
- package/src/constants.ts +26 -1
- package/src/core/boast.ts +280 -0
- package/src/core/config.ts +49 -0
- package/src/core/db.ts +74 -3
- package/src/core/discovery.ts +65 -0
- package/src/core/evolution.ts +215 -0
- package/src/core/hologram/engine.ts +71 -0
- package/src/core/hologram/fallback.ts +11 -0
- package/src/core/hologram/incremental.ts +227 -0
- package/src/core/hologram/py-extractor.ts +132 -0
- package/src/core/hologram/ts-extractor.ts +320 -0
- package/src/core/hologram/types.ts +25 -0
- package/src/core/hologram.ts +64 -236
- package/src/core/hook-manager.ts +259 -0
- package/src/core/i18n/messages.ts +309 -0
- package/src/core/immune.ts +8 -123
- package/src/core/locale.ts +88 -0
- package/src/core/log-rotate.ts +33 -0
- package/src/core/log-utils.ts +38 -0
- package/src/core/lru-map.ts +61 -0
- package/src/core/notify.ts +53 -14
- package/src/core/rule-engine.ts +287 -0
- package/src/core/semantic-diff.ts +432 -0
- package/src/core/telemetry.ts +94 -0
- package/src/core/vaccine-registry.ts +212 -0
- package/src/core/workspace.ts +28 -0
- package/src/core/yaml-minimal.ts +176 -0
- package/src/daemon/client.ts +34 -6
- package/src/daemon/event-batcher.ts +108 -0
- package/src/daemon/guards.ts +13 -0
- package/src/daemon/http-routes.ts +293 -0
- package/src/daemon/mcp-handler.ts +270 -0
- package/src/daemon/server.ts +492 -273
- package/src/daemon/types.ts +100 -0
- package/src/daemon/workspace-map.ts +92 -0
- package/src/platform.ts +60 -0
- package/src/version.ts +15 -0
|
@@ -0,0 +1,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
|
+
/** 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
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Tree } from "web-tree-sitter";
|
|
2
|
+
|
|
3
|
+
export interface HologramResult {
|
|
4
|
+
hologram: string;
|
|
5
|
+
originalLength: number;
|
|
6
|
+
hologramLength: number;
|
|
7
|
+
savings: number; // percentage 0-100
|
|
8
|
+
language?: string;
|
|
9
|
+
isDiff?: boolean;
|
|
10
|
+
changedNodes?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HologramOptions {
|
|
14
|
+
contextFile?: string;
|
|
15
|
+
diffOnly?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LanguageExtractor {
|
|
19
|
+
/** Supported file extensions (without dot) */
|
|
20
|
+
extensions: string[];
|
|
21
|
+
/** Tree-sitter grammar name for WASM resolution */
|
|
22
|
+
grammarName: string;
|
|
23
|
+
/** Extract type signatures from AST */
|
|
24
|
+
extract(tree: Tree, source: string, options?: HologramOptions): string[];
|
|
25
|
+
}
|
package/src/core/hologram.ts
CHANGED
|
@@ -1,243 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Export declarations (re-exports)
|
|
36
|
-
if (ts.isExportDeclaration(node)) {
|
|
37
|
-
return node.getText().replace(/\s+/g, " ").trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Export assignment (export default ...)
|
|
41
|
-
if (ts.isExportAssignment(node)) {
|
|
42
|
-
return `export default ${getTypeName(node.expression)};`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Type alias
|
|
46
|
-
if (ts.isTypeAliasDeclaration(node)) {
|
|
47
|
-
return collapseWhitespace(node.getText());
|
|
1
|
+
/**
|
|
2
|
+
* Hologram Engine — Language Dispatcher
|
|
3
|
+
*
|
|
4
|
+
* Routes file parsing to the appropriate tree-sitter extractor based on extension.
|
|
5
|
+
* Falls back to L0 (full source) for unsupported languages or parse errors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { TreeSitterEngine } from "./hologram/engine";
|
|
9
|
+
import { tsExtractor } from "./hologram/ts-extractor";
|
|
10
|
+
import { pyExtractor } from "./hologram/py-extractor";
|
|
11
|
+
import { fallbackL0 } from "./hologram/fallback";
|
|
12
|
+
import { generateIncrementalHologram, setCachedHologram } from "./hologram/incremental";
|
|
13
|
+
import type { HologramResult, HologramOptions, LanguageExtractor } from "./hologram/types";
|
|
14
|
+
|
|
15
|
+
// Re-export types for backward compatibility
|
|
16
|
+
export type { HologramResult, HologramOptions } from "./hologram/types";
|
|
17
|
+
export { clearHologramCache } from "./hologram/incremental";
|
|
18
|
+
|
|
19
|
+
const extractors: LanguageExtractor[] = [tsExtractor, pyExtractor];
|
|
20
|
+
|
|
21
|
+
function detectExtractor(filePath: string): LanguageExtractor | null {
|
|
22
|
+
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
23
|
+
return extractors.find(e => e.extensions.includes(ext)) ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function generateHologram(
|
|
27
|
+
filePath: string,
|
|
28
|
+
source: string,
|
|
29
|
+
options?: HologramOptions,
|
|
30
|
+
): Promise<HologramResult> {
|
|
31
|
+
const extractor = detectExtractor(filePath);
|
|
32
|
+
|
|
33
|
+
if (!extractor) {
|
|
34
|
+
return fallbackL0(filePath, source);
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (ts.isEnumDeclaration(node)) {
|
|
57
|
-
return extractEnum(node);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Class
|
|
61
|
-
if (ts.isClassDeclaration(node)) {
|
|
62
|
-
return extractClass(node);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Function declaration
|
|
66
|
-
if (ts.isFunctionDeclaration(node)) {
|
|
67
|
-
return extractFunction(node);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Variable statement (const/let/var — may contain arrow functions, objects, etc.)
|
|
71
|
-
if (ts.isVariableStatement(node)) {
|
|
72
|
-
return extractVariableStatement(node);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Fallback: skip unknown top-level statements
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function extractInterface(node: ts.InterfaceDeclaration): string {
|
|
80
|
-
const mods = getModifiers(node);
|
|
81
|
-
const name = node.name.text;
|
|
82
|
-
const ext = node.heritageClauses
|
|
83
|
-
? " " + node.heritageClauses.map(h => h.getText()).join(", ")
|
|
84
|
-
: "";
|
|
85
|
-
const members = node.members.map(m => {
|
|
86
|
-
const text = collapseWhitespace(m.getText()).replace(/;$/, "");
|
|
87
|
-
return " " + text + ";";
|
|
88
|
-
}).join("\n");
|
|
89
|
-
return `${mods}interface ${name}${ext} {\n${members}\n}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function extractEnum(node: ts.EnumDeclaration): string {
|
|
93
|
-
const mods = getModifiers(node);
|
|
94
|
-
const name = node.name.text;
|
|
95
|
-
const members = node.members.map(m => collapseWhitespace(m.getText())).join(", ");
|
|
96
|
-
return `${mods}enum ${name} { ${members} }`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function extractClass(node: ts.ClassDeclaration): string {
|
|
100
|
-
const mods = getModifiers(node);
|
|
101
|
-
const name = node.name?.text ?? "Anonymous";
|
|
102
|
-
const ext = node.heritageClauses
|
|
103
|
-
? " " + node.heritageClauses.map(h => h.getText()).join(", ")
|
|
104
|
-
: "";
|
|
105
|
-
|
|
106
|
-
const members: string[] = [];
|
|
107
|
-
for (const member of node.members) {
|
|
108
|
-
if (ts.isPropertyDeclaration(member)) {
|
|
109
|
-
members.push(" " + extractProperty(member) + ";");
|
|
110
|
-
} else if (ts.isMethodDeclaration(member) || ts.isGetAccessor(member) || ts.isSetAccessor(member)) {
|
|
111
|
-
members.push(" " + extractMethodSignature(member) + ";");
|
|
112
|
-
} else if (ts.isConstructorDeclaration(member)) {
|
|
113
|
-
const params = extractParams(member.parameters);
|
|
114
|
-
members.push(` constructor(${params});`);
|
|
37
|
+
// Incremental diff mode
|
|
38
|
+
if (options?.diffOnly) {
|
|
39
|
+
try {
|
|
40
|
+
return await generateIncrementalHologram(filePath, source, extractor, options);
|
|
41
|
+
} catch {
|
|
42
|
+
return fallbackL0(filePath, source);
|
|
115
43
|
}
|
|
116
44
|
}
|
|
117
45
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (d.initializer) {
|
|
143
|
-
// Arrow function or function expression
|
|
144
|
-
if (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)) {
|
|
145
|
-
const fn = d.initializer;
|
|
146
|
-
const async = hasModifier(fn, ts.SyntaxKind.AsyncKeyword) ? "async " : "";
|
|
147
|
-
const typeParams = fn.typeParameters
|
|
148
|
-
? `<${fn.typeParameters.map(t => t.getText()).join(", ")}>`
|
|
149
|
-
: "";
|
|
150
|
-
const params = extractParams(fn.parameters);
|
|
151
|
-
const ret = fn.type ? ": " + collapseWhitespace(fn.type.getText()) : "";
|
|
152
|
-
return `${name} = ${async}${typeParams}(${params})${ret} => {…}`;
|
|
153
|
-
}
|
|
154
|
-
// Object/array/other — just show type or truncated value
|
|
155
|
-
if (typeAnn) return `${name}${typeAnn}`;
|
|
156
|
-
return `${name} = …`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return `${name}${typeAnn}`;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
return `${mods}${keyword} ${decls.join(", ")};`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function extractProperty(node: ts.PropertyDeclaration): string {
|
|
166
|
-
const mods = getMemberModifiers(node);
|
|
167
|
-
const name = node.name.getText();
|
|
168
|
-
const type = node.type ? ": " + collapseWhitespace(node.type.getText()) : "";
|
|
169
|
-
const optional = node.questionToken ? "?" : "";
|
|
170
|
-
return `${mods}${name}${optional}${type}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function extractMethodSignature(node: ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration): string {
|
|
174
|
-
const mods = getMemberModifiers(node);
|
|
175
|
-
const name = node.name.getText();
|
|
176
|
-
const async = hasModifier(node, ts.SyntaxKind.AsyncKeyword) ? "async " : "";
|
|
177
|
-
|
|
178
|
-
if (ts.isGetAccessor(node)) {
|
|
179
|
-
const ret = node.type ? ": " + collapseWhitespace(node.type.getText()) : "";
|
|
180
|
-
return `${mods}get ${name}()${ret}`;
|
|
181
|
-
}
|
|
182
|
-
if (ts.isSetAccessor(node)) {
|
|
183
|
-
const params = extractParams(node.parameters);
|
|
184
|
-
return `${mods}set ${name}(${params})`;
|
|
46
|
+
try {
|
|
47
|
+
const engine = await TreeSitterEngine.getInstance();
|
|
48
|
+
const tree = await engine.parse(source, extractor.grammarName);
|
|
49
|
+
const lines = extractor.extract(tree, source, options);
|
|
50
|
+
tree.delete();
|
|
51
|
+
|
|
52
|
+
// Cache for future incremental diffs
|
|
53
|
+
setCachedHologram(filePath, lines);
|
|
54
|
+
|
|
55
|
+
const hologram = lines.join("\n");
|
|
56
|
+
const hologramLength = hologram.length;
|
|
57
|
+
const savings = source.length > 0
|
|
58
|
+
? Math.round((source.length - hologramLength) / source.length * 1000) / 10
|
|
59
|
+
: 0;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
hologram,
|
|
63
|
+
originalLength: source.length,
|
|
64
|
+
hologramLength,
|
|
65
|
+
savings,
|
|
66
|
+
language: extractor.grammarName,
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return fallbackL0(filePath, source);
|
|
185
70
|
}
|
|
186
|
-
|
|
187
|
-
const md = node as ts.MethodDeclaration;
|
|
188
|
-
const typeParams = md.typeParameters
|
|
189
|
-
? `<${md.typeParameters.map(t => t.getText()).join(", ")}>`
|
|
190
|
-
: "";
|
|
191
|
-
const params = extractParams(md.parameters);
|
|
192
|
-
const ret = md.type ? ": " + collapseWhitespace(md.type.getText()) : "";
|
|
193
|
-
return `${mods}${async}${name}${typeParams}(${params})${ret}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function extractParams(params: ts.NodeArray<ts.ParameterDeclaration>): string {
|
|
197
|
-
return params.map(p => {
|
|
198
|
-
const name = p.name.getText();
|
|
199
|
-
const optional = p.questionToken ? "?" : "";
|
|
200
|
-
const type = p.type ? ": " + collapseWhitespace(p.type.getText()) : "";
|
|
201
|
-
const rest = p.dotDotDotToken ? "..." : "";
|
|
202
|
-
return `${rest}${name}${optional}${type}`;
|
|
203
|
-
}).join(", ");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function getModifiers(node: ts.Node): string {
|
|
207
|
-
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
208
|
-
if (!mods) return "";
|
|
209
|
-
const relevant = mods
|
|
210
|
-
.filter(m => m.kind === ts.SyntaxKind.ExportKeyword || m.kind === ts.SyntaxKind.DefaultKeyword || m.kind === ts.SyntaxKind.DeclareKeyword)
|
|
211
|
-
.map(m => m.getText());
|
|
212
|
-
return relevant.length ? relevant.join(" ") + " " : "";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function getMemberModifiers(node: ts.Node): string {
|
|
216
|
-
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
217
|
-
if (!mods) return "";
|
|
218
|
-
const relevant = mods
|
|
219
|
-
.filter(m =>
|
|
220
|
-
m.kind === ts.SyntaxKind.PublicKeyword ||
|
|
221
|
-
m.kind === ts.SyntaxKind.PrivateKeyword ||
|
|
222
|
-
m.kind === ts.SyntaxKind.ProtectedKeyword ||
|
|
223
|
-
m.kind === ts.SyntaxKind.StaticKeyword ||
|
|
224
|
-
m.kind === ts.SyntaxKind.ReadonlyKeyword ||
|
|
225
|
-
m.kind === ts.SyntaxKind.AbstractKeyword
|
|
226
|
-
)
|
|
227
|
-
.map(m => m.getText());
|
|
228
|
-
return relevant.length ? relevant.join(" ") + " " : "";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
|
|
232
|
-
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
233
|
-
return mods?.some(m => m.kind === kind) ?? false;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function getTypeName(node: ts.Node): string {
|
|
237
|
-
if (ts.isIdentifier(node)) return node.text;
|
|
238
|
-
return "…";
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function collapseWhitespace(s: string): string {
|
|
242
|
-
return s.replace(/\s+/g, " ").trim();
|
|
243
71
|
}
|