@duytransipher/gitnexus 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/analyze.js +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/tool.d.ts +1 -0
- package/dist/cli/tool.js +31 -9
- package/dist/cli/unreal-progress.d.ts +12 -0
- package/dist/cli/unreal-progress.js +66 -0
- package/dist/mcp/local/local-backend.js +11 -5
- package/dist/mcp/tools.js +108 -103
- package/dist/unreal/blueprint-ingestion.d.ts +8 -1
- package/dist/unreal/blueprint-ingestion.js +35 -3
- package/dist/unreal/bridge.d.ts +1 -1
- package/dist/unreal/bridge.js +94 -2
- package/dist/unreal/types.d.ts +4 -0
- package/package.json +1 -1
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusBlueprintAnalyzerCommandlet.cpp +482 -17
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Public/GitNexusBlueprintAnalyzerCommandlet.h +25 -3
package/dist/cli/analyze.js
CHANGED
|
@@ -185,7 +185,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
185
185
|
// ── Phase 1.5: Blueprint Ingestion (optional) ────────────────────────
|
|
186
186
|
try {
|
|
187
187
|
const { ingestBlueprintsIntoGraph } = await import('../unreal/blueprint-ingestion.js');
|
|
188
|
-
const bpResult = await ingestBlueprintsIntoGraph(pipelineResult.graph, storagePath);
|
|
188
|
+
const bpResult = await ingestBlueprintsIntoGraph(pipelineResult.graph, storagePath, repoPath);
|
|
189
189
|
if (bpResult.nodesAdded > 0) {
|
|
190
190
|
updateBar(61, `Indexed ${bpResult.nodesAdded} Blueprints (${bpResult.edgesAdded} edges)`);
|
|
191
191
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -106,6 +106,7 @@ program
|
|
|
106
106
|
.command('unreal-sync')
|
|
107
107
|
.description('Refresh the Unreal Blueprint asset manifest for the current indexed repo')
|
|
108
108
|
.option('-r, --repo <name>', 'Target repository')
|
|
109
|
+
.option('--deep', 'Deep mode: load Blueprints fully for native_function_refs (slower, higher memory)')
|
|
109
110
|
.action(createLazyAction(() => import('./tool.js'), 'syncUnrealAssetManifestCommand'));
|
|
110
111
|
program
|
|
111
112
|
.command('unreal-find-refs [functionName]')
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare function cypherCommand(query: string, options?: {
|
|
|
39
39
|
}): Promise<void>;
|
|
40
40
|
export declare function syncUnrealAssetManifestCommand(options?: {
|
|
41
41
|
repo?: string;
|
|
42
|
+
deep?: boolean;
|
|
42
43
|
}): Promise<void>;
|
|
43
44
|
export declare function findNativeBlueprintReferencesCommand(functionName: string, options?: {
|
|
44
45
|
repo?: string;
|
package/dist/cli/tool.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { writeSync } from 'node:fs';
|
|
19
19
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
20
|
+
import { withUnrealProgress } from './unreal-progress.js';
|
|
21
|
+
const isUnrealError = (r) => r?.status === 'error' || r?.error != null;
|
|
20
22
|
let _backend = null;
|
|
21
23
|
async function getBackend() {
|
|
22
24
|
if (_backend)
|
|
@@ -127,9 +129,17 @@ export async function cypherCommand(query, options) {
|
|
|
127
129
|
}
|
|
128
130
|
export async function syncUnrealAssetManifestCommand(options) {
|
|
129
131
|
const backend = await getBackend();
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
const mode = options?.deep ? 'deep' : 'metadata';
|
|
133
|
+
const result = await withUnrealProgress(() => backend.callTool('sync_unreal_asset_manifest', { repo: options?.repo, deep: options?.deep }), { phaseLabel: `Syncing Unreal asset manifest (${mode} mode)`, successLabel: 'Manifest synced', failLabel: 'Manifest sync failed', isError: isUnrealError });
|
|
134
|
+
if (result?.status === 'error') {
|
|
135
|
+
console.error(`\n Error: ${result.error}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
if (result?.asset_count != null) {
|
|
139
|
+
console.log(` ${result.asset_count.toLocaleString()} Blueprint assets indexed (${mode} mode)`);
|
|
140
|
+
if (result.manifest_path)
|
|
141
|
+
console.log(` ${result.manifest_path}`);
|
|
142
|
+
}
|
|
133
143
|
output(result);
|
|
134
144
|
}
|
|
135
145
|
export async function findNativeBlueprintReferencesCommand(functionName, options) {
|
|
@@ -138,7 +148,7 @@ export async function findNativeBlueprintReferencesCommand(functionName, options
|
|
|
138
148
|
process.exit(1);
|
|
139
149
|
}
|
|
140
150
|
const backend = await getBackend();
|
|
141
|
-
const result = await backend.callTool('find_native_blueprint_references', {
|
|
151
|
+
const result = await withUnrealProgress(() => backend.callTool('find_native_blueprint_references', {
|
|
142
152
|
function: functionName || undefined,
|
|
143
153
|
symbol_uid: options?.uid,
|
|
144
154
|
class_name: options?.className,
|
|
@@ -146,7 +156,11 @@ export async function findNativeBlueprintReferencesCommand(functionName, options
|
|
|
146
156
|
refresh_manifest: options?.refreshManifest ?? false,
|
|
147
157
|
max_candidates: options?.maxCandidates ? parseInt(options.maxCandidates, 10) : undefined,
|
|
148
158
|
repo: options?.repo,
|
|
149
|
-
});
|
|
159
|
+
}), { phaseLabel: 'Scanning Blueprints for native references', successLabel: 'Blueprint scan complete', failLabel: 'Blueprint scan failed', isError: isUnrealError });
|
|
160
|
+
if (result?.status === 'error') {
|
|
161
|
+
console.error(`\n Error: ${result.error}\n`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
150
164
|
output(result);
|
|
151
165
|
}
|
|
152
166
|
export async function expandBlueprintChainCommand(assetPath, chainAnchorId, options) {
|
|
@@ -155,13 +169,17 @@ export async function expandBlueprintChainCommand(assetPath, chainAnchorId, opti
|
|
|
155
169
|
process.exit(1);
|
|
156
170
|
}
|
|
157
171
|
const backend = await getBackend();
|
|
158
|
-
const result = await backend.callTool('expand_blueprint_chain', {
|
|
172
|
+
const result = await withUnrealProgress(() => backend.callTool('expand_blueprint_chain', {
|
|
159
173
|
asset_path: assetPath,
|
|
160
174
|
chain_anchor_id: chainAnchorId,
|
|
161
175
|
direction: options?.direction || 'downstream',
|
|
162
176
|
max_depth: options?.depth ? parseInt(options.depth, 10) : undefined,
|
|
163
177
|
repo: options?.repo,
|
|
164
|
-
});
|
|
178
|
+
}), { phaseLabel: 'Expanding Blueprint chain', successLabel: 'Chain expanded', failLabel: 'Chain expansion failed', isError: isUnrealError });
|
|
179
|
+
if (result?.status === 'error') {
|
|
180
|
+
console.error(`\n Error: ${result.error}\n`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
165
183
|
output(result);
|
|
166
184
|
}
|
|
167
185
|
export async function findBlueprintsDerivedFromNativeClassCommand(className, options) {
|
|
@@ -170,11 +188,15 @@ export async function findBlueprintsDerivedFromNativeClassCommand(className, opt
|
|
|
170
188
|
process.exit(1);
|
|
171
189
|
}
|
|
172
190
|
const backend = await getBackend();
|
|
173
|
-
const result = await backend.callTool('find_blueprints_derived_from_native_class', {
|
|
191
|
+
const result = await withUnrealProgress(() => backend.callTool('find_blueprints_derived_from_native_class', {
|
|
174
192
|
class_name: className,
|
|
175
193
|
refresh_manifest: options?.refreshManifest ?? false,
|
|
176
194
|
max_results: options?.maxResults ? parseInt(options.maxResults, 10) : undefined,
|
|
177
195
|
repo: options?.repo,
|
|
178
|
-
});
|
|
196
|
+
}), { phaseLabel: 'Finding derived Blueprints', successLabel: 'Derived Blueprint search complete', failLabel: 'Derived Blueprint search failed', isError: isUnrealError });
|
|
197
|
+
if (result?.status === 'error') {
|
|
198
|
+
console.error(`\n Error: ${result.error}\n`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
179
201
|
output(result);
|
|
180
202
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner helper for long-running Unreal CLI commands.
|
|
3
|
+
* Uses braille-dot animation with elapsed time — CLI layer only.
|
|
4
|
+
*/
|
|
5
|
+
export interface SpinnerOptions {
|
|
6
|
+
phaseLabel: string;
|
|
7
|
+
successLabel?: string;
|
|
8
|
+
failLabel?: string;
|
|
9
|
+
/** Check if the result indicates an error (for non-throwing error returns). */
|
|
10
|
+
isError?: (result: any) => boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function withUnrealProgress<T>(operation: () => Promise<T>, opts: SpinnerOptions): Promise<T>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner helper for long-running Unreal CLI commands.
|
|
3
|
+
* Uses braille-dot animation with elapsed time — CLI layer only.
|
|
4
|
+
*/
|
|
5
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
6
|
+
const GREEN = '\x1b[92m';
|
|
7
|
+
const RED = '\x1b[91m';
|
|
8
|
+
const YELLOW = '\x1b[93m';
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
10
|
+
const BOLD = '\x1b[1m';
|
|
11
|
+
const DIM = '\x1b[2m';
|
|
12
|
+
export async function withUnrealProgress(operation, opts) {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
let frame = 0;
|
|
15
|
+
let aborted = false;
|
|
16
|
+
const clearLine = () => process.stdout.write('\r\x1b[2K');
|
|
17
|
+
const render = () => {
|
|
18
|
+
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
19
|
+
const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
20
|
+
const time = elapsed > 0 ? ` ${DIM}(${elapsed}s)${RESET}` : '';
|
|
21
|
+
clearLine();
|
|
22
|
+
process.stdout.write(` ${YELLOW}${spinner}${RESET} ${opts.phaseLabel}...${time}`);
|
|
23
|
+
frame++;
|
|
24
|
+
};
|
|
25
|
+
// Initial render + tick every 80ms for smooth animation
|
|
26
|
+
render();
|
|
27
|
+
const timer = setInterval(render, 80);
|
|
28
|
+
const sigintHandler = () => {
|
|
29
|
+
if (aborted)
|
|
30
|
+
process.exit(1);
|
|
31
|
+
aborted = true;
|
|
32
|
+
clearInterval(timer);
|
|
33
|
+
clearLine();
|
|
34
|
+
process.stdout.write(` ${RED}✗${RESET} Interrupted\n`);
|
|
35
|
+
process.exit(130);
|
|
36
|
+
};
|
|
37
|
+
process.on('SIGINT', sigintHandler);
|
|
38
|
+
const cleanup = () => {
|
|
39
|
+
clearInterval(timer);
|
|
40
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const result = await operation();
|
|
44
|
+
cleanup();
|
|
45
|
+
const totalSec = ((Date.now() - start) / 1000).toFixed(1);
|
|
46
|
+
if (opts.isError?.(result)) {
|
|
47
|
+
const label = opts.failLabel || 'Failed';
|
|
48
|
+
clearLine();
|
|
49
|
+
process.stdout.write(` ${RED}${BOLD}✗${RESET} ${label} ${DIM}(${totalSec}s)${RESET}\n`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const label = opts.successLabel || 'Done';
|
|
53
|
+
clearLine();
|
|
54
|
+
process.stdout.write(` ${GREEN}${BOLD}✓${RESET} ${label} ${DIM}(${totalSec}s)${RESET}\n`);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
cleanup();
|
|
60
|
+
const totalSec = ((Date.now() - start) / 1000).toFixed(1);
|
|
61
|
+
const label = opts.failLabel || 'Failed';
|
|
62
|
+
clearLine();
|
|
63
|
+
process.stdout.write(` ${RED}${BOLD}✗${RESET} ${label} ${DIM}(${totalSec}s)${RESET}\n`);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -199,7 +199,13 @@ export class LocalBackend {
|
|
|
199
199
|
if (this.repos.size === 1) {
|
|
200
200
|
return this.repos.values().next().value;
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
// Multiple repos — try to match by current working directory
|
|
203
|
+
const cwd = process.cwd();
|
|
204
|
+
for (const handle of this.repos.values()) {
|
|
205
|
+
if (cwd.startsWith(handle.repoPath))
|
|
206
|
+
return handle;
|
|
207
|
+
}
|
|
208
|
+
return null; // Multiple repos, no CWD match — ambiguous
|
|
203
209
|
}
|
|
204
210
|
// ─── Lazy LadybugDB Init ────────────────────────────────────────────
|
|
205
211
|
async ensureInitialized(repoId) {
|
|
@@ -270,7 +276,7 @@ export class LocalBackend {
|
|
|
270
276
|
case 'rename':
|
|
271
277
|
return this.rename(repo, params);
|
|
272
278
|
case 'sync_unreal_asset_manifest':
|
|
273
|
-
return this.syncUnrealAssetManifestTool(repo);
|
|
279
|
+
return this.syncUnrealAssetManifestTool(repo, params);
|
|
274
280
|
case 'find_native_blueprint_references':
|
|
275
281
|
return this.findNativeBlueprintReferencesTool(repo, params);
|
|
276
282
|
case 'expand_blueprint_chain':
|
|
@@ -420,7 +426,7 @@ export class LocalBackend {
|
|
|
420
426
|
let manifest = refreshManifest ? null : await loadUnrealAssetManifest(repo.storagePath);
|
|
421
427
|
let manifestRefreshed = false;
|
|
422
428
|
if (!manifest) {
|
|
423
|
-
const syncResult = await syncUnrealAssetManifest(repo.storagePath, config);
|
|
429
|
+
const syncResult = await syncUnrealAssetManifest(repo.storagePath, config, repo.repoPath);
|
|
424
430
|
if (syncResult.status === 'error') {
|
|
425
431
|
return { error: syncResult.error || 'Failed to build Unreal asset manifest.' };
|
|
426
432
|
}
|
|
@@ -437,7 +443,7 @@ export class LocalBackend {
|
|
|
437
443
|
manifestRefreshed,
|
|
438
444
|
};
|
|
439
445
|
}
|
|
440
|
-
async syncUnrealAssetManifestTool(repo) {
|
|
446
|
+
async syncUnrealAssetManifestTool(repo, params) {
|
|
441
447
|
const config = await loadUnrealConfig(repo.storagePath);
|
|
442
448
|
if (!config) {
|
|
443
449
|
const paths = getUnrealStoragePaths(repo.storagePath);
|
|
@@ -445,7 +451,7 @@ export class LocalBackend {
|
|
|
445
451
|
error: `Unreal analyzer is not configured for this repo. Create ${paths.config_path} with editor_cmd and project_path.`,
|
|
446
452
|
};
|
|
447
453
|
}
|
|
448
|
-
return syncUnrealAssetManifest(repo.storagePath, config);
|
|
454
|
+
return syncUnrealAssetManifest(repo.storagePath, config, repo.repoPath, params?.deep);
|
|
449
455
|
}
|
|
450
456
|
async findNativeBlueprintReferencesTool(repo, params) {
|
|
451
457
|
const targetResult = await this.resolveNativeFunctionTarget(repo, params);
|
package/dist/mcp/tools.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
export const GITNEXUS_TOOLS = [
|
|
8
8
|
{
|
|
9
9
|
name: 'list_repos',
|
|
10
|
-
description: `List all indexed repositories available to GitNexus.
|
|
11
|
-
|
|
12
|
-
Returns each repo's name, path, indexed date, last commit, and stats.
|
|
13
|
-
|
|
14
|
-
WHEN TO USE: First step when multiple repos are indexed, or to discover available repos.
|
|
15
|
-
AFTER THIS: READ gitnexus://repo/{name}/context for the repo you want to work with.
|
|
16
|
-
|
|
17
|
-
When multiple repos are indexed, you MUST specify the "repo" parameter
|
|
10
|
+
description: `List all indexed repositories available to GitNexus.
|
|
11
|
+
|
|
12
|
+
Returns each repo's name, path, indexed date, last commit, and stats.
|
|
13
|
+
|
|
14
|
+
WHEN TO USE: First step when multiple repos are indexed, or to discover available repos.
|
|
15
|
+
AFTER THIS: READ gitnexus://repo/{name}/context for the repo you want to work with.
|
|
16
|
+
|
|
17
|
+
When multiple repos are indexed, you MUST specify the "repo" parameter
|
|
18
18
|
on other tools (query, context, impact, etc.) to target the correct one.`,
|
|
19
19
|
inputSchema: {
|
|
20
20
|
type: 'object',
|
|
@@ -29,6 +29,10 @@ on other tools (query, context, impact, etc.) to target the correct one.`,
|
|
|
29
29
|
Uses the configured Unreal Editor commandlet to enumerate Blueprint assets and
|
|
30
30
|
store a manifest under .gitnexus/unreal/asset-manifest.json.
|
|
31
31
|
|
|
32
|
+
Two modes:
|
|
33
|
+
- metadata (default): Uses AssetRegistry only — zero Blueprint loading, fast and safe for large projects.
|
|
34
|
+
- deep: Loads each Blueprint fully to extract native_function_refs and graph data. Uses batch GC. Pass deep=true.
|
|
35
|
+
|
|
32
36
|
WHEN TO USE: Before Blueprint-reference queries, after large Blueprint changes, or
|
|
33
37
|
to validate Unreal analyzer configuration.
|
|
34
38
|
AFTER THIS: Use find_native_blueprint_references() or find_blueprints_derived_from_native_class().`,
|
|
@@ -36,6 +40,7 @@ AFTER THIS: Use find_native_blueprint_references() or find_blueprints_derived_fr
|
|
|
36
40
|
type: 'object',
|
|
37
41
|
properties: {
|
|
38
42
|
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
43
|
+
deep: { type: 'boolean', description: 'Deep mode: fully load Blueprints for native_function_refs (slower, higher memory). Default: false (metadata-only).' },
|
|
39
44
|
},
|
|
40
45
|
required: [],
|
|
41
46
|
},
|
|
@@ -106,15 +111,15 @@ AFTER THIS: Use find_native_blueprint_references() for function-level graph refe
|
|
|
106
111
|
name: 'query',
|
|
107
112
|
description: `Query the code knowledge graph for execution flows related to a concept.
|
|
108
113
|
Returns processes (call chains) ranked by relevance, each with its symbols and file locations.
|
|
109
|
-
|
|
110
|
-
WHEN TO USE: Understanding how code works together. Use this when you need execution flows and relationships, not just file matches. Complements grep/IDE search.
|
|
111
|
-
AFTER THIS: Use context() on a specific symbol for 360-degree view (callers, callees, categorized refs).
|
|
112
|
-
|
|
113
|
-
Returns results grouped by process (execution flow):
|
|
114
|
-
- processes: ranked execution flows with relevance priority
|
|
115
|
-
- process_symbols: all symbols in those flows with file locations and module (functional area)
|
|
116
|
-
- definitions: standalone types/interfaces not in any process
|
|
117
|
-
|
|
114
|
+
|
|
115
|
+
WHEN TO USE: Understanding how code works together. Use this when you need execution flows and relationships, not just file matches. Complements grep/IDE search.
|
|
116
|
+
AFTER THIS: Use context() on a specific symbol for 360-degree view (callers, callees, categorized refs).
|
|
117
|
+
|
|
118
|
+
Returns results grouped by process (execution flow):
|
|
119
|
+
- processes: ranked execution flows with relevance priority
|
|
120
|
+
- process_symbols: all symbols in those flows with file locations and module (functional area)
|
|
121
|
+
- definitions: standalone types/interfaces not in any process
|
|
122
|
+
|
|
118
123
|
Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.`,
|
|
119
124
|
inputSchema: {
|
|
120
125
|
type: 'object',
|
|
@@ -132,49 +137,49 @@ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank
|
|
|
132
137
|
},
|
|
133
138
|
{
|
|
134
139
|
name: 'cypher',
|
|
135
|
-
description: `Execute Cypher query against the code knowledge graph.
|
|
136
|
-
|
|
137
|
-
WHEN TO USE: Complex structural queries that search/explore can't answer. READ gitnexus://repo/{name}/schema first for the full schema.
|
|
138
|
-
AFTER THIS: Use context() on result symbols for deeper context.
|
|
139
|
-
|
|
140
|
-
SCHEMA:
|
|
141
|
-
- Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process
|
|
142
|
-
- Multi-language nodes (use backticks): \`Struct\`, \`Enum\`, \`Trait\`, \`Impl\`, etc.
|
|
143
|
-
- All edges via single CodeRelation table with 'type' property
|
|
144
|
-
- Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, OVERRIDES, MEMBER_OF, STEP_IN_PROCESS
|
|
145
|
-
- Edge properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)
|
|
146
|
-
|
|
147
|
-
EXAMPLES:
|
|
148
|
-
• Find callers of a function:
|
|
149
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
|
|
150
|
-
|
|
151
|
-
• Find community members:
|
|
152
|
-
MATCH (f)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community) WHERE c.heuristicLabel = "Auth" RETURN f.name
|
|
153
|
-
|
|
154
|
-
• Trace a process:
|
|
155
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process) WHERE p.heuristicLabel = "UserLogin" RETURN s.name, r.step ORDER BY r.step
|
|
156
|
-
|
|
157
|
-
• Find all methods of a class:
|
|
158
|
-
MATCH (c:Class {name: "UserService"})-[r:CodeRelation {type: 'HAS_METHOD'}]->(m:Method) RETURN m.name, m.parameterCount, m.returnType
|
|
159
|
-
|
|
160
|
-
• Find all properties of a class:
|
|
161
|
-
MATCH (c:Class {name: "User"})-[r:CodeRelation {type: 'HAS_PROPERTY'}]->(p:Property) RETURN p.name, p.description
|
|
162
|
-
|
|
163
|
-
• Find all writers of a field:
|
|
164
|
-
MATCH (f:Function)-[r:CodeRelation {type: 'ACCESSES', reason: 'write'}]->(p:Property) WHERE p.name = "address" RETURN f.name, f.filePath
|
|
165
|
-
|
|
166
|
-
• Find method overrides (MRO resolution):
|
|
167
|
-
MATCH (winner:Method)-[r:CodeRelation {type: 'OVERRIDES'}]->(loser:Method) RETURN winner.name, winner.filePath, loser.filePath, r.reason
|
|
168
|
-
|
|
169
|
-
• Detect diamond inheritance:
|
|
170
|
-
MATCH (d:Class)-[:CodeRelation {type: 'EXTENDS'}]->(b1), (d)-[:CodeRelation {type: 'EXTENDS'}]->(b2), (b1)-[:CodeRelation {type: 'EXTENDS'}]->(a), (b2)-[:CodeRelation {type: 'EXTENDS'}]->(a) WHERE b1 <> b2 RETURN d.name, b1.name, b2.name, a.name
|
|
171
|
-
|
|
172
|
-
OUTPUT: Returns { markdown, row_count } — results formatted as a Markdown table for easy reading.
|
|
173
|
-
|
|
174
|
-
TIPS:
|
|
175
|
-
- All relationships use single CodeRelation table — filter with {type: 'CALLS'} etc.
|
|
176
|
-
- Community = auto-detected functional area (Leiden algorithm)
|
|
177
|
-
- Process = execution flow trace from entry point to terminal
|
|
140
|
+
description: `Execute Cypher query against the code knowledge graph.
|
|
141
|
+
|
|
142
|
+
WHEN TO USE: Complex structural queries that search/explore can't answer. READ gitnexus://repo/{name}/schema first for the full schema.
|
|
143
|
+
AFTER THIS: Use context() on result symbols for deeper context.
|
|
144
|
+
|
|
145
|
+
SCHEMA:
|
|
146
|
+
- Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process
|
|
147
|
+
- Multi-language nodes (use backticks): \`Struct\`, \`Enum\`, \`Trait\`, \`Impl\`, etc.
|
|
148
|
+
- All edges via single CodeRelation table with 'type' property
|
|
149
|
+
- Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, OVERRIDES, MEMBER_OF, STEP_IN_PROCESS
|
|
150
|
+
- Edge properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)
|
|
151
|
+
|
|
152
|
+
EXAMPLES:
|
|
153
|
+
• Find callers of a function:
|
|
154
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
|
|
155
|
+
|
|
156
|
+
• Find community members:
|
|
157
|
+
MATCH (f)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community) WHERE c.heuristicLabel = "Auth" RETURN f.name
|
|
158
|
+
|
|
159
|
+
• Trace a process:
|
|
160
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process) WHERE p.heuristicLabel = "UserLogin" RETURN s.name, r.step ORDER BY r.step
|
|
161
|
+
|
|
162
|
+
• Find all methods of a class:
|
|
163
|
+
MATCH (c:Class {name: "UserService"})-[r:CodeRelation {type: 'HAS_METHOD'}]->(m:Method) RETURN m.name, m.parameterCount, m.returnType
|
|
164
|
+
|
|
165
|
+
• Find all properties of a class:
|
|
166
|
+
MATCH (c:Class {name: "User"})-[r:CodeRelation {type: 'HAS_PROPERTY'}]->(p:Property) RETURN p.name, p.description
|
|
167
|
+
|
|
168
|
+
• Find all writers of a field:
|
|
169
|
+
MATCH (f:Function)-[r:CodeRelation {type: 'ACCESSES', reason: 'write'}]->(p:Property) WHERE p.name = "address" RETURN f.name, f.filePath
|
|
170
|
+
|
|
171
|
+
• Find method overrides (MRO resolution):
|
|
172
|
+
MATCH (winner:Method)-[r:CodeRelation {type: 'OVERRIDES'}]->(loser:Method) RETURN winner.name, winner.filePath, loser.filePath, r.reason
|
|
173
|
+
|
|
174
|
+
• Detect diamond inheritance:
|
|
175
|
+
MATCH (d:Class)-[:CodeRelation {type: 'EXTENDS'}]->(b1), (d)-[:CodeRelation {type: 'EXTENDS'}]->(b2), (b1)-[:CodeRelation {type: 'EXTENDS'}]->(a), (b2)-[:CodeRelation {type: 'EXTENDS'}]->(a) WHERE b1 <> b2 RETURN d.name, b1.name, b2.name, a.name
|
|
176
|
+
|
|
177
|
+
OUTPUT: Returns { markdown, row_count } — results formatted as a Markdown table for easy reading.
|
|
178
|
+
|
|
179
|
+
TIPS:
|
|
180
|
+
- All relationships use single CodeRelation table — filter with {type: 'CALLS'} etc.
|
|
181
|
+
- Community = auto-detected functional area (Leiden algorithm)
|
|
182
|
+
- Process = execution flow trace from entry point to terminal
|
|
178
183
|
- Use heuristicLabel (not label) for human-readable community/process names`,
|
|
179
184
|
inputSchema: {
|
|
180
185
|
type: 'object',
|
|
@@ -187,14 +192,14 @@ TIPS:
|
|
|
187
192
|
},
|
|
188
193
|
{
|
|
189
194
|
name: 'context',
|
|
190
|
-
description: `360-degree view of a single code symbol.
|
|
191
|
-
Shows categorized incoming/outgoing references (calls, imports, extends, implements, methods, properties, overrides), process participation, and file location.
|
|
192
|
-
|
|
193
|
-
WHEN TO USE: After query() to understand a specific symbol in depth. When you need to know all callers, callees, and what execution flows a symbol participates in.
|
|
194
|
-
AFTER THIS: Use impact() if planning changes, or READ gitnexus://repo/{name}/process/{processName} for full execution trace.
|
|
195
|
-
|
|
196
|
-
Handles disambiguation: if multiple symbols share the same name, returns candidates for you to pick from. Use uid param for zero-ambiguity lookup from prior results.
|
|
197
|
-
|
|
195
|
+
description: `360-degree view of a single code symbol.
|
|
196
|
+
Shows categorized incoming/outgoing references (calls, imports, extends, implements, methods, properties, overrides), process participation, and file location.
|
|
197
|
+
|
|
198
|
+
WHEN TO USE: After query() to understand a specific symbol in depth. When you need to know all callers, callees, and what execution flows a symbol participates in.
|
|
199
|
+
AFTER THIS: Use impact() if planning changes, or READ gitnexus://repo/{name}/process/{processName} for full execution trace.
|
|
200
|
+
|
|
201
|
+
Handles disambiguation: if multiple symbols share the same name, returns candidates for you to pick from. Use uid param for zero-ambiguity lookup from prior results.
|
|
202
|
+
|
|
198
203
|
NOTE: ACCESSES edges (field read/write tracking) are included in context results. Coverage: reads detected during call chain resolution (e.g., user.address.save() emits a read on 'address'). Standalone reads and writes require Phase 2.`,
|
|
199
204
|
inputSchema: {
|
|
200
205
|
type: 'object',
|
|
@@ -210,12 +215,12 @@ NOTE: ACCESSES edges (field read/write tracking) are included in context results
|
|
|
210
215
|
},
|
|
211
216
|
{
|
|
212
217
|
name: 'detect_changes',
|
|
213
|
-
description: `Analyze uncommitted git changes and find affected execution flows.
|
|
214
|
-
Maps git diff hunks to indexed symbols, then traces which processes are impacted.
|
|
215
|
-
|
|
216
|
-
WHEN TO USE: Before committing — to understand what your changes affect. Pre-commit review, PR preparation.
|
|
217
|
-
AFTER THIS: Review affected processes. Use context() on high-risk symbols. READ gitnexus://repo/{name}/process/{name} for full traces.
|
|
218
|
-
|
|
218
|
+
description: `Analyze uncommitted git changes and find affected execution flows.
|
|
219
|
+
Maps git diff hunks to indexed symbols, then traces which processes are impacted.
|
|
220
|
+
|
|
221
|
+
WHEN TO USE: Before committing — to understand what your changes affect. Pre-commit review, PR preparation.
|
|
222
|
+
AFTER THIS: Review affected processes. Use context() on high-risk symbols. READ gitnexus://repo/{name}/process/{name} for full traces.
|
|
223
|
+
|
|
219
224
|
Returns: changed symbols, affected processes, and a risk summary.`,
|
|
220
225
|
inputSchema: {
|
|
221
226
|
type: 'object',
|
|
@@ -229,14 +234,14 @@ Returns: changed symbols, affected processes, and a risk summary.`,
|
|
|
229
234
|
},
|
|
230
235
|
{
|
|
231
236
|
name: 'rename',
|
|
232
|
-
description: `Multi-file coordinated rename using the knowledge graph + text search.
|
|
233
|
-
Finds all references via graph (high confidence) and regex text search (lower confidence). Preview by default.
|
|
234
|
-
|
|
235
|
-
WHEN TO USE: Renaming a function, class, method, or variable across the codebase. Safer than find-and-replace.
|
|
236
|
-
AFTER THIS: Run detect_changes() to verify no unexpected side effects.
|
|
237
|
-
|
|
238
|
-
Each edit is tagged with confidence:
|
|
239
|
-
- "graph": found via knowledge graph relationships (high confidence, safe to accept)
|
|
237
|
+
description: `Multi-file coordinated rename using the knowledge graph + text search.
|
|
238
|
+
Finds all references via graph (high confidence) and regex text search (lower confidence). Preview by default.
|
|
239
|
+
|
|
240
|
+
WHEN TO USE: Renaming a function, class, method, or variable across the codebase. Safer than find-and-replace.
|
|
241
|
+
AFTER THIS: Run detect_changes() to verify no unexpected side effects.
|
|
242
|
+
|
|
243
|
+
Each edit is tagged with confidence:
|
|
244
|
+
- "graph": found via knowledge graph relationships (high confidence, safe to accept)
|
|
240
245
|
- "text_search": found via regex text search (lower confidence, review carefully)`,
|
|
241
246
|
inputSchema: {
|
|
242
247
|
type: 'object',
|
|
@@ -253,27 +258,27 @@ Each edit is tagged with confidence:
|
|
|
253
258
|
},
|
|
254
259
|
{
|
|
255
260
|
name: 'impact',
|
|
256
|
-
description: `Analyze the blast radius of changing a code symbol.
|
|
257
|
-
Returns affected symbols grouped by depth, plus risk assessment, affected execution flows, and affected modules.
|
|
258
|
-
|
|
259
|
-
WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
|
|
260
|
-
AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
|
|
261
|
-
|
|
262
|
-
Output includes:
|
|
263
|
-
- risk: LOW / MEDIUM / HIGH / CRITICAL
|
|
264
|
-
- summary: direct callers, processes affected, modules affected
|
|
265
|
-
- affected_processes: which execution flows break and at which step
|
|
266
|
-
- affected_modules: which functional areas are hit (direct vs indirect)
|
|
267
|
-
- byDepth: all affected symbols grouped by traversal depth
|
|
268
|
-
|
|
269
|
-
Depth groups:
|
|
270
|
-
- d=1: WILL BREAK (direct callers/importers)
|
|
271
|
-
- d=2: LIKELY AFFECTED (indirect)
|
|
272
|
-
- d=3: MAY NEED TESTING (transitive)
|
|
273
|
-
|
|
274
|
-
TIP: Default traversal uses CALLS/IMPORTS/EXTENDS/IMPLEMENTS. For class members, include HAS_METHOD and HAS_PROPERTY in relationTypes. For field access analysis, include ACCESSES in relationTypes.
|
|
275
|
-
|
|
276
|
-
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, OVERRIDES, ACCESSES
|
|
261
|
+
description: `Analyze the blast radius of changing a code symbol.
|
|
262
|
+
Returns affected symbols grouped by depth, plus risk assessment, affected execution flows, and affected modules.
|
|
263
|
+
|
|
264
|
+
WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
|
|
265
|
+
AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
|
|
266
|
+
|
|
267
|
+
Output includes:
|
|
268
|
+
- risk: LOW / MEDIUM / HIGH / CRITICAL
|
|
269
|
+
- summary: direct callers, processes affected, modules affected
|
|
270
|
+
- affected_processes: which execution flows break and at which step
|
|
271
|
+
- affected_modules: which functional areas are hit (direct vs indirect)
|
|
272
|
+
- byDepth: all affected symbols grouped by traversal depth
|
|
273
|
+
|
|
274
|
+
Depth groups:
|
|
275
|
+
- d=1: WILL BREAK (direct callers/importers)
|
|
276
|
+
- d=2: LIKELY AFFECTED (indirect)
|
|
277
|
+
- d=3: MAY NEED TESTING (transitive)
|
|
278
|
+
|
|
279
|
+
TIP: Default traversal uses CALLS/IMPORTS/EXTENDS/IMPLEMENTS. For class members, include HAS_METHOD and HAS_PROPERTY in relationTypes. For field access analysis, include ACCESSES in relationTypes.
|
|
280
|
+
|
|
281
|
+
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, OVERRIDES, ACCESSES
|
|
277
282
|
Confidence: 1.0 = certain, <0.8 = fuzzy match`,
|
|
278
283
|
inputSchema: {
|
|
279
284
|
type: 'object',
|
|
@@ -10,9 +10,16 @@ export interface BlueprintIngestionResult {
|
|
|
10
10
|
nodesAdded: number;
|
|
11
11
|
edgesAdded: number;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Convert an Unreal asset path to a filesystem-relative path for ignore matching.
|
|
15
|
+
* "/Game/Characters/BP_Hero" → "Content/Characters/BP_Hero"
|
|
16
|
+
* "/MyPlugin/Maps/TestMap" → "Plugins/MyPlugin/Content/Maps/TestMap"
|
|
17
|
+
* "/Engine/BasicShapes/Cube" → "Engine/Content/BasicShapes/Cube"
|
|
18
|
+
*/
|
|
19
|
+
export declare const assetPathToRelative: (assetPath: string) => string;
|
|
13
20
|
/**
|
|
14
21
|
* Ingest Blueprint assets from the Unreal asset manifest into the knowledge graph.
|
|
15
22
|
* Creates Blueprint nodes and edges (EXTENDS, CALLS, IMPORTS) linking them to
|
|
16
23
|
* existing C++ symbols in the graph.
|
|
17
24
|
*/
|
|
18
|
-
export declare const ingestBlueprintsIntoGraph: (graph: KnowledgeGraph, storagePath: string) => Promise<BlueprintIngestionResult>;
|
|
25
|
+
export declare const ingestBlueprintsIntoGraph: (graph: KnowledgeGraph, storagePath: string, repoPath?: string) => Promise<BlueprintIngestionResult>;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { generateId } from '../lib/utils.js';
|
|
11
|
+
import { shouldIgnorePath, loadIgnoreRules } from '../config/ignore-service.js';
|
|
11
12
|
/** Extract a display name from an Unreal asset path (last segment). */
|
|
12
13
|
const extractAssetName = (assetPath) => {
|
|
13
14
|
// "/Game/Characters/BP_Hero" → "BP_Hero"
|
|
@@ -29,12 +30,33 @@ const extractClassName = (unrealPath) => {
|
|
|
29
30
|
const slashIdx = unrealPath.lastIndexOf('/');
|
|
30
31
|
return slashIdx >= 0 ? unrealPath.slice(slashIdx + 1) : unrealPath;
|
|
31
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Convert an Unreal asset path to a filesystem-relative path for ignore matching.
|
|
35
|
+
* "/Game/Characters/BP_Hero" → "Content/Characters/BP_Hero"
|
|
36
|
+
* "/MyPlugin/Maps/TestMap" → "Plugins/MyPlugin/Content/Maps/TestMap"
|
|
37
|
+
* "/Engine/BasicShapes/Cube" → "Engine/Content/BasicShapes/Cube"
|
|
38
|
+
*/
|
|
39
|
+
export const assetPathToRelative = (assetPath) => {
|
|
40
|
+
// Strip leading slash
|
|
41
|
+
const trimmed = assetPath.startsWith('/') ? assetPath.slice(1) : assetPath;
|
|
42
|
+
const slashIdx = trimmed.indexOf('/');
|
|
43
|
+
if (slashIdx < 0)
|
|
44
|
+
return trimmed;
|
|
45
|
+
const mount = trimmed.slice(0, slashIdx);
|
|
46
|
+
const rest = trimmed.slice(slashIdx + 1);
|
|
47
|
+
if (mount === 'Game')
|
|
48
|
+
return `Content/${rest}`;
|
|
49
|
+
if (mount === 'Engine')
|
|
50
|
+
return `Engine/Content/${rest}`;
|
|
51
|
+
// Plugin mounts: /PluginName/X → Plugins/PluginName/Content/X
|
|
52
|
+
return `Plugins/${mount}/Content/${rest}`;
|
|
53
|
+
};
|
|
32
54
|
/**
|
|
33
55
|
* Ingest Blueprint assets from the Unreal asset manifest into the knowledge graph.
|
|
34
56
|
* Creates Blueprint nodes and edges (EXTENDS, CALLS, IMPORTS) linking them to
|
|
35
57
|
* existing C++ symbols in the graph.
|
|
36
58
|
*/
|
|
37
|
-
export const ingestBlueprintsIntoGraph = async (graph, storagePath) => {
|
|
59
|
+
export const ingestBlueprintsIntoGraph = async (graph, storagePath, repoPath) => {
|
|
38
60
|
const manifestPath = path.join(storagePath, 'unreal', 'asset-manifest.json');
|
|
39
61
|
let manifest;
|
|
40
62
|
try {
|
|
@@ -47,6 +69,16 @@ export const ingestBlueprintsIntoGraph = async (graph, storagePath) => {
|
|
|
47
69
|
if (!manifest.assets || manifest.assets.length === 0) {
|
|
48
70
|
return { nodesAdded: 0, edgesAdded: 0 };
|
|
49
71
|
}
|
|
72
|
+
// ── Filter assets through ignore rules ─────────────────────────────
|
|
73
|
+
const ig = repoPath ? await loadIgnoreRules(repoPath) : null;
|
|
74
|
+
const assets = manifest.assets.filter(asset => {
|
|
75
|
+
const relPath = assetPathToRelative(asset.asset_path);
|
|
76
|
+
if (shouldIgnorePath(relPath))
|
|
77
|
+
return false;
|
|
78
|
+
if (ig && ig.ignores(relPath))
|
|
79
|
+
return false;
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
50
82
|
// ── Build lookup indexes from existing graph nodes ──────────────────
|
|
51
83
|
// Class/Struct nodes keyed by name (for parent class matching)
|
|
52
84
|
const classByName = new Map();
|
|
@@ -101,7 +133,7 @@ export const ingestBlueprintsIntoGraph = async (graph, storagePath) => {
|
|
|
101
133
|
let edgeCounter = 0;
|
|
102
134
|
// Track created Blueprint IDs for second-pass dependency edges
|
|
103
135
|
const blueprintIdByAssetPath = new Map();
|
|
104
|
-
for (const asset of
|
|
136
|
+
for (const asset of assets) {
|
|
105
137
|
const bpId = generateId('Blueprint', asset.asset_path);
|
|
106
138
|
const bpName = extractAssetName(asset.asset_path);
|
|
107
139
|
graph.addNode({
|
|
@@ -174,7 +206,7 @@ export const ingestBlueprintsIntoGraph = async (graph, storagePath) => {
|
|
|
174
206
|
}
|
|
175
207
|
}
|
|
176
208
|
// ── Second pass: Blueprint-to-Blueprint IMPORTS edges ───────────────
|
|
177
|
-
for (const asset of
|
|
209
|
+
for (const asset of assets) {
|
|
178
210
|
const deps = asset.dependencies || [];
|
|
179
211
|
if (deps.length === 0)
|
|
180
212
|
continue;
|
package/dist/unreal/bridge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ExpandBlueprintChainResult, FindNativeBlueprintReferencesResult, NativeFunctionTarget, SyncUnrealAssetManifestResult, UnrealBlueprintCandidate, UnrealConfig } from './types.js';
|
|
2
|
-
export declare function syncUnrealAssetManifest(storagePath: string, config: UnrealConfig): Promise<SyncUnrealAssetManifestResult>;
|
|
2
|
+
export declare function syncUnrealAssetManifest(storagePath: string, config: UnrealConfig, repoPath?: string, deep?: boolean): Promise<SyncUnrealAssetManifestResult>;
|
|
3
3
|
export declare function findNativeBlueprintReferences(storagePath: string, config: UnrealConfig, target: NativeFunctionTarget, candidateAssets: UnrealBlueprintCandidate[], manifestPath?: string): Promise<FindNativeBlueprintReferencesResult>;
|
|
4
4
|
export declare function expandBlueprintChain(storagePath: string, config: UnrealConfig, assetPath: string, chainAnchorId: string, direction: 'upstream' | 'downstream', maxDepth: number): Promise<ExpandBlueprintChainResult>;
|