@veewo/gitnexus 1.5.0-rc.4 → 1.5.0

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 (54) hide show
  1. package/dist/benchmark/analyze-runner.d.ts +1 -1
  2. package/dist/benchmark/analyze-runner.js +4 -3
  3. package/dist/benchmark/analyze-runner.test.js +7 -0
  4. package/dist/cli/ai-context.d.ts +0 -1
  5. package/dist/cli/ai-context.js +15 -6
  6. package/dist/cli/analyze-options.js +58 -34
  7. package/dist/cli/analyze-options.test.js +57 -0
  8. package/dist/cli/analyze-runtime-summary.js +1 -0
  9. package/dist/cli/analyze-runtime-summary.test.js +10 -0
  10. package/dist/cli/analyze-summary.d.ts +2 -0
  11. package/dist/cli/analyze-summary.js +19 -0
  12. package/dist/cli/analyze.d.ts +11 -0
  13. package/dist/cli/analyze.js +30 -5
  14. package/dist/cli/analyze.test.d.ts +1 -0
  15. package/dist/cli/analyze.test.js +25 -0
  16. package/dist/cli/benchmark-agent-context.js +1 -1
  17. package/dist/cli/benchmark-unity.js +1 -1
  18. package/dist/cli/benchmark-unity.test.js +5 -1
  19. package/dist/cli/index.js +4 -2
  20. package/dist/cli/scope-manifest-config.d.ts +9 -0
  21. package/dist/cli/scope-manifest-config.js +37 -0
  22. package/dist/cli/setup.js +40 -41
  23. package/dist/cli/setup.test.js +14 -14
  24. package/dist/cli/sync-manifest.d.ts +27 -0
  25. package/dist/cli/sync-manifest.js +200 -0
  26. package/dist/cli/sync-manifest.test.d.ts +1 -0
  27. package/dist/cli/sync-manifest.test.js +88 -0
  28. package/dist/core/config/unity-config.d.ts +1 -0
  29. package/dist/core/config/unity-config.js +1 -0
  30. package/dist/core/ingestion/call-processor.d.ts +2 -1
  31. package/dist/core/ingestion/call-processor.js +28 -6
  32. package/dist/core/ingestion/heritage-processor.d.ts +2 -1
  33. package/dist/core/ingestion/heritage-processor.js +30 -7
  34. package/dist/core/ingestion/import-processor.d.ts +2 -1
  35. package/dist/core/ingestion/import-processor.js +28 -6
  36. package/dist/core/ingestion/parsing-processor.d.ts +5 -3
  37. package/dist/core/ingestion/parsing-processor.js +46 -13
  38. package/dist/core/ingestion/pipeline.js +65 -13
  39. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +1 -1
  40. package/dist/core/ingestion/unity-runtime-binding-rules.js +21 -18
  41. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
  42. package/dist/core/ingestion/workers/parse-worker.js +50 -6
  43. package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
  44. package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
  45. package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
  46. package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
  47. package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
  48. package/dist/core/tree-sitter/parser-loader.js +19 -0
  49. package/dist/types/pipeline.d.ts +13 -0
  50. package/package.json +12 -12
  51. package/scripts/check-sync-manifest-traceability.mjs +203 -0
  52. package/scripts/tree-sitter-audit-classify.mjs +172 -0
  53. package/skills/gitnexus-cli.md +36 -4
  54. package/skills/gitnexus-unity-rule-gen.md +2 -2
@@ -1,6 +1,6 @@
1
1
  import { generateId } from '../../lib/utils.js';
2
2
  const RULE_EDGE_CONFIDENCE = 0.75;
