@veewo/gitnexus 1.5.6 → 1.5.8
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/benchmark/analyze-runner.d.ts +0 -2
- package/dist/benchmark/analyze-runner.js +0 -6
- package/dist/benchmark/analyze-runner.test.js +1 -10
- package/dist/benchmark/runner.d.ts +0 -2
- package/dist/benchmark/runner.js +0 -2
- package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +0 -11
- package/dist/benchmark/u2-performance-sampler.js +3 -16
- package/dist/cli/ai-context.js +1 -7
- package/dist/cli/analyze-options.d.ts +19 -6
- package/dist/cli/analyze-options.js +76 -71
- package/dist/cli/analyze-options.test.js +78 -73
- package/dist/cli/analyze-runtime-summary.js +0 -1
- package/dist/cli/analyze-runtime-summary.test.js +0 -2
- package/dist/cli/analyze-summary.d.ts +0 -2
- package/dist/cli/analyze-summary.js +0 -24
- package/dist/cli/analyze-summary.test.js +1 -65
- package/dist/cli/analyze.d.ts +2 -4
- package/dist/cli/analyze.js +14 -30
- package/dist/cli/analyze.test.js +9 -15
- package/dist/cli/benchmark-agent-context.d.ts +0 -2
- package/dist/cli/benchmark-agent-context.js +0 -2
- package/dist/cli/benchmark-agent-safe-query-context.d.ts +0 -2
- package/dist/cli/benchmark-agent-safe-query-context.js +0 -2
- package/dist/cli/benchmark-unity.d.ts +0 -2
- package/dist/cli/benchmark-unity.js +0 -2
- package/dist/cli/clean.d.ts +2 -3
- package/dist/cli/clean.js +4 -25
- package/dist/cli/index.js +1 -12
- package/dist/core/ingestion/pipeline.js +1 -44
- package/dist/mcp/local/agent-safe-response.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +0 -23
- package/dist/mcp/local/local-backend.js +69 -248
- package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
- package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
- package/dist/mcp/tools.js +0 -70
- package/dist/storage/repo-manager.d.ts +1 -0
- package/dist/types/pipeline.d.ts +0 -3
- package/package.json +1 -1
- package/skills/gitnexus-cli.md +62 -38
- package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
- package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +122 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
- package/vendor/tree-sitter-dart/build/Release/.deps/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
- package/vendor/tree-sitter-dart/build/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
- package/vendor/tree-sitter-proto/build/Release/.deps/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
- package/vendor/tree-sitter-proto/build/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
- package/dist/cli/rule-lab.d.ts +0 -38
- package/dist/cli/rule-lab.js +0 -148
- package/dist/cli/rule-lab.test.d.ts +0 -1
- package/dist/cli/rule-lab.test.js +0 -31
- package/dist/cli/scope-manifest-config.d.ts +0 -9
- package/dist/cli/scope-manifest-config.js +0 -37
- package/dist/cli/sync-manifest.d.ts +0 -27
- package/dist/cli/sync-manifest.js +0 -200
- package/dist/cli/sync-manifest.test.d.ts +0 -1
- package/dist/cli/sync-manifest.test.js +0 -88
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
- package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
- package/dist/rule-lab/analyze.d.ts +0 -13
- package/dist/rule-lab/analyze.js +0 -125
- package/dist/rule-lab/analyze.test.d.ts +0 -1
- package/dist/rule-lab/analyze.test.js +0 -246
- package/dist/rule-lab/compile.d.ts +0 -5
- package/dist/rule-lab/compile.js +0 -51
- package/dist/rule-lab/compiled-bundles.d.ts +0 -30
- package/dist/rule-lab/compiled-bundles.js +0 -36
- package/dist/rule-lab/curate.d.ts +0 -33
- package/dist/rule-lab/curate.js +0 -155
- package/dist/rule-lab/curate.test.d.ts +0 -1
- package/dist/rule-lab/curate.test.js +0 -137
- package/dist/rule-lab/curation-input-builder.d.ts +0 -45
- package/dist/rule-lab/curation-input-builder.js +0 -133
- package/dist/rule-lab/discover.d.ts +0 -13
- package/dist/rule-lab/discover.js +0 -74
- package/dist/rule-lab/discover.test.d.ts +0 -1
- package/dist/rule-lab/discover.test.js +0 -42
- package/dist/rule-lab/paths.d.ts +0 -21
- package/dist/rule-lab/paths.js +0 -37
- package/dist/rule-lab/paths.test.d.ts +0 -1
- package/dist/rule-lab/paths.test.js +0 -46
- package/dist/rule-lab/promote.d.ts +0 -26
- package/dist/rule-lab/promote.js +0 -387
- package/dist/rule-lab/promote.test.d.ts +0 -1
- package/dist/rule-lab/promote.test.js +0 -314
- package/dist/rule-lab/regress.d.ts +0 -60
- package/dist/rule-lab/regress.js +0 -122
- package/dist/rule-lab/regress.test.d.ts +0 -1
- package/dist/rule-lab/regress.test.js +0 -68
- package/dist/rule-lab/review-pack.d.ts +0 -34
- package/dist/rule-lab/review-pack.js +0 -165
- package/dist/rule-lab/review-pack.test.d.ts +0 -1
- package/dist/rule-lab/review-pack.test.js +0 -116
- package/dist/rule-lab/types.d.ts +0 -135
- package/dist/rule-lab/types.js +0 -1
- package/skills/_shared/unity-rule-authoring-contract.md +0 -64
- package/skills/gitnexus-unity-rule-gen.md +0 -107
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import { generateId } from '../../lib/utils.js';
|
|
2
|
-
const RULE_EDGE_CONFIDENCE = 0.75;
|
|
3
|
-
const RULE_ANOMALY_PREVIEW_LIMIT = 5;
|
|
4
|
-
export function applyUnityRuntimeBindingRules(graph, rules, config) {
|
|
5
|
-
const ruleResults = [];
|
|
6
|
-
let totalEdges = 0;
|
|
7
|
-
const existingPairs = new Set();
|
|
8
|
-
for (const rel of graph.iterRelationships()) {
|
|
9
|
-
if (rel.type === 'CALLS')
|
|
10
|
-
existingPairs.add(`${rel.sourceId}->${rel.targetId}`);
|
|
11
|
-
}
|
|
12
|
-
const addSyntheticEdge = (sourceId, targetId, reason) => {
|
|
13
|
-
if (sourceId === targetId)
|
|
14
|
-
return false;
|
|
15
|
-
const key = `${sourceId}->${targetId}`;
|
|
16
|
-
if (existingPairs.has(key))
|
|
17
|
-
return false;
|
|
18
|
-
graph.addRelationship({
|
|
19
|
-
id: generateId('CALLS', `${sourceId}->${targetId}:${reason}`),
|
|
20
|
-
sourceId,
|
|
21
|
-
targetId,
|
|
22
|
-
type: 'CALLS',
|
|
23
|
-
confidence: RULE_EDGE_CONFIDENCE,
|
|
24
|
-
reason,
|
|
25
|
-
});
|
|
26
|
-
existingPairs.add(key);
|
|
27
|
-
return true;
|
|
28
|
-
};
|
|
29
|
-
// Pre-build indexes
|
|
30
|
-
const methodsByClassId = new Map();
|
|
31
|
-
const containerNodes = [];
|
|
32
|
-
const containerLabels = config.enableContainerNodes
|
|
33
|
-
? new Set(['Class', 'Struct', 'Interface', 'Record'])
|
|
34
|
-
: new Set(['Class']);
|
|
35
|
-
for (const rel of graph.iterRelationships()) {
|
|
36
|
-
if (rel.type !== 'HAS_METHOD')
|
|
37
|
-
continue;
|
|
38
|
-
const method = graph.getNode(rel.targetId);
|
|
39
|
-
if (!method || (method.label !== 'Method' && method.label !== 'Function'))
|
|
40
|
-
continue;
|
|
41
|
-
const list = methodsByClassId.get(rel.sourceId) ?? [];
|
|
42
|
-
list.push(method);
|
|
43
|
-
methodsByClassId.set(rel.sourceId, list);
|
|
44
|
-
}
|
|
45
|
-
for (const node of graph.iterNodes()) {
|
|
46
|
-
if (containerLabels.has(node.label))
|
|
47
|
-
containerNodes.push(node);
|
|
48
|
-
}
|
|
49
|
-
// Collect UNITY_ASSET_GUID_REF and UNITY_COMPONENT_INSTANCE edges
|
|
50
|
-
const assetGuidRefs = [];
|
|
51
|
-
const componentInstances = [];
|
|
52
|
-
for (const rel of graph.iterRelationships()) {
|
|
53
|
-
if (rel.type === 'UNITY_ASSET_GUID_REF')
|
|
54
|
-
assetGuidRefs.push(rel);
|
|
55
|
-
else if (rel.type === 'UNITY_COMPONENT_INSTANCE')
|
|
56
|
-
componentInstances.push(rel);
|
|
57
|
-
}
|
|
58
|
-
const prefabSourceTargetsBySource = buildPrefabSourceTargetsBySource(assetGuidRefs);
|
|
59
|
-
const methodsByResourceId = buildMethodsByResourceId(componentInstances, methodsByClassId);
|
|
60
|
-
const executionState = {
|
|
61
|
-
methodsByResourceId,
|
|
62
|
-
resourceMethodLookupCache: new Map(),
|
|
63
|
-
sceneRuntimeResourceIdsCache: new Map(),
|
|
64
|
-
diagnostics: {
|
|
65
|
-
rulesEvaluated: rules.length,
|
|
66
|
-
bindingsEvaluated: 0,
|
|
67
|
-
bindingsByKind: {},
|
|
68
|
-
methodLookupCalls: 0,
|
|
69
|
-
methodLookupCacheHits: 0,
|
|
70
|
-
sceneRuntimeTraversalCalls: 0,
|
|
71
|
-
sceneRuntimeTraversalCacheHits: 0,
|
|
72
|
-
sceneRuntimeResourcesVisited: 0,
|
|
73
|
-
anomalySet: new Set(),
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
// Pre-build scene file index: lowercase scene name → fileId[]
|
|
77
|
-
const sceneFilesByName = new Map();
|
|
78
|
-
for (const node of graph.iterNodes()) {
|
|
79
|
-
if (node.label !== 'File')
|
|
80
|
-
continue;
|
|
81
|
-
const filePath = String(node.properties.filePath ?? '');
|
|
82
|
-
if (!filePath.endsWith('.unity'))
|
|
83
|
-
continue;
|
|
84
|
-
const fileName = (filePath.split('/').pop() ?? '').replace(/\.unity$/, '').toLowerCase();
|
|
85
|
-
const list = sceneFilesByName.get(fileName) ?? [];
|
|
86
|
-
list.push(node.id);
|
|
87
|
-
sceneFilesByName.set(fileName, list);
|
|
88
|
-
}
|
|
89
|
-
for (const rule of rules) {
|
|
90
|
-
let ruleEdges = 0;
|
|
91
|
-
for (const binding of rule.resource_bindings ?? []) {
|
|
92
|
-
executionState.diagnostics.bindingsEvaluated += 1;
|
|
93
|
-
executionState.diagnostics.bindingsByKind[binding.kind] =
|
|
94
|
-
(executionState.diagnostics.bindingsByKind[binding.kind] || 0) + 1;
|
|
95
|
-
ruleEdges += processBinding(binding, rule.id, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, prefabSourceTargetsBySource, executionState, addSyntheticEdge);
|
|
96
|
-
}
|
|
97
|
-
if (rule.lifecycle_overrides?.additional_entry_points?.length) {
|
|
98
|
-
ruleEdges += processLifecycleOverrides(rule, methodsByClassId, containerNodes, addSyntheticEdge);
|
|
99
|
-
}
|
|
100
|
-
totalEdges += ruleEdges;
|
|
101
|
-
ruleResults.push({ ruleId: rule.id, edgesInjected: ruleEdges });
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
edgesInjected: totalEdges,
|
|
105
|
-
ruleResults,
|
|
106
|
-
diagnostics: finalizeRuleBindingDiagnostics(executionState.diagnostics, totalEdges),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function findMethodsOnResource(resourceFileId, executionState, entryPoints) {
|
|
110
|
-
executionState.diagnostics.methodLookupCalls += 1;
|
|
111
|
-
const cacheKey = `${resourceFileId}::${entryPoints.join('|')}`;
|
|
112
|
-
const cached = executionState.resourceMethodLookupCache.get(cacheKey);
|
|
113
|
-
if (cached) {
|
|
114
|
-
executionState.diagnostics.methodLookupCacheHits += 1;
|
|
115
|
-
return cached;
|
|
116
|
-
}
|
|
117
|
-
const methods = executionState.methodsByResourceId.get(resourceFileId) ?? [];
|
|
118
|
-
if (methods.length === 0) {
|
|
119
|
-
executionState.resourceMethodLookupCache.set(cacheKey, []);
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
const entrySet = new Set(entryPoints);
|
|
123
|
-
const results = [];
|
|
124
|
-
for (const method of methods) {
|
|
125
|
-
if (entrySet.has(method.properties.name))
|
|
126
|
-
results.push(method);
|
|
127
|
-
}
|
|
128
|
-
executionState.resourceMethodLookupCache.set(cacheKey, results);
|
|
129
|
-
return results;
|
|
130
|
-
}
|
|
131
|
-
function processBinding(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, prefabSourceTargetsBySource, executionState, addEdge) {
|
|
132
|
-
if (binding.kind === 'asset_ref_loads_components') {
|
|
133
|
-
return processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, executionState, addEdge);
|
|
134
|
-
}
|
|
135
|
-
if (binding.kind === 'method_triggers_field_load') {
|
|
136
|
-
return processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, executionState, addEdge);
|
|
137
|
-
}
|
|
138
|
-
if (binding.kind === 'method_triggers_scene_load') {
|
|
139
|
-
return processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, prefabSourceTargetsBySource, executionState, addEdge);
|
|
140
|
-
}
|
|
141
|
-
if (binding.kind === 'method_triggers_method') {
|
|
142
|
-
return processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge);
|
|
143
|
-
}
|
|
144
|
-
addAnomaly(executionState.diagnostics, `rule=${ruleId}: unsupported resource_binding kind "${binding.kind}"`);
|
|
145
|
-
return 0;
|
|
146
|
-
}
|
|
147
|
-
function processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, executionState, addEdge) {
|
|
148
|
-
let count = 0;
|
|
149
|
-
const pattern = binding.ref_field_pattern ? new RegExp(binding.ref_field_pattern) : null;
|
|
150
|
-
const entryPoints = binding.target_entry_points ?? [];
|
|
151
|
-
if (!pattern || entryPoints.length === 0) {
|
|
152
|
-
addAnomaly(executionState.diagnostics, `rule=${ruleId}: asset_ref_loads_components missing ref_field_pattern or target_entry_points`);
|
|
153
|
-
return 0;
|
|
154
|
-
}
|
|
155
|
-
const runtimeRootId = generateId('Method', 'unity-runtime-root');
|
|
156
|
-
for (const ref of assetGuidRefs) {
|
|
157
|
-
let fieldName = '';
|
|
158
|
-
try {
|
|
159
|
-
const parsed = JSON.parse(ref.reason);
|
|
160
|
-
fieldName = parsed.fieldName ?? '';
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (!pattern.test(fieldName))
|
|
166
|
-
continue;
|
|
167
|
-
const targetMethods = findMethodsOnResource(ref.targetId, executionState, entryPoints);
|
|
168
|
-
for (const method of targetMethods) {
|
|
169
|
-
if (addEdge(runtimeRootId, method.id, `unity-rule-resource-load:${ruleId}`))
|
|
170
|
-
count++;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return count;
|
|
174
|
-
}
|
|
175
|
-
function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, executionState, addEdge) {
|
|
176
|
-
let count = 0;
|
|
177
|
-
const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
|
|
178
|
-
const loaderMethodNames = new Set(binding.loader_methods ?? []);
|
|
179
|
-
const entryPoints = binding.target_entry_points ?? [];
|
|
180
|
-
const defaultEntryPoints = ['OnEnable', 'Awake', 'Start'];
|
|
181
|
-
const resolvedEntryPoints = entryPoints.length > 0 ? entryPoints : defaultEntryPoints;
|
|
182
|
-
if (!classPattern || loaderMethodNames.size === 0) {
|
|
183
|
-
addAnomaly(executionState.diagnostics, `rule=${ruleId}: method_triggers_field_load missing host_class_pattern or loader_methods`);
|
|
184
|
-
return 0;
|
|
185
|
-
}
|
|
186
|
-
// Build asset ref index by source file
|
|
187
|
-
const refsBySource = new Map();
|
|
188
|
-
for (const ref of assetGuidRefs) {
|
|
189
|
-
const list = refsBySource.get(ref.sourceId) ?? [];
|
|
190
|
-
list.push(ref);
|
|
191
|
-
refsBySource.set(ref.sourceId, list);
|
|
192
|
-
}
|
|
193
|
-
for (const cls of containerNodes) {
|
|
194
|
-
if (!classPattern.test(cls.properties.name))
|
|
195
|
-
continue;
|
|
196
|
-
const methods = methodsByClassId.get(cls.id) ?? [];
|
|
197
|
-
const loaders = methods.filter(m => loaderMethodNames.has(m.properties.name));
|
|
198
|
-
if (loaders.length === 0)
|
|
199
|
-
continue;
|
|
200
|
-
// Find resource files this class is mounted on
|
|
201
|
-
const resourceFileIds = new Set();
|
|
202
|
-
for (const ci of componentInstances) {
|
|
203
|
-
if (ci.sourceId === cls.id)
|
|
204
|
-
resourceFileIds.add(ci.targetId);
|
|
205
|
-
}
|
|
206
|
-
// Follow asset refs from those resource files
|
|
207
|
-
for (const resourceFileId of resourceFileIds) {
|
|
208
|
-
for (const ref of refsBySource.get(resourceFileId) ?? []) {
|
|
209
|
-
const targetMethods = findMethodsOnResource(ref.targetId, executionState, resolvedEntryPoints);
|
|
210
|
-
for (const loader of loaders) {
|
|
211
|
-
for (const target of targetMethods) {
|
|
212
|
-
if (addEdge(loader.id, target.id, `unity-rule-loader-bridge:${ruleId}`))
|
|
213
|
-
count++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return count;
|
|
220
|
-
}
|
|
221
|
-
function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, prefabSourceTargetsBySource, executionState, addEdge) {
|
|
222
|
-
const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
|
|
223
|
-
const loaderMethodNames = new Set(binding.loader_methods ?? []);
|
|
224
|
-
const sceneName = binding.scene_name;
|
|
225
|
-
const defaultEntryPoints = ['OnEnable', 'Awake', 'Start'];
|
|
226
|
-
const entryPoints = (binding.target_entry_points ?? []).length > 0
|
|
227
|
-
? binding.target_entry_points
|
|
228
|
-
: defaultEntryPoints;
|
|
229
|
-
if (!classPattern || loaderMethodNames.size === 0 || !sceneName) {
|
|
230
|
-
addAnomaly(executionState.diagnostics, `rule=${ruleId}: method_triggers_scene_load missing host_class_pattern, loader_methods, or scene_name`);
|
|
231
|
-
return 0;
|
|
232
|
-
}
|
|
233
|
-
const sceneFileIds = sceneFilesByName.get(sceneName.toLowerCase()) ?? [];
|
|
234
|
-
if (sceneFileIds.length === 0) {
|
|
235
|
-
addAnomaly(executionState.diagnostics, `rule=${ruleId}: scene "${sceneName}" not found in File(.unity) index`);
|
|
236
|
-
return 0;
|
|
237
|
-
}
|
|
238
|
-
let count = 0;
|
|
239
|
-
for (const cls of containerNodes) {
|
|
240
|
-
if (!classPattern.test(cls.properties.name))
|
|
241
|
-
continue;
|
|
242
|
-
const methods = methodsByClassId.get(cls.id) ?? [];
|
|
243
|
-
const loaders = methods.filter(m => loaderMethodNames.has(m.properties.name));
|
|
244
|
-
if (loaders.length === 0)
|
|
245
|
-
continue;
|
|
246
|
-
for (const sceneFileId of sceneFileIds) {
|
|
247
|
-
const runtimeResourceIds = getSceneRuntimeResourceIds(sceneFileId, prefabSourceTargetsBySource, executionState);
|
|
248
|
-
for (const runtimeResourceId of runtimeResourceIds) {
|
|
249
|
-
const targetMethods = findMethodsOnResource(runtimeResourceId, executionState, entryPoints);
|
|
250
|
-
for (const loader of loaders) {
|
|
251
|
-
for (const target of targetMethods) {
|
|
252
|
-
if (addEdge(loader.id, target.id, `unity-rule-scene-load:${ruleId}`))
|
|
253
|
-
count++;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return count;
|
|
260
|
-
}
|
|
261
|
-
function buildMethodsByResourceId(componentInstances, methodsByClassId) {
|
|
262
|
-
const methodsByResourceId = new Map();
|
|
263
|
-
for (const componentRef of componentInstances) {
|
|
264
|
-
const methods = methodsByClassId.get(componentRef.sourceId) ?? [];
|
|
265
|
-
if (methods.length === 0)
|
|
266
|
-
continue;
|
|
267
|
-
const list = methodsByResourceId.get(componentRef.targetId) ?? [];
|
|
268
|
-
list.push(...methods);
|
|
269
|
-
methodsByResourceId.set(componentRef.targetId, list);
|
|
270
|
-
}
|
|
271
|
-
return methodsByResourceId;
|
|
272
|
-
}
|
|
273
|
-
function buildPrefabSourceTargetsBySource(assetGuidRefs) {
|
|
274
|
-
const targetsBySource = new Map();
|
|
275
|
-
for (const ref of assetGuidRefs) {
|
|
276
|
-
let fieldName = '';
|
|
277
|
-
try {
|
|
278
|
-
const parsed = JSON.parse(String(ref.reason || '{}'));
|
|
279
|
-
fieldName = String(parsed?.fieldName || '');
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (fieldName !== 'm_SourcePrefab')
|
|
285
|
-
continue;
|
|
286
|
-
const targets = targetsBySource.get(ref.sourceId) ?? new Set();
|
|
287
|
-
targets.add(ref.targetId);
|
|
288
|
-
targetsBySource.set(ref.sourceId, targets);
|
|
289
|
-
}
|
|
290
|
-
const out = new Map();
|
|
291
|
-
for (const [sourceId, targets] of targetsBySource.entries()) {
|
|
292
|
-
out.set(sourceId, [...targets]);
|
|
293
|
-
}
|
|
294
|
-
return out;
|
|
295
|
-
}
|
|
296
|
-
function collectSceneRuntimeResourceIds(sceneFileId, prefabSourceTargetsBySource) {
|
|
297
|
-
const visited = new Set([sceneFileId]);
|
|
298
|
-
const queue = [sceneFileId];
|
|
299
|
-
while (queue.length > 0) {
|
|
300
|
-
const currentId = queue.shift();
|
|
301
|
-
for (const targetId of prefabSourceTargetsBySource.get(currentId) ?? []) {
|
|
302
|
-
if (visited.has(targetId))
|
|
303
|
-
continue;
|
|
304
|
-
visited.add(targetId);
|
|
305
|
-
queue.push(targetId);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return [...visited];
|
|
309
|
-
}
|
|
310
|
-
function getSceneRuntimeResourceIds(sceneFileId, prefabSourceTargetsBySource, executionState) {
|
|
311
|
-
executionState.diagnostics.sceneRuntimeTraversalCalls += 1;
|
|
312
|
-
const cached = executionState.sceneRuntimeResourceIdsCache.get(sceneFileId);
|
|
313
|
-
if (cached) {
|
|
314
|
-
executionState.diagnostics.sceneRuntimeTraversalCacheHits += 1;
|
|
315
|
-
return cached;
|
|
316
|
-
}
|
|
317
|
-
const ids = collectSceneRuntimeResourceIds(sceneFileId, prefabSourceTargetsBySource);
|
|
318
|
-
executionState.sceneRuntimeResourceIdsCache.set(sceneFileId, ids);
|
|
319
|
-
executionState.diagnostics.sceneRuntimeResourcesVisited += ids.length;
|
|
320
|
-
return ids;
|
|
321
|
-
}
|
|
322
|
-
function processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge) {
|
|
323
|
-
const { source_class_pattern, source_method, target_class_pattern, target_method } = binding;
|
|
324
|
-
if (!source_class_pattern || !source_method || !target_class_pattern || !target_method)
|
|
325
|
-
return 0;
|
|
326
|
-
const srcPattern = new RegExp(source_class_pattern);
|
|
327
|
-
const tgtPattern = new RegExp(target_class_pattern);
|
|
328
|
-
let sourceMethodId;
|
|
329
|
-
let targetMethodId;
|
|
330
|
-
for (const cls of containerNodes) {
|
|
331
|
-
if (!sourceMethodId && srcPattern.test(cls.properties.name)) {
|
|
332
|
-
const match = (methodsByClassId.get(cls.id) ?? []).find(m => m.properties.name === source_method);
|
|
333
|
-
if (match)
|
|
334
|
-
sourceMethodId = match.id;
|
|
335
|
-
}
|
|
336
|
-
if (!targetMethodId && tgtPattern.test(cls.properties.name)) {
|
|
337
|
-
const match = (methodsByClassId.get(cls.id) ?? []).find(m => m.properties.name === target_method);
|
|
338
|
-
if (match)
|
|
339
|
-
targetMethodId = match.id;
|
|
340
|
-
}
|
|
341
|
-
if (sourceMethodId && targetMethodId)
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
if (!sourceMethodId || !targetMethodId)
|
|
345
|
-
return 0;
|
|
346
|
-
return addEdge(sourceMethodId, targetMethodId, `unity-rule-method-bridge:${ruleId}`) ? 1 : 0;
|
|
347
|
-
}
|
|
348
|
-
function processLifecycleOverrides(rule, methodsByClassId, containerNodes, addEdge) {
|
|
349
|
-
const overrides = rule.lifecycle_overrides;
|
|
350
|
-
if (!overrides?.additional_entry_points?.length)
|
|
351
|
-
return 0;
|
|
352
|
-
const entrySet = new Set(overrides.additional_entry_points);
|
|
353
|
-
const scopePattern = overrides.scope ? new RegExp(overrides.scope) : null;
|
|
354
|
-
let count = 0;
|
|
355
|
-
const runtimeRootId = generateId('Method', 'unity-runtime-root');
|
|
356
|
-
for (const cls of containerNodes) {
|
|
357
|
-
if (scopePattern && !scopePattern.test(cls.properties.filePath ?? cls.properties.name))
|
|
358
|
-
continue;
|
|
359
|
-
for (const method of methodsByClassId.get(cls.id) ?? []) {
|
|
360
|
-
if (!entrySet.has(method.properties.name))
|
|
361
|
-
continue;
|
|
362
|
-
if (addEdge(runtimeRootId, method.id, `unity-rule-lifecycle-override:${rule.id}`))
|
|
363
|
-
count++;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return count;
|
|
367
|
-
}
|
|
368
|
-
function addAnomaly(diagnostics, message) {
|
|
369
|
-
diagnostics.anomalySet.add(message);
|
|
370
|
-
}
|
|
371
|
-
function finalizeRuleBindingDiagnostics(diagnostics, edgesInjected) {
|
|
372
|
-
const anomalies = [...diagnostics.anomalySet];
|
|
373
|
-
const shouldAgentReport = anomalies.length > 0;
|
|
374
|
-
const bindingsByKind = Object.fromEntries(Object.entries(diagnostics.bindingsByKind).sort(([a], [b]) => a.localeCompare(b)));
|
|
375
|
-
const kindSummary = Object.entries(bindingsByKind)
|
|
376
|
-
.map(([kind, count]) => `${kind}=${count}`)
|
|
377
|
-
.join(', ') || 'none';
|
|
378
|
-
const summary = [
|
|
379
|
-
`rule_binding.summary: rules=${diagnostics.rulesEvaluated}, bindings=${diagnostics.bindingsEvaluated}, edges=${edgesInjected}`,
|
|
380
|
-
`rule_binding.bindings_by_kind: ${kindSummary}`,
|
|
381
|
-
`rule_binding.lookup: method_calls=${diagnostics.methodLookupCalls}, cache_hits=${diagnostics.methodLookupCacheHits}`,
|
|
382
|
-
`rule_binding.scene_closure: traversals=${diagnostics.sceneRuntimeTraversalCalls}, cache_hits=${diagnostics.sceneRuntimeTraversalCacheHits}, visited_resources=${diagnostics.sceneRuntimeResourcesVisited}`,
|
|
383
|
-
`rule_binding.agent_report: should_report=${shouldAgentReport} reason="${shouldAgentReport ? 'rule-binding anomalies detected' : 'no anomalies detected'}"`,
|
|
384
|
-
];
|
|
385
|
-
if (anomalies.length > 0) {
|
|
386
|
-
summary.push(`rule_binding.anomalies: count=${anomalies.length}`);
|
|
387
|
-
for (const anomaly of anomalies.slice(0, RULE_ANOMALY_PREVIEW_LIMIT)) {
|
|
388
|
-
summary.push(`rule_binding.anomaly: ${anomaly}`);
|
|
389
|
-
}
|
|
390
|
-
if (anomalies.length > RULE_ANOMALY_PREVIEW_LIMIT) {
|
|
391
|
-
summary.push(`rule_binding.anomaly: ... ${anomalies.length - RULE_ANOMALY_PREVIEW_LIMIT} more`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
rulesEvaluated: diagnostics.rulesEvaluated,
|
|
396
|
-
bindingsEvaluated: diagnostics.bindingsEvaluated,
|
|
397
|
-
bindingsByKind,
|
|
398
|
-
methodLookupCalls: diagnostics.methodLookupCalls,
|
|
399
|
-
methodLookupCacheHits: diagnostics.methodLookupCacheHits,
|
|
400
|
-
sceneRuntimeTraversalCalls: diagnostics.sceneRuntimeTraversalCalls,
|
|
401
|
-
sceneRuntimeTraversalCacheHits: diagnostics.sceneRuntimeTraversalCacheHits,
|
|
402
|
-
sceneRuntimeResourcesVisited: diagnostics.sceneRuntimeResourcesVisited,
|
|
403
|
-
anomalies,
|
|
404
|
-
shouldAgentReport,
|
|
405
|
-
agentReportReason: shouldAgentReport ? 'rule-binding anomalies detected' : 'no anomalies detected',
|
|
406
|
-
summary,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getRuleLabPaths } from './paths.js';
|
|
2
|
-
import type { RuleLabCandidate, RuleLabSlice } from './types.js';
|
|
3
|
-
export interface AnalyzeInput {
|
|
4
|
-
repoPath: string;
|
|
5
|
-
runId: string;
|
|
6
|
-
sliceId: string;
|
|
7
|
-
}
|
|
8
|
-
export interface AnalyzeOutput {
|
|
9
|
-
paths: ReturnType<typeof getRuleLabPaths>;
|
|
10
|
-
candidates: RuleLabCandidate[];
|
|
11
|
-
slice: RuleLabSlice;
|
|
12
|
-
}
|
|
13
|
-
export declare function analyzeRuleLabSlice(input: AnalyzeInput): Promise<AnalyzeOutput>;
|
package/dist/rule-lab/analyze.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import { getRuleLabPaths } from './paths.js';
|
|
5
|
-
import { buildCurationInput } from './curation-input-builder.js';
|
|
6
|
-
function buildCandidateId(slice, variant) {
|
|
7
|
-
return createHash('sha1')
|
|
8
|
-
.update(`${slice.id}:${slice.trigger_family}:${slice.resource_types.join('|')}:${slice.host_base_type.join('|')}:${variant}`)
|
|
9
|
-
.digest('hex')
|
|
10
|
-
.slice(0, 12);
|
|
11
|
-
}
|
|
12
|
-
function normalizeToken(value) {
|
|
13
|
-
return String(value || '')
|
|
14
|
-
.trim()
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
17
|
-
.replace(/^-+|-+$/g, '');
|
|
18
|
-
}
|
|
19
|
-
function inferRuleStem(slice, pair) {
|
|
20
|
-
const source = normalizeToken(pair.source_anchor.symbol || pair.source_anchor.file || `${slice.id}-source`);
|
|
21
|
-
const target = normalizeToken(pair.target_anchor.symbol || pair.target_anchor.file || `${slice.id}-target`);
|
|
22
|
-
const joined = [source, target].filter(Boolean).join('-');
|
|
23
|
-
return joined || normalizeToken(slice.id) || 'runtime-rule';
|
|
24
|
-
}
|
|
25
|
-
function buildProposalTopology(slice) {
|
|
26
|
-
const requiredHops = Array.isArray(slice.required_hops) && slice.required_hops.length > 0
|
|
27
|
-
? slice.required_hops
|
|
28
|
-
: ['resource', 'code_runtime'];
|
|
29
|
-
return requiredHops.map((hop) => ({
|
|
30
|
-
hop,
|
|
31
|
-
from: { entity: hop === 'resource' ? 'resource' : 'script' },
|
|
32
|
-
to: { entity: hop === 'code_runtime' ? 'runtime' : 'script' },
|
|
33
|
-
edge: { kind: hop === 'resource' ? 'binds_script' : 'calls' },
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
function buildExactPairCandidates(slice) {
|
|
37
|
-
const pairs = Array.isArray(slice.exact_pairs) ? slice.exact_pairs : [];
|
|
38
|
-
if (pairs.length === 0) {
|
|
39
|
-
throw new Error('exact_pairs must be non-empty for reduced rule-lab analyze flow');
|
|
40
|
-
}
|
|
41
|
-
const seenPairIds = new Set();
|
|
42
|
-
for (const pair of pairs) {
|
|
43
|
-
const pairId = String(pair.id || '').trim();
|
|
44
|
-
if (!pairId)
|
|
45
|
-
continue;
|
|
46
|
-
if (seenPairIds.has(pairId)) {
|
|
47
|
-
throw new Error(`duplicate_exact_pair_id: ${pairId}`);
|
|
48
|
-
}
|
|
49
|
-
seenPairIds.add(pairId);
|
|
50
|
-
}
|
|
51
|
-
const topology = buildProposalTopology(slice);
|
|
52
|
-
return pairs.map((pair, index) => {
|
|
53
|
-
const pairKey = String(pair.id || `${index + 1}`).trim();
|
|
54
|
-
const sourceAnchor = `${String(pair.source_anchor.file || '').trim()}:${Number(pair.source_anchor.line || 1)}`;
|
|
55
|
-
const targetAnchor = `${String(pair.target_anchor.file || '').trim()}:${Number(pair.target_anchor.line || 1)}`;
|
|
56
|
-
const draftRuleId = String(pair.draft_rule_id || '').trim() || `unity.event.${inferRuleStem(slice, pair)}.v1`;
|
|
57
|
-
const bindingKind = pair.binding_kind || 'method_triggers_method';
|
|
58
|
-
return {
|
|
59
|
-
id: buildCandidateId(slice, `exact:${pairKey}`),
|
|
60
|
-
title: `${slice.trigger_family} exact pair ${pairKey}`,
|
|
61
|
-
rule_hint: `${slice.trigger_family}.${slice.id}.exact.${pairKey}`,
|
|
62
|
-
proposal_kind: 'per_anchor_rule',
|
|
63
|
-
aggregation_mode: 'per_anchor_rules',
|
|
64
|
-
binding_kind: bindingKind,
|
|
65
|
-
draft_rule_id: draftRuleId,
|
|
66
|
-
topology,
|
|
67
|
-
closure: {
|
|
68
|
-
required_hops: topology.map((hop) => hop.hop),
|
|
69
|
-
failure_map: {
|
|
70
|
-
missing_evidence: 'rule_matched_but_evidence_missing',
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
claims: {
|
|
74
|
-
guarantees: [`exact pair linked: ${sourceAnchor} -> ${targetAnchor}`],
|
|
75
|
-
non_guarantees: ['sparse gap path only; no exhaustive discovery semantics'],
|
|
76
|
-
next_action: `gitnexus query "${slice.trigger_family}"`,
|
|
77
|
-
},
|
|
78
|
-
exact_pair: pair,
|
|
79
|
-
evidence: {
|
|
80
|
-
hops: [
|
|
81
|
-
{
|
|
82
|
-
hop_type: 'code_runtime',
|
|
83
|
-
anchor: sourceAnchor,
|
|
84
|
-
snippet: String(pair.source_anchor.symbol || 'source'),
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
hop_type: 'code_runtime',
|
|
88
|
-
anchor: targetAnchor,
|
|
89
|
-
snippet: String(pair.target_anchor.symbol || 'target'),
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
function assertNoPlaceholderIds(runId, sliceId) {
|
|
97
|
-
const placeholderRe = /<[^>]+>|placeholder|todo|tbd/i;
|
|
98
|
-
if (placeholderRe.test(runId) || placeholderRe.test(sliceId)) {
|
|
99
|
-
throw new Error('placeholder run/slice ids are not allowed');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
export async function analyzeRuleLabSlice(input) {
|
|
103
|
-
assertNoPlaceholderIds(input.runId, input.sliceId);
|
|
104
|
-
const normalizedRepoPath = path.resolve(input.repoPath);
|
|
105
|
-
const paths = getRuleLabPaths(normalizedRepoPath, input.runId, input.sliceId);
|
|
106
|
-
const slicePath = path.join(paths.slicesRoot, input.sliceId, 'slice.json');
|
|
107
|
-
const raw = await fs.readFile(slicePath, 'utf-8');
|
|
108
|
-
const slice = JSON.parse(raw);
|
|
109
|
-
const candidates = buildExactPairCandidates(slice);
|
|
110
|
-
const curation = buildCurationInput({
|
|
111
|
-
runId: input.runId,
|
|
112
|
-
sliceId: input.sliceId,
|
|
113
|
-
slice,
|
|
114
|
-
candidates,
|
|
115
|
-
});
|
|
116
|
-
await fs.mkdir(path.dirname(paths.candidatesPath), { recursive: true });
|
|
117
|
-
await fs.writeFile(paths.candidatesPath, `${candidates.map((candidate) => JSON.stringify(candidate)).join('\n')}\n`, 'utf-8');
|
|
118
|
-
await fs.writeFile(slicePath, `${JSON.stringify(slice, null, 2)}\n`, 'utf-8');
|
|
119
|
-
await fs.writeFile(path.join(path.dirname(paths.candidatesPath), 'curation-input.json'), `${JSON.stringify(curation, null, 2)}\n`, 'utf-8');
|
|
120
|
-
return {
|
|
121
|
-
paths,
|
|
122
|
-
candidates,
|
|
123
|
-
slice,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|