@veewo/gitnexus 1.5.7 → 1.5.9

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.
Files changed (77) hide show
  1. package/dist/cli/ai-context.js +1 -7
  2. package/dist/cli/analyze-options.d.ts +4 -0
  3. package/dist/cli/analyze-options.js +14 -1
  4. package/dist/cli/analyze-options.test.js +23 -0
  5. package/dist/cli/analyze-runtime-summary.js +0 -1
  6. package/dist/cli/analyze-runtime-summary.test.js +0 -2
  7. package/dist/cli/analyze-summary.d.ts +0 -2
  8. package/dist/cli/analyze-summary.js +0 -24
  9. package/dist/cli/analyze-summary.test.js +1 -65
  10. package/dist/cli/analyze.d.ts +1 -0
  11. package/dist/cli/analyze.js +26 -18
  12. package/dist/cli/clean.js +23 -2
  13. package/dist/cli/index.js +3 -3
  14. package/dist/cli/repo-manager-alias.test.js +2 -0
  15. package/dist/core/ingestion/pipeline.js +0 -43
  16. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  17. package/dist/core/ingestion/tree-sitter-queries.js +3 -3
  18. package/dist/mcp/local/agent-safe-response.js +1 -1
  19. package/dist/mcp/local/local-backend.d.ts +0 -23
  20. package/dist/mcp/local/local-backend.js +69 -248
  21. package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
  22. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
  23. package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
  24. package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
  25. package/dist/mcp/tools.js +0 -70
  26. package/dist/storage/repo-manager.d.ts +1 -0
  27. package/dist/types/pipeline.d.ts +0 -3
  28. package/package.json +4 -4
  29. package/skills/gitnexus-cli.md +5 -2
  30. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
  31. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
  32. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
  33. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
  34. package/dist/cli/rule-lab.d.ts +0 -38
  35. package/dist/cli/rule-lab.js +0 -148
  36. package/dist/cli/rule-lab.test.d.ts +0 -1
  37. package/dist/cli/rule-lab.test.js +0 -31
  38. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
  39. package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
  40. package/dist/rule-lab/analyze.d.ts +0 -13
  41. package/dist/rule-lab/analyze.js +0 -125
  42. package/dist/rule-lab/analyze.test.d.ts +0 -1
  43. package/dist/rule-lab/analyze.test.js +0 -246
  44. package/dist/rule-lab/compile.d.ts +0 -5
  45. package/dist/rule-lab/compile.js +0 -51
  46. package/dist/rule-lab/compiled-bundles.d.ts +0 -30
  47. package/dist/rule-lab/compiled-bundles.js +0 -36
  48. package/dist/rule-lab/curate.d.ts +0 -33
  49. package/dist/rule-lab/curate.js +0 -155
  50. package/dist/rule-lab/curate.test.d.ts +0 -1
  51. package/dist/rule-lab/curate.test.js +0 -137
  52. package/dist/rule-lab/curation-input-builder.d.ts +0 -45
  53. package/dist/rule-lab/curation-input-builder.js +0 -133
  54. package/dist/rule-lab/discover.d.ts +0 -13
  55. package/dist/rule-lab/discover.js +0 -74
  56. package/dist/rule-lab/discover.test.d.ts +0 -1
  57. package/dist/rule-lab/discover.test.js +0 -42
  58. package/dist/rule-lab/paths.d.ts +0 -21
  59. package/dist/rule-lab/paths.js +0 -37
  60. package/dist/rule-lab/paths.test.d.ts +0 -1
  61. package/dist/rule-lab/paths.test.js +0 -46
  62. package/dist/rule-lab/promote.d.ts +0 -26
  63. package/dist/rule-lab/promote.js +0 -387
  64. package/dist/rule-lab/promote.test.d.ts +0 -1
  65. package/dist/rule-lab/promote.test.js +0 -314
  66. package/dist/rule-lab/regress.d.ts +0 -60
  67. package/dist/rule-lab/regress.js +0 -122
  68. package/dist/rule-lab/regress.test.d.ts +0 -1
  69. package/dist/rule-lab/regress.test.js +0 -68
  70. package/dist/rule-lab/review-pack.d.ts +0 -34
  71. package/dist/rule-lab/review-pack.js +0 -165
  72. package/dist/rule-lab/review-pack.test.d.ts +0 -1
  73. package/dist/rule-lab/review-pack.test.js +0 -116
  74. package/dist/rule-lab/types.d.ts +0 -135
  75. package/dist/rule-lab/types.js +0 -1
  76. package/skills/_shared/unity-rule-authoring-contract.md +0 -64
  77. package/skills/gitnexus-unity-rule-gen.md +0 -107
