@specverse/engines 6.0.8 → 6.0.10
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/dist/analyse-prepass/backends/gitnexus.d.ts +109 -0
- package/dist/analyse-prepass/backends/gitnexus.d.ts.map +1 -0
- package/dist/analyse-prepass/backends/gitnexus.js +453 -0
- package/dist/analyse-prepass/backends/gitnexus.js.map +1 -0
- package/dist/analyse-prepass/backends/index.d.ts +14 -6
- package/dist/analyse-prepass/backends/index.d.ts.map +1 -1
- package/dist/analyse-prepass/backends/index.js +13 -4
- package/dist/analyse-prepass/backends/index.js.map +1 -1
- package/dist/analyse-prepass/index.d.ts +1 -1
- package/dist/analyse-prepass/index.d.ts.map +1 -1
- package/dist/analyse-prepass/index.js +1 -1
- package/dist/analyse-prepass/index.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +104 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +104 -0
- package/package.json +1 -1
- package/libs/instance-factories/cli/templates/commander/command-generator.d.ts +0 -14
- package/libs/instance-factories/cli/templates/commander/command-generator.js +0 -182
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* Canonical repo name as registered in `~/.gitnexus/registry.json`.
|
|
12
|
+
* Resolved by `init()` from the registry; passed as `--repo <name>`
|
|
13
|
+
* to every `gitnexus cypher` call so multi-repo registries don't
|
|
14
|
+
* cross-contaminate query results.
|
|
15
|
+
*/
|
|
16
|
+
private repoName;
|
|
17
|
+
private gitnexusPath;
|
|
18
|
+
private reuseExistingIndex;
|
|
19
|
+
capabilities: {
|
|
20
|
+
callGraph: boolean;
|
|
21
|
+
crossFileResolution: boolean;
|
|
22
|
+
componentClustering: boolean;
|
|
23
|
+
fullTextSearch: boolean;
|
|
24
|
+
fileWatching: boolean;
|
|
25
|
+
};
|
|
26
|
+
constructor(options?: GitNexusBackendOptions);
|
|
27
|
+
static isAvailable(): boolean;
|
|
28
|
+
init(sourceDir: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a sourceDir to its canonical gitnexus repo name by reading
|
|
31
|
+
* `~/.gitnexus/registry.json`. Falls back to the dir's basename if the
|
|
32
|
+
* registry isn't readable or has no matching entry — but a basename
|
|
33
|
+
* fallback is unsafe in multi-repo registries (it can collide with
|
|
34
|
+
* another project that happens to have the same dir name), so we log
|
|
35
|
+
* a warning to stderr in that case.
|
|
36
|
+
*/
|
|
37
|
+
private resolveRepoName;
|
|
38
|
+
index(): Promise<IndexResult>;
|
|
39
|
+
listFiles(filter?: FileFilter): Promise<string[]>;
|
|
40
|
+
listClasses(filter?: ClassFilter): Promise<Symbol[]>;
|
|
41
|
+
listMethods(filter?: MethodFilter): Promise<Symbol[]>;
|
|
42
|
+
listInterfaces(filter?: InterfaceFilter): Promise<Symbol[]>;
|
|
43
|
+
listImports(file: string): Promise<Import[]>;
|
|
44
|
+
fileSourceText(file: string): Promise<string>;
|
|
45
|
+
callers(symbol: string): Promise<Symbol[]>;
|
|
46
|
+
callees(symbol: string): Promise<Symbol[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Cluster files via GitNexus's Leiden community detection.
|
|
49
|
+
* This is the distinguishing feature vs CodeGraph — surfaces natural
|
|
50
|
+
* component boundaries for SpecVerse `components:` inference.
|
|
51
|
+
*
|
|
52
|
+
* GitNexus's edge model: a single `CodeRelation` table with a `type`
|
|
53
|
+
* property — relationship discrimination happens via WHERE on that
|
|
54
|
+
* property, not by edge label. Community membership is `MEMBER_OF`.
|
|
55
|
+
*
|
|
56
|
+
* Note: GitNexus's MEMBER_OF connects symbols (not files directly) to
|
|
57
|
+
* communities. We aggregate per-community by filePath of the member symbols.
|
|
58
|
+
*/
|
|
59
|
+
clusterFiles(): Promise<Array<{
|
|
60
|
+
id: string;
|
|
61
|
+
files: string[];
|
|
62
|
+
}>>;
|
|
63
|
+
/**
|
|
64
|
+
* GitNexus's call graph traversal — same `CodeRelation` table pattern
|
|
65
|
+
* as MEMBER_OF, with `type='CALLS'` as the discriminating property.
|
|
66
|
+
* (Override the default callers/callees from the parent class above
|
|
67
|
+
* because the relation type isn't `:CALLS` — it's `:CodeRelation` with
|
|
68
|
+
* a property filter.)
|
|
69
|
+
*/
|
|
70
|
+
getMethodDetails(qualifiedName: string): Promise<MethodFactSheet | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Run a Cypher query via the `gitnexus cypher` CLI and parse the
|
|
73
|
+
* markdown-table output into a row array. Each row is a map of
|
|
74
|
+
* column name → value (string, number, or array depending on Cypher).
|
|
75
|
+
*
|
|
76
|
+
* Passes `--repo <name>` to disambiguate when multiple repos are
|
|
77
|
+
* registered globally. The name comes from `~/.gitnexus/registry.json`
|
|
78
|
+
* (resolved by `init()`), NOT the basename of sourceDir — the basename
|
|
79
|
+
* is unsafe because gitnexus derives the registered name from the git
|
|
80
|
+
* remote / package.json, which usually differs from the dir name
|
|
81
|
+
* (e.g. clones into `./source/` register as `nestjs-realworld-example-app`).
|
|
82
|
+
*/
|
|
83
|
+
private cypher;
|
|
84
|
+
private rowToSymbol;
|
|
85
|
+
private mapLabel;
|
|
86
|
+
private toRelativePath;
|
|
87
|
+
private escape;
|
|
88
|
+
private detectBinary;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extract the basename of a sourceDir for fallback-naming. `/foo/bar/source/`
|
|
92
|
+
* → `source`. Empty path → `source` (the gitnexus default for unnamed dirs).
|
|
93
|
+
*/
|
|
94
|
+
export declare function basenameOrSource(sourceDir: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Pure-function version of the registry-lookup logic — takes the registry
|
|
97
|
+
* file content + the sourceDir and returns the canonical name (or null if
|
|
98
|
+
* not found, plus an optional warning). Exported for unit testing without
|
|
99
|
+
* needing gitnexus installed or a real ~/.gitnexus directory.
|
|
100
|
+
*
|
|
101
|
+
* Match order:
|
|
102
|
+
* 1. Exact `path === sourceDir`
|
|
103
|
+
* 2. `storagePath` is inside sourceDir (handles symlink-resolved paths)
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveRepoNameFromRegistry(registryContent: string, sourceDir: string): {
|
|
106
|
+
name: string | null;
|
|
107
|
+
warning: string | null;
|
|
108
|
+
};
|
|
109
|
+
//# 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":"AAqBA,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;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAM;IACtB,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;IAqB5C;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAkBjB,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;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM;IA2Bd,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,YAAY;CASrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAuBjD"}
|
|
@@ -0,0 +1,453 @@
|
|
|
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 { homedir } from 'os';
|
|
21
|
+
import { join } from 'path';
|
|
22
|
+
import { walkSourceTree, detectLanguage, globToRegex } from './walk.js';
|
|
23
|
+
import { extractEmits, extractDbWrites, extractExternalCalls, extractThrows, extractAsyncBoundaries, countBranchPoints, sliceBody, } from './method-patterns.js';
|
|
24
|
+
export class GitNexusBackend {
|
|
25
|
+
sourceDir = '';
|
|
26
|
+
/**
|
|
27
|
+
* Canonical repo name as registered in `~/.gitnexus/registry.json`.
|
|
28
|
+
* Resolved by `init()` from the registry; passed as `--repo <name>`
|
|
29
|
+
* to every `gitnexus cypher` call so multi-repo registries don't
|
|
30
|
+
* cross-contaminate query results.
|
|
31
|
+
*/
|
|
32
|
+
repoName = '';
|
|
33
|
+
gitnexusPath;
|
|
34
|
+
reuseExistingIndex;
|
|
35
|
+
capabilities = {
|
|
36
|
+
callGraph: true,
|
|
37
|
+
crossFileResolution: true,
|
|
38
|
+
componentClustering: true, // Leiden community detection (the differentiator)
|
|
39
|
+
fullTextSearch: true, // hybrid BM25 + semantic + RRF
|
|
40
|
+
fileWatching: false, // GitNexus doesn't auto-sync; needs explicit re-analyze
|
|
41
|
+
};
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.gitnexusPath = options.gitnexusPath ?? this.detectBinary('gitnexus');
|
|
44
|
+
this.reuseExistingIndex = options.reuseExistingIndex ?? false;
|
|
45
|
+
}
|
|
46
|
+
static isAvailable() {
|
|
47
|
+
try {
|
|
48
|
+
execSync('gitnexus --version', { stdio: 'ignore', timeout: 5000 });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async init(sourceDir) {
|
|
56
|
+
this.sourceDir = sourceDir;
|
|
57
|
+
const gitnexusDir = join(sourceDir, '.gitnexus');
|
|
58
|
+
if (!this.reuseExistingIndex || !existsSync(gitnexusDir)) {
|
|
59
|
+
// Run `gitnexus analyze` to build the index. --skip-git lets us index
|
|
60
|
+
// arbitrary directories (like our realized backends in /private/tmp).
|
|
61
|
+
if (!existsSync(gitnexusDir)) {
|
|
62
|
+
execSync(`${this.gitnexusPath} analyze --skip-git .`, {
|
|
63
|
+
cwd: sourceDir,
|
|
64
|
+
stdio: 'ignore',
|
|
65
|
+
timeout: 600_000,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Resolve the canonical repo name from the gitnexus registry. The
|
|
70
|
+
// registry is the authoritative mapping of source paths → repo names
|
|
71
|
+
// (gitnexus derives `name` from the git remote, the `package.json`,
|
|
72
|
+
// or the dir basename — we don't replicate that logic, just look it up).
|
|
73
|
+
this.repoName = this.resolveRepoName(sourceDir);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a sourceDir to its canonical gitnexus repo name by reading
|
|
77
|
+
* `~/.gitnexus/registry.json`. Falls back to the dir's basename if the
|
|
78
|
+
* registry isn't readable or has no matching entry — but a basename
|
|
79
|
+
* fallback is unsafe in multi-repo registries (it can collide with
|
|
80
|
+
* another project that happens to have the same dir name), so we log
|
|
81
|
+
* a warning to stderr in that case.
|
|
82
|
+
*/
|
|
83
|
+
resolveRepoName(sourceDir) {
|
|
84
|
+
const registryPath = join(homedir(), '.gitnexus', 'registry.json');
|
|
85
|
+
const fallback = basenameOrSource(sourceDir);
|
|
86
|
+
if (!existsSync(registryPath))
|
|
87
|
+
return fallback;
|
|
88
|
+
let registryContent;
|
|
89
|
+
try {
|
|
90
|
+
registryContent = readFileSync(registryPath, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
process.stderr.write(`[gitnexus-backend] failed to read registry: ${err.message}; using basename fallback "${fallback}".\n`);
|
|
94
|
+
return fallback;
|
|
95
|
+
}
|
|
96
|
+
const result = resolveRepoNameFromRegistry(registryContent, sourceDir);
|
|
97
|
+
if (result.warning)
|
|
98
|
+
process.stderr.write(`[gitnexus-backend] ${result.warning}\n`);
|
|
99
|
+
return result.name ?? fallback;
|
|
100
|
+
}
|
|
101
|
+
async index() {
|
|
102
|
+
const start = Date.now();
|
|
103
|
+
if (!existsSync(join(this.sourceDir, '.gitnexus'))) {
|
|
104
|
+
execSync(`${this.gitnexusPath} analyze --skip-git .`, {
|
|
105
|
+
cwd: this.sourceDir,
|
|
106
|
+
stdio: 'ignore',
|
|
107
|
+
timeout: 600_000,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// GitNexus prints stats during analyze; query for them via Cypher
|
|
111
|
+
const rows = this.cypher('MATCH (n) RETURN count(*) AS c');
|
|
112
|
+
const totalNodes = rows.length > 0 ? Number(rows[0].c) : 0;
|
|
113
|
+
const fileRows = this.cypher('MATCH (n:File) RETURN count(*) AS c');
|
|
114
|
+
const fileCount = fileRows.length > 0 ? Number(fileRows[0].c) : 0;
|
|
115
|
+
return {
|
|
116
|
+
files: fileCount,
|
|
117
|
+
symbols: totalNodes - fileCount,
|
|
118
|
+
durationMs: Date.now() - start,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async listFiles(filter) {
|
|
122
|
+
// Same pattern as CodeGraph backend — FS walk, not the index, so adapters
|
|
123
|
+
// can find arbitrary file types (schema.prisma, package.json, etc.) the
|
|
124
|
+
// tree-sitter indexer doesn't track.
|
|
125
|
+
let result = walkSourceTree(this.sourceDir);
|
|
126
|
+
if (filter?.dir)
|
|
127
|
+
result = result.filter((f) => f.startsWith(filter.dir));
|
|
128
|
+
if (filter?.lang)
|
|
129
|
+
result = result.filter((f) => detectLanguage(f) === filter.lang);
|
|
130
|
+
if (filter?.glob) {
|
|
131
|
+
const re = globToRegex(filter.glob);
|
|
132
|
+
result = result.filter((f) => re.test(f));
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
async listClasses(filter) {
|
|
137
|
+
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`;
|
|
138
|
+
if (filter?.dir) {
|
|
139
|
+
const rel = this.toRelativePath(filter.dir);
|
|
140
|
+
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`;
|
|
141
|
+
}
|
|
142
|
+
const rows = this.cypher(cypher);
|
|
143
|
+
return rows
|
|
144
|
+
.filter((r) => !filter?.namePattern || filter.namePattern.test(r.name))
|
|
145
|
+
.map((r) => this.rowToSymbol(r, 'class'));
|
|
146
|
+
}
|
|
147
|
+
async listMethods(filter) {
|
|
148
|
+
// GitNexus doesn't store class membership as a property; we derive it
|
|
149
|
+
// from the Method node's `name` plus filePath. For the `class` filter,
|
|
150
|
+
// we scope by the class's file path range.
|
|
151
|
+
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`;
|
|
152
|
+
if (filter?.class) {
|
|
153
|
+
// Find the class's file + line range first, then filter methods by it.
|
|
154
|
+
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`);
|
|
155
|
+
if (classRows.length === 0)
|
|
156
|
+
return [];
|
|
157
|
+
const cls = classRows[0];
|
|
158
|
+
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`;
|
|
159
|
+
}
|
|
160
|
+
const rows = this.cypher(cypher);
|
|
161
|
+
let methods = rows.map((r) => {
|
|
162
|
+
const sym = this.rowToSymbol(r, 'method');
|
|
163
|
+
// Approximate qualifiedName: ClassName.methodName when class context is known
|
|
164
|
+
if (filter?.class)
|
|
165
|
+
sym.qualifiedName = `${filter.class}.${sym.name}`;
|
|
166
|
+
return sym;
|
|
167
|
+
});
|
|
168
|
+
if (filter?.nameIn && filter.nameIn.length > 0) {
|
|
169
|
+
const allowedNames = new Set(filter.nameIn);
|
|
170
|
+
methods = methods.filter((m) => allowedNames.has(m.name));
|
|
171
|
+
}
|
|
172
|
+
return methods;
|
|
173
|
+
}
|
|
174
|
+
async listInterfaces(filter) {
|
|
175
|
+
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`;
|
|
176
|
+
if (filter?.dir) {
|
|
177
|
+
const rel = this.toRelativePath(filter.dir);
|
|
178
|
+
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`;
|
|
179
|
+
}
|
|
180
|
+
const rows = this.cypher(cypher);
|
|
181
|
+
return rows
|
|
182
|
+
.filter((r) => !filter?.namePattern || filter.namePattern.test(r.name))
|
|
183
|
+
.map((r) => this.rowToSymbol(r, 'interface'));
|
|
184
|
+
}
|
|
185
|
+
async listImports(file) {
|
|
186
|
+
// GitNexus tracks imports via CodeRelation edges with type='IMPORTS'.
|
|
187
|
+
const rel = this.toRelativePath(file);
|
|
188
|
+
const cypher = `MATCH (f:File {filePath: '${this.escape(rel)}'})-[:CodeRelation {type: "IMPORTS"}]->(target) RETURN target.name AS name, target.filePath AS targetPath`;
|
|
189
|
+
const rows = this.cypher(cypher);
|
|
190
|
+
return rows.map((r) => ({
|
|
191
|
+
fromFile: file,
|
|
192
|
+
toModule: r.targetPath || r.name,
|
|
193
|
+
symbols: [r.name],
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
async fileSourceText(file) {
|
|
197
|
+
return readFileSync(file, 'utf8');
|
|
198
|
+
}
|
|
199
|
+
async callers(symbol) {
|
|
200
|
+
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`;
|
|
201
|
+
const rows = this.cypher(cypher);
|
|
202
|
+
return rows.map((r) => this.rowToSymbol(r, this.mapLabel(r.kind)));
|
|
203
|
+
}
|
|
204
|
+
async callees(symbol) {
|
|
205
|
+
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`;
|
|
206
|
+
const rows = this.cypher(cypher);
|
|
207
|
+
return rows.map((r) => this.rowToSymbol(r, this.mapLabel(r.kind)));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Cluster files via GitNexus's Leiden community detection.
|
|
211
|
+
* This is the distinguishing feature vs CodeGraph — surfaces natural
|
|
212
|
+
* component boundaries for SpecVerse `components:` inference.
|
|
213
|
+
*
|
|
214
|
+
* GitNexus's edge model: a single `CodeRelation` table with a `type`
|
|
215
|
+
* property — relationship discrimination happens via WHERE on that
|
|
216
|
+
* property, not by edge label. Community membership is `MEMBER_OF`.
|
|
217
|
+
*
|
|
218
|
+
* Note: GitNexus's MEMBER_OF connects symbols (not files directly) to
|
|
219
|
+
* communities. We aggregate per-community by filePath of the member symbols.
|
|
220
|
+
*/
|
|
221
|
+
async clusterFiles() {
|
|
222
|
+
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`;
|
|
223
|
+
const rows = this.cypher(cypher);
|
|
224
|
+
return rows.map((r) => ({
|
|
225
|
+
id: String(r.label || r.id || 'unnamed'),
|
|
226
|
+
files: Array.isArray(r.files) ? r.files.filter(Boolean) : [],
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* GitNexus's call graph traversal — same `CodeRelation` table pattern
|
|
231
|
+
* as MEMBER_OF, with `type='CALLS'` as the discriminating property.
|
|
232
|
+
* (Override the default callers/callees from the parent class above
|
|
233
|
+
* because the relation type isn't `:CALLS` — it's `:CodeRelation` with
|
|
234
|
+
* a property filter.)
|
|
235
|
+
*/
|
|
236
|
+
async getMethodDetails(qualifiedName) {
|
|
237
|
+
const lastDot = qualifiedName.lastIndexOf('.');
|
|
238
|
+
if (lastDot < 0)
|
|
239
|
+
return null;
|
|
240
|
+
const className = qualifiedName.slice(0, lastDot);
|
|
241
|
+
const methodName = qualifiedName.slice(lastDot + 1);
|
|
242
|
+
// Find the method via class-scoped query
|
|
243
|
+
const methods = await this.listMethods({ class: className, nameIn: [methodName] });
|
|
244
|
+
if (methods.length === 0)
|
|
245
|
+
return null;
|
|
246
|
+
const method = methods[0];
|
|
247
|
+
const fileText = readFileSync(method.filePath, 'utf8');
|
|
248
|
+
const fileLines = fileText.split('\n');
|
|
249
|
+
const bodyLines = fileLines.slice(method.startLine - 1, method.endLine);
|
|
250
|
+
const body = bodyLines.join('\n');
|
|
251
|
+
// Calls — use the call graph (GitNexus's strength like CodeGraph's)
|
|
252
|
+
const calls = await this.callees(qualifiedName);
|
|
253
|
+
return {
|
|
254
|
+
qualifiedName,
|
|
255
|
+
signature: method.signature ?? '',
|
|
256
|
+
body,
|
|
257
|
+
filePath: method.filePath,
|
|
258
|
+
startLine: method.startLine,
|
|
259
|
+
endLine: method.endLine,
|
|
260
|
+
language: method.language,
|
|
261
|
+
calls,
|
|
262
|
+
emits: extractEmits(body),
|
|
263
|
+
dbWrites: extractDbWrites(body),
|
|
264
|
+
externalCalls: extractExternalCalls(body),
|
|
265
|
+
throws: extractThrows(body),
|
|
266
|
+
asyncBoundaries: extractAsyncBoundaries(body),
|
|
267
|
+
branchPoints: countBranchPoints(body),
|
|
268
|
+
bodyTextSliced: sliceBody(body),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// ────────────────────────────────────────────────────────────────────
|
|
272
|
+
// Internals
|
|
273
|
+
// ────────────────────────────────────────────────────────────────────
|
|
274
|
+
/**
|
|
275
|
+
* Run a Cypher query via the `gitnexus cypher` CLI and parse the
|
|
276
|
+
* markdown-table output into a row array. Each row is a map of
|
|
277
|
+
* column name → value (string, number, or array depending on Cypher).
|
|
278
|
+
*
|
|
279
|
+
* Passes `--repo <name>` to disambiguate when multiple repos are
|
|
280
|
+
* registered globally. The name comes from `~/.gitnexus/registry.json`
|
|
281
|
+
* (resolved by `init()`), NOT the basename of sourceDir — the basename
|
|
282
|
+
* is unsafe because gitnexus derives the registered name from the git
|
|
283
|
+
* remote / package.json, which usually differs from the dir name
|
|
284
|
+
* (e.g. clones into `./source/` register as `nestjs-realworld-example-app`).
|
|
285
|
+
*/
|
|
286
|
+
cypher(query) {
|
|
287
|
+
if (!this.repoName) {
|
|
288
|
+
throw new Error('[gitnexus-backend] repoName not set — did you call init() before querying? ' +
|
|
289
|
+
'init() resolves the canonical repo name from ~/.gitnexus/registry.json.');
|
|
290
|
+
}
|
|
291
|
+
const stdout = execFileSync(this.gitnexusPath, ['cypher', '--repo', this.repoName, query], {
|
|
292
|
+
cwd: this.sourceDir,
|
|
293
|
+
encoding: 'utf8',
|
|
294
|
+
timeout: 60_000,
|
|
295
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
296
|
+
});
|
|
297
|
+
if (!stdout.trim())
|
|
298
|
+
return [];
|
|
299
|
+
let payload;
|
|
300
|
+
try {
|
|
301
|
+
payload = JSON.parse(stdout);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw new Error(`gitnexus cypher: failed to parse JSON output:\n${stdout.slice(0, 500)}`);
|
|
305
|
+
}
|
|
306
|
+
if (payload.error) {
|
|
307
|
+
throw new Error(`gitnexus cypher error: ${payload.error}`);
|
|
308
|
+
}
|
|
309
|
+
if (!payload.markdown)
|
|
310
|
+
return [];
|
|
311
|
+
return parseMarkdownTable(payload.markdown);
|
|
312
|
+
}
|
|
313
|
+
rowToSymbol(row, kind) {
|
|
314
|
+
const filePath = String(row.filePath || '');
|
|
315
|
+
const absPath = filePath.startsWith('/') ? filePath : join(this.sourceDir, filePath);
|
|
316
|
+
return {
|
|
317
|
+
kind,
|
|
318
|
+
name: String(row.name),
|
|
319
|
+
qualifiedName: String(row.name), // GitNexus's qualified IDs are colon-separated; we use plain name for now
|
|
320
|
+
filePath: absPath,
|
|
321
|
+
startLine: Number(row.startLine ?? 0),
|
|
322
|
+
endLine: Number(row.endLine ?? 0),
|
|
323
|
+
signature: row.signature ?? undefined,
|
|
324
|
+
docstring: row.docstring ?? undefined,
|
|
325
|
+
isExported: row.isExported === 'true' || row.isExported === true,
|
|
326
|
+
language: detectLanguage(absPath) || 'unknown',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
mapLabel(label) {
|
|
330
|
+
switch (label) {
|
|
331
|
+
case 'Class': return 'class';
|
|
332
|
+
case 'Method': return 'method';
|
|
333
|
+
case 'Function': return 'function';
|
|
334
|
+
case 'Interface': return 'interface';
|
|
335
|
+
case 'Const': return 'constant';
|
|
336
|
+
default: return 'function';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
toRelativePath(p) {
|
|
340
|
+
return p.startsWith(this.sourceDir)
|
|
341
|
+
? p.slice(this.sourceDir.length).replace(/^\//, '')
|
|
342
|
+
: p;
|
|
343
|
+
}
|
|
344
|
+
escape(s) {
|
|
345
|
+
return s.replace(/'/g, "\\'");
|
|
346
|
+
}
|
|
347
|
+
detectBinary(name) {
|
|
348
|
+
try {
|
|
349
|
+
const path = execSync(`which ${name}`, { encoding: 'utf8', timeout: 3000 }).trim();
|
|
350
|
+
if (path)
|
|
351
|
+
return path;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// Fall through
|
|
355
|
+
}
|
|
356
|
+
return name;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Extract the basename of a sourceDir for fallback-naming. `/foo/bar/source/`
|
|
361
|
+
* → `source`. Empty path → `source` (the gitnexus default for unnamed dirs).
|
|
362
|
+
*/
|
|
363
|
+
export function basenameOrSource(sourceDir) {
|
|
364
|
+
return sourceDir.split('/').filter(Boolean).pop() || 'source';
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Pure-function version of the registry-lookup logic — takes the registry
|
|
368
|
+
* file content + the sourceDir and returns the canonical name (or null if
|
|
369
|
+
* not found, plus an optional warning). Exported for unit testing without
|
|
370
|
+
* needing gitnexus installed or a real ~/.gitnexus directory.
|
|
371
|
+
*
|
|
372
|
+
* Match order:
|
|
373
|
+
* 1. Exact `path === sourceDir`
|
|
374
|
+
* 2. `storagePath` is inside sourceDir (handles symlink-resolved paths)
|
|
375
|
+
*/
|
|
376
|
+
export function resolveRepoNameFromRegistry(registryContent, sourceDir) {
|
|
377
|
+
let entries;
|
|
378
|
+
try {
|
|
379
|
+
entries = JSON.parse(registryContent);
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
return {
|
|
383
|
+
name: null,
|
|
384
|
+
warning: `failed to parse registry JSON: ${err.message}`,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (!Array.isArray(entries)) {
|
|
388
|
+
return { name: null, warning: 'registry is not an array' };
|
|
389
|
+
}
|
|
390
|
+
const match = entries.find((e) => e.path === sourceDir) ??
|
|
391
|
+
entries.find((e) => e.storagePath?.startsWith(sourceDir + '/'));
|
|
392
|
+
if (match)
|
|
393
|
+
return { name: match.name, warning: null };
|
|
394
|
+
return {
|
|
395
|
+
name: null,
|
|
396
|
+
warning: `no registry entry for ${sourceDir}; using basename fallback. ` +
|
|
397
|
+
`Multi-repo queries may cross-contaminate.`,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Parse a GitHub-flavored markdown table into a row array.
|
|
402
|
+
*
|
|
403
|
+
* Input shape:
|
|
404
|
+
* | col1 | col2 |
|
|
405
|
+
* | --- | --- |
|
|
406
|
+
* | val1 | val2 |
|
|
407
|
+
*
|
|
408
|
+
* Returns: [ { col1: 'val1', col2: 'val2' }, ... ]
|
|
409
|
+
*
|
|
410
|
+
* Cell values are strings; callers can convert to number/boolean/JSON
|
|
411
|
+
* as needed. Cells starting with `[` are parsed as JSON arrays
|
|
412
|
+
* (GitNexus's `collect()` returns arrays in markdown form).
|
|
413
|
+
*/
|
|
414
|
+
function parseMarkdownTable(md) {
|
|
415
|
+
const lines = md.split('\n').filter((l) => l.trim().length > 0);
|
|
416
|
+
if (lines.length < 2)
|
|
417
|
+
return [];
|
|
418
|
+
const headerLine = lines[0];
|
|
419
|
+
const headers = headerLine
|
|
420
|
+
.split('|')
|
|
421
|
+
.map((s) => s.trim())
|
|
422
|
+
.filter(Boolean);
|
|
423
|
+
// Skip the separator row (---|---|---)
|
|
424
|
+
const rows = [];
|
|
425
|
+
for (let i = 2; i < lines.length; i++) {
|
|
426
|
+
const cells = lines[i]
|
|
427
|
+
.split('|')
|
|
428
|
+
.map((s) => s.trim())
|
|
429
|
+
.filter((_, idx, arr) => idx > 0 && idx < arr.length - 1 || arr.length === headers.length);
|
|
430
|
+
// Sometimes the leading/trailing | are missing — fall back to a permissive split.
|
|
431
|
+
const rawCells = lines[i].split('|');
|
|
432
|
+
const cleaned = rawCells.length === headers.length + 2
|
|
433
|
+
? rawCells.slice(1, -1).map((s) => s.trim())
|
|
434
|
+
: rawCells.map((s) => s.trim()).filter(Boolean);
|
|
435
|
+
const row = {};
|
|
436
|
+
for (let j = 0; j < headers.length; j++) {
|
|
437
|
+
let val = cleaned[j] ?? '';
|
|
438
|
+
// Try to parse JSON arrays (collect() output)
|
|
439
|
+
if (typeof val === 'string' && val.startsWith('[') && val.endsWith(']')) {
|
|
440
|
+
try {
|
|
441
|
+
val = JSON.parse(val);
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// leave as string
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
row[headers[j]] = val;
|
|
448
|
+
}
|
|
449
|
+
rows.push(row);
|
|
450
|
+
}
|
|
451
|
+
return rows;
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=gitnexus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitnexus.js","sourceRoot":"","sources":["../../../src/analyse-prepass/backends/gitnexus.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backend selector — picks the best available `StructuralPrepass`.
|
|
3
3
|
*
|
|
4
|
-
* Order:
|
|
5
|
-
*
|
|
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
|
-
|
|
11
|
-
export
|
|
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
|
|
19
|
+
* Select a backend by name, or auto-pick a sensible default.
|
|
14
20
|
*
|
|
15
21
|
* `auto` semantics:
|
|
16
|
-
* - CodeGraph + sqlite3 both available → CodeGraph (
|
|
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
|
|
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
|
-
|
|
3
|
+
import { GitNexusBackend } from './gitnexus.js';
|
|
4
|
+
export { GrepOnlyBackend, CodeGraphBackend, GitNexusBackend };
|
|
4
5
|
/**
|
|
5
|
-
* Select a backend by name, or auto-pick
|
|
6
|
+
* Select a backend by name, or auto-pick a sensible default.
|
|
6
7
|
*
|
|
7
8
|
* `auto` semantics:
|
|
8
|
-
* - CodeGraph + sqlite3 both available → CodeGraph (
|
|
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":"
|
|
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,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
|