@veewo/gitnexus 1.5.0-rc.4 → 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/analyze-runner.d.ts +1 -1
- package/dist/benchmark/analyze-runner.js +4 -3
- package/dist/benchmark/analyze-runner.test.js +7 -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.d.ts +0 -1
- package/dist/cli/ai-context.js +5 -6
- package/dist/cli/ai-context.test.js +8 -0
- package/dist/cli/analyze-options.js +58 -34
- package/dist/cli/analyze-options.test.js +57 -0
- package/dist/cli/analyze-runtime-summary.js +2 -0
- package/dist/cli/analyze-runtime-summary.test.js +12 -0
- package/dist/cli/analyze-summary.d.ts +4 -0
- package/dist/cli/analyze-summary.js +43 -0
- package/dist/cli/analyze-summary.test.js +65 -1
- package/dist/cli/analyze.d.ts +11 -0
- package/dist/cli/analyze.js +34 -5
- package/dist/cli/analyze.test.d.ts +1 -0
- package/dist/cli/analyze.test.js +25 -0
- package/dist/cli/benchmark-agent-context.js +1 -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-unity.js +1 -1
- package/dist/cli/benchmark-unity.test.js +5 -1
- package/dist/cli/benchmark.d.ts +29 -0
- package/dist/cli/benchmark.js +55 -0
- package/dist/cli/index.js +27 -2
- 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/scope-manifest-config.d.ts +9 -0
- package/dist/cli/scope-manifest-config.js +37 -0
- package/dist/cli/setup.js +40 -41
- package/dist/cli/setup.test.js +14 -14
- package/dist/cli/sync-manifest.d.ts +27 -0
- package/dist/cli/sync-manifest.js +200 -0
- package/dist/cli/sync-manifest.test.d.ts +1 -0
- package/dist/cli/sync-manifest.test.js +88 -0
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +2 -0
- package/dist/core/config/unity-config.d.ts +1 -1
- package/dist/core/config/unity-config.js +1 -1
- package/dist/core/ingestion/call-processor.d.ts +2 -1
- package/dist/core/ingestion/call-processor.js +28 -6
- package/dist/core/ingestion/heritage-processor.d.ts +2 -1
- package/dist/core/ingestion/heritage-processor.js +30 -7
- package/dist/core/ingestion/import-processor.d.ts +2 -1
- package/dist/core/ingestion/import-processor.js +28 -6
- package/dist/core/ingestion/parsing-processor.d.ts +5 -3
- package/dist/core/ingestion/parsing-processor.js +46 -13
- package/dist/core/ingestion/pipeline.js +100 -19
- 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 +16 -1
- package/dist/core/ingestion/unity-runtime-binding-rules.js +193 -42
- package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
- package/dist/core/ingestion/workers/parse-worker.js +50 -6
- package/dist/core/lbug/csv-generator.test.js +2 -2
- package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
- package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
- package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
- package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
- package/dist/core/tree-sitter/parser-loader.js +19 -0
- 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 +16 -0
- package/package.json +14 -13
- package/scripts/check-sync-manifest-traceability.mjs +203 -0
- package/scripts/run-node-tests.mjs +61 -0
- package/scripts/tree-sitter-audit-classify.mjs +172 -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 +44 -4
- 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,261 @@
|
|
|
1
|
+
class ExprParser {
|
|
2
|
+
tokens;
|
|
3
|
+
index = 0;
|
|
4
|
+
defines;
|
|
5
|
+
undefinedSymbols;
|
|
6
|
+
constructor(tokens, defines, undefinedSymbols) {
|
|
7
|
+
this.tokens = tokens;
|
|
8
|
+
this.defines = defines;
|
|
9
|
+
this.undefinedSymbols = undefinedSymbols;
|
|
10
|
+
}
|
|
11
|
+
parse() {
|
|
12
|
+
const value = this.parseOr();
|
|
13
|
+
this.expect('eof');
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
parseOr() {
|
|
17
|
+
let value = this.parseAnd();
|
|
18
|
+
while (this.match('or')) {
|
|
19
|
+
value = value || this.parseAnd();
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
parseAnd() {
|
|
24
|
+
let value = this.parseUnary();
|
|
25
|
+
while (this.match('and')) {
|
|
26
|
+
value = value && this.parseUnary();
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
parseUnary() {
|
|
31
|
+
if (this.match('not')) {
|
|
32
|
+
return !this.parseUnary();
|
|
33
|
+
}
|
|
34
|
+
return this.parsePrimary();
|
|
35
|
+
}
|
|
36
|
+
parsePrimary() {
|
|
37
|
+
if (this.match('lparen')) {
|
|
38
|
+
const value = this.parseOr();
|
|
39
|
+
this.expect('rparen');
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
const token = this.current();
|
|
43
|
+
if (token.type === 'bool') {
|
|
44
|
+
this.index += 1;
|
|
45
|
+
return token.value === 'true';
|
|
46
|
+
}
|
|
47
|
+
if (token.type === 'ident') {
|
|
48
|
+
this.index += 1;
|
|
49
|
+
const symbol = token.value || '';
|
|
50
|
+
if (!this.defines.has(symbol))
|
|
51
|
+
this.undefinedSymbols.add(symbol);
|
|
52
|
+
return this.defines.has(symbol);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Unexpected token "${token.type}" in preprocessor expression`);
|
|
55
|
+
}
|
|
56
|
+
current() {
|
|
57
|
+
return this.tokens[this.index] || { type: 'eof' };
|
|
58
|
+
}
|
|
59
|
+
match(type) {
|
|
60
|
+
if (this.current().type !== type)
|
|
61
|
+
return false;
|
|
62
|
+
this.index += 1;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
expect(type) {
|
|
66
|
+
if (!this.match(type)) {
|
|
67
|
+
throw new Error(`Expected token "${type}" in preprocessor expression`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function tokenizeExpression(expression) {
|
|
72
|
+
const tokens = [];
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < expression.length) {
|
|
75
|
+
const ch = expression[i] || '';
|
|
76
|
+
if (/\s/.test(ch)) {
|
|
77
|
+
i += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (expression.startsWith('&&', i)) {
|
|
81
|
+
tokens.push({ type: 'and' });
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (expression.startsWith('||', i)) {
|
|
86
|
+
tokens.push({ type: 'or' });
|
|
87
|
+
i += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (ch === '!') {
|
|
91
|
+
tokens.push({ type: 'not' });
|
|
92
|
+
i += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === '(') {
|
|
96
|
+
tokens.push({ type: 'lparen' });
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (ch === ')') {
|
|
101
|
+
tokens.push({ type: 'rparen' });
|
|
102
|
+
i += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
106
|
+
let j = i + 1;
|
|
107
|
+
while (j < expression.length && /[A-Za-z0-9_]/.test(expression[j] || '')) {
|
|
108
|
+
j += 1;
|
|
109
|
+
}
|
|
110
|
+
const ident = expression.slice(i, j);
|
|
111
|
+
if (ident === 'true' || ident === 'false') {
|
|
112
|
+
tokens.push({ type: 'bool', value: ident });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
tokens.push({ type: 'ident', value: ident });
|
|
116
|
+
}
|
|
117
|
+
i = j;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Unsupported token "${ch}" in preprocessor expression: ${expression}`);
|
|
121
|
+
}
|
|
122
|
+
tokens.push({ type: 'eof' });
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
function evaluateExpression(expression, defines, undefinedSymbols) {
|
|
126
|
+
if (!expression.trim())
|
|
127
|
+
return { value: false, ok: false };
|
|
128
|
+
try {
|
|
129
|
+
const parser = new ExprParser(tokenizeExpression(expression), defines, undefinedSymbols);
|
|
130
|
+
return { value: parser.parse(), ok: true };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return { value: false, ok: false };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function deriveLineEnding(source) {
|
|
137
|
+
return source.includes('\r\n') ? '\r\n' : '\n';
|
|
138
|
+
}
|
|
139
|
+
function hasTrailingLineEnding(source) {
|
|
140
|
+
return /\r?\n$/.test(source);
|
|
141
|
+
}
|
|
142
|
+
export function normalizeCSharpPreprocessorBranches(source, defines) {
|
|
143
|
+
if (!source.includes('#if') && !source.includes('#elif') && !source.includes('#else') && !source.includes('#endif')) {
|
|
144
|
+
return {
|
|
145
|
+
normalizedText: source,
|
|
146
|
+
changed: false,
|
|
147
|
+
diagnostics: {
|
|
148
|
+
directivesSeen: 0,
|
|
149
|
+
inactiveLines: 0,
|
|
150
|
+
undefinedSymbols: [],
|
|
151
|
+
expressionErrors: 0,
|
|
152
|
+
unmatchedEndif: 0,
|
|
153
|
+
unterminatedIfBlocks: 0,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const lineEnding = deriveLineEnding(source);
|
|
158
|
+
const trailingLineEnding = hasTrailingLineEnding(source);
|
|
159
|
+
const lines = source.split(/\r?\n/);
|
|
160
|
+
const outputLines = [];
|
|
161
|
+
const stack = [];
|
|
162
|
+
const undefinedSymbols = new Set();
|
|
163
|
+
let active = true;
|
|
164
|
+
let changed = false;
|
|
165
|
+
let directivesSeen = 0;
|
|
166
|
+
let inactiveLines = 0;
|
|
167
|
+
let expressionErrors = 0;
|
|
168
|
+
let unmatchedEndif = 0;
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const directive = line.match(/^\s*#\s*(if|elif|else|endif)\b(.*)$/);
|
|
171
|
+
if (directive) {
|
|
172
|
+
directivesSeen += 1;
|
|
173
|
+
changed = true;
|
|
174
|
+
const keyword = directive[1] || '';
|
|
175
|
+
const rawExpression = (directive[2] || '').trim();
|
|
176
|
+
if (keyword === 'if') {
|
|
177
|
+
const evalResult = evaluateExpression(rawExpression, defines, undefinedSymbols);
|
|
178
|
+
if (!evalResult.ok)
|
|
179
|
+
expressionErrors += 1;
|
|
180
|
+
const branchActive = active && evalResult.value;
|
|
181
|
+
stack.push({
|
|
182
|
+
parentActive: active,
|
|
183
|
+
branchTaken: branchActive,
|
|
184
|
+
currentActive: branchActive,
|
|
185
|
+
seenElse: false,
|
|
186
|
+
});
|
|
187
|
+
active = branchActive;
|
|
188
|
+
}
|
|
189
|
+
else if (keyword === 'elif') {
|
|
190
|
+
const frame = stack[stack.length - 1];
|
|
191
|
+
if (!frame) {
|
|
192
|
+
unmatchedEndif += 1;
|
|
193
|
+
}
|
|
194
|
+
else if (frame.seenElse) {
|
|
195
|
+
expressionErrors += 1;
|
|
196
|
+
frame.currentActive = false;
|
|
197
|
+
active = false;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const evalResult = evaluateExpression(rawExpression, defines, undefinedSymbols);
|
|
201
|
+
if (!evalResult.ok)
|
|
202
|
+
expressionErrors += 1;
|
|
203
|
+
const branchActive = frame.parentActive && !frame.branchTaken && evalResult.value;
|
|
204
|
+
frame.currentActive = branchActive;
|
|
205
|
+
frame.branchTaken = frame.branchTaken || branchActive;
|
|
206
|
+
active = frame.currentActive;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (keyword === 'else') {
|
|
210
|
+
const frame = stack[stack.length - 1];
|
|
211
|
+
if (!frame) {
|
|
212
|
+
unmatchedEndif += 1;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const branchActive = frame.parentActive && !frame.branchTaken;
|
|
216
|
+
frame.currentActive = branchActive;
|
|
217
|
+
frame.branchTaken = true;
|
|
218
|
+
frame.seenElse = true;
|
|
219
|
+
active = frame.currentActive;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (keyword === 'endif') {
|
|
223
|
+
if (stack.length === 0) {
|
|
224
|
+
unmatchedEndif += 1;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
stack.pop();
|
|
228
|
+
}
|
|
229
|
+
const top = stack[stack.length - 1];
|
|
230
|
+
active = top ? top.currentActive : true;
|
|
231
|
+
}
|
|
232
|
+
outputLines.push('');
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (active) {
|
|
236
|
+
outputLines.push(line);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (line.length > 0) {
|
|
240
|
+
changed = true;
|
|
241
|
+
inactiveLines += 1;
|
|
242
|
+
}
|
|
243
|
+
outputLines.push('');
|
|
244
|
+
}
|
|
245
|
+
let normalizedText = outputLines.join(lineEnding);
|
|
246
|
+
if (trailingLineEnding && !normalizedText.endsWith(lineEnding)) {
|
|
247
|
+
normalizedText += lineEnding;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
normalizedText,
|
|
251
|
+
changed,
|
|
252
|
+
diagnostics: {
|
|
253
|
+
directivesSeen,
|
|
254
|
+
inactiveLines,
|
|
255
|
+
undefinedSymbols: [...undefinedSymbols].sort(),
|
|
256
|
+
expressionErrors,
|
|
257
|
+
unmatchedEndif,
|
|
258
|
+
unterminatedIfBlocks: stack.length,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -3,3 +3,13 @@ import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
|
3
3
|
export declare const isLanguageAvailable: (language: SupportedLanguages) => boolean;
|
|
4
4
|
export declare const loadParser: () => Promise<Parser>;
|
|
5
5
|
export declare const loadLanguage: (language: SupportedLanguages, filePath?: string) => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Parse source code using tree-sitter's chunked callback API.
|
|
8
|
+
* Avoids the native binding's single-buffer size limit (< 32768 bytes)
|
|
9
|
+
* that causes "Invalid argument" errors on large files.
|
|
10
|
+
*
|
|
11
|
+
* @param content - Full source file content as UTF-8 string
|
|
12
|
+
* @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
|
|
13
|
+
* @returns Parsed syntax tree
|
|
14
|
+
*/
|
|
15
|
+
export declare const parseContent: (content: string, oldTree?: any) => any;
|
|
@@ -61,3 +61,22 @@ export const loadLanguage = async (language, filePath) => {
|
|
|
61
61
|
}
|
|
62
62
|
parser.setLanguage(lang);
|
|
63
63
|
};
|
|
64
|
+
const MAX_CHUNK = 4096;
|
|
65
|
+
/**
|
|
66
|
+
* Parse source code using tree-sitter's chunked callback API.
|
|
67
|
+
* Avoids the native binding's single-buffer size limit (< 32768 bytes)
|
|
68
|
+
* that causes "Invalid argument" errors on large files.
|
|
69
|
+
*
|
|
70
|
+
* @param content - Full source file content as UTF-8 string
|
|
71
|
+
* @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
|
|
72
|
+
* @returns Parsed syntax tree
|
|
73
|
+
*/
|
|
74
|
+
export const parseContent = (content, oldTree) => {
|
|
75
|
+
if (!parser)
|
|
76
|
+
throw new Error('Parser not initialized — call loadParser() first');
|
|
77
|
+
return parser.parse((index) => {
|
|
78
|
+
if (index >= content.length)
|
|
79
|
+
return null;
|
|
80
|
+
return content.slice(index, index + MAX_CHUNK);
|
|
81
|
+
}, oldTree);
|
|
82
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const gitnexusRoot = path.resolve(here, '../../..');
|
|
8
|
+
const repoRoot = path.resolve(gitnexusRoot, '..');
|
|
9
|
+
test('scan-context carrier contract matches code and docs', async () => {
|
|
10
|
+
const bindingDoc = await fs.readFile(path.join(repoRoot, 'UNITY_RESOURCE_BINDING.md'), 'utf-8');
|
|
11
|
+
const ssot = await fs.readFile(path.join(repoRoot, 'docs/unity-runtime-process-source-of-truth.md'), 'utf-8');
|
|
12
|
+
const design = await fs.readFile(path.join(repoRoot, 'docs/plans/2026-04-10-prefab-source-streaming-consumption-memory-optimization-design.md'), 'utf-8');
|
|
13
|
+
const scanContextCode = await fs.readFile(path.join(gitnexusRoot, 'src/core/unity/scan-context.ts'), 'utf-8');
|
|
14
|
+
const processorCode = await fs.readFile(path.join(gitnexusRoot, 'src/core/ingestion/unity-resource-processor.ts'), 'utf-8');
|
|
15
|
+
const pipelineCode = await fs.readFile(path.join(gitnexusRoot, 'src/core/ingestion/pipeline.ts'), 'utf-8');
|
|
16
|
+
assert.match(bindingDoc, /scan-context.*承载器|resource signal carrier|scan-context.*carrier/i);
|
|
17
|
+
assert.match(bindingDoc, /streaming delivery|incremental consumption/i);
|
|
18
|
+
assert.match(ssot, /As-Built[\s\S]*Design Direction/i);
|
|
19
|
+
assert.match(ssot, /scan-context[\s\S]*does not write graph/i);
|
|
20
|
+
assert.match(ssot, /统一消费点契约/i);
|
|
21
|
+
assert.match(design, /scan-context[\s\S]*(统一消费|unified consumer)/i);
|
|
22
|
+
assert.match(scanContextCode, /streamPrefabSourceRefs/);
|
|
23
|
+
assert.match(processorCode, /streamPrefabSourceRefs\(/);
|
|
24
|
+
assert.match(processorCode, /emitPrefabSourceGuidRefsFromScanContext/);
|
|
25
|
+
assert.doesNotMatch(processorCode, /emitPrefabSourceGuidRefs\(/);
|
|
26
|
+
assert.doesNotMatch(scanContextCode, /addRelationship\(/);
|
|
27
|
+
assert.ok(pipelineCode.indexOf('processUnityResources(') >= 0
|
|
28
|
+
&& pipelineCode.indexOf('applyUnityLifecycleSyntheticCalls(') >= 0
|
|
29
|
+
&& pipelineCode.indexOf('processUnityResources(') < pipelineCode.indexOf('applyUnityLifecycleSyntheticCalls('));
|
|
30
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface PrefabSourceScanRow {
|
|
2
|
+
sourceResourcePath: string;
|
|
3
|
+
targetGuid: string;
|
|
4
|
+
targetResourcePath?: string;
|
|
5
|
+
fileId?: string;
|
|
6
|
+
fieldName: 'm_SourcePrefab';
|
|
7
|
+
sourceLayer: 'scene' | 'prefab';
|
|
8
|
+
}
|
|
9
|
+
export interface StreamPrefabSourceRefsInput {
|
|
10
|
+
repoRoot: string;
|
|
11
|
+
resourceFiles: string[];
|
|
12
|
+
assetGuidToPath: Map<string, string>;
|
|
13
|
+
queue?: {
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
maxDepth?: number;
|
|
16
|
+
};
|
|
17
|
+
hooks?: {
|
|
18
|
+
onFileOpen?: (resourcePath: string) => void;
|
|
19
|
+
onYield?: (row: PrefabSourceScanRow) => void;
|
|
20
|
+
onQueueDepth?: (depth: number) => void;
|
|
21
|
+
onFileError?: (resourcePath: string, error: unknown) => void;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare function collectPrefabSourceRefs(args: StreamPrefabSourceRefsInput): Promise<PrefabSourceScanRow[]>;
|
|
25
|
+
export declare function streamPrefabSourceRefs(args: StreamPrefabSourceRefsInput): AsyncGenerator<PrefabSourceScanRow>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
const PREFAB_INSTANCE_PATTERN = /^\s*PrefabInstance:\s*$/;
|
|
5
|
+
const YAML_OBJECT_BOUNDARY_PATTERN = /^\s*---\s*!u!\d+\s+&/;
|
|
6
|
+
const SOURCE_PREFAB_LINE_PATTERN = /m_SourcePrefab\s*:\s*\{[^}]*fileID\s*:\s*([^,\s}]+)[^}]*guid\s*:\s*([0-9a-fA-F]{32})[^}]*\}/;
|
|
7
|
+
const ZERO_GUID = '00000000000000000000000000000000';
|
|
8
|
+
export async function collectPrefabSourceRefs(args) {
|
|
9
|
+
const rows = [];
|
|
10
|
+
for await (const row of streamPrefabSourceRefs(args)) {
|
|
11
|
+
rows.push(row);
|
|
12
|
+
}
|
|
13
|
+
return rows;
|
|
14
|
+
}
|
|
15
|
+
export async function* streamPrefabSourceRefs(args) {
|
|
16
|
+
const resources = [...new Set((args.resourceFiles || []).map((value) => normalizePath(value)))]
|
|
17
|
+
.filter((value) => value.endsWith('.unity') || value.endsWith('.prefab'));
|
|
18
|
+
if (!args.queue?.enabled) {
|
|
19
|
+
for await (const row of streamPrefabSourceRefsSequential(resources, args)) {
|
|
20
|
+
yield row;
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const maxDepth = Math.max(1, Number(args.queue.maxDepth || 64));
|
|
25
|
+
const queue = [];
|
|
26
|
+
const waitingReaders = [];
|
|
27
|
+
const waitingWriters = [];
|
|
28
|
+
let producerDone = false;
|
|
29
|
+
let producerError;
|
|
30
|
+
const notifyDepth = () => args.hooks?.onQueueDepth?.(queue.length);
|
|
31
|
+
const push = async (row) => {
|
|
32
|
+
while (queue.length >= maxDepth) {
|
|
33
|
+
await new Promise((resolve) => waitingWriters.push(resolve));
|
|
34
|
+
}
|
|
35
|
+
if (waitingReaders.length > 0) {
|
|
36
|
+
const resolveReader = waitingReaders.shift();
|
|
37
|
+
resolveReader(row);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
queue.push(row);
|
|
41
|
+
notifyDepth();
|
|
42
|
+
};
|
|
43
|
+
const shift = async () => {
|
|
44
|
+
if (queue.length > 0) {
|
|
45
|
+
const value = queue.shift();
|
|
46
|
+
notifyDepth();
|
|
47
|
+
const resolveWriter = waitingWriters.shift();
|
|
48
|
+
if (resolveWriter)
|
|
49
|
+
resolveWriter();
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
if (producerDone) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return await new Promise((resolve) => waitingReaders.push(resolve));
|
|
56
|
+
};
|
|
57
|
+
const finishReaders = () => {
|
|
58
|
+
while (waitingReaders.length > 0) {
|
|
59
|
+
const resolveReader = waitingReaders.shift();
|
|
60
|
+
resolveReader(null);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const finishWriters = () => {
|
|
64
|
+
while (waitingWriters.length > 0) {
|
|
65
|
+
const resolveWriter = waitingWriters.shift();
|
|
66
|
+
resolveWriter();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const producer = (async () => {
|
|
70
|
+
try {
|
|
71
|
+
for await (const row of streamPrefabSourceRefsSequential(resources, args)) {
|
|
72
|
+
await push(row);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
producerError = error;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
producerDone = true;
|
|
80
|
+
finishReaders();
|
|
81
|
+
finishWriters();
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
while (true) {
|
|
85
|
+
const row = await shift();
|
|
86
|
+
if (!row)
|
|
87
|
+
break;
|
|
88
|
+
yield row;
|
|
89
|
+
}
|
|
90
|
+
await producer;
|
|
91
|
+
if (producerError) {
|
|
92
|
+
throw producerError;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function* streamPrefabSourceRefsSequential(resources, args) {
|
|
96
|
+
const hooks = args.hooks;
|
|
97
|
+
for (const resourcePath of resources) {
|
|
98
|
+
hooks?.onFileOpen?.(resourcePath);
|
|
99
|
+
const absolutePath = path.join(args.repoRoot, resourcePath);
|
|
100
|
+
const stream = createReadStream(absolutePath, { encoding: 'utf-8' });
|
|
101
|
+
const reader = createInterface({
|
|
102
|
+
input: stream,
|
|
103
|
+
crlfDelay: Infinity,
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
let inPrefabInstance = false;
|
|
107
|
+
for await (const line of reader) {
|
|
108
|
+
if (YAML_OBJECT_BOUNDARY_PATTERN.test(line)) {
|
|
109
|
+
inPrefabInstance = false;
|
|
110
|
+
}
|
|
111
|
+
if (PREFAB_INSTANCE_PATTERN.test(line)) {
|
|
112
|
+
inPrefabInstance = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!inPrefabInstance)
|
|
116
|
+
continue;
|
|
117
|
+
const match = line.match(SOURCE_PREFAB_LINE_PATTERN);
|
|
118
|
+
if (!match)
|
|
119
|
+
continue;
|
|
120
|
+
const fileId = String(match[1] || '').trim();
|
|
121
|
+
const guid = String(match[2] || '').trim().toLowerCase();
|
|
122
|
+
if (!guid || guid === ZERO_GUID)
|
|
123
|
+
continue;
|
|
124
|
+
const targetResourcePath = normalizePath(args.assetGuidToPath.get(guid) || args.assetGuidToPath.get(guid.toLowerCase()) || '');
|
|
125
|
+
if (!targetResourcePath || !targetResourcePath.endsWith('.prefab'))
|
|
126
|
+
continue;
|
|
127
|
+
const row = {
|
|
128
|
+
sourceResourcePath: resourcePath,
|
|
129
|
+
targetGuid: guid,
|
|
130
|
+
targetResourcePath,
|
|
131
|
+
fileId: fileId || undefined,
|
|
132
|
+
fieldName: 'm_SourcePrefab',
|
|
133
|
+
sourceLayer: resourcePath.endsWith('.unity') ? 'scene' : 'prefab',
|
|
134
|
+
};
|
|
135
|
+
hooks?.onYield?.(row);
|
|
136
|
+
yield row;
|
|
137
|
+
inPrefabInstance = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
hooks?.onFileError?.(resourcePath, error);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
reader.close();
|
|
146
|
+
stream.destroy();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function normalizePath(value) {
|
|
151
|
+
return String(value || '').replace(/\\/g, '/').trim();
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { streamPrefabSourceRefs } from './prefab-source-scan.js';
|
|
6
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const fixtureRoot = path.resolve(here, '../../../src/core/unity/__fixtures__/mini-unity');
|
|
8
|
+
const assetGuidToPath = new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]);
|
|
9
|
+
const scopedFiles = ['Assets/Scene/MainUIManager.unity', 'Assets/Prefabs/BattleMode.prefab'];
|
|
10
|
+
test('same source can yield prefab-source rows while script-guid flow remains independent', async () => {
|
|
11
|
+
const rows = [];
|
|
12
|
+
for await (const row of streamPrefabSourceRefs({
|
|
13
|
+
repoRoot: fixtureRoot,
|
|
14
|
+
resourceFiles: ['Assets/Scene/MainUIManager.unity'],
|
|
15
|
+
assetGuidToPath,
|
|
16
|
+
})) {
|
|
17
|
+
rows.push(row);
|
|
18
|
+
}
|
|
19
|
+
assert.ok(rows.length > 0);
|
|
20
|
+
assert.equal(rows.every((r) => r.fieldName === 'm_SourcePrefab'), true);
|
|
21
|
+
});
|
|
22
|
+
test('streamPrefabSourceRefs does not open second file before first row is yielded', async () => {
|
|
23
|
+
const probe = [];
|
|
24
|
+
const iterator = streamPrefabSourceRefs({
|
|
25
|
+
repoRoot: fixtureRoot,
|
|
26
|
+
resourceFiles: scopedFiles,
|
|
27
|
+
assetGuidToPath,
|
|
28
|
+
hooks: {
|
|
29
|
+
onFileOpen: (filePath) => probe.push(`open:${filePath}`),
|
|
30
|
+
onYield: () => probe.push('yield'),
|
|
31
|
+
},
|
|
32
|
+
})[Symbol.asyncIterator]();
|
|
33
|
+
const first = await iterator.next();
|
|
34
|
+
assert.equal(first.done, false);
|
|
35
|
+
assert.equal(first.value.fieldName, 'm_SourcePrefab');
|
|
36
|
+
assert.equal(probe.includes('open:Assets/Prefabs/BattleMode.prefab'), false);
|
|
37
|
+
await iterator.return?.(undefined);
|
|
38
|
+
});
|
|
39
|
+
test('producer rows are immutable snapshots (consumer mutation does not backflow)', async () => {
|
|
40
|
+
for await (const row of streamPrefabSourceRefs({
|
|
41
|
+
repoRoot: fixtureRoot,
|
|
42
|
+
resourceFiles: scopedFiles,
|
|
43
|
+
assetGuidToPath,
|
|
44
|
+
})) {
|
|
45
|
+
const copy = { ...row };
|
|
46
|
+
copy.targetResourcePath = '__PLACEHOLDER__';
|
|
47
|
+
}
|
|
48
|
+
const again = [];
|
|
49
|
+
for await (const row of streamPrefabSourceRefs({
|
|
50
|
+
repoRoot: fixtureRoot,
|
|
51
|
+
resourceFiles: scopedFiles,
|
|
52
|
+
assetGuidToPath,
|
|
53
|
+
})) {
|
|
54
|
+
again.push(row);
|
|
55
|
+
}
|
|
56
|
+
assert.equal(again.some((r) => r.targetResourcePath === '__PLACEHOLDER__'), false);
|
|
57
|
+
});
|
|
58
|
+
test('bounded queue backpressure never exceeds configured depth when decoupled mode is enabled', async () => {
|
|
59
|
+
const depthSamples = [];
|
|
60
|
+
for await (const _row of streamPrefabSourceRefs({
|
|
61
|
+
repoRoot: fixtureRoot,
|
|
62
|
+
resourceFiles: scopedFiles,
|
|
63
|
+
assetGuidToPath,
|
|
64
|
+
queue: { enabled: true, maxDepth: 64 },
|
|
65
|
+
hooks: { onQueueDepth: (depth) => depthSamples.push(depth) },
|
|
66
|
+
})) {
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
68
|
+
}
|
|
69
|
+
assert.equal(depthSamples.every((depth) => depth <= 64), true);
|
|
70
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StreamPrefabSourceRefsInput } from './prefab-source-scan.js';
|
|
1
2
|
import type { UnityResourceGuidHit } from './resource-hit-scanner.js';
|
|
2
3
|
import type { UnityObjectBlock } from './yaml-object-graph.js';
|
|
3
4
|
import type { UnityParitySeed } from '../ingestion/unity-parity-seed.js';
|
|
@@ -10,6 +11,14 @@ export interface UnitySymbolDeclaration {
|
|
|
10
11
|
symbol: string;
|
|
11
12
|
scriptPath: string;
|
|
12
13
|
}
|
|
14
|
+
export interface UnityPrefabSourceRef {
|
|
15
|
+
sourceResourcePath: string;
|
|
16
|
+
targetGuid: string;
|
|
17
|
+
targetResourcePath?: string;
|
|
18
|
+
fileId?: string;
|
|
19
|
+
fieldName: 'm_SourcePrefab';
|
|
20
|
+
sourceLayer: 'scene' | 'prefab';
|
|
21
|
+
}
|
|
13
22
|
export interface UnityScanContext {
|
|
14
23
|
symbolToScriptPaths: Map<string, string[]>;
|
|
15
24
|
symbolToCanonicalScriptPath: Map<string, string>;
|
|
@@ -21,6 +30,9 @@ export interface UnityScanContext {
|
|
|
21
30
|
assetGuidToPath?: Map<string, string>;
|
|
22
31
|
uxmlGuidToPath?: Map<string, string>;
|
|
23
32
|
ussGuidToPath?: Map<string, string>;
|
|
33
|
+
prefabSourceRefs: UnityPrefabSourceRef[];
|
|
34
|
+
streamPrefabSourceRefs: (options?: Pick<StreamPrefabSourceRefsInput, 'queue' | 'hooks'>) => AsyncIterable<UnityPrefabSourceRef>;
|
|
35
|
+
resourceFiles: string[];
|
|
24
36
|
resourceDocCache: Map<string, UnityObjectBlock[]>;
|
|
25
37
|
}
|
|
26
38
|
export declare function buildUnityScanContext(input: BuildScanContextInput): Promise<UnityScanContext>;
|