@veewo/gitnexus 1.5.0 → 1.5.1
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/agent-context/runner.js +3 -0
- package/dist/benchmark/agent-context/runner.test.js +22 -0
- package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
- package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
- package/dist/benchmark/agent-safe-query-context/io.js +86 -0
- package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
- package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
- package/dist/benchmark/agent-safe-query-context/report.js +159 -0
- package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
- package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
- package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
- package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
- package/dist/benchmark/agent-safe-query-context/types.js +8 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
- package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
- package/dist/benchmark/runtime-poc/runner.js +163 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
- package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
- package/dist/cli/ai-context.js +2 -12
- package/dist/cli/ai-context.test.js +8 -0
- package/dist/cli/analyze-runtime-summary.js +1 -0
- package/dist/cli/analyze-runtime-summary.test.js +2 -0
- package/dist/cli/analyze-summary.d.ts +2 -0
- package/dist/cli/analyze-summary.js +24 -0
- package/dist/cli/analyze-summary.test.js +65 -1
- package/dist/cli/analyze.js +5 -1
- package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
- package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
- package/dist/cli/benchmark.d.ts +29 -0
- package/dist/cli/benchmark.js +55 -0
- package/dist/cli/index.js +23 -0
- package/dist/cli/rule-lab.d.ts +3 -7
- package/dist/cli/rule-lab.js +13 -22
- package/dist/cli/rule-lab.test.js +23 -3
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +2 -0
- package/dist/core/config/unity-config.d.ts +0 -1
- package/dist/core/config/unity-config.js +0 -1
- package/dist/core/ingestion/pipeline.js +35 -6
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
- package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
- package/dist/core/ingestion/unity-parity-seed.js +8 -0
- package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
- package/dist/core/ingestion/unity-resource-processor.js +102 -0
- package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +15 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +178 -30
- package/dist/core/lbug/csv-generator.test.js +2 -2
- package/dist/core/unity/doc-contract.test.d.ts +1 -0
- package/dist/core/unity/doc-contract.test.js +30 -0
- package/dist/core/unity/prefab-source-scan.d.ts +25 -0
- package/dist/core/unity/prefab-source-scan.js +152 -0
- package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
- package/dist/core/unity/prefab-source-scan.test.js +70 -0
- package/dist/core/unity/scan-context.d.ts +12 -0
- package/dist/core/unity/scan-context.js +50 -2
- package/dist/core/unity/scan-context.test.js +74 -0
- package/dist/mcp/local/agent-safe-response.d.ts +10 -0
- package/dist/mcp/local/agent-safe-response.js +639 -0
- package/dist/mcp/local/derived-process-reader.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +18 -1
- package/dist/mcp/local/local-backend.js +319 -125
- package/dist/mcp/local/process-confidence.d.ts +1 -2
- package/dist/mcp/local/process-confidence.js +0 -3
- package/dist/mcp/local/process-confidence.test.js +4 -2
- package/dist/mcp/local/process-evidence.d.ts +1 -8
- package/dist/mcp/local/process-evidence.js +1 -23
- package/dist/mcp/local/process-evidence.test.js +2 -16
- package/dist/mcp/local/process-ref.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
- package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-verify.js +149 -138
- package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
- package/dist/mcp/local/runtime-claim.d.ts +11 -0
- package/dist/mcp/local/runtime-claim.js +28 -0
- package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
- package/dist/mcp/local/unity-evidence-view.js +1 -1
- package/dist/mcp/local/unity-evidence-view.test.js +22 -0
- package/dist/mcp/tools.js +51 -21
- package/dist/rule-lab/analyze.d.ts +2 -1
- package/dist/rule-lab/analyze.js +94 -59
- package/dist/rule-lab/analyze.test.js +238 -20
- package/dist/rule-lab/curate.d.ts +2 -1
- package/dist/rule-lab/curate.js +24 -3
- package/dist/rule-lab/curate.test.js +65 -0
- package/dist/rule-lab/curation-input-builder.d.ts +45 -0
- package/dist/rule-lab/curation-input-builder.js +133 -0
- package/dist/rule-lab/promote.js +80 -7
- package/dist/rule-lab/promote.test.js +150 -0
- package/dist/rule-lab/review-pack.d.ts +3 -0
- package/dist/rule-lab/review-pack.js +41 -1
- package/dist/rule-lab/review-pack.test.js +67 -0
- package/dist/rule-lab/types.d.ts +29 -0
- package/dist/types/pipeline.d.ts +3 -0
- package/package.json +3 -2
- package/scripts/run-node-tests.mjs +61 -0
- package/skills/_shared/unity-rule-authoring-contract.md +64 -0
- package/skills/_shared/unity-runtime-process-contract.md +16 -0
- package/skills/gitnexus-cli.md +8 -0
- package/skills/gitnexus-debugging.md +9 -0
- package/skills/gitnexus-exploring.md +66 -18
- package/skills/gitnexus-guide.md +42 -3
- package/skills/gitnexus-impact-analysis.md +8 -0
- package/skills/gitnexus-pr-review.md +8 -0
- package/skills/gitnexus-refactoring.md +8 -0
- package/skills/gitnexus-unity-rule-gen.md +66 -312
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeRuntimeProvenanceArtifact, } from '../benchmark/runtime-poc/provenance-artifact.js';
|
|
4
|
+
import { runRuntimePocBenchmark } from '../benchmark/runtime-poc/runner.js';
|
|
5
|
+
async function loadRuntimePocRecords(recordsPath) {
|
|
6
|
+
if (!recordsPath)
|
|
7
|
+
return [];
|
|
8
|
+
const raw = await fs.readFile(path.resolve(recordsPath), 'utf-8');
|
|
9
|
+
const parsed = JSON.parse(raw);
|
|
10
|
+
if (!Array.isArray(parsed)) {
|
|
11
|
+
throw new Error('runtime-poc records file must be a JSON array');
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
export async function benchmarkCommand(suite, options = {}) {
|
|
16
|
+
const normalizedSuite = String(suite || '').trim().toLowerCase();
|
|
17
|
+
if (normalizedSuite !== 'runtime-poc') {
|
|
18
|
+
throw new Error(`unsupported benchmark suite: ${suite}`);
|
|
19
|
+
}
|
|
20
|
+
const repo = String(options.repo || 'unknown-repo').trim();
|
|
21
|
+
const reportDir = path.resolve(options.reportDir || 'docs/reports/runtime-poc');
|
|
22
|
+
if (options.recordsPath) {
|
|
23
|
+
const records = await loadRuntimePocRecords(options.recordsPath);
|
|
24
|
+
const out = await writeRuntimeProvenanceArtifact({
|
|
25
|
+
reportDir,
|
|
26
|
+
repo,
|
|
27
|
+
records,
|
|
28
|
+
});
|
|
29
|
+
process.stdout.write(`runtime-poc provenance artifact written: ${out.artifactPath}\n`);
|
|
30
|
+
process.stdout.write(`runtime-poc provenance index updated: ${out.indexPath}\n`);
|
|
31
|
+
return {
|
|
32
|
+
artifactPath: out.artifactPath,
|
|
33
|
+
indexPath: out.indexPath,
|
|
34
|
+
sha256: out.sha256,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const run = await runRuntimePocBenchmark({
|
|
38
|
+
repo,
|
|
39
|
+
reportDir,
|
|
40
|
+
casesPath: options.casesPath,
|
|
41
|
+
});
|
|
42
|
+
process.stdout.write(`runtime-poc comparison report written: ${run.comparisonPath}\n`);
|
|
43
|
+
process.stdout.write(`runtime-poc markdown summary written: ${run.summaryPath}\n`);
|
|
44
|
+
process.stdout.write(`runtime-poc provenance artifact written: ${run.provenanceArtifactPath}\n`);
|
|
45
|
+
process.stdout.write(`runtime-poc provenance index updated: ${run.provenanceIndexPath}\n`);
|
|
46
|
+
return {
|
|
47
|
+
comparisonPath: run.comparisonPath,
|
|
48
|
+
summaryPath: run.summaryPath,
|
|
49
|
+
provenanceArtifactPath: run.provenanceArtifactPath,
|
|
50
|
+
provenanceIndexPath: run.provenanceIndexPath,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function benchmarkSuiteCommand(suite, options) {
|
|
54
|
+
return await benchmarkCommand(suite, options);
|
|
55
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -86,6 +86,7 @@ program
|
|
|
86
86
|
.option('-g, --goal <text>', 'What you want to find')
|
|
87
87
|
.option('-l, --limit <n>', 'Max processes to return (default: 5)')
|
|
88
88
|
.option('--content', 'Include full symbol source code')
|
|
89
|
+
.option('--response-profile <profile>', 'Response payload profile: slim|full', 'slim')
|
|
89
90
|
.option('--scope-preset <preset>', 'Scope preset for retrieval: unity-gameplay|unity-all')
|
|
90
91
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
91
92
|
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
@@ -101,6 +102,7 @@ program
|
|
|
101
102
|
.option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
|
|
102
103
|
.option('-f, --file <path>', 'File path to disambiguate common names')
|
|
103
104
|
.option('--content', 'Include full symbol source code')
|
|
105
|
+
.option('--response-profile <profile>', 'Response payload profile: slim|full', 'slim')
|
|
104
106
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
105
107
|
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
106
108
|
.option('--unity-evidence <mode>', 'Unity evidence payload mode: summary|focused|full', 'summary')
|
|
@@ -170,10 +172,31 @@ program
|
|
|
170
172
|
.option('--scope-prefix <pathPrefix>', 'Analyze scope path prefix (repeatable)', collectValues, [])
|
|
171
173
|
.option('--skip-analyze', 'Skip analyze stage and evaluate current index only')
|
|
172
174
|
.action(createLazyAction(() => import('./benchmark-agent-context.js'), 'benchmarkAgentContextCommand'));
|
|
175
|
+
program
|
|
176
|
+
.command('benchmark-agent-safe-query-context <dataset>')
|
|
177
|
+
.description('Run the agent-safe Unity query/context benchmark')
|
|
178
|
+
.option('-r, --repo <name>', 'Target indexed repo')
|
|
179
|
+
.option('--repo-alias <name>', 'Analyze-time repo alias and default evaluation repo when --repo is omitted')
|
|
180
|
+
.option('--target-path <path>', 'Path to analyze before evaluation (required unless --skip-analyze)')
|
|
181
|
+
.option('--subagent-runs-dir <path>', 'Directory containing session-generated subagent run artifacts')
|
|
182
|
+
.option('--report-dir <path>', 'Output directory for benchmark-report.json and benchmark-summary.md', '.gitnexus/benchmark-agent-safe-query-context')
|
|
183
|
+
.option('--extensions <list>', 'Analyze extension filter (comma-separated, optional)')
|
|
184
|
+
.option('--scope-manifest <path>', 'Analyze scope manifest file')
|
|
185
|
+
.option('--scope-prefix <pathPrefix>', 'Analyze scope path prefix (repeatable)', collectValues, [])
|
|
186
|
+
.option('--skip-analyze', 'Skip analyze stage and evaluate current index only')
|
|
187
|
+
.action(createLazyAction(() => import('./benchmark-agent-safe-query-context.js'), 'benchmarkAgentSafeQueryContextCommand'));
|
|
173
188
|
program
|
|
174
189
|
.command('benchmark-u2-e2e')
|
|
175
190
|
.description('Run fail-fast full neonspark U2 E2E benchmark and emit evidence reports')
|
|
176
191
|
.option('--config <path>', 'Path to E2E config JSON')
|
|
177
192
|
.option('--report-dir <path>', 'Output directory for reports')
|
|
178
193
|
.action(createLazyAction(() => import('./benchmark-u2-e2e.js'), 'benchmarkU2E2ECommand'));
|
|
194
|
+
program
|
|
195
|
+
.command('benchmark <suite>')
|
|
196
|
+
.description('Run benchmark suite (currently supports: runtime-poc)')
|
|
197
|
+
.option('-r, --repo <name>', 'Target indexed repo')
|
|
198
|
+
.option('--report-dir <path>', 'Output directory for runtime-poc reports', 'docs/reports/runtime-poc')
|
|
199
|
+
.option('--cases-path <path>', 'Optional JSON cases file for runtime-poc comparison run')
|
|
200
|
+
.option('--records-path <path>', 'Optional JSON records file to emit provenance artifact only')
|
|
201
|
+
.action(createLazyAction(() => import('./benchmark.js'), 'benchmarkSuiteCommand'));
|
|
179
202
|
program.parse(process.argv);
|
package/dist/cli/rule-lab.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
declare const RULE_LAB_COMMANDS: readonly ["
|
|
3
|
-
type RuleLabHandlerName = '
|
|
2
|
+
declare const RULE_LAB_COMMANDS: readonly ["analyze", "review-pack", "curate", "promote", "regress"];
|
|
3
|
+
type RuleLabHandlerName = 'ruleLabAnalyzeCommand' | 'ruleLabReviewPackCommand' | 'ruleLabCurateCommand' | 'ruleLabPromoteCommand' | 'ruleLabRegressCommand';
|
|
4
4
|
type LazyFactory = (handlerName: RuleLabHandlerName) => (...args: any[]) => void | Promise<void>;
|
|
5
5
|
export declare function getRuleLabCommandNames(program: Command): string[];
|
|
6
6
|
export declare function attachRuleLabCommands(program: Command, lazyFactory?: LazyFactory): void;
|
|
7
|
-
export declare function ruleLabDiscoverCommand(options: {
|
|
8
|
-
repoPath?: string;
|
|
9
|
-
scope?: 'full' | 'diff';
|
|
10
|
-
seed?: string;
|
|
11
|
-
}): Promise<void>;
|
|
12
7
|
export declare function ruleLabAnalyzeCommand(options: {
|
|
13
8
|
repoPath?: string;
|
|
14
9
|
runId: string;
|
|
@@ -30,6 +25,7 @@ export declare function ruleLabPromoteCommand(options: {
|
|
|
30
25
|
repoPath?: string;
|
|
31
26
|
runId: string;
|
|
32
27
|
sliceId: string;
|
|
28
|
+
ruleVersion?: string;
|
|
33
29
|
version?: string;
|
|
34
30
|
}): Promise<void>;
|
|
35
31
|
export declare function ruleLabRegressCommand(options: {
|
package/dist/cli/rule-lab.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { writeSync } from 'node:fs';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { discoverRuleLabRun } from '../rule-lab/discover.js';
|
|
5
4
|
import { analyzeRuleLabSlice } from '../rule-lab/analyze.js';
|
|
6
5
|
import { buildReviewPack } from '../rule-lab/review-pack.js';
|
|
7
6
|
import { curateRuleLabSlice } from '../rule-lab/curate.js';
|
|
8
7
|
import { promoteCuratedRules } from '../rule-lab/promote.js';
|
|
9
8
|
import { runRuleLabRegress } from '../rule-lab/regress.js';
|
|
10
9
|
import { compileRules } from '../rule-lab/compile.js';
|
|
11
|
-
const RULE_LAB_COMMANDS = ['
|
|
10
|
+
const RULE_LAB_COMMANDS = ['analyze', 'review-pack', 'curate', 'promote', 'regress'];
|
|
12
11
|
function output(data) {
|
|
13
12
|
const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
14
13
|
writeSync(1, `${text}\n`);
|
|
@@ -16,6 +15,11 @@ function output(data) {
|
|
|
16
15
|
function resolveRepoPath(repoPath) {
|
|
17
16
|
return path.resolve(repoPath || process.cwd());
|
|
18
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
|
+
}
|
|
19
23
|
export function getRuleLabCommandNames(program) {
|
|
20
24
|
const root = program.commands.find((command) => command.name() === 'rule-lab');
|
|
21
25
|
if (!root)
|
|
@@ -27,8 +31,6 @@ export function attachRuleLabCommands(program, lazyFactory) {
|
|
|
27
31
|
if (lazyFactory)
|
|
28
32
|
return lazyFactory(handlerName);
|
|
29
33
|
switch (handlerName) {
|
|
30
|
-
case 'ruleLabDiscoverCommand':
|
|
31
|
-
return (options) => ruleLabDiscoverCommand(options);
|
|
32
34
|
case 'ruleLabAnalyzeCommand':
|
|
33
35
|
return (options) => ruleLabAnalyzeCommand(options);
|
|
34
36
|
case 'ruleLabReviewPackCommand':
|
|
@@ -47,13 +49,7 @@ export function attachRuleLabCommands(program, lazyFactory) {
|
|
|
47
49
|
};
|
|
48
50
|
const root = program
|
|
49
51
|
.command('rule-lab')
|
|
50
|
-
.description('Offline rule-lab workflow for
|
|
51
|
-
root
|
|
52
|
-
.command('discover')
|
|
53
|
-
.option('--repo-path <path>', 'Repository path (default: cwd)')
|
|
54
|
-
.option('--scope <scope>', 'Discovery scope: full|diff', 'full')
|
|
55
|
-
.option('--seed <seed>', 'Optional deterministic seed')
|
|
56
|
-
.action(action('ruleLabDiscoverCommand'));
|
|
52
|
+
.description('Offline rule-lab workflow for analyze/review-pack/curate/promote/regress');
|
|
57
53
|
root
|
|
58
54
|
.command('analyze')
|
|
59
55
|
.requiredOption('--run-id <id>', 'Rule-lab run id')
|
|
@@ -79,7 +75,7 @@ export function attachRuleLabCommands(program, lazyFactory) {
|
|
|
79
75
|
.requiredOption('--run-id <id>', 'Rule-lab run id')
|
|
80
76
|
.requiredOption('--slice-id <id>', 'Slice id')
|
|
81
77
|
.option('--repo-path <path>', 'Repository path (default: cwd)')
|
|
82
|
-
.option('--version <version>', 'Promoted rule version', '1.0.0')
|
|
78
|
+
.option('--rule-version <version>', 'Promoted rule version', '1.0.0')
|
|
83
79
|
.action(action('ruleLabPromoteCommand'));
|
|
84
80
|
root
|
|
85
81
|
.command('regress')
|
|
@@ -96,17 +92,11 @@ export function attachRuleLabCommands(program, lazyFactory) {
|
|
|
96
92
|
.option('--family <family>', 'Rule family to compile', 'analyze_rules')
|
|
97
93
|
.action((options) => compileRules({ repoPath: options.repoPath, family: options.family }));
|
|
98
94
|
}
|
|
99
|
-
export async function ruleLabDiscoverCommand(options) {
|
|
100
|
-
const result = await discoverRuleLabRun({
|
|
101
|
-
repoPath: resolveRepoPath(options?.repoPath),
|
|
102
|
-
scope: options?.scope || 'full',
|
|
103
|
-
seed: options?.seed,
|
|
104
|
-
});
|
|
105
|
-
output(result);
|
|
106
|
-
}
|
|
107
95
|
export async function ruleLabAnalyzeCommand(options) {
|
|
96
|
+
assertConcreteRunSliceIds(options.runId, options.sliceId);
|
|
97
|
+
const repoPath = resolveRepoPath(options?.repoPath);
|
|
108
98
|
const result = await analyzeRuleLabSlice({
|
|
109
|
-
repoPath
|
|
99
|
+
repoPath,
|
|
110
100
|
runId: options.runId,
|
|
111
101
|
sliceId: options.sliceId,
|
|
112
102
|
});
|
|
@@ -131,11 +121,12 @@ export async function ruleLabCurateCommand(options) {
|
|
|
131
121
|
output(result);
|
|
132
122
|
}
|
|
133
123
|
export async function ruleLabPromoteCommand(options) {
|
|
124
|
+
const version = options.ruleVersion ?? options.version;
|
|
134
125
|
const result = await promoteCuratedRules({
|
|
135
126
|
repoPath: resolveRepoPath(options?.repoPath),
|
|
136
127
|
runId: options.runId,
|
|
137
128
|
sliceId: options.sliceId,
|
|
138
|
-
version
|
|
129
|
+
version,
|
|
139
130
|
});
|
|
140
131
|
output(result);
|
|
141
132
|
}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { attachRuleLabCommands, getRuleLabCommandNames } from './rule-lab.js';
|
|
3
|
+
import { attachRuleLabCommands, getRuleLabCommandNames, ruleLabAnalyzeCommand } from './rule-lab.js';
|
|
4
4
|
describe('rule-lab cli', () => {
|
|
5
|
-
it('registers all
|
|
5
|
+
it('registers all rule-lab subcommands', async () => {
|
|
6
6
|
const program = new Command();
|
|
7
|
+
program.version('test-version');
|
|
7
8
|
attachRuleLabCommands(program);
|
|
8
9
|
const cmds = getRuleLabCommandNames(program);
|
|
9
|
-
expect(cmds).toEqual(['
|
|
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);
|
|
10
30
|
});
|
|
11
31
|
});
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare function queryCommand(queryText: string, options?: {
|
|
|
21
21
|
goal?: string;
|
|
22
22
|
limit?: string;
|
|
23
23
|
content?: boolean;
|
|
24
|
+
responseProfile?: 'slim' | 'full';
|
|
24
25
|
scopePreset?: 'unity-gameplay' | 'unity-all';
|
|
25
26
|
unityResources?: UnityResourcesMode;
|
|
26
27
|
unityHydration?: UnityHydrationMode;
|
|
@@ -34,6 +35,7 @@ export declare function contextCommand(name: string, options?: {
|
|
|
34
35
|
file?: string;
|
|
35
36
|
uid?: string;
|
|
36
37
|
content?: boolean;
|
|
38
|
+
responseProfile?: 'slim' | 'full';
|
|
37
39
|
unityResources?: UnityResourcesMode;
|
|
38
40
|
unityHydration?: UnityHydrationMode;
|
|
39
41
|
unityEvidence?: UnityEvidenceMode;
|
package/dist/cli/tool.js
CHANGED
|
@@ -91,6 +91,7 @@ export async function queryCommand(queryText, options) {
|
|
|
91
91
|
goal: options?.goal,
|
|
92
92
|
limit: options?.limit ? parseInt(options.limit) : undefined,
|
|
93
93
|
include_content: options?.content ?? false,
|
|
94
|
+
response_profile: options?.responseProfile,
|
|
94
95
|
scope_preset: options?.scopePreset,
|
|
95
96
|
unity_resources: options?.unityResources,
|
|
96
97
|
unity_hydration_mode: options?.unityHydration,
|
|
@@ -114,6 +115,7 @@ export async function contextCommand(name, options) {
|
|
|
114
115
|
uid: options?.uid,
|
|
115
116
|
file_path: options?.file,
|
|
116
117
|
include_content: options?.content ?? false,
|
|
118
|
+
response_profile: options?.responseProfile,
|
|
117
119
|
unity_resources: options?.unityResources,
|
|
118
120
|
unity_hydration_mode: options?.unityHydration,
|
|
119
121
|
unity_evidence_mode: options?.unityEvidence,
|
|
@@ -341,6 +341,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
341
341
|
let communityResult;
|
|
342
342
|
let processResult;
|
|
343
343
|
let unityResult;
|
|
344
|
+
let unityRuleBindingResult;
|
|
344
345
|
if (!options?.skipGraphPhases) {
|
|
345
346
|
// ── Phase 4.5: Method Resolution Order ──────────────────────────────
|
|
346
347
|
onProgress({
|
|
@@ -406,7 +407,9 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
406
407
|
// ── Phase 5.6: Unity lifecycle synthetic calls (auto-detect) ────────
|
|
407
408
|
const isUnityProject = allPaths.some(p => p.startsWith('Assets/') && p.endsWith('.cs'));
|
|
408
409
|
const unityConfig = resolveUnityConfig();
|
|
409
|
-
|
|
410
|
+
// Persistence is coupled to the Unity resource-binding indexing flow:
|
|
411
|
+
// if Unity project auto-detection is active, persist lifecycle metadata.
|
|
412
|
+
const persistLifecycleProcessMetadata = isUnityProject;
|
|
410
413
|
const unityLifecycleSyntheticResult = isUnityProject
|
|
411
414
|
? applyUnityLifecycleSyntheticCalls(graph, {
|
|
412
415
|
maxSyntheticEdgesPerClass: unityConfig.config.maxSyntheticEdgesPerClass,
|
|
@@ -422,16 +425,41 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
422
425
|
// Phase 5.7: rule-driven binding injection (Phase 3)
|
|
423
426
|
try {
|
|
424
427
|
const analyzeRules = await loadAnalyzeRules(repoPath);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
428
|
+
const bindingResult = applyUnityRuntimeBindingRules(graph, analyzeRules, unityConfig.config);
|
|
429
|
+
unityRuleBindingResult = bindingResult;
|
|
430
|
+
if (isDev && bindingResult.edgesInjected > 0) {
|
|
431
|
+
console.log(`[UnityRuleBinding] injected ${bindingResult.edgesInjected} edges from ${analyzeRules.length} rule(s)`);
|
|
430
432
|
}
|
|
431
433
|
}
|
|
432
434
|
catch (err) {
|
|
433
435
|
// rule catalog missing or invalid — skip silently
|
|
434
436
|
console.warn(`[UnityRuleBinding] failed to load or apply analyze rules: ${err instanceof Error ? err.message : String(err)}`);
|
|
437
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
438
|
+
unityRuleBindingResult = {
|
|
439
|
+
edgesInjected: 0,
|
|
440
|
+
ruleResults: [],
|
|
441
|
+
diagnostics: {
|
|
442
|
+
rulesEvaluated: 0,
|
|
443
|
+
bindingsEvaluated: 0,
|
|
444
|
+
bindingsByKind: {},
|
|
445
|
+
methodLookupCalls: 0,
|
|
446
|
+
methodLookupCacheHits: 0,
|
|
447
|
+
sceneRuntimeTraversalCalls: 0,
|
|
448
|
+
sceneRuntimeTraversalCacheHits: 0,
|
|
449
|
+
sceneRuntimeResourcesVisited: 0,
|
|
450
|
+
anomalies: [`failed to load/apply analyze rules: ${reason}`],
|
|
451
|
+
shouldAgentReport: true,
|
|
452
|
+
agentReportReason: 'failed to load/apply analyze rules',
|
|
453
|
+
summary: [
|
|
454
|
+
'rule_binding.summary: rules=0, bindings=0, edges=0',
|
|
455
|
+
'rule_binding.bindings_by_kind: none',
|
|
456
|
+
'rule_binding.lookup: method_calls=0, cache_hits=0',
|
|
457
|
+
'rule_binding.scene_closure: traversals=0, cache_hits=0, visited_resources=0',
|
|
458
|
+
'rule_binding.agent_report: should_report=true reason="failed to load/apply analyze rules"',
|
|
459
|
+
`rule_binding.anomaly: failed to load/apply analyze rules: ${reason}`,
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
};
|
|
435
463
|
}
|
|
436
464
|
// ── Phase 6: Processes ─────────────────────────────────────────────
|
|
437
465
|
onProgress({
|
|
@@ -517,6 +545,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
517
545
|
communityResult,
|
|
518
546
|
processResult,
|
|
519
547
|
unityResult,
|
|
548
|
+
unityRuleBindingResult,
|
|
520
549
|
scopeDiagnostics: scopeSelection.diagnostics,
|
|
521
550
|
csharpPreprocDiagnostics,
|
|
522
551
|
};
|
|
@@ -245,7 +245,7 @@ test('detects Unity hosts through transitive inheritance chains', () => {
|
|
|
245
245
|
assert.equal(gunGraphHost.baseType, 'ScriptableObject');
|
|
246
246
|
assert.ok(gunGraphHost.methods.length > 0);
|
|
247
247
|
});
|
|
248
|
-
test('
|
|
248
|
+
test('respects global lifecycle edge budget without emitting loader bridges', () => {
|
|
249
249
|
const graph = createKnowledgeGraph();
|
|
250
250
|
for (let i = 0; i < 6; i += 1) {
|
|
251
251
|
addClass(graph, {
|
|
@@ -275,12 +275,13 @@ test('prioritizes gameplay lifecycle hosts when synthetic edge budget is tight',
|
|
|
275
275
|
maxSyntheticEdgesPerClass: 4,
|
|
276
276
|
maxSyntheticEdgesTotal: 8,
|
|
277
277
|
});
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
const targets = new Set(runtimeEdges.map((edge) => edge.targetId));
|
|
278
|
+
const lifecycleEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-lifecycle-synthetic');
|
|
279
|
+
const loaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
|
|
281
280
|
assert.equal(result.syntheticEdgeCount, 8);
|
|
282
|
-
assert.equal(
|
|
283
|
-
assert.equal(
|
|
281
|
+
assert.equal(result.lifecycleEdgeCount, 8);
|
|
282
|
+
assert.equal(result.loaderEdgeCount, 0);
|
|
283
|
+
assert.equal(lifecycleEdges.length, 8);
|
|
284
|
+
assert.equal(loaderEdges.length, 0);
|
|
284
285
|
});
|
|
285
286
|
test('resolves named class inheritance targets when direct target node lookup is ambiguous', () => {
|
|
286
287
|
const graph = createKnowledgeGraph();
|
|
@@ -336,7 +337,7 @@ test('resolves named class inheritance targets when direct target node lookup is
|
|
|
336
337
|
assert.ok(gunGraphHost);
|
|
337
338
|
assert.equal(gunGraphHost.baseType, 'ScriptableObject');
|
|
338
339
|
});
|
|
339
|
-
test('
|
|
340
|
+
test('lifecycle phase does not emit runtime-loader bridge edges', () => {
|
|
340
341
|
const graph = createKnowledgeGraph();
|
|
341
342
|
addClass(graph, {
|
|
342
343
|
className: 'GunGraphMB',
|
|
@@ -429,16 +430,15 @@ test('emits deterministic runtime loader bridge chain after pre-bridge budget is
|
|
|
429
430
|
maxSyntheticEdgesPerClass: 12,
|
|
430
431
|
maxSyntheticEdgesTotal: 10,
|
|
431
432
|
});
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
assert.equal(result.syntheticEdgeCount,
|
|
435
|
-
assert.equal(
|
|
436
|
-
assert.equal(
|
|
437
|
-
assert.equal(
|
|
438
|
-
assert.equal(
|
|
439
|
-
assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:ReloadBase.CheckReload')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs:Reload.ReloadRoutine')}`), true);
|
|
433
|
+
const syntheticLoaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
|
|
434
|
+
const lifecycleEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-lifecycle-synthetic');
|
|
435
|
+
assert.equal(result.syntheticEdgeCount, 3);
|
|
436
|
+
assert.equal(result.lifecycleEdgeCount, 3);
|
|
437
|
+
assert.equal(result.loaderEdgeCount, 0);
|
|
438
|
+
assert.equal(lifecycleEdges.length, 3);
|
|
439
|
+
assert.equal(syntheticLoaderEdges.length, 0);
|
|
440
440
|
});
|
|
441
|
-
test('
|
|
441
|
+
test('lifecycle edge cap does not rely on runtime-loader bridge injection', () => {
|
|
442
442
|
const graph = createKnowledgeGraph();
|
|
443
443
|
for (let i = 0; i < 20; i += 1) {
|
|
444
444
|
addClass(graph, {
|
|
@@ -534,8 +534,6 @@ test('preserves bridge budget when accepted host count exceeds synthetic edge ca
|
|
|
534
534
|
maxSyntheticEdgesPerClass: 12,
|
|
535
535
|
maxSyntheticEdgesTotal: 12,
|
|
536
536
|
});
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
.map((edge) => `${edge.sourceId}->${edge.targetId}`));
|
|
540
|
-
assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Core/GunGraphMB.cs:GunGraphMB.RegisterGraphEvents')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.RegisterEvents')}`), true);
|
|
537
|
+
const loaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
|
|
538
|
+
assert.equal(loaderEdges.length, 0);
|
|
541
539
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { UnityScanContext } from '../unity/scan-context.js';
|
|
1
|
+
import type { UnityPrefabSourceRef, UnityScanContext } from '../unity/scan-context.js';
|
|
2
2
|
export interface UnityParitySeed {
|
|
3
3
|
version: 1;
|
|
4
4
|
symbolToScriptPath: Record<string, string>;
|
|
5
5
|
scriptPathToGuid: Record<string, string>;
|
|
6
6
|
guidToResourcePaths: Record<string, string[]>;
|
|
7
7
|
assetGuidToPath?: Record<string, string>;
|
|
8
|
+
prefabSourceRefs?: UnityPrefabSourceRef[];
|
|
8
9
|
}
|
|
9
10
|
export declare function buildUnityParitySeed(scanContext: UnityScanContext): UnityParitySeed;
|
|
@@ -59,6 +59,14 @@ export function buildUnityParitySeed(scanContext) {
|
|
|
59
59
|
scriptPathToGuid: sortRecord(scriptPathToGuid),
|
|
60
60
|
guidToResourcePaths: sortRecord(guidToResourcePaths),
|
|
61
61
|
assetGuidToPath: Object.keys(assetGuidToPath).length > 0 ? sortRecord(assetGuidToPath) : undefined,
|
|
62
|
+
prefabSourceRefs: scanContext.prefabSourceRefs?.map((row) => ({
|
|
63
|
+
sourceResourcePath: normalizePath(row.sourceResourcePath),
|
|
64
|
+
targetGuid: String(row.targetGuid || '').trim().toLowerCase(),
|
|
65
|
+
targetResourcePath: normalizePath(row.targetResourcePath || '') || undefined,
|
|
66
|
+
fileId: String(row.fileId || '').trim() || undefined,
|
|
67
|
+
fieldName: 'm_SourcePrefab',
|
|
68
|
+
sourceLayer: row.sourceLayer === 'scene' ? 'scene' : 'prefab',
|
|
69
|
+
})),
|
|
62
70
|
};
|
|
63
71
|
}
|
|
64
72
|
function normalizePath(input) {
|
|
@@ -7,6 +7,7 @@ export interface UnityResourceProcessingResult {
|
|
|
7
7
|
bindingCount: number;
|
|
8
8
|
componentCount: number;
|
|
9
9
|
diagnostics: string[];
|
|
10
|
+
prefabSourceStats: PrefabSourcePassStats;
|
|
10
11
|
paritySeed?: UnityParitySeed;
|
|
11
12
|
timingsMs: {
|
|
12
13
|
scanContext: number;
|
|
@@ -25,4 +26,14 @@ export interface UnityResourceProcessingDeps {
|
|
|
25
26
|
buildScanContext?: typeof buildUnityScanContext;
|
|
26
27
|
resolveBindings?: typeof resolveUnityBindings;
|
|
27
28
|
}
|
|
29
|
+
interface PrefabSourcePassStats {
|
|
30
|
+
rowsParsed: number;
|
|
31
|
+
rowsFilteredZeroGuid: number;
|
|
32
|
+
rowsFilteredPlaceholder: number;
|
|
33
|
+
rowsFilteredUnresolved: number;
|
|
34
|
+
rowsDeduped: number;
|
|
35
|
+
rowsEmitted: number;
|
|
36
|
+
fileErrors: number;
|
|
37
|
+
}
|
|
28
38
|
export declare function processUnityResources(graph: KnowledgeGraph, options: UnityResourceProcessingOptions, deps?: UnityResourceProcessingDeps): Promise<UnityResourceProcessingResult>;
|
|
39
|
+
export {};
|
|
@@ -5,6 +5,7 @@ import { resolveUnityBindings } from '../unity/resolver.js';
|
|
|
5
5
|
import { buildUnityParitySeed } from './unity-parity-seed.js';
|
|
6
6
|
import { resolveUnityConfig } from '../config/unity-config.js';
|
|
7
7
|
const UNITY_DIAGNOSTIC_SAMPLE_LIMIT = 3;
|
|
8
|
+
const PREFAB_SOURCE_PASS_DISABLE_ENV = 'GITNEXUS_DISABLE_PREFAB_SOURCE_PASS';
|
|
8
9
|
export async function processUnityResources(graph, options, deps) {
|
|
9
10
|
const tStart = performance.now();
|
|
10
11
|
const buildScanContextFn = deps?.buildScanContext || buildUnityScanContext;
|
|
@@ -36,6 +37,7 @@ export async function processUnityResources(graph, options, deps) {
|
|
|
36
37
|
let scanContextMs = 0;
|
|
37
38
|
let resolveMs = 0;
|
|
38
39
|
let graphWriteMs = 0;
|
|
40
|
+
let prefabSourceStats = initPrefabSourcePassStats();
|
|
39
41
|
try {
|
|
40
42
|
const tScanContextStart = performance.now();
|
|
41
43
|
scanContext = await buildScanContextFn({
|
|
@@ -51,6 +53,22 @@ export async function processUnityResources(graph, options, deps) {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
diagnostics.push(`scanContext: scripts=${scanContext.symbolToScriptPath.size}, guids=${scanContext.scriptPathToGuid.size}, resources=${uniqueResourcePaths.size}`);
|
|
56
|
+
if (isPrefabSourcePassDisabledByEnv()) {
|
|
57
|
+
diagnostics.push(`prefab-source: skipped (env ${PREFAB_SOURCE_PASS_DISABLE_ENV}=1)`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const tPrefabSourceStart = performance.now();
|
|
61
|
+
prefabSourceStats = await emitPrefabSourceGuidRefsFromScanContext(graph, scanContext);
|
|
62
|
+
graphWriteMs += performance.now() - tPrefabSourceStart;
|
|
63
|
+
diagnostics.push(`prefab-source: emitted=${prefabSourceStats.rowsEmitted}`);
|
|
64
|
+
diagnostics.push(`prefab_source.rows_parsed=${prefabSourceStats.rowsParsed}`);
|
|
65
|
+
diagnostics.push(`prefab_source.rows_filtered_zero_guid=${prefabSourceStats.rowsFilteredZeroGuid}`);
|
|
66
|
+
diagnostics.push(`prefab_source.rows_filtered_placeholder=${prefabSourceStats.rowsFilteredPlaceholder}`);
|
|
67
|
+
diagnostics.push(`prefab_source.rows_filtered_unresolved=${prefabSourceStats.rowsFilteredUnresolved}`);
|
|
68
|
+
diagnostics.push(`prefab_source.rows_deduped=${prefabSourceStats.rowsDeduped}`);
|
|
69
|
+
diagnostics.push(`prefab_source.rows_emitted=${prefabSourceStats.rowsEmitted}`);
|
|
70
|
+
diagnostics.push(`prefab_source.file_errors=${prefabSourceStats.fileErrors}`);
|
|
71
|
+
}
|
|
54
72
|
symbolsWithResourceHits = collectSymbolsWithResourceHits(scanContext);
|
|
55
73
|
}
|
|
56
74
|
catch (error) {
|
|
@@ -228,6 +246,7 @@ export async function processUnityResources(graph, options, deps) {
|
|
|
228
246
|
bindingCount,
|
|
229
247
|
componentCount,
|
|
230
248
|
diagnostics,
|
|
249
|
+
prefabSourceStats,
|
|
231
250
|
paritySeed: scanContext ? buildUnityParitySeed(scanContext) : undefined,
|
|
232
251
|
timingsMs: {
|
|
233
252
|
scanContext: roundMs(scanContextMs),
|
|
@@ -460,3 +479,86 @@ function classifyUnityDiagnostic(message) {
|
|
|
460
479
|
}
|
|
461
480
|
return 'other';
|
|
462
481
|
}
|
|
482
|
+
async function emitPrefabSourceGuidRefsFromScanContext(graph, scanContext) {
|
|
483
|
+
const stats = initPrefabSourcePassStats();
|
|
484
|
+
const dedupeBySource = new Map();
|
|
485
|
+
const iterable = typeof scanContext.streamPrefabSourceRefs === 'function'
|
|
486
|
+
? scanContext.streamPrefabSourceRefs({
|
|
487
|
+
hooks: {
|
|
488
|
+
onFileError: () => {
|
|
489
|
+
stats.fileErrors += 1;
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
})
|
|
493
|
+
: (async function* () {
|
|
494
|
+
for (const row of scanContext.prefabSourceRefs || []) {
|
|
495
|
+
yield row;
|
|
496
|
+
}
|
|
497
|
+
})();
|
|
498
|
+
for await (const row of iterable) {
|
|
499
|
+
stats.rowsParsed += 1;
|
|
500
|
+
const source = normalizePath(String(row.sourceResourcePath || '').trim());
|
|
501
|
+
const guid = String(row.targetGuid || '').trim().toLowerCase();
|
|
502
|
+
if (!guid || guid === '00000000000000000000000000000000') {
|
|
503
|
+
stats.rowsFilteredZeroGuid += 1;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const hintedTarget = normalizePath(String(row.targetResourcePath || '').trim());
|
|
507
|
+
if (hintedTarget === '__PLACEHOLDER__') {
|
|
508
|
+
stats.rowsFilteredPlaceholder += 1;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const resolvedTarget = hintedTarget
|
|
512
|
+
|| normalizePath(scanContext.assetGuidToPath?.get(guid) || scanContext.assetGuidToPath?.get(guid.toLowerCase()) || '');
|
|
513
|
+
if (!resolvedTarget || !resolvedTarget.endsWith('.prefab')) {
|
|
514
|
+
stats.rowsFilteredUnresolved += 1;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (!source) {
|
|
518
|
+
stats.rowsFilteredUnresolved += 1;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const perSourceDedupe = dedupeBySource.get(source) || new Set();
|
|
522
|
+
const dedupeKey = `${resolvedTarget}|m_SourcePrefab|${guid}|${String(row.fileId || '').trim()}|${row.sourceLayer === 'scene' ? 'scene' : 'prefab'}`;
|
|
523
|
+
if (perSourceDedupe.has(dedupeKey)) {
|
|
524
|
+
stats.rowsDeduped += 1;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
perSourceDedupe.add(dedupeKey);
|
|
528
|
+
dedupeBySource.set(source, perSourceDedupe);
|
|
529
|
+
const sourceFileId = ensureResourceFileNode(graph, source);
|
|
530
|
+
const targetFileId = ensureResourceFileNode(graph, resolvedTarget);
|
|
531
|
+
graph.addRelationship({
|
|
532
|
+
id: generateId('UNITY_ASSET_GUID_REF', `${sourceFileId}->${targetFileId}:m_SourcePrefab:${guid}:${String(row.fileId || '')}`),
|
|
533
|
+
type: 'UNITY_ASSET_GUID_REF',
|
|
534
|
+
sourceId: sourceFileId,
|
|
535
|
+
targetId: targetFileId,
|
|
536
|
+
confidence: 1.0,
|
|
537
|
+
reason: JSON.stringify({
|
|
538
|
+
resourcePath: source,
|
|
539
|
+
targetResourcePath: resolvedTarget,
|
|
540
|
+
guid,
|
|
541
|
+
fileId: String(row.fileId || ''),
|
|
542
|
+
fieldName: 'm_SourcePrefab',
|
|
543
|
+
sourceLayer: row.sourceLayer === 'scene' ? 'scene' : 'prefab',
|
|
544
|
+
}),
|
|
545
|
+
});
|
|
546
|
+
stats.rowsEmitted += 1;
|
|
547
|
+
}
|
|
548
|
+
return stats;
|
|
549
|
+
}
|
|
550
|
+
function initPrefabSourcePassStats() {
|
|
551
|
+
return {
|
|
552
|
+
rowsParsed: 0,
|
|
553
|
+
rowsFilteredZeroGuid: 0,
|
|
554
|
+
rowsFilteredPlaceholder: 0,
|
|
555
|
+
rowsFilteredUnresolved: 0,
|
|
556
|
+
rowsDeduped: 0,
|
|
557
|
+
rowsEmitted: 0,
|
|
558
|
+
fileErrors: 0,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function isPrefabSourcePassDisabledByEnv() {
|
|
562
|
+
const value = String(process.env[PREFAB_SOURCE_PASS_DISABLE_ENV] || '').trim().toLowerCase();
|
|
563
|
+
return value === '1' || value === 'true' || value === 'yes' || value === 'on';
|
|
564
|
+
}
|