3
- export function applyUnityRuntimeBindingRules(graph, rules, _config) {
3
+ export function applyUnityRuntimeBindingRules(graph, rules, config) {
4
4
  const ruleResults = [];
5
5
  let totalEdges = 0;
6
6
  const existingPairs = new Set();
@@ -27,7 +27,10 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
27
27
  };
28
28
  // Pre-build indexes
29
29
  const methodsByClassId = new Map();
30
- const classNodes = [];
30
+ const containerNodes = [];
31
+ const containerLabels = config.enableContainerNodes
32
+ ? new Set(['Class', 'Struct', 'Interface', 'Record'])
33
+ : new Set(['Class']);
31
34
  for (const rel of graph.iterRelationships()) {
32
35
  if (rel.type !== 'HAS_METHOD')
33
36
  continue;
@@ -39,8 +42,8 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
39
42
  methodsByClassId.set(rel.sourceId, list);
40
43
  }
41
44
  for (const node of graph.iterNodes()) {
42
- if (node.label === 'Class')
43
- classNodes.push(node);
45
+ if (containerLabels.has(node.label))
46
+ containerNodes.push(node);
44
47
  }
45
48
  // Collect UNITY_ASSET_GUID_REF and UNITY_COMPONENT_INSTANCE edges
46
49
  const assetGuidRefs = [];
@@ -67,10 +70,10 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
67
70
  for (const rule of rules) {
68
71
  let ruleEdges = 0;
69
72
  for (const binding of rule.resource_bindings ?? []) {
70
- ruleEdges += processBinding(binding, rule.id, assetGuidRefs, componentInstances, methodsByClassId, classNodes, sceneFilesByName, addSyntheticEdge);
73
+ ruleEdges += processBinding(binding, rule.id, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addSyntheticEdge);
71
74
  }
72
75
  if (rule.lifecycle_overrides?.additional_entry_points?.length) {
73
- ruleEdges += processLifecycleOverrides(rule, methodsByClassId, classNodes, addSyntheticEdge);
76
+ ruleEdges += processLifecycleOverrides(rule, methodsByClassId, containerNodes, addSyntheticEdge);
74
77
  }
75
78
  totalEdges += ruleEdges;
76
79
  ruleResults.push({ ruleId: rule.id, edgesInjected: ruleEdges });
@@ -91,18 +94,18 @@ function findMethodsOnResource(resourceFileId, componentInstances, methodsByClas
91
94
  }
92
95
  return results;
93
96
  }
94
- function processBinding(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, classNodes, sceneFilesByName, addEdge) {
97
+ function processBinding(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge) {
95
98
  if (binding.kind === 'asset_ref_loads_components') {
96
99
  return processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, addEdge);
97
100
  }
98
101
  if (binding.kind === 'method_triggers_field_load') {
99
- return processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, classNodes, addEdge);
102
+ return processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, addEdge);
100
103
  }
101
104
  if (binding.kind === 'method_triggers_scene_load') {
102
- return processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, classNodes, sceneFilesByName, addEdge);
105
+ return processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge);
103
106
  }
104
107
  if (binding.kind === 'method_triggers_method') {
105
- return processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNodes, addEdge);
108
+ return processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge);
106
109
  }
107
110
  return 0;
108
111
  }
@@ -132,7 +135,7 @@ function processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, componen
132
135
  }
133
136
  return count;
134
137
  }
135
- function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, classNodes, addEdge) {
138
+ function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, addEdge) {
136
139
  let count = 0;
137
140
  const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
138
141
  const loaderMethodNames = new Set(binding.loader_methods ?? []);
@@ -148,7 +151,7 @@ function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componen
148
151
  list.push(ref);
149
152
  refsBySource.set(ref.sourceId, list);
150
153
  }
151
- for (const cls of classNodes) {
154
+ for (const cls of containerNodes) {
152
155
  if (!classPattern.test(cls.properties.name))
153
156
  continue;
154
157
  const methods = methodsByClassId.get(cls.id) ?? [];
@@ -176,7 +179,7 @@ function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componen
176
179
  }
177
180
  return count;
178
181
  }
