@duytransipher/gitnexus 1.4.6-sipher.0 → 1.4.6-sipher.2

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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
- "name": "@duytransipher/gitnexus",
3
- "version": "1.4.6-sipher.0",
4
- "description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
5
- "author": "DuyTranSipher",
6
- "license": "PolyForm-Noncommercial-1.0.0",
7
- "homepage": "https://github.com/DuyTranSipher/sipher-git-nexus#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/DuyTranSipher/sipher-git-nexus.git",
11
- "directory": "gitnexus"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/DuyTranSipher/sipher-git-nexus/issues"
15
- },
2
+ "name": "@duytransipher/gitnexus",
3
+ "version": "1.4.6-sipher.2",
4
+ "description": "Sipher-maintained fork of GitNexus for graph-powered code intelligence via MCP and CLI.",
5
+ "author": "DuyTranSipher",
6
+ "license": "PolyForm-Noncommercial-1.0.0",
7
+ "homepage": "https://github.com/DuyTranSipher/sipher-git-nexus#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/DuyTranSipher/sipher-git-nexus.git",
11
+ "directory": "gitnexus"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/DuyTranSipher/sipher-git-nexus/issues"
15
+ },
16
16
  "keywords": [
17
17
  "mcp",
18
18
  "model-context-protocol",
@@ -36,19 +36,19 @@
36
36
  "skills",
37
37
  "vendor"
38
38
  ],
