@specverse/engines 6.0.8 → 6.0.9

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,71 @@
1
+ import type { StructuralPrepass, Symbol, Import, FileFilter, ClassFilter, MethodFilter, InterfaceFilter, IndexResult, MethodFactSheet } from '../interface.js';
2
+ export interface GitNexusBackendOptions {
3
+ /** Path to the gitnexus binary. Auto-detected via PATH if omitted. */
4
+ gitnexusPath?: string;
5
+ /** Skip running `gitnexus analyze` if .gitnexus/ already exists. */
6
+ reuseExistingIndex?: boolean;
7
+ }
8
+ export declare class GitNexusBackend implements StructuralPrepass {
9
+ private sourceDir;
10
+ private gitnexusPath;
11
+ private reuseExistingIndex;
12
+ capabilities: {
13
+ callGraph: boolean;
14
+ crossFileResolution: boolean;
15
+ componentClustering: boolean;
16
+ fullTextSearch: boolean;
17
+ fileWatching: boolean;
18
+ };
19
+ constructor(options?: GitNexusBackendOptions);
20
+ static isAvailable(): boolean;
21
+ init(sourceDir: string): Promise<void>;
22
+ index(): Promise<IndexResult>;
23
+ listFiles(filter?: FileFilter): Promise<string[]>;
24
+ listClasses(filter?: ClassFilter): Promise<Symbol[]>;
25
+ listMethods(filter?: MethodFilter): Promise<Symbol[]>;
26
+ listInterfaces(filter?: InterfaceFilter): Promise<Symbol[]>;
27
+ listImports(file: string): Promise<Import[]>;
28
+ fileSourceText(file: string): Promise<string>;
29
+ callers(symbol: string): Promise<Symbol[]>;
30
+ callees(symbol: string): Promise<Symbol[]>;
31
+ /**
32
+ * Cluster files via GitNexus's Leiden community detection.
33
+ * This is the distinguishing feature vs CodeGraph — surfaces natural
34
+ * component boundaries for SpecVerse `components:` inference.
35
+ *
36
+ * GitNexus's edge model: a single `CodeRelation` table with a `type`
37
+ * property — relationship discrimination happens via WHERE on that
38
+ * property, not by edge label. Community membership is `MEMBER_OF`.
39
+ *
40
+ * Note: GitNexus's MEMBER_OF connects symbols (not files directly) to
41
+ * communities. We aggregate per-community by filePath of the member symbols.
42
+ */
43
+ clusterFiles(): Promise<Array<{
44
+ id: string;
45
+ files: string[];
46
+ }>>;
47
+ /**
48
+ * GitNexus's call graph traversal — same `CodeRelation` table pattern
49
+ * as MEMBER_OF, with `type='CALLS'` as the discriminating property.
50
+ * (Override the default callers/callees from the parent class above
51
+ * because the relation type isn't `:CALLS` — it's `:CodeRelation` with
52
+ * a property filter.)
53
+ */
54
+ getMethodDetails(qualifiedName: string): Promise<MethodFactSheet | null>;
55
+ /**
56
+ * Run a Cypher query via the `gitnexus cypher` CLI and parse the
57
+ * markdown-table output into a row array. Each row is a map of
58
+ * column name → value (string, number, or array depending on Cypher).
59
+ *
60
+ * Always passes `--repo <basename>` to disambiguate when multiple repos
61
+ * are registered globally. GitNexus uses path basename as the registry
62
+ * key (or 'source' if the dir is literally named source/).
63
+ */
64
+ private cypher;
65
+ private rowToSymbol;
66
+ private mapLabel;
67
+ private toRelativePath;
68
+ private escape;
69
+ private detectBinary;
70
+ }
71
+ //# sourceMappingURL=gitnexus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitnexus.d.ts","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/gitnexus.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,WAAW,EACX,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAYzB,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,kBAAkB,CAAU;IAEpC,YAAY;;;;;;MAMV;gBAEU,OAAO,GAAE,sBAA2B;IAKhD,MAAM,CAAC,WAAW,IAAI,OAAO;IASvB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAetC,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAqB7B,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAcjD,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAYpD,WAAW,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA4BrD,cAAc,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY3D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY5C,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAM1C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAMhD;;;;;;;;;;;OAWG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IASrE;;;;;;OAMG;IAEG,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA0C9E;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,YAAY;CASrB"}
@@ -0,0 +1,367 @@
1
+ /**
2
+ * GitNexus backend — `StructuralPrepass` against `gitnexus` CLI.
3
+ *
4
+ * GitNexus indexes a codebase into a graph (LadybugDB) with tree-sitter
5
+ * symbol extraction + cross-file resolution + Leiden community detection.
6
+ * It exposes Cypher queries via the `gitnexus cypher <query>` CLI command,
7
+ * which returns JSON with a markdown-table inner format. We shell out to
8
+ * the CLI and parse the markdown table.
9
+ *
10
+ * The distinguishing capability vs CodeGraph is `clusterFiles()` — GitNexus
11
+ * tracks `Community` nodes (via Leiden community detection on the call graph)
12
+ * which give natural component-boundary suggestions for SpecVerse `components:`.
13
+ *
14
+ * Storage: GitNexus stores per-repo data in `.gitnexus/` and a global
15
+ * registry pointer at `~/.gitnexus/`. Like CodeGraph, indexing happens once;
16
+ * subsequent queries hit the persistent index.
17
+ */
18
+ import { execSync, execFileSync } from 'child_process';
19
+ import { existsSync, readFileSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { walkSourceTree, detectLanguage, globToRegex } from './walk.js';
22
+ import { extractEmits, extractDbWrites, extractExternalCalls, extractThrows, extractAsyncBoundaries, countBranchPoints, sliceBody, } from './method-patterns.js';
23
+ export class GitNexusBackend {
24
+ sourceDir = '';
25
+ gitnexusPath;
26
+ reuseExistingIndex;
27
+ capabilities = {
28
+ callGraph: true,
29
+ crossFileResolution: true,
30
+ componentClustering: true, // Leiden community detection (the differentiator)
31
+ fullTextSearch: true, // hybrid BM25 + semantic + RRF
32
+ fileWatching: false, // GitNexus doesn't auto-sync; needs explicit re-analyze
33
+ };
34
+ constructor(options = {}) {
35
+ this.gitnexusPath = options.gitnexusPath ?? this.detectBinary('gitnexus');
36
+ this.reuseExistingIndex = options.reuseExistingIndex ?? false;
37
+ }
38
+ static isAvailable() {
39
+ try {
40
+ execSync('gitnexus --version', { stdio: 'ignore', timeout: 5000 });
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ async init(sourceDir) {
48
+ this.sourceDir = sourceDir;
49
+ const gitnexusDir = join(sourceDir, '.gitnexus');
50
+ if (this.reuseExistingIndex && existsSync(gitnexusDir))
51
+ return;
52
+ // Run `gitnexus analyze` to build the index. --skip-git lets us index
53
+ // arbitrary directories (like our realized backends in /private/tmp).
54
+ if (!existsSync(gitnexusDir)) {
55
+ execSync(`${this.gitnexusPath} analyze --skip-git .`, {
56
+ cwd: sourceDir,
57
+ stdio: 'ignore',
58
+ timeout: 600_000,
59
+ });
60
+ }
61
+ }
62
+ async index() {
63
+ const start = Date.now();
64
+ if (!existsSync(join(this.sourceDir, '.gitnexus'))) {
65
+ execSync(`${this.gitnexusPath} analyze --skip-git .`, {
66
+ cwd: this.sourceDir,
67
+ stdio: 'ignore',
68
+ timeout: 600_000,
69
+ });
70
+ }
71
+ // GitNexus prints stats during analyze; query for them via Cypher
72
+ const rows = this.cypher('MATCH (n) RETURN count(*) AS c');
73
+ const totalNodes = rows.length > 0 ? Number(rows[0].c) : 0;
74
+ const fileRows = this.cypher('MATCH (n:File) RETURN count(*) AS c');
75
+ const fileCount = fileRows.length > 0 ? Number(fileRows[0].c) : 0;
76
+ return {
77
+ files: fileCount,
78
+ symbols: totalNodes - fileCount,
79
+ durationMs: Date.now() - start,
80
+ };
81
+ }
82
+ async listFiles(filter) {
83
+ // Same pattern as CodeGraph backend — FS walk, not the index, so adapters
84
+ // can find arbitrary file types (schema.prisma, package.json, etc.) the
85
+ // tree-sitter indexer doesn't track.
86
+ let result = walkSourceTree(this.sourceDir);
87
+ if (filter?.dir)
88
+ result = result.filter((f) => f.startsWith(filter.dir));
89
+ if (filter?.lang)
90
+ result = result.filter((f) => detectLanguage(f) === filter.lang);
91
+ if (filter?.glob) {
92
+ const re = globToRegex(filter.glob);
93
+ result = result.filter((f) => re.test(f));
94
+ }
95
+ return result;
96
+ }
97
+ async listClasses(filter) {
98
+ let cypher = `MATCH (n:Class) RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
99
+ if (filter?.dir) {
100
+ const rel = this.toRelativePath(filter.dir);
101
+ cypher = `MATCH (n:Class) WHERE n.filePath STARTS WITH '${this.escape(rel)}' RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
102
+ }
103
+ const rows = this.cypher(cypher);
104
+ return rows
105
+ .filter((r) => !filter?.namePattern || filter.namePattern.test(r.name))
106
+ .map((r) => this.rowToSymbol(r, 'class'));
107
+ }
108
+ async listMethods(filter) {
109
+ // GitNexus doesn't store class membership as a property; we derive it
110
+ // from the Method node's `name` plus filePath. For the `class` filter,
111
+ // we scope by the class's file path range.
112
+ let cypher = `MATCH (n:Method) RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
113
+ if (filter?.class) {
114
+ // Find the class's file + line range first, then filter methods by it.
115
+ const classRows = this.cypher(`MATCH (c:Class {name: '${this.escape(filter.class)}'}) RETURN c.filePath AS filePath, c.startLine AS startLine, c.endLine AS endLine LIMIT 1`);
116
+ if (classRows.length === 0)
117
+ return [];
118
+ const cls = classRows[0];
119
+ cypher = `MATCH (n:Method) WHERE n.filePath = '${this.escape(cls.filePath)}' AND n.startLine >= ${Number(cls.startLine)} AND n.startLine <= ${Number(cls.endLine)} RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
120
+ }
121
+ const rows = this.cypher(cypher);
122
+ let methods = rows.map((r) => {
123
+ const sym = this.rowToSymbol(r, 'method');
124
+ // Approximate qualifiedName: ClassName.methodName when class context is known
125
+ if (filter?.class)
126
+ sym.qualifiedName = `${filter.class}.${sym.name}`;
127
+ return sym;
128
+ });
129
+ if (filter?.nameIn && filter.nameIn.length > 0) {
130
+ const allowedNames = new Set(filter.nameIn);
131
+ methods = methods.filter((m) => allowedNames.has(m.name));
132
+ }
133
+ return methods;
134
+ }
135
+ async listInterfaces(filter) {
136
+ let cypher = `MATCH (n:Interface) RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
137
+ if (filter?.dir) {
138
+ const rel = this.toRelativePath(filter.dir);
139
+ cypher = `MATCH (n:Interface) WHERE n.filePath STARTS WITH '${this.escape(rel)}' RETURN n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.isExported AS isExported`;
140
+ }
141
+ const rows = this.cypher(cypher);
142
+ return rows
143
+ .filter((r) => !filter?.namePattern || filter.namePattern.test(r.name))
144
+ .map((r) => this.rowToSymbol(r, 'interface'));
145
+ }
146
+ async listImports(file) {
147
+ // GitNexus tracks imports via CodeRelation edges with type='IMPORTS'.
148
+ const rel = this.toRelativePath(file);
149
+ const cypher = `MATCH (f:File {filePath: '${this.escape(rel)}'})-[:CodeRelation {type: "IMPORTS"}]->(target) RETURN target.name AS name, target.filePath AS targetPath`;
150
+ const rows = this.cypher(cypher);
151
+ return rows.map((r) => ({
152
+ fromFile: file,
153
+ toModule: r.targetPath || r.name,
154
+ symbols: [r.name],
155
+ }));
156
+ }
157
+ async fileSourceText(file) {
158
+ return readFileSync(file, 'utf8');
159
+ }
160
+ async callers(symbol) {
161
+ const cypher = `MATCH (caller)-[:CodeRelation {type: "CALLS"}]->(callee) WHERE callee.name = '${this.escape(symbol)}' RETURN caller.name AS name, caller.filePath AS filePath, caller.startLine AS startLine, caller.endLine AS endLine, labels(caller)[0] AS kind LIMIT 100`;
162
+ const rows = this.cypher(cypher);
163
+ return rows.map((r) => this.rowToSymbol(r, this.mapLabel(r.kind)));
164
+ }
165
+ async callees(symbol) {
166
+ const cypher = `MATCH (caller)-[:CodeRelation {type: "CALLS"}]->(callee) WHERE caller.name = '${this.escape(symbol)}' RETURN callee.name AS name, callee.filePath AS filePath, callee.startLine AS startLine, callee.endLine AS endLine, labels(callee)[0] AS kind LIMIT 100`;
167
+ const rows = this.cypher(cypher);
168
+ return rows.map((r) => this.rowToSymbol(r, this.mapLabel(r.kind)));
169
+ }
170
+ /**
171
+ * Cluster files via GitNexus's Leiden community detection.
172
+ * This is the distinguishing feature vs CodeGraph — surfaces natural
173
+ * component boundaries for SpecVerse `components:` inference.
174
+ *
175
+ * GitNexus's edge model: a single `CodeRelation` table with a `type`
176
+ * property — relationship discrimination happens via WHERE on that
177
+ * property, not by edge label. Community membership is `MEMBER_OF`.
178
+ *
179
+ * Note: GitNexus's MEMBER_OF connects symbols (not files directly) to
180
+ * communities. We aggregate per-community by filePath of the member symbols.
181
+ */
182
+ async clusterFiles() {
183
+ const cypher = `MATCH (member)-[:CodeRelation {type: "MEMBER_OF"}]->(c:Community) RETURN c.id AS id, c.heuristicLabel AS label, collect(DISTINCT member.filePath) AS files`;
184
+ const rows = this.cypher(cypher);
185
+ return rows.map((r) => ({
186
+ id: String(r.label || r.id || 'unnamed'),
187
+ files: Array.isArray(r.files) ? r.files.filter(Boolean) : [],
188
+ }));
189
+ }
190
+ /**
191
+ * GitNexus's call graph traversal — same `CodeRelation` table pattern
192
+ * as MEMBER_OF, with `type='CALLS'` as the discriminating property.
193
+ * (Override the default callers/callees from the parent class above
194
+ * because the relation type isn't `:CALLS` — it's `:CodeRelation` with
195
+ * a property filter.)
196
+ */
197
+ async getMethodDetails(qualifiedName) {
198
+ const lastDot = qualifiedName.lastIndexOf('.');
199
+ if (lastDot < 0)
200
+ return null;
201
+ const className = qualifiedName.slice(0, lastDot);
202
+ const methodName = qualifiedName.slice(lastDot + 1);
203
+ // Find the method via class-scoped query
204
+ const methods = await this.listMethods({ class: className, nameIn: [methodName] });
205
+ if (methods.length === 0)
206
+ return null;
207
+ const method = methods[0];
208
+ const fileText = readFileSync(method.filePath, 'utf8');
209
+ const fileLines = fileText.split('\n');
210
+ const bodyLines = fileLines.slice(method.startLine - 1, method.endLine);
211
+ const body = bodyLines.join('\n');
212
+ // Calls — use the call graph (GitNexus's strength like CodeGraph's)
213
+ const calls = await this.callees(qualifiedName);
214
+ return {
215
+ qualifiedName,
216
+ signature: method.signature ?? '',
217
+ body,
218
+ filePath: method.filePath,
219
+ startLine: method.startLine,
220
+ endLine: method.endLine,
221
+ language: method.language,
222
+ calls,
223
+ emits: extractEmits(body),
224
+ dbWrites: extractDbWrites(body),
225
+ externalCalls: extractExternalCalls(body),
226
+ throws: extractThrows(body),
227
+ asyncBoundaries: extractAsyncBoundaries(body),
228
+ branchPoints: countBranchPoints(body),
229
+ bodyTextSliced: sliceBody(body),
230
+ };
231
+ }
232
+ // ────────────────────────────────────────────────────────────────────
233
+ // Internals
234
+ // ────────────────────────────────────────────────────────────────────
235
+ /**
236
+ * Run a Cypher query via the `gitnexus cypher` CLI and parse the
237
+ * markdown-table output into a row array. Each row is a map of
238
+ * column name → value (string, number, or array depending on Cypher).
239
+ *
240
+ * Always passes `--repo <basename>` to disambiguate when multiple repos
241
+ * are registered globally. GitNexus uses path basename as the registry
242
+ * key (or 'source' if the dir is literally named source/).
243
+ */
244
+ cypher(query) {
245
+ const repoName = this.sourceDir.split('/').filter(Boolean).pop() || 'source';
246
+ const stdout = execFileSync(this.gitnexusPath, ['cypher', '--repo', repoName, query], {
247
+ cwd: this.sourceDir,
248
+ encoding: 'utf8',
249
+ timeout: 60_000,
250
+ maxBuffer: 50 * 1024 * 1024,
251
+ });
252
+ if (!stdout.trim())
253
+ return [];
254
+ let payload;
255
+ try {
256
+ payload = JSON.parse(stdout);
257
+ }
258
+ catch {
259
+ throw new Error(`gitnexus cypher: failed to parse JSON output:\n${stdout.slice(0, 500)}`);
260
+ }
261
+ if (payload.error) {
262
+ throw new Error(`gitnexus cypher error: ${payload.error}`);
263
+ }
264
+ if (!payload.markdown)
265
+ return [];
266
+ return parseMarkdownTable(payload.markdown);
267
+ }
268
+ rowToSymbol(row, kind) {
269
+ const filePath = String(row.filePath || '');
270
+ const absPath = filePath.startsWith('/') ? filePath : join(this.sourceDir, filePath);
271
+ return {
272
+ kind,
273
+ name: String(row.name),
274
+ qualifiedName: String(row.name), // GitNexus's qualified IDs are colon-separated; we use plain name for now
275
+ filePath: absPath,
276
+ startLine: Number(row.startLine ?? 0),
277
+ endLine: Number(row.endLine ?? 0),
278
+ signature: row.signature ?? undefined,
279
+ docstring: row.docstring ?? undefined,
280
+ isExported: row.isExported === 'true' || row.isExported === true,
281
+ language: detectLanguage(absPath) || 'unknown',
282
+ };
283
+ }
284
+ mapLabel(label) {
285
+ switch (label) {
286
+ case 'Class': return 'class';
287
+ case 'Method': return 'method';
288
+ case 'Function': return 'function';
289
+ case 'Interface': return 'interface';
290
+ case 'Const': return 'constant';
291
+ default: return 'function';
292
+ }
293
+ }
294
+ toRelativePath(p) {
295
+ return p.startsWith(this.sourceDir)
296
+ ? p.slice(this.sourceDir.length).replace(/^\//, '')
297
+ : p;
298
+ }
299
+ escape(s) {
300
+ return s.replace(/'/g, "\\'");
301
+ }
302
+ detectBinary(name) {
303
+ try {
304
+ const path = execSync(`which ${name}`, { encoding: 'utf8', timeout: 3000 }).trim();
305
+ if (path)
306
+ return path;
307
+ }
308
+ catch {
309
+ // Fall through
310
+ }
311
+ return name;
312
+ }
313
+ }
314
+ /**
315
+ * Parse a GitHub-flavored markdown table into a row array.
316
+ *
317
+ * Input shape:
318
+ * | col1 | col2 |
319
+ * | --- | --- |
320
+ * | val1 | val2 |
321
+ *
322
+ * Returns: [ { col1: 'val1', col2: 'val2' }, ... ]
323
+ *
324
+ * Cell values are strings; callers can convert to number/boolean/JSON
325
+ * as needed. Cells starting with `[` are parsed as JSON arrays
326
+ * (GitNexus's `collect()` returns arrays in markdown form).
327
+ */
328
+ function parseMarkdownTable(md) {
329
+ const lines = md.split('\n').filter((l) => l.trim().length > 0);
330
+ if (lines.length < 2)
331
+ return [];
332
+ const headerLine = lines[0];
333
+ const headers = headerLine
334
+ .split('|')
335
+ .map((s) => s.trim())
336
+ .filter(Boolean);
337
+ // Skip the separator row (---|---|---)
338
+ const rows = [];
339
+ for (let i = 2; i < lines.length; i++) {
340
+ const cells = lines[i]
341
+ .split('|')
342
+ .map((s) => s.trim())
343
+ .filter((_, idx, arr) => idx > 0 && idx < arr.length - 1 || arr.length === headers.length);
344
+ // Sometimes the leading/trailing | are missing — fall back to a permissive split.
345
+ const rawCells = lines[i].split('|');
346
+ const cleaned = rawCells.length === headers.length + 2
347
+ ? rawCells.slice(1, -1).map((s) => s.trim())
348
+ : rawCells.map((s) => s.trim()).filter(Boolean);
349
+ const row = {};
350
+ for (let j = 0; j < headers.length; j++) {
351
+ let val = cleaned[j] ?? '';
352
+ // Try to parse JSON arrays (collect() output)
353
+ if (typeof val === 'string' && val.startsWith('[') && val.endsWith(']')) {
354
+ try {
355
+ val = JSON.parse(val);
356
+ }
357
+ catch {
358
+ // leave as string
359
+ }
360
+ }
361
+ row[headers[j]] = val;
362
+ }
363
+ rows.push(row);
364
+ }
365
+ return rows;
366
+ }
367
+ //# sourceMappingURL=gitnexus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitnexus.js","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/gitnexus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAY5B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EACL,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,SAAS,GACV,MAAM,sBAAsB,CAAC;AAS9B,MAAM,OAAO,eAAe;IAClB,SAAS,GAAG,EAAE,CAAC;IACf,YAAY,CAAS;IACrB,kBAAkB,CAAU;IAEpC,YAAY,GAAG;QACb,SAAS,EAAE,IAAI;QACf,mBAAmB,EAAE,IAAI;QACzB,mBAAmB,EAAE,IAAI,EAAI,kDAAkD;QAC/E,cAAc,EAAE,IAAI,EAAU,+BAA+B;QAC7D,YAAY,EAAE,KAAK,EAAW,wDAAwD;KACvF,CAAC;IAEF,YAAY,UAAkC,EAAE;QAC9C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC1E,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAC;IAChE,CAAC;IAED,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC;YACH,QAAQ,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,kBAAkB,IAAI,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO;QAC/D,sEAAsE;QACtE,sEAAsE;QACtE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,GAAG,IAAI,CAAC,YAAY,uBAAuB,EAAE;gBACpD,GAAG,EAAE,SAAS;gBACd,KAAK,EAAE,QAAQ;gBACf,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;YACnD,QAAQ,CAAC,GAAG,IAAI,CAAC,YAAY,uBAAuB,EAAE;gBACpD,GAAG,EAAE,IAAI,CAAC,SAAS;gBACnB,KAAK,EAAE,QAAQ;gBACf,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;QACL,CAAC;QACD,kEAAkE;QAClE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,gCAAgC,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,UAAU,GAAG,SAAS;YAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC/B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAmB;QACjC,0EAA0E;QAC1E,wEAAwE;QACxE,qCAAqC;QACrC,IAAI,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,GAAG;YAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,GAAI,CAAC,CAAC,CAAC;QAC1E,IAAI,MAAM,EAAE,IAAI;YAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;QACnF,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;YACjB,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAoB;QACpC,IAAI,MAAM,GAAG,2IAA2I,CAAC;QACzJ,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAChB,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5C,MAAM,GAAG,iDAAiD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6HAA6H,CAAC;QAC1M,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI;aACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACtE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAqB;QACrC,sEAAsE;QACtE,uEAAuE;QACvE,2CAA2C;QAC3C,IAAI,MAAM,GAAG,4IAA4I,CAAC;QAC1J,IAAI,MAAM,EAAE,KAAK,EAAE,CAAC;YAClB,uEAAuE;YACvE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAC3B,0BAA0B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,2FAA2F,CAC/I,CAAC;YACF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,GAAG,wCAAwC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,uBAAuB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,4HAA4H,CAAC;QAChS,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC1C,8EAA8E;YAC9E,IAAI,MAAM,EAAE,KAAK;gBAAE,GAAG,CAAC,aAAa,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YACrE,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5C,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAAwB;QAC3C,IAAI,MAAM,GAAG,+IAA+I,CAAC;QAC7J,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAChB,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5C,MAAM,GAAG,qDAAqD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6HAA6H,CAAC;QAC9M,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI;aACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACtE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,sEAAsE;QACtE,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,6BAA6B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2GAA2G,CAAC;QACxK,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtB,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,IAAI;YAChC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAY;QAC/B,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,MAAM,GAAG,iFAAiF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,0JAA0J,CAAC;QAC9Q,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,MAAM,GAAG,iFAAiF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,0JAA0J,CAAC;QAC9Q,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,4JAA4J,CAAC;QAC5K,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,SAAS,CAAC;YACxC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;SAC7D,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IAEH,KAAK,CAAC,gBAAgB,CAAC,aAAqB;QAC1C,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,OAAO,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAEpD,yCAAyC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAE1B,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAElC,oEAAoE;QACpE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAEhD,OAAO;YACL,aAAa;YACb,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE;YACjC,IAAI;YACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,KAAK;YACL,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC;YACzB,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC;YAC/B,aAAa,EAAE,oBAAoB,CAAC,IAAI,CAAC;YACzC,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;YAC3B,eAAe,EAAE,sBAAsB,CAAC,IAAI,CAAC;YAC7C,YAAY,EAAE,iBAAiB,CAAC,IAAI,CAAC;YACrC,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,YAAY;IACZ,uEAAuE;IAEvE;;;;;;;;OAQG;IACK,MAAM,CAAC,KAAa;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,QAAQ,CAAC;QAC7E,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE;YACpF,GAAG,EAAE,IAAI,CAAC,SAAS;YACnB,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,OAA8C,CAAC;QACnD,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,kDAAkD,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,0BAA0B,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAC;QACjC,OAAO,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAEO,WAAW,CAAC,GAAwB,EAAE,IAAoB;QAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrF,OAAO;YACL,IAAI;YACJ,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YACtB,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAG,0EAA0E;YAC5G,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC;YACrC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACjC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;YACrC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;YACrC,UAAU,EAAE,GAAG,CAAC,UAAU,KAAK,MAAM,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI;YAChE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,SAAS;SAC/C,CAAC;IACJ,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;YAC7B,KAAK,QAAQ,CAAC,CAAC,OAAO,QAAQ,CAAC;YAC/B,KAAK,UAAU,CAAC,CAAC,OAAO,UAAU,CAAC;YACnC,KAAK,WAAW,CAAC,CAAC,OAAO,WAAW,CAAC;YACrC,KAAK,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;YAChC,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,CAAS;QAC9B,OAAO,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;YACjC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;YACnD,CAAC,CAAC,CAAC,CAAC;IACR,CAAC;IAEO,MAAM,CAAC,CAAS;QACtB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAEO,YAAY,CAAC,IAAY;QAC/B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACnF,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,kBAAkB,CAAC,EAAU;IACpC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,UAAU;SACvB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,uCAAuC;IACvC,MAAM,IAAI,GAA+B,EAAE,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;aACnB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7F,kFAAkF;QAClF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,CAAC;YACpD,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClD,MAAM,GAAG,GAAwB,EAAE,CAAC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,IAAI,GAAG,GAAQ,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAChC,8CAA8C;YAC9C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxE,IAAI,CAAC;oBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACP,kBAAkB;gBACpB,CAAC;YACH,CAAC;YACD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,20 +1,28 @@
1
1
  /**
2
2
  * Backend selector — picks the best available `StructuralPrepass`.
3
3
  *
4
- * Order: codegraph (if available) → grep-only (always works).
5
- * GitNexus to be added later.
4
+ * Order: gitnexus (if available + opted-in) → codegraph (if available)
5
+ * grep-only (always works).
6
+ *
7
+ * GitNexus is more invasive (heavier stack, longer index time) but provides
8
+ * Leiden community detection (clusterFiles capability) which can help with
9
+ * multi-component codebases. Default `auto` prefers CodeGraph for the
10
+ * smaller-footprint case; explicitly request `gitnexus` for clustering.
6
11
  */
7
12
  import type { StructuralPrepass } from '../interface.js';
8
13
  import { GrepOnlyBackend } from './grep-only.js';
9
14
  import { CodeGraphBackend } from './codegraph.js';
10
- export { GrepOnlyBackend, CodeGraphBackend };
11
- export type BackendName = 'codegraph' | 'grep-only' | 'auto';
15
+ import { GitNexusBackend } from './gitnexus.js';
16
+ export { GrepOnlyBackend, CodeGraphBackend, GitNexusBackend };
17
+ export type BackendName = 'codegraph' | 'gitnexus' | 'grep-only' | 'auto';
12
18
  /**
13
- * Select a backend by name, or auto-pick the richest available.
19
+ * Select a backend by name, or auto-pick a sensible default.
14
20
  *
15
21
  * `auto` semantics:
16
- * - CodeGraph + sqlite3 both available → CodeGraph (richer, has call graph)
22
+ * - CodeGraph + sqlite3 both available → CodeGraph (lighter; richest baseline)
17
23
  * - Otherwise → grep-only (always works, no external tools)
24
+ * - Note: GitNexus is NOT auto-selected even when available, because its
25
+ * index overhead is significant. Explicit opt-in via name='gitnexus'.
18
26
  */
19
27
  export declare function selectBackend(name?: BackendName): StructuralPrepass;
20
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;AAE7C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,MAAM,CAAC;AAE7D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,WAAoB,GAAG,iBAAiB,CAiB3E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;AAE1E;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,WAAoB,GAAG,iBAAiB,CAyB3E"}
@@ -1,12 +1,15 @@
1
1
  import { GrepOnlyBackend } from './grep-only.js';
2
2
  import { CodeGraphBackend } from './codegraph.js';
3
- export { GrepOnlyBackend, CodeGraphBackend };
3
+ import { GitNexusBackend } from './gitnexus.js';
4
+ export { GrepOnlyBackend, CodeGraphBackend, GitNexusBackend };
4
5
  /**
5
- * Select a backend by name, or auto-pick the richest available.
6
+ * Select a backend by name, or auto-pick a sensible default.
6
7
  *
7
8
  * `auto` semantics:
8
- * - CodeGraph + sqlite3 both available → CodeGraph (richer, has call graph)
9
+ * - CodeGraph + sqlite3 both available → CodeGraph (lighter; richest baseline)
9
10
  * - Otherwise → grep-only (always works, no external tools)
11
+ * - Note: GitNexus is NOT auto-selected even when available, because its
12
+ * index overhead is significant. Explicit opt-in via name='gitnexus'.
10
13
  */
11
14
  export function selectBackend(name = 'auto') {
12
15
  if (name === 'codegraph') {
@@ -15,10 +18,16 @@ export function selectBackend(name = 'auto') {
15
18
  }
16
19
  return new CodeGraphBackend();
17
20
  }
21
+ if (name === 'gitnexus') {
22
+ if (!GitNexusBackend.isAvailable()) {
23
+ throw new Error('GitNexus backend requested but not available. Install with `npm i -g gitnexus`.');
24
+ }
25
+ return new GitNexusBackend();
26
+ }
18
27
  if (name === 'grep-only') {
19
28
  return new GrepOnlyBackend();
20
29
  }
21
- // auto
30
+ // auto — prefer CodeGraph (lighter, fast); GitNexus stays opt-in.
22
31
  if (CodeGraphBackend.isAvailable()) {
23
32
  return new CodeGraphBackend();
24
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;AAI7C;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,OAAoB,MAAM;IACtD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,+HAA+H,CAChI,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO,IAAI,eAAe,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO;IACP,IAAI,gBAAgB,CAAC,WAAW,EAAE,EAAE,CAAC;QACnC,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,eAAe,EAAE,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAI9D;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,OAAoB,MAAM;IACtD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,+HAA+H,CAChI,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,eAAe,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO,IAAI,eAAe,EAAE,CAAC;IAC/B,CAAC;IACD,kEAAkE;IAClE,IAAI,gBAAgB,CAAC,WAAW,EAAE,EAAE,CAAC;QACnC,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,eAAe,EAAE,CAAC;AAC/B,CAAC"}
@@ -14,7 +14,7 @@
14
14
  * // facts.entities, facts.relationships, facts.lifecycles, ...
15
15
  */
16
16
  export type { StructuralPrepass, Symbol, Import, Capabilities, FileFilter, ClassFilter, MethodFilter, InterfaceFilter, IndexResult, MethodFactSheet, } from './interface.js';
17
- export { GrepOnlyBackend, CodeGraphBackend, selectBackend, type BackendName, } from './backends/index.js';
17
+ export { GrepOnlyBackend, CodeGraphBackend, GitNexusBackend, selectBackend, type BackendName, } from './backends/index.js';
18
18
  export { extractTypeScriptPrisma, type PrismaFacts, type PrismaEntity, type PrismaField, type PrismaRelation, } from './adapters/index.js';
19
19
  import { type BackendName } from './backends/index.js';
20
20
  import type { StructuralPrepass } from './interface.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyse-prepass/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,YAAY,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,WAAW,EACX,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;IACH,+CAA+C;IAC/C,aAAa,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;QACzC,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;IACH,kFAAkF;IAClF,UAAU,EAAE,KAAK,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC,CAAC;IACH,+DAA+D;IAC/D,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7C,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,qEAAqE;IACrE,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD5G;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA2CnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyse-prepass/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,YAAY,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,WAAW,EACX,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC,CAAC;IACH,+CAA+C;IAC/C,aAAa,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;QACzC,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;IACH,kFAAkF;IAClF,UAAU,EAAE,KAAK,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC,CAAC;IACH,+DAA+D;IAC/D,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7C,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,qEAAqE;IACrE,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD5G;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA2CnE"}
@@ -1,4 +1,4 @@
1
- export { GrepOnlyBackend, CodeGraphBackend, selectBackend, } from './backends/index.js';
1
+ export { GrepOnlyBackend, CodeGraphBackend, GitNexusBackend, selectBackend, } from './backends/index.js';
2
2
  export { extractTypeScriptPrisma, } from './adapters/index.js';
3
3
  import { selectBackend } from './backends/index.js';
4
4
  import { extractTypeScriptPrisma } from './adapters/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyse-prepass/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyse-prepass/index.ts"],"names":[],"mappings":"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"}
@@ -374,6 +374,110 @@ import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/ty
374
374
  }
375
375
  const inferredSpec = { ...componentData, componentName, components: inferredYaml?.components || {} };
376
376
 
377
+ // --estimate: report what realize WOULD do, broken down by layer.
378
+ // Skip the actual realizeAll call (no manifest needed, no LLM cost).
379
+ // The three layers:
380
+ // L1 \u2014 Instance factory (templates, no LLM)
381
+ // L2 \u2014 Convention pattern matching (CURVED ops + default events, no LLM)
382
+ // L3 \u2014 AI from steps (one LLM call per declared step)
383
+ if (options.estimate) {
384
+ const components = inferredSpec.components || {};
385
+ const compNames = Object.keys(components);
386
+ let entityCount = 0, controllerCount = 0, serviceCount = 0, eventCount = 0, viewCount = 0;
387
+ let modelBehaviorSteps = 0, controllerActionSteps = 0, serviceOpSteps = 0;
388
+ let modelBehaviorsWithSteps = 0, controllerActionsWithSteps = 0, serviceOpsWithSteps = 0;
389
+ let curvedOpCount = 0;
390
+ const sumSteps = (block: any) => {
391
+ if (!block || typeof block !== 'object') return { ops: 0, opsWithSteps: 0, totalSteps: 0 };
392
+ let ops = 0, opsWithSteps = 0, totalSteps = 0;
393
+ for (const def of Object.values(block)) {
394
+ ops++;
395
+ const steps = (def as any)?.steps;
396
+ if (Array.isArray(steps) && steps.length > 0) {
397
+ opsWithSteps++;
398
+ totalSteps += steps.length;
399
+ }
400
+ }
401
+ return { ops, opsWithSteps, totalSteps };
402
+ };
403
+ for (const compName of compNames) {
404
+ const comp = components[compName] || {};
405
+ const models = comp.models || {};
406
+ const controllers = comp.controllers || {};
407
+ const services = comp.services || {};
408
+ const events = comp.events || {};
409
+ const views = comp.views || {};
410
+ entityCount += Object.keys(models).length;
411
+ controllerCount += Object.keys(controllers).length;
412
+ serviceCount += Object.keys(services).length;
413
+ eventCount += Object.keys(events).length;
414
+ viewCount += Object.keys(views).length;
415
+ for (const m of Object.values(models)) {
416
+ const r = sumSteps((m as any)?.behaviors);
417
+ modelBehaviorsWithSteps += r.opsWithSteps;
418
+ modelBehaviorSteps += r.totalSteps;
419
+ }
420
+ for (const c of Object.values(controllers)) {
421
+ const r = sumSteps((c as any)?.actions);
422
+ controllerActionsWithSteps += r.opsWithSteps;
423
+ controllerActionSteps += r.totalSteps;
424
+ const cured = (c as any)?.cured || (c as any)?.curved;
425
+ if (cured && typeof cured === 'object') curvedOpCount += Object.keys(cured).length;
426
+ }
427
+ for (const s of Object.values(services)) {
428
+ const r = sumSteps((s as any)?.operations);
429
+ serviceOpsWithSteps += r.opsWithSteps;
430
+ serviceOpSteps += r.totalSteps;
431
+ }
432
+ }
433
+ const totalLlmCalls = modelBehaviorSteps + controllerActionSteps + serviceOpSteps;
434
+ const fileEstimate = entityCount * 7 + controllerCount + serviceCount * 2 + viewCount + 15;
435
+ console.log('');
436
+ console.log('\u2554\u2550\u2550 spv realize --estimate \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
437
+ console.log('\u2551');
438
+ console.log('\u2551 Type: ' + type);
439
+ console.log('\u2551 Components: ' + compNames.length + (compNames.length > 0 ? ' (' + compNames.join(', ') + ')' : ''));
440
+ console.log('\u2551');
441
+ console.log('\u2551 \u2500\u2500 Spec inventory (post-inference) \u2500\u2500');
442
+ console.log('\u2551 Entities (models): ' + entityCount);
443
+ console.log('\u2551 Controllers: ' + controllerCount);
444
+ console.log('\u2551 Services: ' + serviceCount);
445
+ console.log('\u2551 Events: ' + eventCount);
446
+ console.log('\u2551 Views: ' + viewCount);
447
+ console.log('\u2551');
448
+ console.log('\u2551 \u2500\u2500 Output by realize layer \u2500\u2500');
449
+ console.log('\u2551');
450
+ console.log('\u2551 L1 \u2014 Instance factory (file scaffolding, no LLM):');
451
+ console.log('\u2551 File scaffolding for each entity / controller / service / view');
452
+ console.log('\u2551 + framework boilerplate (Fastify routes, Prisma schema, React shell)');
453
+ console.log('\u2551 Estimate: ~' + fileEstimate + ' files');
454
+ console.log('\u2551');
455
+ console.log('\u2551 L2 \u2014 Convention pattern matching (bodies, no LLM):');
456
+ console.log('\u2551 CURVED ops auto-implemented: ' + curvedOpCount);
457
+ console.log('\u2551 Default events on lifecycles: ' + eventCount + ' (declared) + auto-derived');
458
+ console.log('\u2551 Default validation patterns: ~' + (entityCount * 2));
459
+ console.log('\u2551');
460
+ console.log('\u2551 L3 \u2014 AI from steps (LLM, 1 call per step):');
461
+ console.log('\u2551 Model behaviors with steps: ' + modelBehaviorsWithSteps + ' behaviors \u2192 ' + modelBehaviorSteps + ' steps');
462
+ console.log('\u2551 Controller actions with steps: ' + controllerActionsWithSteps + ' actions \u2192 ' + controllerActionSteps + ' steps');
463
+ console.log('\u2551 Service ops with steps: ' + serviceOpsWithSteps + ' operations \u2192 ' + serviceOpSteps + ' steps');
464
+ console.log('\u2551 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');
465
+ console.log('\u2551 Total LLM calls: ' + totalLlmCalls);
466
+ console.log('\u2551');
467
+ if (totalLlmCalls > 0) {
468
+ console.log('\u2551 \u2500\u2500 Estimated wall time + cost (L3 only) \u2500\u2500');
469
+ console.log('\u2551 On Opus 4.7 / Max: ~' + Math.ceil(totalLlmCalls * 0.2) + ' min wall (free, claude-cli session-cached)');
470
+ console.log('\u2551 On Sonnet 4.6: ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall (free)');
471
+ console.log('\u2551 On Anthropic API: ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall, ~$' + (totalLlmCalls * 0.015).toFixed(2) + ' (Sonnet rates)');
472
+ console.log('\u2551 On DeepSeek/Together: ~' + Math.ceil(totalLlmCalls * 0.08) + ' min wall, ~$' + (totalLlmCalls * 0.0008).toFixed(3) + ' (10-30\xD7 cheaper)');
473
+ } else {
474
+ console.log('\u2551 No LLM cost \u2014 all output is L1 + L2 (templates + conventions only).');
475
+ }
476
+ console.log('\u2551');
477
+ console.log('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
478
+ return;
479
+ }
480
+
377
481
  // Realize \u2014 let the realize engine handle its own library.
378
482
  // Locate the nearest implementation manifest by walking up from the
379
483
  // spec file, then falling back to the user's cwd. This lets the same
@@ -429,6 +429,110 @@ import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/ty
429
429
  }
430
430
  const inferredSpec = { ...componentData, componentName, components: inferredYaml?.components || {} };
431
431
 
432
+ // --estimate: report what realize WOULD do, broken down by layer.
433
+ // Skip the actual realizeAll call (no manifest needed, no LLM cost).
434
+ // The three layers:
435
+ // L1 — Instance factory (templates, no LLM)
436
+ // L2 — Convention pattern matching (CURVED ops + default events, no LLM)
437
+ // L3 — AI from steps (one LLM call per declared step)
438
+ if (options.estimate) {
439
+ const components = inferredSpec.components || {};
440
+ const compNames = Object.keys(components);
441
+ let entityCount = 0, controllerCount = 0, serviceCount = 0, eventCount = 0, viewCount = 0;
442
+ let modelBehaviorSteps = 0, controllerActionSteps = 0, serviceOpSteps = 0;
443
+ let modelBehaviorsWithSteps = 0, controllerActionsWithSteps = 0, serviceOpsWithSteps = 0;
444
+ let curvedOpCount = 0;
445
+ const sumSteps = (block: any) => {
446
+ if (!block || typeof block !== 'object') return { ops: 0, opsWithSteps: 0, totalSteps: 0 };
447
+ let ops = 0, opsWithSteps = 0, totalSteps = 0;
448
+ for (const def of Object.values(block)) {
449
+ ops++;
450
+ const steps = (def as any)?.steps;
451
+ if (Array.isArray(steps) && steps.length > 0) {
452
+ opsWithSteps++;
453
+ totalSteps += steps.length;
454
+ }
455
+ }
456
+ return { ops, opsWithSteps, totalSteps };
457
+ };
458
+ for (const compName of compNames) {
459
+ const comp = components[compName] || {};
460
+ const models = comp.models || {};
461
+ const controllers = comp.controllers || {};
462
+ const services = comp.services || {};
463
+ const events = comp.events || {};
464
+ const views = comp.views || {};
465
+ entityCount += Object.keys(models).length;
466
+ controllerCount += Object.keys(controllers).length;
467
+ serviceCount += Object.keys(services).length;
468
+ eventCount += Object.keys(events).length;
469
+ viewCount += Object.keys(views).length;
470
+ for (const m of Object.values(models)) {
471
+ const r = sumSteps((m as any)?.behaviors);
472
+ modelBehaviorsWithSteps += r.opsWithSteps;
473
+ modelBehaviorSteps += r.totalSteps;
474
+ }
475
+ for (const c of Object.values(controllers)) {
476
+ const r = sumSteps((c as any)?.actions);
477
+ controllerActionsWithSteps += r.opsWithSteps;
478
+ controllerActionSteps += r.totalSteps;
479
+ const cured = (c as any)?.cured || (c as any)?.curved;
480
+ if (cured && typeof cured === 'object') curvedOpCount += Object.keys(cured).length;
481
+ }
482
+ for (const s of Object.values(services)) {
483
+ const r = sumSteps((s as any)?.operations);
484
+ serviceOpsWithSteps += r.opsWithSteps;
485
+ serviceOpSteps += r.totalSteps;
486
+ }
487
+ }
488
+ const totalLlmCalls = modelBehaviorSteps + controllerActionSteps + serviceOpSteps;
489
+ const fileEstimate = entityCount * 7 + controllerCount + serviceCount * 2 + viewCount + 15;
490
+ console.log('');
491
+ console.log('╔══ spv realize --estimate ══════════════════════════════════════════');
492
+ console.log('║');
493
+ console.log('║ Type: ' + type);
494
+ console.log('║ Components: ' + compNames.length + (compNames.length > 0 ? ' (' + compNames.join(', ') + ')' : ''));
495
+ console.log('║');
496
+ console.log('║ ── Spec inventory (post-inference) ──');
497
+ console.log('║ Entities (models): ' + entityCount);
498
+ console.log('║ Controllers: ' + controllerCount);
499
+ console.log('║ Services: ' + serviceCount);
500
+ console.log('║ Events: ' + eventCount);
501
+ console.log('║ Views: ' + viewCount);
502
+ console.log('║');
503
+ console.log('║ ── Output by realize layer ──');
504
+ console.log('║');
505
+ console.log('║ L1 — Instance factory (file scaffolding, no LLM):');
506
+ console.log('║ File scaffolding for each entity / controller / service / view');
507
+ console.log('║ + framework boilerplate (Fastify routes, Prisma schema, React shell)');
508
+ console.log('║ Estimate: ~' + fileEstimate + ' files');
509
+ console.log('║');
510
+ console.log('║ L2 — Convention pattern matching (bodies, no LLM):');
511
+ console.log('║ CURVED ops auto-implemented: ' + curvedOpCount);
512
+ console.log('║ Default events on lifecycles: ' + eventCount + ' (declared) + auto-derived');
513
+ console.log('║ Default validation patterns: ~' + (entityCount * 2));
514
+ console.log('║');
515
+ console.log('║ L3 — AI from steps (LLM, 1 call per step):');
516
+ console.log('║ Model behaviors with steps: ' + modelBehaviorsWithSteps + ' behaviors → ' + modelBehaviorSteps + ' steps');
517
+ console.log('║ Controller actions with steps: ' + controllerActionsWithSteps + ' actions → ' + controllerActionSteps + ' steps');
518
+ console.log('║ Service ops with steps: ' + serviceOpsWithSteps + ' operations → ' + serviceOpSteps + ' steps');
519
+ console.log('║ ────────────────────────────────────────────────────────');
520
+ console.log('║ Total LLM calls: ' + totalLlmCalls);
521
+ console.log('║');
522
+ if (totalLlmCalls > 0) {
523
+ console.log('║ ── Estimated wall time + cost (L3 only) ──');
524
+ console.log('║ On Opus 4.7 / Max: ~' + Math.ceil(totalLlmCalls * 0.2) + ' min wall (free, claude-cli session-cached)');
525
+ console.log('║ On Sonnet 4.6: ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall (free)');
526
+ console.log('║ On Anthropic API: ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall, ~$' + (totalLlmCalls * 0.015).toFixed(2) + ' (Sonnet rates)');
527
+ console.log('║ On DeepSeek/Together: ~' + Math.ceil(totalLlmCalls * 0.08) + ' min wall, ~$' + (totalLlmCalls * 0.0008).toFixed(3) + ' (10-30× cheaper)');
528
+ } else {
529
+ console.log('║ No LLM cost — all output is L1 + L2 (templates + conventions only).');
530
+ }
531
+ console.log('║');
532
+ console.log('╚════════════════════════════════════════════════════════════════════');
533
+ return;
534
+ }
535
+
432
536
  // Realize — let the realize engine handle its own library.
433
537
  // Locate the nearest implementation manifest by walking up from the
434
538
  // spec file, then falling back to the user's cwd. This lets the same
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "6.0.8",
3
+ "version": "6.0.9",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry, bundles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,14 +0,0 @@
1
- /**
2
- * Command Generator
3
- *
4
- * Generates individual command files from command specifications.
5
- * Each command registers itself on a Commander program and wires
6
- * to its corresponding service for business logic delegation.
7
- */
8
- import type { TemplateContext } from '@specverse/types';
9
- /**
10
- * Generate a single command file.
11
- * Called once per command in the spec.
12
- */
13
- export default function generateCommand(context: TemplateContext): string;
14
- //# sourceMappingURL=command-generator.d.ts.map
@@ -1,182 +0,0 @@
1
- /**
2
- * Command Generator
3
- *
4
- * Generates individual command files from command specifications.
5
- * Each command registers itself on a Commander program and wires
6
- * to its corresponding service for business logic delegation.
7
- */
8
- /**
9
- * Generate a single command file.
10
- * Called once per command in the spec.
11
- */
12
- export default function generateCommand(context) {
13
- const { command } = context;
14
- if (!command) {
15
- throw new Error('Command is required in template context');
16
- }
17
- const name = command.name;
18
- const description = command.description || '';
19
- const args = command.arguments || {};
20
- const flags = command.flags || {};
21
- const exitCodes = command.exitCodes || {};
22
- const subcommands = command.subcommands || {};
23
- const serviceRef = command.serviceRef;
24
- // Build positional argument string for Commander
25
- const positionalArgs = Object.entries(args)
26
- .filter(([_, arg]) => arg.positional)
27
- .sort((a, b) => (a[1].position || 0) - (b[1].position || 0))
28
- .map(([argName, arg]) => {
29
- const required = arg.required;
30
- return required ? `<${argName}>` : `[${argName}]`;
31
- })
32
- .join(' ');
33
- const commandStr = positionalArgs ? `${name} ${positionalArgs}` : name;
34
- // Build options
35
- const optionDefs = Object.entries(flags).map(([flagName, flag]) => {
36
- const alias = flag.alias ? `${flag.alias}, ` : '';
37
- const flagType = flag.type?.toLowerCase();
38
- const valuePart = flagType === 'boolean' ? '' : ` <${flagName.replace(/^--/, '')}>`;
39
- const defaultVal = flag.default !== undefined ? `, ${JSON.stringify(flag.default)}` : '';
40
- const desc = flag.description || `${flagName} option`;
41
- return ` .option('${alias}${flagName}${valuePart}', '${desc}'${defaultVal})`;
42
- });
43
- // Build type interface for options
44
- const optionTypes = Object.entries(flags).map(([flagName, flag]) => {
45
- const tsType = mapFlagTypeToTS(flag.type);
46
- const key = flagName.replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase());
47
- return ` ${key}${flag.required ? '' : '?'}: ${tsType};`;
48
- });
49
- // Build positional arg types
50
- const argTypes = Object.entries(args)
51
- .filter(([_, arg]) => arg.positional)
52
- .map(([argName, arg]) => {
53
- const tsType = mapArgTypeToTS(arg.type);
54
- return `${argName}: ${tsType}`;
55
- });
56
- // Generate action handler
57
- const actionParams = argTypes.length > 0
58
- ? argTypes.join(', ') + ', options: CommandOptions'
59
- : 'options: CommandOptions';
60
- // Generate exit code comments
61
- const exitCodeComments = Object.entries(exitCodes).length > 0
62
- ? Object.entries(exitCodes).map(([code, meaning]) => ` // ${code}: ${meaning}`).join('\n')
63
- : '';
64
- // Handle subcommands
65
- const hasSubcommands = Object.keys(subcommands).length > 0;
66
- const subcommandRegistrations = hasSubcommands
67
- ? generateSubcommandRegistrations(name, subcommands)
68
- : '';
69
- // Service import
70
- const serviceImport = serviceRef
71
- ? `import { ${serviceRef} } from '../services/${serviceRef}.js';`
72
- : '';
73
- return `/**
74
- * ${name} command
75
- * ${description}
76
- * Generated from SpecVerse specification
77
- */
78
-
79
- import { Command } from 'commander';
80
- ${serviceImport}
81
-
82
- interface CommandOptions {
83
- ${optionTypes.length > 0 ? optionTypes.join('\n') : ' [key: string]: any;'}
84
- }
85
-
86
- ${exitCodeComments ? `/**\n * Exit codes:\n${exitCodeComments}\n */` : ''}
87
-
88
- /**
89
- * Register the ${name} command on the program.
90
- */
91
- export function register${capitalize(name)}Command(program: Command): void {
92
- ${hasSubcommands ? generateCommandWithSubcommands(name, description, subcommands, optionDefs) : generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes)}
93
- }
94
- ${subcommandRegistrations}
95
- `;
96
- }
97
- function generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes) {
98
- const handler = serviceRef
99
- ? `const service = new ${serviceRef}();
100
- const result = await service.execute(${actionParams.includes(':') ? '{ ' + actionParams.split(',').map(p => p.trim().split(':')[0].trim()).join(', ') + ', ...options }' : 'options'});
101
- console.log(result);`
102
- : `console.log('Executing ${name}...');
103
- // TODO: Wire to service`;
104
- return `const cmd = program
105
- .command('${commandStr}')
106
- .description('${description}')
107
- ${optionDefs.join('\n')}
108
- .action(async (${actionParams}) => {
109
- try {
110
- ${handler}
111
- } catch (error: any) {
112
- console.error('Error:', error.message);
113
- process.exit(${Object.keys(exitCodes).find(k => k !== '0') || '1'});
114
- }
115
- });`;
116
- }
117
- function generateCommandWithSubcommands(name, description, subcommands, optionDefs) {
118
- const subcmdRegistrations = Object.entries(subcommands).map(([subName, subDef]) => {
119
- const subDesc = subDef.description || '';
120
- const subArgs = subDef.arguments || {};
121
- const subFlags = subDef.flags || {};
122
- const positionalStr = Object.entries(subArgs)
123
- .filter(([_, a]) => a.positional)
124
- .map(([n, a]) => a.required ? `<${n}>` : `[${n}]`)
125
- .join(' ');
126
- const subCmdStr = positionalStr ? `${subName} ${positionalStr}` : subName;
127
- const subOptionDefs = Object.entries(subFlags).map(([flagName, flag]) => {
128
- const alias = flag.alias ? `${flag.alias}, ` : '';
129
- const flagType = flag.type?.toLowerCase();
130
- const valuePart = flagType === 'boolean' ? '' : ` <${flagName.replace(/^--/, '')}>`;
131
- const defaultVal = flag.default !== undefined ? `, ${JSON.stringify(flag.default)}` : '';
132
- return ` .option('${alias}${flagName}${valuePart}', '${flag.description || flagName}'${defaultVal})`;
133
- });
134
- return `
135
- cmd
136
- .command('${subCmdStr}')
137
- .description('${subDesc}')
138
- ${subOptionDefs.join('\n')}
139
- .action(async (...args: any[]) => {
140
- try {
141
- console.log('Executing ${name} ${subName}...');
142
- // TODO: Wire to service
143
- } catch (error: any) {
144
- console.error('Error:', error.message);
145
- process.exit(1);
146
- }
147
- });`;
148
- });
149
- return `const cmd = program
150
- .command('${name}')
151
- .description('${description}');
152
- ${subcmdRegistrations.join('\n')}`;
153
- }
154
- function generateSubcommandRegistrations(_parentName, _subcommands) {
155
- return ''; // Subcommands are registered inline
156
- }
157
- function mapFlagTypeToTS(type) {
158
- if (!type)
159
- return 'string';
160
- const lower = type.toLowerCase();
161
- if (lower === 'boolean')
162
- return 'boolean';
163
- if (lower === 'number' || lower === 'integer')
164
- return 'number';
165
- return 'string';
166
- }
167
- function mapArgTypeToTS(type) {
168
- if (!type)
169
- return 'string';
170
- const lower = type.toLowerCase();
171
- if (lower === 'filepath' || lower === 'string')
172
- return 'string';
173
- if (lower === 'number' || lower === 'integer')
174
- return 'number';
175
- if (lower === 'boolean')
176
- return 'boolean';
177
- return 'string';
178
- }
179
- function capitalize(str) {
180
- return str.charAt(0).toUpperCase() + str.slice(1);
181
- }
182
- //# sourceMappingURL=command-generator.js.map