179
- function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, classNodes, sceneFilesByName, addEdge) {
182
+ function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge) {
180
183
  const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
181
184
  const loaderMethodNames = new Set(binding.loader_methods ?? []);
182
185
  const sceneName = binding.scene_name;
@@ -190,7 +193,7 @@ function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, met
190
193
  if (sceneFileIds.length === 0)
191
194
  return 0;
192
195
  let count = 0;
193
- for (const cls of classNodes) {
196
+ for (const cls of containerNodes) {
194
197
  if (!classPattern.test(cls.properties.name))
195
198
  continue;
196
199
  const methods = methodsByClassId.get(cls.id) ?? [];
@@ -209,7 +212,7 @@ function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, met
209
212
  }
210
213
  return count;
211
214
  }
212
- function processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNodes, addEdge) {
215
+ function processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge) {
213
216
  const { source_class_pattern, source_method, target_class_pattern, target_method } = binding;
214
217
  if (!source_class_pattern || !source_method || !target_class_pattern || !target_method)
215
218
  return 0;
@@ -217,7 +220,7 @@ function processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNod
217
220
  const tgtPattern = new RegExp(target_class_pattern);
218
221
  let sourceMethodId;
219
222
  let targetMethodId;
220
- for (const cls of classNodes) {
223
+ for (const cls of containerNodes) {
221
224
  if (!sourceMethodId && srcPattern.test(cls.properties.name)) {
222
225
  const match = (methodsByClassId.get(cls.id) ?? []).find(m => m.properties.name === source_method);
223
226
  if (match)
@@ -235,7 +238,7 @@ function processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNod
235
238
  return 0;
236
239
  return addEdge(sourceMethodId, targetMethodId, `unity-rule-method-bridge:${ruleId}`) ? 1 : 0;
237
240
  }
238
- function processLifecycleOverrides(rule, methodsByClassId, classNodes, addEdge) {
241
+ function processLifecycleOverrides(rule, methodsByClassId, containerNodes, addEdge) {
239
242
  const overrides = rule.lifecycle_overrides;
240
243
  if (!overrides?.additional_entry_points?.length)
241
244
  return 0;
@@ -243,7 +246,7 @@ function processLifecycleOverrides(rule, methodsByClassId, classNodes, addEdge)
243
246
  const scopePattern = overrides.scope ? new RegExp(overrides.scope) : null;
244
247
  let count = 0;
245
248
  const runtimeRootId = generateId('Method', 'unity-runtime-root');
246
- for (const cls of classNodes) {
249
+ for (const cls of containerNodes) {
247
250
  if (scopePattern && !scopePattern.test(cls.properties.filePath ?? cls.properties.name))
248
251
  continue;
249
252
  for (const method of methodsByClassId.get(cls.id) ?? []) {
@@ -97,10 +97,12 @@ export interface ParseWorkerResult {
97
97
  routes: ExtractedRoute[];
98
98
  constructorBindings: FileConstructorBindings[];
99
99
  skippedLanguages: Record<string, number>;
100
+ csharpPreprocFallbackFiles: number;
100
101
  fileCount: number;
101
102
  }
102
103
  export interface ParseWorkerInput {
103
104
  path: string;
104
105
  content: string;
106
+ rawContent?: string;
105
107
  }
106
108
  export {};
@@ -14,7 +14,7 @@ import Ruby from 'tree-sitter-ruby';
14
14
  import { createRequire } from 'node:module';
15
15
  import { SupportedLanguages } from '../../../config/supported-languages.js';
16
16
  import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
17
- import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
17
+ import { TREE_SITTER_MAX_BUFFER } from '../constants.js';
18
18
  // tree-sitter-swift is an optionalDependency — may not be installed
19
19
  const _require = createRequire(import.meta.url);
20
20
  let Swift = null;
@@ -166,6 +166,7 @@ const processBatch = (files, onProgress) => {
166
166
  routes: [],
167
167
  constructorBindings: [],
168
168
  skippedLanguages: {},
169
+ csharpPreprocFallbackFiles: 0,
169
170
  fileCount: 0,
170
171
  };
171
172
  // Group by language to minimize setLanguage calls
@@ -716,14 +717,56 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
716
717
  if (file.content.length > TREE_SITTER_MAX_BUFFER)
717
718
  continue;
718
719
  let tree;
720
+ let usedRawContentFallback = false;
719
721
  try {
720
- tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
722
+ const MAX_CHUNK = 4096;
723
+ tree = parser.parse((index) => {
724
+ if (index >= file.content.length)
725
+ return null;
726
+ return file.content.slice(index, index + MAX_CHUNK);
727
+ });
721
728
  }
722
729
  catch (err) {
723
- console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
724
- continue;
730
+ if (file.rawContent && file.rawContent !== file.content) {
731
+ try {
732
+ const MAX_CHUNK = 4096;
733
+ tree = parser.parse((index) => {
734
+ if (index >= file.rawContent.length)
735
+ return null;
736
+ return file.rawContent.slice(index, index + MAX_CHUNK);
737
+ });
738
+ usedRawContentFallback = true;
739
+ }
740
+ catch {
741
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
742
+ continue;
743
+ }
744
+ }
745
+ else {
746
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
747
+ continue;
748
+ }
749
+ }
750
+ if (file.rawContent && file.rawContent !== file.content && tree.rootNode?.hasError) {
751
+ try {
752
+ const MAX_CHUNK = 4096;
753
+ const rawTree = parser.parse((index) => {
754
+ if (index >= file.rawContent.length)
755
+ return null;
756
+ return file.rawContent.slice(index, index + MAX_CHUNK);
757
+ });
758
+ if (!rawTree.rootNode?.hasError) {
759
+ tree = rawTree;
760
+ usedRawContentFallback = true;
761
+ }
762
+ }
763
+ catch {
764
+ // Keep normalized parse result when raw fallback parsing fails
765
+ }
725
766
  }
726
767
  result.fileCount++;
768
+ if (usedRawContentFallback)
769
+ result.csharpPreprocFallbackFiles += 1;
727
770
  onFileProcessed?.();
728
771
  // Build per-file type environment + constructor bindings in a single AST walk.
729
772
  // Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
@@ -1030,7 +1073,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1030
1073
  /** Accumulated result across sub-batches */
1031
1074
  let accumulated = {
1032
1075
  nodes: [], relationships: [], symbols: [],
1033
- imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
1076
+ imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, csharpPreprocFallbackFiles: 0, fileCount: 0,
1034
1077
  };
1035
1078
  let cumulativeProcessed = 0;
1036
1079
  const mergeResult = (target, src) => {
@@ -1045,6 +1088,7 @@ const mergeResult = (target, src) => {
1045
1088
  for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1046
1089
  target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1047
1090
  }
1091
+ target.csharpPreprocFallbackFiles += src.csharpPreprocFallbackFiles;
1048
1092
  target.fileCount += src.fileCount;
1049
1093
  };
1050
1094
  parentPort.on('message', (msg) => {
@@ -1064,7 +1108,7 @@ parentPort.on('message', (msg) => {
1064
1108
  if (msg && msg.type === 'flush') {
1065
1109
  parentPort.postMessage({ type: 'result', data: accumulated });
1066
1110
  // Reset for potential reuse
1067
- accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
1111
+ accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, csharpPreprocFallbackFiles: 0, fileCount: 0 };
1068
1112
  cumulativeProcessed = 0;
1069
1113
  return;
1070
1114
  }
@@ -0,0 +1,6 @@
1
+ export interface CSharpDefineProfile {
2
+ symbols: Set<string>;
3
+ sourcePath: string;
4
+ rawDefineConstants: string[];
5
+ }
6
+ export declare function loadCSharpDefineProfileFromCsproj(csprojPath: string): Promise<CSharpDefineProfile>;
@@ -0,0 +1,43 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ const DEFINE_CONSTANTS_RE = /<DefineConstants>\s*([\s\S]*?)\s*<\/DefineConstants>/gi;
4
+ const CSHARP_SYMBOL_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ function parseSymbols(rawDefineConstants) {
6
+ const symbols = new Set();
7
+ for (const raw of rawDefineConstants) {
8
+ for (const token of raw.split(';')) {
9
+ const symbol = token.trim();
10
+ if (!symbol)
11
+ continue;
12
+ // Drop MSBuild placeholders such as $(DefineConstants)
13
+ if (symbol.includes('$('))
14
+ continue;
15
+ if (!CSHARP_SYMBOL_RE.test(symbol))
16
+ continue;
17
+ symbols.add(symbol);
18
+ }
19
+ }
20
+ return symbols;
21
+ }
22
+ export async function loadCSharpDefineProfileFromCsproj(csprojPath) {
23
+ const sourcePath = path.resolve(csprojPath);
24
+ let content;
25
+ try {
26
+ content = await fs.readFile(sourcePath, 'utf-8');
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`Failed to read C# csproj: ${sourcePath} (${message})`);
31
+ }
32
+ const rawDefineConstants = [];
33
+ for (const match of content.matchAll(DEFINE_CONSTANTS_RE)) {
34
+ const raw = (match[1] || '').trim();
35
+ if (raw)
36
+ rawDefineConstants.push(raw);
37
+ }
38
+ return {
39
+ symbols: parseSymbols(rawDefineConstants),
40
+ sourcePath,
41
+ rawDefineConstants,
42
+ };
43
+ }
@@ -0,0 +1,14 @@
1
+ export interface CSharpPreprocNormalizationDiagnostics {
2
+ directivesSeen: number;
3
+ inactiveLines: number;
4
+ undefinedSymbols: string[];
5
+ expressionErrors: number;
6
+ unmatchedEndif: number;
7
+ unterminatedIfBlocks: number;
8
+ }
9
+ export interface CSharpPreprocNormalizationResult {
10
+ normalizedText: string;
11
+ changed: boolean;
12
+ diagnostics: CSharpPreprocNormalizationDiagnostics;
13
+ }
14
+ export declare function normalizeCSharpPreprocessorBranches(source: string, defines: Set<string>): CSharpPreprocNormalizationResult;
@@ -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
+ };
@@ -18,6 +18,17 @@ export interface PipelineProgress {
18
18
  export interface PipelineRunOptions {
19
19
  includeExtensions?: string[];
20
20
  scopeRules?: string[];
21
+ csharpDefineCsproj?: string;
22
+ }
23
+ export interface CSharpPreprocDiagnostics {
24
+ enabled: boolean;
25
+ sourcePath?: string;
26
+ defineSymbolCount: number;
27
+ normalizedFiles: number;
28
+ fallbackFiles: number;
29
+ skippedFiles: number;
30
+ expressionErrors: number;
31
+ undefinedSymbols: string[];
21
32
  }
22
33
  export interface PipelineResult {
23
34
  graph: KnowledgeGraph;
@@ -29,6 +40,7 @@ export interface PipelineResult {
29
40
  processResult?: ProcessDetectionResult;
30
41
  unityResult?: UnityResourceProcessingResult;
31
42
  scopeDiagnostics?: ScopeSelectionDiagnostics;
43
+ csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
32
44
  }
33
45
  export interface PipelineRuntimeSummary {
34
46
  totalFileCount: number;
@@ -36,6 +48,7 @@ export interface PipelineRuntimeSummary {
36
48
  processResult?: ProcessDetectionResult;
37
49
  unityResult?: UnityResourceProcessingResult;
38
50
  scopeDiagnostics?: ScopeSelectionDiagnostics;
51
+ csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
39
52
  }
40
53
  export interface SerializablePipelineResult {
41
54
  nodes: GraphNode[];