gitnexus 1.6.11-rc.2 → 1.6.11-rc.21

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 (158) hide show
  1. package/README.md +43 -1
  2. package/dist/_shared/impact-risk.d.ts +37 -0
  3. package/dist/_shared/impact-risk.d.ts.map +1 -0
  4. package/dist/_shared/impact-risk.js +92 -0
  5. package/dist/_shared/impact-risk.js.map +1 -0
  6. package/dist/_shared/index.d.ts +2 -0
  7. package/dist/_shared/index.d.ts.map +1 -1
  8. package/dist/_shared/index.js +2 -0
  9. package/dist/_shared/index.js.map +1 -1
  10. package/dist/cli/ai-context.js +4 -4
  11. package/dist/cli/analyze-config.d.ts +2 -0
  12. package/dist/cli/analyze-config.js +16 -0
  13. package/dist/cli/analyze-options.d.ts +4 -0
  14. package/dist/cli/analyze.d.ts +17 -0
  15. package/dist/cli/analyze.js +43 -7
  16. package/dist/cli/eval-server.js +22 -5
  17. package/dist/cli/group.js +7 -1
  18. package/dist/cli/help-i18n.js +2 -0
  19. package/dist/cli/i18n/en.d.ts +12 -2
  20. package/dist/cli/i18n/en.js +12 -2
  21. package/dist/cli/i18n/resources.d.ts +22 -2
  22. package/dist/cli/i18n/zh-CN.d.ts +10 -0
  23. package/dist/cli/i18n/zh-CN.js +12 -2
  24. package/dist/cli/index.js +11 -4
  25. package/dist/cli/status.js +91 -6
  26. package/dist/cli/watch-queue.d.ts +41 -0
  27. package/dist/cli/watch-queue.js +184 -0
  28. package/dist/cli/watch.d.ts +20 -0
  29. package/dist/cli/watch.js +372 -0
  30. package/dist/cli/wiki.js +15 -2
  31. package/dist/config/ignore-service.d.ts +11 -0
  32. package/dist/config/ignore-service.js +41 -4
  33. package/dist/config/repo-control-file.d.ts +3 -0
  34. package/dist/config/repo-control-file.js +115 -0
  35. package/dist/core/group/config-parser.js +20 -2
  36. package/dist/core/group/cross-impact.d.ts +2 -1
  37. package/dist/core/group/cross-impact.js +33 -2
  38. package/dist/core/group/extractors/fs-utils.d.ts +2 -0
  39. package/dist/core/group/extractors/fs-utils.js +86 -0
  40. package/dist/core/group/extractors/graphql-extractor.d.ts +17 -0
  41. package/dist/core/group/extractors/graphql-extractor.js +652 -0
  42. package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
  43. package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
  44. package/dist/core/group/extractors/manifest-extractor.js +1 -1
  45. package/dist/core/group/matching.js +8 -1
  46. package/dist/core/group/service.js +5 -1
  47. package/dist/core/group/storage.js +1 -0
  48. package/dist/core/group/sync.d.ts +3 -1
  49. package/dist/core/group/sync.js +43 -5
  50. package/dist/core/group/types.d.ts +19 -5
  51. package/dist/core/incremental/derived-writeback.d.ts +36 -0
  52. package/dist/core/incremental/derived-writeback.js +68 -0
  53. package/dist/core/incremental/subgraph-extract.d.ts +6 -4
  54. package/dist/core/incremental/subgraph-extract.js +7 -5
  55. package/dist/core/index-content-drift.d.ts +54 -0
  56. package/dist/core/index-content-drift.js +127 -0
  57. package/dist/core/ingestion/filesystem-walker.d.ts +15 -5
  58. package/dist/core/ingestion/filesystem-walker.js +20 -3
  59. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
  60. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
  61. package/dist/core/ingestion/language-provider.d.ts +43 -0
  62. package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
  63. package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
  64. package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
  65. package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
  66. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
  67. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +4 -0
  68. package/dist/core/ingestion/languages/java/capture-side-channel.js +16 -0
  69. package/dist/core/ingestion/languages/java/captures.js +14 -1
  70. package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
  71. package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
  72. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
  73. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
  74. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
  75. package/dist/core/ingestion/languages/java.js +2 -0
  76. package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
  77. package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
  78. package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
  79. package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
  80. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +5 -0
  81. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +16 -0
  82. package/dist/core/ingestion/languages/kotlin/captures.js +14 -1
  83. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
  84. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
  85. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +2 -0
  86. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
  87. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
  88. package/dist/core/ingestion/languages/kotlin.js +2 -0
  89. package/dist/core/ingestion/pipeline-phases/di.js +47 -14
  90. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +3 -1
  91. package/dist/core/ingestion/pipeline-phases/parse-impl.js +57 -86
  92. package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -0
  93. package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
  94. package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
  95. package/dist/core/ingestion/pipeline-phases/scan.js +25 -13
  96. package/dist/core/ingestion/pipeline.d.ts +6 -0
  97. package/dist/core/ingestion/pipeline.js +44 -12
  98. package/dist/core/ingestion/scope-extractor.js +1 -0
  99. package/dist/core/ingestion/utils/symbol-labels.d.ts +2 -2
  100. package/dist/core/ingestion/utils/symbol-labels.js +2 -2
  101. package/dist/core/ingestion/workers/parse-worker.js +28 -2
  102. package/dist/core/lbug/lbug-adapter.d.ts +59 -0
  103. package/dist/core/lbug/lbug-adapter.js +154 -1
  104. package/dist/core/run-analyze.d.ts +17 -0
  105. package/dist/core/run-analyze.js +231 -23
  106. package/dist/core/search/fts-indexes.d.ts +19 -1
  107. package/dist/core/search/fts-indexes.js +28 -1
  108. package/dist/core/wiki/generator.js +8 -0
  109. package/dist/core/wiki/grok-client.d.ts +21 -0
  110. package/dist/core/wiki/grok-client.js +287 -0
  111. package/dist/core/wiki/llm-client.d.ts +1 -1
  112. package/dist/core/wiki/llm-client.js +5 -2
  113. package/dist/core/wiki/local-cli-client.d.ts +11 -0
  114. package/dist/core/wiki/local-cli-client.js +22 -9
  115. package/dist/mcp/local/local-backend.d.ts +24 -7
  116. package/dist/mcp/local/local-backend.js +190 -81
  117. package/dist/mcp/local/pdg-impact.d.ts +8 -4
  118. package/dist/mcp/local/pdg-impact.js +7 -2
  119. package/dist/mcp/repository-policy.d.ts +5 -1
  120. package/dist/mcp/repository-policy.js +48 -4
  121. package/dist/mcp/resources.js +2 -1
  122. package/dist/mcp/server.js +6 -5
  123. package/dist/mcp/tools.js +31 -19
  124. package/dist/server/api.js +23 -64
  125. package/dist/server/grep-params.d.ts +18 -0
  126. package/dist/server/grep-params.js +83 -0
  127. package/dist/server/grep-scan.d.ts +23 -0
  128. package/dist/server/grep-scan.js +107 -0
  129. package/dist/server/grep-worker.d.ts +1 -0
  130. package/dist/server/grep-worker.js +12 -0
  131. package/dist/server/mcp-http.d.ts +8 -0
  132. package/dist/server/mcp-http.js +16 -1
  133. package/dist/storage/file-hash.d.ts +5 -0
  134. package/dist/storage/file-hash.js +16 -6
  135. package/dist/storage/fs-atomic.d.ts +24 -0
  136. package/dist/storage/fs-atomic.js +86 -2
  137. package/dist/storage/git.d.ts +19 -8
  138. package/dist/storage/git.js +83 -24
  139. package/dist/storage/gitnexus-managed-paths.d.ts +36 -0
  140. package/dist/storage/gitnexus-managed-paths.js +46 -0
  141. package/dist/storage/parse-cache.d.ts +22 -4
  142. package/dist/storage/parse-cache.js +106 -29
  143. package/dist/storage/parsedfile-store.d.ts +34 -60
  144. package/dist/storage/parsedfile-store.js +177 -171
  145. package/dist/storage/repo-manager.d.ts +11 -1
  146. package/dist/storage/repo-manager.js +23 -2
  147. package/dist/storage/repo-meta.d.ts +12 -0
  148. package/dist/storage/v8-sidecar.d.ts +48 -0
  149. package/dist/storage/v8-sidecar.js +347 -0
  150. package/dist/types/pipeline.d.ts +14 -0
  151. package/package.json +4 -1
  152. package/scripts/cross-platform-shard.ts +4 -2
  153. package/scripts/cross-platform-tests.ts +7 -1
  154. package/skills/gitnexus-cli.md +11 -3
  155. package/skills/gitnexus-impact-analysis.md +9 -0
  156. package/web/assets/{agent-Dr4l5EOp.js → agent-CFqT4hjR.js} +108 -104
  157. package/web/assets/{index-2zdvEdzg.js → index-BMIniRtX.js} +3 -3
  158. package/web/index.html +1 -1