39
- "scripts": {
40
- "build": "tsc",
41
- "dev": "tsx watch src/cli/index.ts",
42
- "pack:dry-run": "npm pack --dry-run",
43
- "test": "vitest run",
44
- "test:unit": "vitest run test/unit",
45
- "test:integration": "vitest run test/integration",
46
- "test:watch": "vitest",
47
- "test:coverage": "vitest run --coverage",
48
- "prepare": "npm run build",
49
- "postinstall": "node scripts/patch-tree-sitter-swift.cjs",
50
- "prepack": "npm run build && node scripts/ensure-cli-executable.cjs"
51
- },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "dev": "tsx watch src/cli/index.ts",
42
+ "pack:dry-run": "npm pack --dry-run",
43
+ "test": "vitest run",
44
+ "test:unit": "vitest run test/unit",
45
+ "test:integration": "vitest run test/integration",
46
+ "test:watch": "vitest",
47
+ "test:coverage": "vitest run --coverage",
48
+ "prepare": "npm run build",
49
+ "postinstall": "node scripts/patch-tree-sitter-swift.cjs",
50
+ "prepack": "npm run build && node scripts/ensure-cli-executable.cjs"
51
+ },
52
52
  "dependencies": {
53
53
  "@huggingface/transformers": "^3.0.0",
54
54
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -5,7 +5,7 @@ param(
5
5
 
6
6
  [string]$ProjectFile,
7
7
 
8
- [string]$PluginSource = (Join-Path $PSScriptRoot '..\..\gitnexus-unreal\GitNexusUnreal'),
8
+ [string]$PluginSource = (Join-Path $PSScriptRoot '..\vendor\GitNexusUnreal'),
9
9
 
10
10
  [string]$EditorCmd,
11
11
 
@@ -0,0 +1,18 @@
1
+ {
2
+ "FileVersion": 3,
3
+ "Version": 1,
4
+ "VersionName": "0.1.0",
5
+ "FriendlyName": "GitNexus Unreal",
6
+ "Description": "Blueprint reference analysis commandlet used by GitNexus.",
7
+ "Category": "Developer",
8
+ "CanContainContent": false,
9
+ "IsBetaVersion": true,
10
+ "Installed": false,
11
+ "Modules": [
12
+ {
13
+ "Name": "GitNexusUnreal",
14
+ "Type": "Editor",
15
+ "LoadingPhase": "Default"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,29 @@
1
+ using UnrealBuildTool;
2
+
3
+ public class GitNexusUnreal : ModuleRules
4
+ {
5
+ public GitNexusUnreal(ReadOnlyTargetRules Target) : base(Target)
6
+ {
7
+ PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
8
+
9
+ PublicDependencyModuleNames.AddRange(new[]
10
+ {
11
+ "Core",
12
+ "CoreUObject",
13
+ "Engine"
14
+ });
15
+
16
+ PrivateDependencyModuleNames.AddRange(new[]
17
+ {
18
+ "AssetRegistry",
19
+ "BlueprintGraph",
20
+ "Json",
21
+ "JsonUtilities",
22
+ "Kismet",
23
+ "KismetCompiler",
24
+ "Projects",
25
+ "SlateCore",
26
+ "UnrealEd"
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,465 @@
1
+ #include "GitNexusBlueprintAnalyzerCommandlet.h"
2
+
3
+ #include "AssetRegistry/AssetRegistryModule.h"
4
+ #include "AssetRegistry/IAssetRegistry.h"
5
+ #include "Blueprint/BlueprintSupport.h"
6
+ #include "Blueprint/UserWidget.h"
7
+ #include "Dom/JsonObject.h"
8
+ #include "EdGraph/EdGraph.h"
9
+ #include "EdGraph/EdGraphNode.h"
10
+ #include "EdGraph/EdGraphPin.h"
11
+ #include "Engine/Blueprint.h"
12
+ #include "K2Node_CallFunction.h"
13
+ #include "K2Node_Event.h"
14
+ #include "Kismet2/BlueprintEditorUtils.h"
15
+ #include "Misc/FileHelper.h"
16
+ #include "Misc/Guid.h"
17
+ #include "Misc/PackageName.h"
18
+ #include "Misc/Paths.h"
19
+ #include "Serialization/JsonReader.h"
20
+ #include "Serialization/JsonSerializer.h"
21
+ #include "UObject/SoftObjectPath.h"
22
+
23
+ UGitNexusBlueprintAnalyzerCommandlet::UGitNexusBlueprintAnalyzerCommandlet()
24
+ {
25
+ IsClient = false;
26
+ IsEditor = true;
27
+ LogToConsole = true;
28
+ ShowErrorCount = true;
29
+ }
30
+
31
+ int32 UGitNexusBlueprintAnalyzerCommandlet::Main(const FString& Params)
32
+ {
33
+ FString Operation;
34
+ FString OutputJsonPath;
35
+ FParse::Value(*Params, TEXT("Operation="), Operation);
36
+ FParse::Value(*Params, TEXT("OutputJson="), OutputJsonPath);
37
+
38
+ if (Operation.IsEmpty() || OutputJsonPath.IsEmpty())
39
+ {
40
+ UE_LOG(LogTemp, Error, TEXT("GitNexusBlueprintAnalyzer requires Operation= and OutputJson= parameters."));
41
+ return 1;
42
+ }
43
+
44
+ if (Operation.Equals(TEXT("SyncAssets"), ESearchCase::IgnoreCase))
45
+ {
46
+ return RunSyncAssets(OutputJsonPath);
47
+ }
48
+
49
+ if (Operation.Equals(TEXT("FindNativeBlueprintReferences"), ESearchCase::IgnoreCase))
50
+ {
51
+ FString CandidatesJsonPath;
52
+ FString TargetSymbolKey;
53
+ FString TargetClassName;
54
+ FString TargetFunctionName;
55
+ FParse::Value(*Params, TEXT("CandidatesJson="), CandidatesJsonPath);
56
+ FParse::Value(*Params, TEXT("TargetSymbolKey="), TargetSymbolKey);
57
+ FParse::Value(*Params, TEXT("TargetClass="), TargetClassName);
58
+ FParse::Value(*Params, TEXT("TargetFunction="), TargetFunctionName);
59
+ return RunFindNativeBlueprintReferences(OutputJsonPath, CandidatesJsonPath, TargetSymbolKey, TargetClassName, TargetFunctionName);
60
+ }
61
+
62
+ if (Operation.Equals(TEXT("ExpandBlueprintChain"), ESearchCase::IgnoreCase))
63
+ {
64
+ FString AssetPath;
65
+ FString ChainAnchorId;
66
+ FString Direction = TEXT("downstream");
67
+ int32 MaxDepth = 5;
68
+ FParse::Value(*Params, TEXT("AssetPath="), AssetPath);
69
+ FParse::Value(*Params, TEXT("ChainAnchorId="), ChainAnchorId);
70
+ FParse::Value(*Params, TEXT("Direction="), Direction);
71
+ FParse::Value(*Params, TEXT("MaxDepth="), MaxDepth);
72
+ return RunExpandBlueprintChain(OutputJsonPath, AssetPath, ChainAnchorId, Direction, MaxDepth);
73
+ }
74
+
75
+ UE_LOG(LogTemp, Error, TEXT("Unsupported GitNexusBlueprintAnalyzer operation: %s"), *Operation);
76
+ return 1;
77
+ }
78
+
79
+ int32 UGitNexusBlueprintAnalyzerCommandlet::RunSyncAssets(const FString& OutputJsonPath)
80
+ {
81
+ TArray<TSharedPtr<FJsonValue>> AssetValues;
82
+
83
+ for (const FAssetData& AssetData : GetAllBlueprintAssets())
84
+ {
85
+ UBlueprint* Blueprint = Cast<UBlueprint>(AssetData.GetAsset());
86
+ if (!Blueprint)
87
+ {
88
+ continue;
89
+ }
90
+
91
+ TSharedPtr<FJsonObject> AssetObject = MakeShared<FJsonObject>();
92
+ AssetObject->SetStringField(TEXT("asset_path"), AssetData.GetSoftObjectPath().ToString());
93
+
94
+ if (Blueprint->GeneratedClass)
95
+ {
96
+ AssetObject->SetStringField(TEXT("generated_class"), Blueprint->GeneratedClass->GetPathName());
97
+ }
98
+
99
+ if (Blueprint->ParentClass)
100
+ {
101
+ AssetObject->SetStringField(TEXT("parent_class"), Blueprint->ParentClass->GetPathName());
102
+ }
103
+
104
+ TArray<TSharedPtr<FJsonValue>> NativeParents;
105
+ for (UClass* Class = Blueprint->ParentClass; Class; Class = Class->GetSuperClass())
106
+ {
107
+ if (Class->ClassGeneratedBy == nullptr)
108
+ {
109
+ NativeParents.Add(MakeShared<FJsonValueString>(Class->GetName()));
110
+ }
111
+ }
112
+ AssetObject->SetArrayField(TEXT("native_parents"), NativeParents);
113
+
114
+ TArray<FName> Dependencies;
115
+ IAssetRegistry::GetChecked().GetDependencies(AssetData.PackageName, Dependencies, UE::AssetRegistry::EDependencyCategory::Package);
116
+ TArray<TSharedPtr<FJsonValue>> DependencyValues;
117
+ for (const FName& Dependency : Dependencies)
118
+ {
119
+ DependencyValues.Add(MakeShared<FJsonValueString>(Dependency.ToString()));
120
+ }
121
+ AssetObject->SetArrayField(TEXT("dependencies"), DependencyValues);
122
+
123
+ TArray<UEdGraph*> Graphs;
124
+ CollectBlueprintGraphs(Blueprint, Graphs);
125
+ TSet<FString> NativeFunctionRefs;
126
+ for (const UEdGraph* Graph : Graphs)
127
+ {
128
+ if (!Graph)
129
+ {
130
+ continue;
131
+ }
132
+
133
+ for (const UEdGraphNode* Node : Graph->Nodes)
134
+ {
135
+ if (const UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Node))
136
+ {
137
+ if (const UFunction* TargetFunction = CallNode->GetTargetFunction())
138
+ {
139
+ const UClass* OwnerClass = TargetFunction->GetOwnerClass();
140
+ const FString SymbolKey = OwnerClass
141
+ ? FString::Printf(TEXT("%s::%s"), *OwnerClass->GetName(), *TargetFunction->GetName())
142
+ : TargetFunction->GetName();
143
+ NativeFunctionRefs.Add(SymbolKey);
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ TArray<TSharedPtr<FJsonValue>> NativeFunctionRefValues;
150
+ for (const FString& Ref : NativeFunctionRefs)
151
+ {
152
+ NativeFunctionRefValues.Add(MakeShared<FJsonValueString>(Ref));
153
+ }
154
+ AssetObject->SetArrayField(TEXT("native_function_refs"), NativeFunctionRefValues);
155
+
156
+ AssetValues.Add(MakeShared<FJsonValueObject>(AssetObject));
157
+ }
158
+
159
+ TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
160
+ RootObject->SetNumberField(TEXT("version"), 1);
161
+ RootObject->SetStringField(TEXT("generated_at"), FDateTime::UtcNow().ToIso8601());
162
+ RootObject->SetStringField(TEXT("project_path"), FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()));
163
+ RootObject->SetArrayField(TEXT("assets"), AssetValues);
164
+
165
+ return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
166
+ }
167
+
168
+ int32 UGitNexusBlueprintAnalyzerCommandlet::RunFindNativeBlueprintReferences(
169
+ const FString& OutputJsonPath,
170
+ const FString& CandidatesJsonPath,
171
+ const FString& TargetSymbolKey,
172
+ const FString& TargetClassName,
173
+ const FString& TargetFunctionName
174
+ )
175
+ {
176
+ TArray<TSharedPtr<FJsonValue>> ConfirmedReferences;
177
+ TArray<FString> CandidateAssets = LoadCandidateAssets(CandidatesJsonPath);
178
+
179
+ for (const FString& AssetPath : CandidateAssets)
180
+ {
181
+ UBlueprint* Blueprint = LoadBlueprintFromAssetPath(AssetPath);
182
+ if (!Blueprint)
183
+ {
184
+ continue;
185
+ }
186
+
187
+ TArray<UEdGraph*> Graphs;
188
+ CollectBlueprintGraphs(Blueprint, Graphs);
189
+ for (const UEdGraph* Graph : Graphs)
190
+ {
191
+ if (!Graph)
192
+ {
193
+ continue;
194
+ }
195
+
196
+ for (const UEdGraphNode* Node : Graph->Nodes)
197
+ {
198
+ if (Node && IsTargetFunctionNode(Node, TargetSymbolKey, TargetClassName, TargetFunctionName))
199
+ {
200
+ ConfirmedReferences.Add(MakeShared<FJsonValueObject>(BuildReferenceJson(Blueprint, Graph, Node)));
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
207
+ TSharedPtr<FJsonObject> TargetObject = MakeShared<FJsonObject>();
208
+ TargetObject->SetStringField(TEXT("symbol_key"), TargetSymbolKey);
209
+ TargetObject->SetStringField(TEXT("class_name"), TargetClassName);
210
+ TargetObject->SetStringField(TEXT("symbol_name"), TargetFunctionName);
211
+ RootObject->SetObjectField(TEXT("target_function"), TargetObject);
212
+ RootObject->SetNumberField(TEXT("candidates_scanned"), CandidateAssets.Num());
213
+ RootObject->SetArrayField(TEXT("confirmed_references"), ConfirmedReferences);
214
+
215
+ return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
216
+ }
217
+
218
+ int32 UGitNexusBlueprintAnalyzerCommandlet::RunExpandBlueprintChain(
219
+ const FString& OutputJsonPath,
220
+ const FString& AssetPath,
221
+ const FString& ChainAnchorId,
222
+ const FString& Direction,
223
+ int32 MaxDepth
224
+ )
225
+ {
226
+ UBlueprint* Blueprint = LoadBlueprintFromAssetPath(AssetPath);
227
+ if (!Blueprint)
228
+ {
229
+ return 1;
230
+ }
231
+
232
+ UEdGraphNode* StartNode = FindNodeByGuid(Blueprint, ChainAnchorId);
233
+ if (!StartNode)
234
+ {
235
+ return 1;
236
+ }
237
+
238
+ TArray<TSharedPtr<FJsonValue>> NodeValues;
239
+ TSet<FGuid> Visited;
240
+ TArray<TPair<UEdGraphNode*, int32>> Frontier;
241
+ Frontier.Add(TPair<UEdGraphNode*, int32>(StartNode, 0));
242
+ Visited.Add(StartNode->NodeGuid);
243
+
244
+ const bool bUpstream = Direction.Equals(TEXT("upstream"), ESearchCase::IgnoreCase);
245
+
246
+ while (Frontier.Num() > 0)
247
+ {
248
+ const TPair<UEdGraphNode*, int32> Current = Frontier[0];
249
+ Frontier.RemoveAt(0);
250
+
251
+ NodeValues.Add(MakeShared<FJsonValueObject>(BuildChainNodeJson(Current.Key->GetGraph(), Current.Key, Current.Value)));
252
+ if (Current.Value >= MaxDepth)
253
+ {
254
+ continue;
255
+ }
256
+
257
+ for (UEdGraphPin* Pin : Current.Key->Pins)
258
+ {
259
+ if (!Pin)
260
+ {
261
+ continue;
262
+ }
263
+
264
+ const bool bMatchesDirection = bUpstream ? Pin->Direction == EGPD_Input : Pin->Direction == EGPD_Output;
265
+ if (!bMatchesDirection)
266
+ {
267
+ continue;
268
+ }
269
+
270
+ for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
271
+ {
272
+ if (!LinkedPin || !LinkedPin->GetOwningNode())
273
+ {
274
+ continue;
275
+ }
276
+
277
+ UEdGraphNode* NextNode = LinkedPin->GetOwningNode();
278
+ if (Visited.Contains(NextNode->NodeGuid))
279
+ {
280
+ continue;
281
+ }
282
+
283
+ Visited.Add(NextNode->NodeGuid);
284
+ Frontier.Add(TPair<UEdGraphNode*, int32>(NextNode, Current.Value + 1));
285
+ }
286
+ }
287
+ }
288
+
289
+ TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
290
+ RootObject->SetArrayField(TEXT("nodes"), NodeValues);
291
+ return WriteJsonToFile(OutputJsonPath, RootObject) ? 0 : 1;
292
+ }
293
+
294
+ bool UGitNexusBlueprintAnalyzerCommandlet::WriteJsonToFile(const FString& OutputJsonPath, const TSharedPtr<FJsonObject>& RootObject) const
295
+ {
296
+ FString JsonText;
297
+ const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
298
+ if (!FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer))
299
+ {
300
+ return false;
301
+ }
302
+
303
+ return FFileHelper::SaveStringToFile(JsonText, *OutputJsonPath);
304
+ }
305
+
306
+ TArray<FString> UGitNexusBlueprintAnalyzerCommandlet::LoadCandidateAssets(const FString& CandidatesJsonPath) const
307
+ {
308
+ TArray<FString> Result;
309
+ if (CandidatesJsonPath.IsEmpty())
310
+ {
311
+ return Result;
312
+ }
313
+
314
+ FString RawJson;
315
+ if (!FFileHelper::LoadFileToString(RawJson, *CandidatesJsonPath))
316
+ {
317
+ return Result;
318
+ }
319
+
320
+ TSharedPtr<FJsonObject> RootObject;
321
+ const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawJson);
322
+ if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
323
+ {
324
+ return Result;
325
+ }
326
+
327
+ const TArray<TSharedPtr<FJsonValue>>* CandidateValues = nullptr;
328
+ if (!RootObject->TryGetArrayField(TEXT("candidate_assets"), CandidateValues) || !CandidateValues)
329
+ {
330
+ return Result;
331
+ }
332
+
333
+ for (const TSharedPtr<FJsonValue>& Value : *CandidateValues)
334
+ {
335
+ const TSharedPtr<FJsonObject>* CandidateObject = nullptr;
336
+ if (Value.IsValid() && Value->TryGetObject(CandidateObject) && CandidateObject && CandidateObject->IsValid())
337
+ {
338
+ FString AssetPath;
339
+ if ((*CandidateObject)->TryGetStringField(TEXT("asset_path"), AssetPath))
340
+ {
341
+ Result.Add(AssetPath);
342
+ }
343
+ }
344
+ }
345
+
346
+ return Result;
347
+ }
348
+
349
+ TArray<FAssetData> UGitNexusBlueprintAnalyzerCommandlet::GetAllBlueprintAssets() const
350
+ {
351
+ FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
352
+ IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
353
+ AssetRegistry.SearchAllAssets(true);
354
+
355
+ FARFilter Filter;
356
+ Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
357
+ Filter.bRecursiveClasses = true;
358
+
359
+ TArray<FAssetData> Assets;
360
+ AssetRegistry.GetAssets(Filter, Assets);
361
+ return Assets;
362
+ }
363
+
364
+ UBlueprint* UGitNexusBlueprintAnalyzerCommandlet::LoadBlueprintFromAssetPath(const FString& AssetPath) const
365
+ {
366
+ const FSoftObjectPath SoftObjectPath(AssetPath);
367
+ return Cast<UBlueprint>(SoftObjectPath.TryLoad());
368
+ }
369
+
370
+ void UGitNexusBlueprintAnalyzerCommandlet::CollectBlueprintGraphs(UBlueprint* Blueprint, TArray<UEdGraph*>& OutGraphs) const
371
+ {
372
+ if (!Blueprint)
373
+ {
374
+ return;
375
+ }
376
+
377
+ Blueprint->GetAllGraphs(OutGraphs);
378
+ }
379
+
380
+ bool UGitNexusBlueprintAnalyzerCommandlet::IsTargetFunctionNode(
381
+ const UEdGraphNode* Node,
382
+ const FString& TargetSymbolKey,
383
+ const FString& TargetClassName,
384
+ const FString& TargetFunctionName
385
+ ) const
386
+ {
387
+ if (const UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Node))
388
+ {
389
+ if (const UFunction* TargetFunction = CallNode->GetTargetFunction())
390
+ {
391
+ const UClass* OwnerClass = TargetFunction->GetOwnerClass();
392
+ const FString SymbolKey = OwnerClass
393
+ ? FString::Printf(TEXT("%s::%s"), *OwnerClass->GetName(), *TargetFunction->GetName())
394
+ : TargetFunction->GetName();
395
+ return SymbolKey == TargetSymbolKey
396
+ || TargetFunction->GetName() == TargetFunctionName
397
+ || (OwnerClass && OwnerClass->GetName() == TargetClassName && TargetFunction->GetName() == TargetFunctionName);
398
+ }
399
+ }
400
+
401
+ if (const UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node))
402
+ {
403
+ return EventNode->GetFunctionName().ToString() == TargetFunctionName;
404
+ }
405
+
406
+ return false;
407
+ }
408
+
409
+ TSharedPtr<FJsonObject> UGitNexusBlueprintAnalyzerCommandlet::BuildReferenceJson(UBlueprint* Blueprint, const UEdGraph* Graph, const UEdGraphNode* Node) const
410
+ {
411
+ TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
412
+ Object->SetStringField(TEXT("asset_path"), Blueprint ? Blueprint->GetPathName() : FString());
413
+ Object->SetStringField(TEXT("graph_name"), Graph ? Graph->GetName() : FString());
414
+ Object->SetStringField(TEXT("node_kind"), Node ? Node->GetClass()->GetName() : FString());
415
+ Object->SetStringField(TEXT("node_title"), Node ? Node->GetNodeTitle(ENodeTitleType::ListView).ToString() : FString());
416
+ Object->SetStringField(TEXT("blueprint_owner_function"), Graph ? Graph->GetName() : FString());
417
+ Object->SetStringField(TEXT("chain_anchor_id"), Node ? Node->NodeGuid.ToString(EGuidFormats::DigitsWithHyphens) : FString());
418
+ Object->SetStringField(TEXT("source"), TEXT("editor_confirmed"));
419
+ return Object;
420
+ }
421
+
422
+ TSharedPtr<FJsonObject> UGitNexusBlueprintAnalyzerCommandlet::BuildChainNodeJson(const UEdGraph* Graph, const UEdGraphNode* Node, int32 Depth) const
423
+ {
424
+ TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
425
+ Object->SetStringField(TEXT("node_id"), Node ? Node->NodeGuid.ToString(EGuidFormats::DigitsWithHyphens) : FString());
426
+ Object->SetStringField(TEXT("graph_name"), Graph ? Graph->GetName() : FString());
427
+ Object->SetStringField(TEXT("node_kind"), Node ? Node->GetClass()->GetName() : FString());
428
+ Object->SetStringField(TEXT("node_title"), Node ? Node->GetNodeTitle(ENodeTitleType::ListView).ToString() : FString());
429
+ Object->SetNumberField(TEXT("depth"), Depth);
430
+ return Object;
431
+ }
432
+
433
+ UEdGraphNode* UGitNexusBlueprintAnalyzerCommandlet::FindNodeByGuid(UBlueprint* Blueprint, const FString& NodeGuid) const
434
+ {
435
+ if (!Blueprint)
436
+ {
437
+ return nullptr;
438
+ }
439
+
440
+ FGuid Guid;
441
+ if (!FGuid::Parse(NodeGuid, Guid))
442
+ {
443
+ return nullptr;
444
+ }
445
+
446
+ TArray<UEdGraph*> Graphs;
447
+ CollectBlueprintGraphs(Blueprint, Graphs);
448
+ for (UEdGraph* Graph : Graphs)
449
+ {
450
+ if (!Graph)
451
+ {
452
+ continue;
453
+ }
454
+
455
+ for (UEdGraphNode* Node : Graph->Nodes)
456
+ {
457
+ if (Node && Node->NodeGuid == Guid)
458
+ {
459
+ return Node;
460
+ }
461
+ }
462
+ }
463
+
464
+ return nullptr;
465
+ }
@@ -0,0 +1,15 @@
1
+ #include "GitNexusUnrealModule.h"
2
+
3
+ #define LOCTEXT_NAMESPACE "FGitNexusUnrealModule"
4
+
5
+ void FGitNexusUnrealModule::StartupModule()
6
+ {
7
+ }
8
+
9
+ void FGitNexusUnrealModule::ShutdownModule()
10
+ {
11
+ }
12
+
13
+ #undef LOCTEXT_NAMESPACE
14
+
15
+ IMPLEMENT_MODULE(FGitNexusUnrealModule, GitNexusUnreal)
@@ -0,0 +1,46 @@
1
+ #pragma once
2
+
3
+ #include "Commandlets/Commandlet.h"
4
+ #include "GitNexusBlueprintAnalyzerCommandlet.generated.h"
5
+
6
+ class UBlueprint;
7
+ class UEdGraph;
8
+ class UEdGraphNode;
9
+
10
+ UCLASS()
11
+ class GITNEXUSUNREAL_API UGitNexusBlueprintAnalyzerCommandlet : public UCommandlet
12
+ {
13
+ GENERATED_BODY()
14
+
15
+ public:
16
+ UGitNexusBlueprintAnalyzerCommandlet();
17
+
18
+ virtual int32 Main(const FString& Params) override;
19
+
20
+ private:
21
+ int32 RunSyncAssets(const FString& OutputJsonPath);
22
+ int32 RunFindNativeBlueprintReferences(
23
+ const FString& OutputJsonPath,
24
+ const FString& CandidatesJsonPath,
25
+ const FString& TargetSymbolKey,
26
+ const FString& TargetClassName,
27
+ const FString& TargetFunctionName
28
+ );
29
+ int32 RunExpandBlueprintChain(
30
+ const FString& OutputJsonPath,
31
+ const FString& AssetPath,
32
+ const FString& ChainAnchorId,
33
+ const FString& Direction,
34
+ int32 MaxDepth
35
+ );
36
+
37
+ bool WriteJsonToFile(const FString& OutputJsonPath, const TSharedPtr<FJsonObject>& RootObject) const;
38
+ TArray<FString> LoadCandidateAssets(const FString& CandidatesJsonPath) const;
39
+ TArray<FAssetData> GetAllBlueprintAssets() const;
40
+ UBlueprint* LoadBlueprintFromAssetPath(const FString& AssetPath) const;
41
+ void CollectBlueprintGraphs(UBlueprint* Blueprint, TArray<UEdGraph*>& OutGraphs) const;
42
+ bool IsTargetFunctionNode(const UEdGraphNode* Node, const FString& TargetSymbolKey, const FString& TargetClassName, const FString& TargetFunctionName) const;
43
+ TSharedPtr<FJsonObject> BuildReferenceJson(UBlueprint* Blueprint, const UEdGraph* Graph, const UEdGraphNode* Node) const;
44
+ TSharedPtr<FJsonObject> BuildChainNodeJson(const UEdGraph* Graph, const UEdGraphNode* Node, int32 Depth) const;
45
+ UEdGraphNode* FindNodeByGuid(UBlueprint* Blueprint, const FString& NodeGuid) const;
46
+ };
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+
3
+ #include "Modules/ModuleManager.h"
4
+
5
+ class FGitNexusUnrealModule final : public IModuleInterface
6
+ {
7
+ public:
8
+ virtual void StartupModule() override;
9
+ virtual void ShutdownModule() override;
10
+ };