@duytransipher/gitnexus 1.2.2 → 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 +3 -2
- package/dist/mcp/local/local-backend.js +4 -4
- 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 +66 -1
- package/dist/unreal/types.d.ts +4 -0
- package/package.json +1 -1
- package/vendor/GitNexusUnreal/Source/GitNexusUnreal/Private/GitNexusBlueprintAnalyzerCommandlet.cpp +930 -465
- 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
|
@@ -129,13 +129,14 @@ export async function cypherCommand(query, options) {
|
|
|
129
129
|
}
|
|
130
130
|
export async function syncUnrealAssetManifestCommand(options) {
|
|
131
131
|
const backend = await getBackend();
|
|
132
|
-
const
|
|
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 });
|
|
133
134
|
if (result?.status === 'error') {
|
|
134
135
|
console.error(`\n Error: ${result.error}\n`);
|
|
135
136
|
process.exit(1);
|
|
136
137
|
}
|
|
137
138
|
if (result?.asset_count != null) {
|
|
138
|
-
console.log(` ${result.asset_count.toLocaleString()} Blueprint assets indexed`);
|
|
139
|
+
console.log(` ${result.asset_count.toLocaleString()} Blueprint assets indexed (${mode} mode)`);
|
|
139
140
|
if (result.manifest_path)
|
|
140
141
|
console.log(` ${result.manifest_path}`);
|
|
141
142
|
}
|
|
@@ -276,7 +276,7 @@ export class LocalBackend {
|
|
|
276
276
|
case 'rename':
|
|
277
277
|
return this.rename(repo, params);
|
|
278
278
|
case 'sync_unreal_asset_manifest':
|
|
279
|
-
return this.syncUnrealAssetManifestTool(repo);
|
|
279
|
+
return this.syncUnrealAssetManifestTool(repo, params);
|
|
280
280
|
case 'find_native_blueprint_references':
|
|
281
281
|
return this.findNativeBlueprintReferencesTool(repo, params);
|
|
282
282
|
case 'expand_blueprint_chain':
|
|
@@ -426,7 +426,7 @@ export class LocalBackend {
|
|
|
426
426
|
let manifest = refreshManifest ? null : await loadUnrealAssetManifest(repo.storagePath);
|
|
427
427
|
let manifestRefreshed = false;
|
|
428
428
|
if (!manifest) {
|
|
429
|
-
const syncResult = await syncUnrealAssetManifest(repo.storagePath, config);
|
|
429
|
+
const syncResult = await syncUnrealAssetManifest(repo.storagePath, config, repo.repoPath);
|
|
430
430
|
if (syncResult.status === 'error') {
|
|
431
431
|
return { error: syncResult.error || 'Failed to build Unreal asset manifest.' };
|
|
432
432
|
}
|
|
@@ -443,7 +443,7 @@ export class LocalBackend {
|
|
|
443
443
|
manifestRefreshed,
|
|
444
444
|
};
|
|
445
445
|
}
|
|
446
|
-
async syncUnrealAssetManifestTool(repo) {
|
|
446
|
+
async syncUnrealAssetManifestTool(repo, params) {
|
|
447
447
|
const config = await loadUnrealConfig(repo.storagePath);
|
|
448
448
|
if (!config) {
|
|
449
449
|
const paths = getUnrealStoragePaths(repo.storagePath);
|
|
@@ -451,7 +451,7 @@ export class LocalBackend {
|
|
|
451
451
|
error: `Unreal analyzer is not configured for this repo. Create ${paths.config_path} with editor_cmd and project_path.`,
|
|
452
452
|
};
|
|
453
453
|
}
|
|
454
|
-
return syncUnrealAssetManifest(repo.storagePath, config);
|
|
454
|
+
return syncUnrealAssetManifest(repo.storagePath, config, repo.repoPath, params?.deep);
|
|
455
455
|
}
|
|
456
456
|
async findNativeBlueprintReferencesTool(repo, params) {
|
|
457
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>;
|
package/dist/unreal/bridge.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execFile } from 'node:child_process';
|
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { ensureUnrealStorage, saveUnrealAssetManifest } from './config.js';
|
|
7
|
+
import { loadIgnoreRules } from '../config/ignore-service.js';
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
function buildBaseArgs(config, operation, outputPath) {
|
|
9
10
|
return [
|
|
@@ -68,10 +69,74 @@ async function readOutputJson(outputPath, stdout) {
|
|
|
68
69
|
return JSON.parse(stdout);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Build include/exclude prefix filters for the Unreal commandlet.
|
|
74
|
+
* Merges config `include_paths`/`exclude_paths` with .gitnexusignore patterns,
|
|
75
|
+
* mapping filesystem patterns to Unreal asset path prefixes.
|
|
76
|
+
*/
|
|
77
|
+
async function buildFilterPrefixes(repoPath, config) {
|
|
78
|
+
const include_prefixes = [];
|
|
79
|
+
const exclude_prefixes = [];
|
|
80
|
+
// Add explicit include_paths (whitelist) from unreal config
|
|
81
|
+
if (config.include_paths && Array.isArray(config.include_paths)) {
|
|
82
|
+
for (const p of config.include_paths) {
|
|
83
|
+
include_prefixes.push(p);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Add explicit exclude_paths from unreal config
|
|
87
|
+
if (config.exclude_paths && Array.isArray(config.exclude_paths)) {
|
|
88
|
+
for (const p of config.exclude_paths) {
|
|
89
|
+
exclude_prefixes.push(p);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Derive exclude prefixes from .gitnexusignore patterns
|
|
93
|
+
if (repoPath) {
|
|
94
|
+
const ig = await loadIgnoreRules(repoPath);
|
|
95
|
+
if (ig) {
|
|
96
|
+
const projectDir = path.dirname(config.project_path);
|
|
97
|
+
try {
|
|
98
|
+
const contentDir = path.join(projectDir, 'Content');
|
|
99
|
+
const entries = await fs.readdir(contentDir, { withFileTypes: true });
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
const relPath = `Content/${entry.name}`;
|
|
103
|
+
if (ig.ignores(relPath) || ig.ignores(relPath + '/')) {
|
|
104
|
+
exclude_prefixes.push(`/Game/${entry.name}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch { /* Content dir might not exist */ }
|
|
110
|
+
try {
|
|
111
|
+
const pluginsDir = path.join(projectDir, 'Plugins');
|
|
112
|
+
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
const relPath = `Plugins/${entry.name}`;
|
|
116
|
+
if (ig.ignores(relPath) || ig.ignores(relPath + '/')) {
|
|
117
|
+
exclude_prefixes.push(`/${entry.name}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch { /* Plugins dir might not exist */ }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { include_prefixes, exclude_prefixes };
|
|
126
|
+
}
|
|
127
|
+
export async function syncUnrealAssetManifest(storagePath, config, repoPath, deep) {
|
|
72
128
|
const unrealPaths = await ensureUnrealStorage(storagePath);
|
|
73
129
|
const { outputPath } = requestPaths(unrealPaths);
|
|
74
130
|
const args = buildBaseArgs(config, 'SyncAssets', outputPath);
|
|
131
|
+
// Pass scan mode: metadata (default, zero loading) or deep (full Blueprint loading)
|
|
132
|
+
args.push(`-Mode=${deep ? 'deep' : 'metadata'}`);
|
|
133
|
+
// Build and pass filter prefixes (include + exclude)
|
|
134
|
+
const filters = await buildFilterPrefixes(repoPath, config);
|
|
135
|
+
if (filters.include_prefixes.length > 0 || filters.exclude_prefixes.length > 0) {
|
|
136
|
+
const filterJsonPath = path.join(unrealPaths.requests_dir, `filter-${randomUUID()}.json`);
|
|
137
|
+
await fs.writeFile(filterJsonPath, JSON.stringify(filters), 'utf-8');
|
|
138
|
+
args.push(`-FilterJson=${filterJsonPath}`);
|
|
139
|
+
}
|
|
75
140
|
try {
|
|
76
141
|
const { stdout } = await runCommand(config, 'SyncAssets', args);
|
|
77
142
|
const manifest = await readOutputJson(outputPath, stdout);
|
package/dist/unreal/types.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export interface UnrealConfig {
|
|
|
19
19
|
timeout_ms?: number;
|
|
20
20
|
working_directory?: string;
|
|
21
21
|
extra_args?: string[];
|
|
22
|
+
/** Unreal package path prefixes to exclude from sync (e.g., "/Game/ThirdParty", "/SomePlugin") */
|
|
23
|
+
exclude_paths?: string[];
|
|
24
|
+
/** Unreal package path prefixes to include (whitelist). If set, ONLY assets under these prefixes are scanned. */
|
|
25
|
+
include_paths?: string[];
|
|
22
26
|
}
|
|
23
27
|
export interface UnrealStoragePaths {
|
|
24
28
|
root_dir: string;
|
package/package.json
CHANGED