@@ -0,0 +1,417 @@
1
+ import { booleanIsPrefixBase, jvmGetterName, jvmSetterName } from '../jvm/beanspec.js';
2
+ import { createExistingMethodIndex, createJvmAccessorSynthesis, hasExistingMethod, jvmTypeSimpleName, rememberExistingMethod, } from '../jvm/accessor-synthesis.js';
3
+ const KOTLIN_TYPE_DECLS = new Set(['class_declaration', 'object_declaration', 'companion_object']);
4
+ function capitalizeAscii(name) {
5
+ const first = name.charAt(0);
6
+ return first >= 'a' && first <= 'z'
7
+ ? String.fromCharCode(first.charCodeAt(0) - 32) + name.slice(1)
8
+ : name;
9
+ }
10
+ export function kotlinGetterName(propertyName) {
11
+ return jvmGetterName(propertyName, booleanIsPrefixBase(propertyName, true) !== null, capitalizeAscii);
12
+ }
13
+ export function kotlinSetterName(propertyName) {
14
+ return jvmSetterName(propertyName, true, capitalizeAscii);
15
+ }
16
+ function collectKotlinImports(root) {
17
+ const byLocalName = new Map();
18
+ const shadowedSimpleNames = new Set();
19
+ for (const child of root.children) {
20
+ if (child.type !== 'class_declaration')
21
+ continue;
22
+ const name = jvmTypeSimpleName(child);
23
+ if (name)
24
+ shadowedSimpleNames.add(name);
25
+ }
26
+ const importList = root.children.find((child) => child.type === 'import_list');
27
+ for (const child of importList?.children ?? []) {
28
+ if (child.type !== 'import_header')
29
+ continue;
30
+ const text = child.text
31
+ .replace(/^import\s+/, '')
32
+ .replace(/\/\*[\s\S]*?\*\//g, '')
33
+ .trim();
34
+ const [pathText, aliasText] = text.split(/\s+as\s+/, 2);
35
+ const importPath = pathText?.replace(/\s+/g, '');
36
+ if (!importPath || importPath.endsWith('.*'))
37
+ continue;
38
+ const localName = aliasText?.trim() || importPath.split('.').pop();
39
+ if (localName)
40
+ byLocalName.set(localName, importPath);
41
+ }
42
+ return { byLocalName, shadowedSimpleNames };
43
+ }
44
+ function annotationUserTypeText(annotation) {
45
+ const constructor = annotation.namedChildren.find((c) => c.type === 'constructor_invocation');
46
+ const userType = constructor?.namedChildren.find((c) => c.type === 'user_type') ??
47
+ annotation.namedChildren.find((c) => c.type === 'user_type');
48
+ return userType?.text ?? '';
49
+ }
50
+ function isKotlinJvmAnnotation(annotation, name, imports) {
51
+ const typeText = annotationUserTypeText(annotation);
52
+ const canonical = `kotlin.jvm.${name}`;
53
+ if (typeText.includes('.'))
54
+ return typeText === canonical;
55
+ const imported = imports.byLocalName.get(typeText);
56
+ if (imported !== undefined)
57
+ return imported === canonical;
58
+ if (imports.shadowedSimpleNames.has(typeText))
59
+ return false;
60
+ return typeText === name;
61
+ }
62
+ function kotlinVisibility(modifiers) {
63
+ if (!modifiers)
64
+ return 'public';
65
+ for (const child of modifiers.namedChildren) {
66
+ if (child.type !== 'visibility_modifier')
67
+ continue;
68
+ if (child.text === 'private')
69
+ return 'private';
70
+ if (child.text === 'protected')
71
+ return 'protected';
72
+ if (child.text === 'internal')
73
+ return 'package';
74
+ }
75
+ return 'public';
76
+ }
77
+ function hasJvmField(node, imports) {
78
+ const mods = node.children.find((c) => c.type === 'modifiers');
79
+ return (mods?.namedChildren.some((child) => child.type === 'annotation' && isKotlinJvmAnnotation(child, 'JvmField', imports)) === true);
80
+ }
81
+ function hasConst(node) {
82
+ const mods = node.children.find((c) => c.type === 'modifiers');
83
+ if (mods?.namedChildren.some((child) => child.type === 'property_modifier' && child.text === 'const')) {
84
+ return true;
85
+ }
86
+ return node.namedChildren.some((child) => child.type === 'const');
87
+ }
88
+ function isVarBinding(node) {
89
+ const kind = node.children.find((c) => c.type === 'binding_pattern_kind');
90
+ const text = kind?.text;
91
+ if (text === 'var')
92
+ return true;
93
+ if (text === 'val')
94
+ return false;
95
+ return null;
96
+ }
97
+ function inferredInitializerType(node) {
98
+ switch (node.type) {
99
+ case 'string_literal':
100
+ case 'line_string_literal':
101
+ case 'multi_line_string_literal':
102
+ return 'String';
103
+ case 'character_literal':
104
+ return 'Char';
105
+ case 'boolean_literal':
106
+ case 'true':
107
+ case 'false':
108
+ return 'Boolean';
109
+ case 'long_literal':
110
+ return 'Long';
111
+ case 'unsigned_literal':
112
+ return /l$/i.test(node.text) ? 'ULong' : 'UInt';
113
+ case 'integer_literal':
114
+ case 'decimal_integer_literal':
115
+ case 'hex_integer_literal':
116
+ case 'octal_integer_literal':
117
+ case 'binary_integer_literal':
118
+ return 'Int';
119
+ case 'real_literal':
120
+ case 'decimal_floating_point_literal':
121
+ return /f$/i.test(node.text) ? 'Float' : 'Double';
122
+ case 'prefix_expression': {
123
+ const operand = node.namedChildren.at(-1);
124
+ return operand ? inferredInitializerType(operand) : undefined;
125
+ }
126
+ case 'call_expression': {
127
+ const callee = node.namedChildren.find((child) => child.type === 'simple_identifier');
128
+ if (!callee)
129
+ return undefined;
130
+ const first = callee.text.charAt(0);
131
+ return first !== '' && first === first.toUpperCase() ? callee.text : undefined;
132
+ }
133
+ default:
134
+ return undefined;
135
+ }
136
+ }
137
+ function propertyTypeText(node) {
138
+ const declarator = node.type === 'class_parameter'
139
+ ? node
140
+ : (node.children.find((c) => c.type === 'variable_declaration') ?? node);
141
+ const colon = declarator.children.find((c) => c.type === ':');
142
+ let typeNode = colon?.nextNamedSibling ?? null;
143
+ while (typeNode?.type === 'type_modifiers')
144
+ typeNode = typeNode.nextNamedSibling;
145
+ if (typeNode)
146
+ return typeNode.text;
147
+ const initializer = node.namedChildren.find((child) => child.id !== declarator.id &&
148
+ child.type !== 'binding_pattern_kind' &&
149
+ child.type !== 'modifiers');
150
+ return initializer ? (inferredInitializerType(initializer) ?? 'unknown') : 'unknown';
151
+ }
152
+ function propertyNameNode(node) {
153
+ if (node.type === 'class_parameter') {
154
+ return node.children.find((c) => c.type === 'simple_identifier') ?? null;
155
+ }
156
+ const decl = node.children.find((c) => c.type === 'variable_declaration');
157
+ if (decl) {
158
+ return decl.children.find((c) => c.type === 'simple_identifier') ?? null;
159
+ }
160
+ return node.children.find((c) => c.type === 'simple_identifier') ?? null;
161
+ }
162
+ function accessorMetadata(prop, propertyVisibility, imports) {
163
+ let getter = propertyVisibility;
164
+ let setter = propertyVisibility;
165
+ let skipGetter = false;
166
+ let skipSetter = false;
167
+ const propertyModifiers = prop.children.find((c) => c.type === 'modifiers');
168
+ for (const annotation of propertyModifiers?.namedChildren ?? []) {
169
+ if (annotation.type !== 'annotation' ||
170
+ !isKotlinJvmAnnotation(annotation, 'JvmName', imports)) {
171
+ continue;
172
+ }
173
+ const target = annotation.children.find((c) => c.type === 'use_site_target')?.text;
174
+ if (target === 'get:')
175
+ skipGetter = true;
176
+ if (target === 'set:')
177
+ skipSetter = true;
178
+ }
179
+ const apply = (node) => {
180
+ const modifiers = node.children.find((c) => c.type === 'modifiers');
181
+ if (!modifiers)
182
+ return;
183
+ if (node.type === 'getter')
184
+ getter = kotlinVisibility(modifiers);
185
+ if (node.type === 'setter')
186
+ setter = kotlinVisibility(modifiers);
187
+ if (modifiers.namedChildren.some((annotation) => isKotlinJvmAnnotation(annotation, 'JvmName', imports))) {
188
+ if (node.type === 'getter')
189
+ skipGetter = true;
190
+ if (node.type === 'setter')
191
+ skipSetter = true;
192
+ }
193
+ };
194
+ for (const child of prop.children) {
195
+ if (child.type === 'getter' || child.type === 'setter')
196
+ apply(child);
197
+ }
198
+ let sib = prop.nextNamedSibling;
199
+ while (sib && (sib.type === 'getter' || sib.type === 'setter')) {
200
+ apply(sib);
201
+ sib = sib.nextNamedSibling;
202
+ }
203
+ return {
204
+ getterVisibility: getter,
205
+ setterVisibility: setter,
206
+ skipGetter,
207
+ skipSetter,
208
+ };
209
+ }
210
+ function hasKotlinAccessorBody(prop, kind) {
211
+ const hasBody = (node) => node.type === kind && node.children.some((child) => child.type === 'function_body');
212
+ if (prop.children.some(hasBody))
213
+ return true;
214
+ let sib = prop.nextNamedSibling;
215
+ while (sib && (sib.type === 'getter' || sib.type === 'setter')) {
216
+ if (hasBody(sib))
217
+ return true;
218
+ sib = sib.nextNamedSibling;
219
+ }
220
+ return false;
221
+ }
222
+ function functionName(node) {
223
+ return node.children.find((c) => c.type === 'simple_identifier')?.text;
224
+ }
225
+ function functionArity(node) {
226
+ const params = node.children.find((c) => c.type === 'function_value_parameters');
227
+ let arity = node.childForFieldName('receiver') !== null ||
228
+ node.namedChildren.some((child) => child.type === 'receiver_type')
229
+ ? 1
230
+ : 0;
231
+ const modifiers = node.children.find((child) => child.type === 'modifiers');
232
+ if (modifiers?.namedChildren.some((child) => child.type === 'function_modifier' && child.text === 'suspend')) {
233
+ arity += 1;
234
+ }
235
+ for (const child of params?.namedChildren ?? []) {
236
+ if (child.type === 'parameter' || child.type === 'parameter_with_optional_type')
237
+ arity += 1;
238
+ }
239
+ return arity;
240
+ }
241
+ function collectExistingMethods(...bodies) {
242
+ const index = createExistingMethodIndex('exact');
243
+ for (const body of bodies) {
244
+ if (!body)
245
+ continue;
246
+ for (const child of body.children) {
247
+ if (child.type !== 'function_declaration')
248
+ continue;
249
+ const name = functionName(child);
250
+ if (!name)
251
+ continue;
252
+ rememberExistingMethod(index, name, functionArity(child));
253
+ }
254
+ }
255
+ return index;
256
+ }
257
+ function toKtProperty(child, imports) {
258
+ const isVar = isVarBinding(child);
259
+ if (isVar === null)
260
+ return null;
261
+ if (hasJvmField(child, imports) || hasConst(child))
262
+ return null;
263
+ const nameNode = propertyNameNode(child);
264
+ if (!nameNode)
265
+ return null;
266
+ const mods = child.children.find((c) => c.type === 'modifiers');
267
+ const visibility = kotlinVisibility(mods);
268
+ const accessor = accessorMetadata(child, visibility, imports);
269
+ return {
270
+ name: nameNode.text,
271
+ type: propertyTypeText(child),
272
+ isVar,
273
+ skipGetter: accessor.skipGetter,
274
+ skipSetter: accessor.skipSetter,
275
+ getterVisibility: accessor.getterVisibility,
276
+ setterVisibility: accessor.setterVisibility,
277
+ startLine: child.startPosition.row + 1,
278
+ endLine: child.endPosition.row + 1,
279
+ propertyNode: child,
280
+ declaratorNode: nameNode,
281
+ };
282
+ }
283
+ function collectTypedProperties(parent, type, imports) {
284
+ if (!parent)
285
+ return [];
286
+ const out = [];
287
+ for (const child of parent.namedChildren) {
288
+ if (child.type !== type)
289
+ continue;
290
+ const prop = toKtProperty(child, imports);
291
+ if (prop)
292
+ out.push(prop);
293
+ }
294
+ return out;
295
+ }
296
+ function findKtClasses(root, imports) {
297
+ const classes = [];
298
+ const graphOwnerNode = (node) => {
299
+ if (node.type !== 'companion_object')
300
+ return node;
301
+ if (jvmTypeSimpleName(node))
302
+ return node;
303
+ let current = node.parent;
304
+ while (current && !KOTLIN_TYPE_DECLS.has(current.type))
305
+ current = current.parent;
306
+ return current ?? node;
307
+ };
308
+ const walk = (node) => {
309
+ if (KOTLIN_TYPE_DECLS.has(node.type)) {
310
+ const ownerNode = graphOwnerNode(node);
311
+ const name = jvmTypeSimpleName(ownerNode) ?? '';
312
+ const ctor = node.children.find((c) => c.type === 'primary_constructor') ?? null;
313
+ const body = node.children.find((c) => c.type === 'class_body') ?? null;
314
+ if (name) {
315
+ const properties = [
316
+ ...collectTypedProperties(ctor, 'class_parameter', imports),
317
+ ...collectTypedProperties(body, 'property_declaration', imports),
318
+ ];
319
+ if (properties.length > 0) {
320
+ const ownerBody = ownerNode.id === node.id
321
+ ? null
322
+ : (ownerNode.children.find((child) => child.type === 'class_body') ?? null);
323
+ classes.push({
324
+ node: ownerNode,
325
+ name,
326
+ isStatic: node.type === 'companion_object',
327
+ isInterface: node.children.some((child) => child.type === 'interface'),
328
+ wasHoisted: ownerNode.id !== node.id,
329
+ properties,
330
+ existingMethods: collectExistingMethods(body, ownerBody),
331
+ });
332
+ }
333
+ }
334
+ if (body) {
335
+ for (const child of body.namedChildren) {
336
+ if (KOTLIN_TYPE_DECLS.has(child.type))
337
+ walk(child);
338
+ }
339
+ }
340
+ return;
341
+ }
342
+ for (const child of node.namedChildren)
343
+ walk(child);
344
+ };
345
+ walk(root);
346
+ return classes;
347
+ }
348
+ function planAccessors(cls) {
349
+ const planned = [];
350
+ for (const prop of cls.properties) {
351
+ const gName = kotlinGetterName(prop.name);
352
+ if (!prop.skipGetter && !hasExistingMethod(cls.existingMethods, gName, 0)) {
353
+ planned.push({
354
+ kind: 'getter',
355
+ name: gName,
356
+ returnType: prop.type,
357
+ parameterTypes: [],
358
+ visibility: prop.getterVisibility,
359
+ isStatic: cls.isStatic,
360
+ isAbstract: cls.isInterface && !hasKotlinAccessorBody(prop.propertyNode, 'getter'),
361
+ startLine: prop.startLine,
362
+ endLine: prop.endLine,
363
+ declaratorNode: prop.declaratorNode,
364
+ });
365
+ }
366
+ if (prop.isVar && !prop.skipSetter) {
367
+ const sName = kotlinSetterName(prop.name);
368
+ if (!hasExistingMethod(cls.existingMethods, sName, 1)) {
369
+ planned.push({
370
+ kind: 'setter',
371
+ name: sName,
372
+ returnType: 'void',
373
+ parameterTypes: [prop.type],
374
+ visibility: prop.setterVisibility,
375
+ isStatic: cls.isStatic,
376
+ isAbstract: cls.isInterface && !hasKotlinAccessorBody(prop.propertyNode, 'setter'),
377
+ startLine: prop.startLine,
378
+ endLine: prop.endLine,
379
+ declaratorNode: prop.declaratorNode,
380
+ });
381
+ }
382
+ }
383
+ }
384
+ return planned;
385
+ }
386
+ function planKotlinAccessorOwners(rootNode) {
387
+ const owners = [];
388
+ const imports = collectKotlinImports(rootNode);
389
+ for (const cls of findKtClasses(rootNode, imports)) {
390
+ const accessors = planAccessors(cls);
391
+ const existingIndex = cls.wasHoisted
392
+ ? owners.findIndex((owner) => owner.node.id === cls.node.id)
393
+ : -1;
394
+ const existing = existingIndex >= 0 ? owners[existingIndex] : undefined;
395
+ if (existing) {
396
+ owners[existingIndex] = {
397
+ ...existing,
398
+ accessors: [...existing.accessors, ...accessors],
399
+ };
400
+ }
401
+ else {
402
+ owners.push({ node: cls.node, name: cls.name, accessors });
403
+ }
404
+ }
405
+ return owners;
406
+ }
407
+ const lombokAccessorSynthesis = createJvmAccessorSynthesis({
408
+ language: 'kotlin',
409
+ synthetic: 'kotlin-jvm',
410
+ planOwners: planKotlinAccessorOwners,
411
+ });
412
+ export function synthesizeLombokAccessors(tree, filePath, classOwnersById) {
413
+ return lombokAccessorSynthesis.synthesize(tree, filePath, classOwnersById);
414
+ }
415
+ export function synthesizeLombokAccessorCaptures(rootNode) {
416
+ return lombokAccessorSynthesis.captures(rootNode);
417
+ }
@@ -15,6 +15,7 @@ import { clearKotlinPackageFacts } from './package-facts.js';
15
15
  import { attachKotlinSpringDiMetadata } from './spring-di.js';
16
16
  import { attachKotlinSpringConditionalMetadata } from './spring-conditionals.js';
17
17
  import { attachKotlinSpringNonHttpHandlerMetadata } from './spring-non-http-handlers.js';
18
+ import { attachKotlinSpringDynamicLookup } from './spring-dynamic-lookup.js';
18
19
  /**
19
20
  * Kotlin scope resolver for RFC #909 Ring 3.
20
21
  *
@@ -121,6 +122,7 @@ export const kotlinScopeResolver = {
121
122
  attachKotlinSpringConditionalMetadata(graph, parsedFiles, nodeLookup, indexes);
122
123
  attachKotlinSpringDiMetadata(graph, parsedFiles, nodeLookup, indexes);
123
124
  attachKotlinSpringNonHttpHandlerMetadata(graph, parsedFiles, nodeLookup, indexes);
125
+ attachKotlinSpringDynamicLookup(graph, parsedFiles, nodeLookup, indexes);
124
126
  },
125
127
  };
126
128
  /**
@@ -0,0 +1,8 @@
1
+ import { type SpringDynamicLookupFact } from '../../frameworks/spring/dynamic-lookups.js';
2
+ import { type SyntaxNode } from '../../utils/ast-helpers.js';
3
+ /** Capture real Kotlin calls using `Type::class` or `Type::class.java`. */
4
+ export declare function captureKotlinSpringDynamicLookupFact(node: SyntaxNode, filePath: string): SpringDynamicLookupFact | null;
5
+ /** Standalone extractor for focused tests; production reuses scope-query call nodes. */
6
+ export declare function captureKotlinSpringDynamicLookupFacts(rootNode: SyntaxNode, filePath: string): SpringDynamicLookupFact[];
7
+ /** Attach Kotlin lookup facts for later resolution by the shared DI phase. */
8
+ export declare const attachKotlinSpringDynamicLookup: (graph: import("../../../graph/types.js").KnowledgeGraph, parsedFiles: readonly import('../../../../_shared/index.js').ParsedFile[], nodeLookup: import("../../scope-resolution/graph-bridge/node-lookup.js").GraphNodeLookup, indexes: import("../../model/scope-resolution-indexes.js").ScopeResolutionIndexes) => void;
@@ -0,0 +1,77 @@
1
+ import { makeScopeId } from '../../../../_shared/index.js';
2
+ import { createSpringDynamicLookupMetadataAttacher, springDynamicLookupCardinality, } from '../../frameworks/spring/dynamic-lookups.js';
3
+ import { findAncestorBeforeBoundary, nodeToCapture, } from '../../utils/ast-helpers.js';
4
+ import { getKotlinSpringDynamicLookupFacts } from './capture-side-channel.js';
5
+ // Kotlin emits graph callables for functions and secondary constructors.
6
+ // `init {}` / primary-constructor bodies have no independent callable node, so
7
+ // attributing their lookups to the enclosing Class would violate graph semantics.
8
+ const CALLABLE_NODE_TYPES = new Set(['function_declaration', 'secondary_constructor']);
9
+ const NO_CALLABLE_BOUNDARIES = new Set();
10
+ const KOTLIN_CLASS_LITERAL = /^([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*)::class(?:\.java)?$/;
11
+ function navigationParts(node) {
12
+ if (node.type !== 'navigation_expression')
13
+ return null;
14
+ const text = node.text.trim();
15
+ const separator = text.lastIndexOf('.');
16
+ if (separator <= 0 || separator === text.length - 1)
17
+ return null;
18
+ return {
19
+ receiverName: text.slice(0, separator),
20
+ methodName: text.slice(separator + 1),
21
+ };
22
+ }
23
+ function singleClassLiteralArgument(node) {
24
+ const suffix = node.namedChildren.find((child) => child.type === 'call_suffix');
25
+ const argumentsNode = suffix?.namedChildren.find((child) => child.type === 'value_arguments');
26
+ if (argumentsNode === undefined)
27
+ return null;
28
+ const argumentsWithoutComments = argumentsNode.namedChildren.filter((child) => child.type !== 'line_comment' && child.type !== 'multiline_comment');
29
+ if (argumentsWithoutComments.length !== 1)
30
+ return null;
31
+ const value = argumentsWithoutComments[0];
32
+ if (value?.type !== 'value_argument' || value.namedChildCount !== 1)
33
+ return null;
34
+ return value.namedChild(0)?.text.trim().match(KOTLIN_CLASS_LITERAL)?.[1] ?? null;
35
+ }
36
+ /** Capture real Kotlin calls using `Type::class` or `Type::class.java`. */
37
+ export function captureKotlinSpringDynamicLookupFact(node, filePath) {
38
+ if (node.type !== 'call_expression')
39
+ return null;
40
+ const callee = node.namedChildren.find((child) => child.type === 'navigation_expression');
41
+ if (callee === undefined)
42
+ return null;
43
+ const parts = navigationParts(callee);
44
+ if (parts === null)
45
+ return null;
46
+ if (springDynamicLookupCardinality(parts.receiverName, parts.methodName) === null)
47
+ return null;
48
+ const targetTypeName = singleClassLiteralArgument(node);
49
+ if (targetTypeName === null)
50
+ return null;
51
+ const owner = findAncestorBeforeBoundary(node, CALLABLE_NODE_TYPES, NO_CALLABLE_BOUNDARIES);
52
+ if (owner === null)
53
+ return null;
54
+ const ownerCapture = nodeToCapture('@spring-dynamic-lookup.owner', owner);
55
+ return {
56
+ ownerScopeId: makeScopeId({
57
+ filePath,
58
+ range: ownerCapture.range,
59
+ kind: 'Function',
60
+ }),
61
+ ownerRange: ownerCapture.range,
62
+ receiverName: parts.receiverName,
63
+ methodName: parts.methodName,
64
+ targetTypeName,
65
+ };
66
+ }
67
+ /** Standalone extractor for focused tests; production reuses scope-query call nodes. */
68
+ export function captureKotlinSpringDynamicLookupFacts(rootNode, filePath) {
69
+ return rootNode
70
+ .descendantsOfType('call_expression')
71
+ .map((node) => captureKotlinSpringDynamicLookupFact(node, filePath))
72
+ .filter((fact) => fact !== null);
73
+ }
74
+ /** Attach Kotlin lookup facts for later resolution by the shared DI phase. */
75
+ export const attachKotlinSpringDynamicLookup = createSpringDynamicLookupMetadataAttacher({
76
+ getFacts: getKotlinSpringDynamicLookupFacts,
77
+ });
@@ -28,6 +28,7 @@ import { kotlinMethodConfig } from '../method-extractors/configs/jvm.js';
28
28
  import { createVariableExtractor } from '../variable-extractors/generic.js';
29
29
  import { kotlinVariableConfig } from '../variable-extractors/configs/jvm.js';
30
30
  import { collectKotlinCaptureSideChannel, emitKotlinScopeCaptures, interpretKotlinImport, interpretKotlinTypeBinding, kotlinArityCompatibility, kotlinBindingScopeFor, kotlinImportOwningScope, kotlinMergeBindings, kotlinReceiverBinding, } from './kotlin/index.js';
31
+ import { synthesizeLombokAccessors } from './kotlin/lombok-synthesizer.js';
31
32
  /** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
32
33
  * Kotlin grammar uses function_declaration for both top-level functions and class methods.
33
34
  * Returns true when the captured definition node has a class_body ancestor. */
@@ -183,4 +184,5 @@ export const kotlinProvider = defineLanguage({
183
184
  mergeBindings: (_scope, bindings) => kotlinMergeBindings(bindings),
184
185
  receiverBinding: kotlinReceiverBinding,
185
186
  arityCompatibility: kotlinArityCompatibility,
187
+ synthesizeStructureMembers: synthesizeLombokAccessors,
186
188
  });
@@ -48,6 +48,9 @@ function providerCandidates(ids, providers) {
48
48
  // candidates when none are known instead of dropping the injection entirely.
49
49
  return recognized.length > 0 ? recognized : all;
50
50
  }
51
+ function isConcreteTypeNode(node) {
52
+ return node?.label === 'Class' || node?.label === 'Record' || node?.label === 'Enum';
53
+ }
51
54
  export const diPhase = {
52
55
  name: 'di',
53
56
  deps: ['mro'],
@@ -103,21 +106,25 @@ export const diPhase = {
103
106
  ambiguousInjections: 0,
104
107
  };
105
108
  }
106
- const interfaceToImplementers = new Map();
109
+ const directSubtypes = new Map();
107
110
  const directSupertypes = new Map();
108
111
  for (const rel of ctx.graph.iterRelationshipsByType('IMPLEMENTS')) {
109
- const set = interfaceToImplementers.get(rel.targetId) ?? new Set();
110
- set.add(rel.sourceId);
111
- interfaceToImplementers.set(rel.targetId, set);
112
+ const subtypes = directSubtypes.get(rel.targetId) ?? new Set();
113
+ subtypes.add(rel.sourceId);
114
+ directSubtypes.set(rel.targetId, subtypes);
112
115
  const supertypes = directSupertypes.get(rel.sourceId) ?? new Set();
113
116
  supertypes.add(rel.targetId);
114
117
  directSupertypes.set(rel.sourceId, supertypes);
115
118
  }
116
119
  for (const rel of ctx.graph.iterRelationshipsByType('EXTENDS')) {
120
+ const subtypes = directSubtypes.get(rel.targetId) ?? new Set();
121
+ subtypes.add(rel.sourceId);
122
+ directSubtypes.set(rel.targetId, subtypes);
117
123
  const supertypes = directSupertypes.get(rel.sourceId) ?? new Set();
118
124
  supertypes.add(rel.targetId);
119
125
  directSupertypes.set(rel.sourceId, supertypes);
120
126
  }
127
+ const orderedDirectSubtypes = new Map([...directSubtypes].map(([typeId, subtypes]) => [typeId, [...subtypes].sort().reverse()]));
121
128
  const memberToClass = new Map();
122
129
  for (const relationType of ['HAS_PROPERTY', 'HAS_METHOD']) {
123
130
  for (const rel of ctx.graph.iterRelationshipsByType(relationType)) {
@@ -128,18 +135,45 @@ export const diPhase = {
128
135
  const interfacesByLanguage = new Map();
129
136
  const classesByLanguage = new Map();
130
137
  ctx.graph.forEachNode((node) => {
131
- if (node.label !== 'Class' && node.label !== 'Interface')
138
+ const concreteType = isConcreteTypeNode(node);
139
+ if (!concreteType && node.label !== 'Interface')
132
140
  return;
133
141
  const language = node.properties.language;
134
142
  if (typeof language !== 'string' || !candidateLanguages.has(language))
135
143
  return;
136
- const indexes = node.label === 'Class' ? classesByLanguage : interfacesByLanguage;
144
+ const indexes = concreteType ? classesByLanguage : interfacesByLanguage;
137
145
  const index = indexes.get(language) ?? emptyNameIndex();
138
146
  addIndexedName(index, node);
139
147
  indexes.set(language, index);
140
- if (node.label === 'Class')
148
+ if (concreteType)
141
149
  providerNodes.set(node.id, node);
142
150
  });
151
+ const concreteSubtypesByRoot = new Map();
152
+ const concreteSubtypes = (rootTypeId, language) => {
153
+ const cacheKey = `${language}\0${rootTypeId}`;
154
+ const cached = concreteSubtypesByRoot.get(cacheKey);
155
+ if (cached !== undefined)
156
+ return cached;
157
+ const concrete = new Set();
158
+ const queue = [rootTypeId];
159
+ const visited = new Set();
160
+ while (queue.length > 0) {
161
+ const typeId = queue.pop();
162
+ if (typeId === undefined || visited.has(typeId))
163
+ continue;
164
+ visited.add(typeId);
165
+ const typeNode = ctx.graph.getNode(typeId);
166
+ if (typeNode !== undefined &&
167
+ isConcreteTypeNode(typeNode) &&
168
+ typeNode.properties.language === language) {
169
+ concrete.add(typeId);
170
+ }
171
+ const children = orderedDirectSubtypes.get(typeId) ?? [];
172
+ queue.push(...children);
173
+ }
174
+ concreteSubtypesByRoot.set(cacheKey, concrete);
175
+ return concrete;
176
+ };
143
177
  // A declaration returning a concrete class is assignable to every class or
144
178
  // interface that type extends/implements. Expand once per language+type and
145
179
  // register the declaration under those ancestor names. This keeps named
@@ -227,13 +261,12 @@ export const diPhase = {
227
261
  ambiguousTypeNames.add(candidate.targetTypeName);
228
262
  continue;
229
263
  }
230
- const structural = new Set();
231
- if (typeof classEntry === 'string')
232
- structural.add(classEntry);
233
- if (typeof interfaceEntry === 'string') {
234
- for (const id of interfaceToImplementers.get(interfaceEntry) ?? [])
235
- structural.add(id);
236
- }
264
+ const rootTypeId = typeof classEntry === 'string'
265
+ ? classEntry
266
+ : typeof interfaceEntry === 'string'
267
+ ? interfaceEntry
268
+ : undefined;
269
+ const structural = new Set(rootTypeId === undefined ? [] : concreteSubtypes(rootTypeId, candidate.language));
237
270
  for (const id of providedTypes.get(candidate.language)?.get(candidate.targetTypeName) ?? []) {
238
271
  structural.add(id);
239
272
  }
@@ -2,7 +2,7 @@
2
2
  * Parse implementation — chunked parse + resolve loop.
3
3
  *
4
4
  * This is the core parsing engine of the ingestion pipeline. It reads
5
- * source files in byte-budget chunks (~20MB each), parses via the worker
5
+ * source files in stable hash-bucket packs (~2MB each by default), parses via the worker
6
6
  * pool (the sole parse path — there is no sequential fallback), and emits
7
7
  * route CALLS edges. Import,
8
8
  * call, and inheritance resolution are owned by the scope-resolution
@@ -94,6 +94,8 @@ export declare function runChunkedParseAndResolve(graph: KnowledgeGraph, scanned
94
94
  * files. There is no sequential parser — the pool is the sole parse path
95
95
  * whenever a chunk misses the cache. */
96
96
  usedWorkerPool: boolean;
97
+ /** Files dispatched to parser workers after parse-cache lookup. */
98
+ reparsedFileCount: number;
97
99
  /** Worker-produced ParsedFile artifacts aggregated across chunks.
98
100
  * Threaded into scope-resolution as a re-extract cache so the warm-
99
101
  * cache analyze run can skip the dominant `extractParsedFile` cost