@@ -1,148 +0,0 @@
1
- import { writeSync } from 'node:fs';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { analyzeRuleLabSlice } from '../rule-lab/analyze.js';
5
- import { buildReviewPack } from '../rule-lab/review-pack.js';
6
- import { curateRuleLabSlice } from '../rule-lab/curate.js';
7
- import { promoteCuratedRules } from '../rule-lab/promote.js';
8
- import { runRuleLabRegress } from '../rule-lab/regress.js';
9
- import { compileRules } from '../rule-lab/compile.js';
10
- const RULE_LAB_COMMANDS = ['analyze', 'review-pack', 'curate', 'promote', 'regress'];
11
- function output(data) {
12
- const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
13
- writeSync(1, `${text}\n`);
14
- }
15
- function resolveRepoPath(repoPath) {
16
- return path.resolve(repoPath || process.cwd());
17
- }
18
- function assertConcreteRunSliceIds(runId, sliceId) {
19
- if (/[<>]/.test(runId) || /[<>]/.test(sliceId)) {
20
- throw new Error('Invalid run/slice id: placeholder values are not allowed');
21
- }
22
- }
23
- export function getRuleLabCommandNames(program) {
24
- const root = program.commands.find((command) => command.name() === 'rule-lab');
25
- if (!root)
26
- return [];
27
- return root.commands.map((command) => command.name());
28
- }
29
- export function attachRuleLabCommands(program, lazyFactory) {
30
- const action = (handlerName) => {
31
- if (lazyFactory)
32
- return lazyFactory(handlerName);
33
- switch (handlerName) {
34
- case 'ruleLabAnalyzeCommand':
35
- return (options) => ruleLabAnalyzeCommand(options);
36
- case 'ruleLabReviewPackCommand':
37
- return (options) => ruleLabReviewPackCommand(options);
38
- case 'ruleLabCurateCommand':
39
- return (options) => ruleLabCurateCommand(options);
40
- case 'ruleLabPromoteCommand':
41
- return (options) => ruleLabPromoteCommand(options);
42
- case 'ruleLabRegressCommand':
43
- return (options) => ruleLabRegressCommand(options);
44
- default:
45
- return () => {
46
- throw new Error(`Unknown rule lab handler: ${handlerName}`);
47
- };
48
- }
49
- };
50
- const root = program
51
- .command('rule-lab')
52
- .description('Offline rule-lab workflow for analyze/review-pack/curate/promote/regress');
53
- root
54
- .command('analyze')
55
- .requiredOption('--run-id <id>', 'Rule-lab run id')
56
- .requiredOption('--slice-id <id>', 'Slice id')
57
- .option('--repo-path <path>', 'Repository path (default: cwd)')
58
- .action(action('ruleLabAnalyzeCommand'));
59
- root
60
- .command('review-pack')
61
- .requiredOption('--run-id <id>', 'Rule-lab run id')
62
- .requiredOption('--slice-id <id>', 'Slice id')
63
- .option('--repo-path <path>', 'Repository path (default: cwd)')
64
- .option('--max-tokens <n>', 'Token budget', '6000')
65
- .action(action('ruleLabReviewPackCommand'));
66
- root
67
- .command('curate')
68
- .requiredOption('--run-id <id>', 'Rule-lab run id')
69
- .requiredOption('--slice-id <id>', 'Slice id')
70
- .requiredOption('--input-path <path>', 'Path to curation input JSON')
71
- .option('--repo-path <path>', 'Repository path (default: cwd)')
72
- .action(action('ruleLabCurateCommand'));
73
- root
74
- .command('promote')
75
- .requiredOption('--run-id <id>', 'Rule-lab run id')
76
- .requiredOption('--slice-id <id>', 'Slice id')
77
- .option('--repo-path <path>', 'Repository path (default: cwd)')
78
- .option('--rule-version <version>', 'Promoted rule version', '1.0.0')
79
- .action(action('ruleLabPromoteCommand'));
80
- root
81
- .command('regress')
82
- .requiredOption('--precision <n>', 'Precision metric')
83
- .requiredOption('--coverage <n>', 'Coverage metric')
84
- .option('--repo-path <path>', 'Repository path (default: cwd)')
85
- .option('--run-id <id>', 'Run id (if provided, write report to .gitnexus/rules/reports)')
86
- .option('--probes-path <path>', 'Optional JSON file containing regress probes')
87
- .action(action('ruleLabRegressCommand'));
88
- root
89
- .command('compile')
90
- .description('Compile approved YAML rules into a JSON bundle')
91
- .option('--repo-path <path>', 'Repository path (default: cwd)')
92
- .option('--family <family>', 'Rule family to compile', 'analyze_rules')
93
- .action((options) => compileRules({ repoPath: options.repoPath, family: options.family }));
94
- }
95
- export async function ruleLabAnalyzeCommand(options) {
96
- assertConcreteRunSliceIds(options.runId, options.sliceId);
97
- const repoPath = resolveRepoPath(options?.repoPath);
98
- const result = await analyzeRuleLabSlice({
99
- repoPath,
100
- runId: options.runId,
101
- sliceId: options.sliceId,
102
- });
103
- output(result);
104
- }
105
- export async function ruleLabReviewPackCommand(options) {
106
- const result = await buildReviewPack({
107
- repoPath: resolveRepoPath(options?.repoPath),
108
- runId: options.runId,
109
- sliceId: options.sliceId,
110
- maxTokens: Number(options.maxTokens || 6000),
111
- });
112
- output(result);
113
- }
114
- export async function ruleLabCurateCommand(options) {
115
- const result = await curateRuleLabSlice({
116
- repoPath: resolveRepoPath(options?.repoPath),
117
- runId: options.runId,
118
- sliceId: options.sliceId,
119
- inputPath: path.resolve(options.inputPath),
120
- });
121
- output(result);
122
- }
123
- export async function ruleLabPromoteCommand(options) {
124
- const version = options.ruleVersion ?? options.version;
125
- const result = await promoteCuratedRules({
126
- repoPath: resolveRepoPath(options?.repoPath),
127
- runId: options.runId,
128
- sliceId: options.sliceId,
129
- version,
130
- });
131
- output(result);
132
- }
133
- export async function ruleLabRegressCommand(options) {
134
- let probes;
135
- if (options.probesPath) {
136
- const raw = await fs.readFile(path.resolve(options.probesPath), 'utf-8');
137
- probes = JSON.parse(raw);
138
- }
139
- const result = await runRuleLabRegress({
140
- precision: Number(options.precision),
141
- coverage: Number(options.coverage),
142
- repoPath: options.repoPath ? resolveRepoPath(options.repoPath) : undefined,
143
- runId: options.runId,
144
- probes,
145
- });
146
- output(result);
147
- }
148
- export { RULE_LAB_COMMANDS };
@@ -1 +0,0 @@
1
- export {};
@@ -1,31 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { Command } from 'commander';
3
- import { attachRuleLabCommands, getRuleLabCommandNames, ruleLabAnalyzeCommand } from './rule-lab.js';
4
- describe('rule-lab cli', () => {
5
- it('registers all rule-lab subcommands', async () => {
6
- const program = new Command();
7
- program.version('test-version');
8
- attachRuleLabCommands(program);
9
- const cmds = getRuleLabCommandNames(program);
10
- expect(cmds).toEqual(['analyze', 'review-pack', 'curate', 'promote', 'regress', 'compile']);
11
- });
12
- it('uses --rule-version for promote to avoid root --version collision', () => {
13
- const program = new Command();
14
- program.version('test-version');
15
- attachRuleLabCommands(program);
16
- const ruleLab = program.commands.find((command) => command.name() === 'rule-lab');
17
- expect(ruleLab).toBeTruthy();
18
- const promote = ruleLab?.commands.find((command) => command.name() === 'promote');
19
- expect(promote).toBeTruthy();
20
- const optionNames = (promote?.options || []).map((option) => option.long);
21
- expect(optionNames).toContain('--rule-version');
22
- expect(optionNames).not.toContain('--version');
23
- });
24
- it('rejects placeholder run/slice ids at analyze command boundary', async () => {
25
- await expect(ruleLabAnalyzeCommand({
26
- repoPath: process.cwd(),
27
- runId: '<run_id>',
28
- sliceId: '<slice_id>',
29
- })).rejects.toThrow(/placeholder/i);
30
- });
31
- });
@@ -1,26 +0,0 @@
1
- import type { KnowledgeGraph } from '../graph/types.js';
2
- import type { RuntimeClaimRule } from '../../mcp/local/runtime-claim-rule-registry.js';
3
- import type { UnityConfig } from '../config/unity-config.js';
4
- export interface UnityRuntimeBindingResult {
5
- edgesInjected: number;
6
- ruleResults: Array<{
7
- ruleId: string;
8
- edgesInjected: number;
9
- }>;
10
- diagnostics: UnityRuntimeBindingDiagnostics;
11
- }
12
- export interface UnityRuntimeBindingDiagnostics {
13
- rulesEvaluated: number;
14
- bindingsEvaluated: number;
15
- bindingsByKind: Record<string, number>;
16
- methodLookupCalls: number;
17
- methodLookupCacheHits: number;
18
- sceneRuntimeTraversalCalls: number;
19
- sceneRuntimeTraversalCacheHits: number;
20
- sceneRuntimeResourcesVisited: number;
21
- anomalies: string[];
22
- shouldAgentReport: boolean;
23
- agentReportReason: string;
24
- summary: string[];
25
- }
26
- export declare function applyUnityRuntimeBindingRules(graph: KnowledgeGraph, rules: RuntimeClaimRule[], config: UnityConfig): UnityRuntimeBindingResult;
@@ -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>;