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,173 @@
1
+ import { toZeroBasedLine } from '../../utils/line-base.js';
2
+ export function createExistingMethodIndex(matching) {
3
+ return { matching, aritiesByName: new Map(), arityRangesByName: new Map() };
4
+ }
5
+ function methodKey(index, name) {
6
+ return index.matching === 'case-folded' ? name.toLowerCase() : name;
7
+ }
8
+ export function rememberExistingMethod(index, name, arity) {
9
+ const key = methodKey(index, name);
10
+ let arities = index.aritiesByName.get(key);
11
+ if (!arities) {
12
+ arities = new Set();
13
+ index.aritiesByName.set(key, arities);
14
+ }
15
+ arities.add(arity);
16
+ }
17
+ export function rememberExistingMethodRange(index, name, min, max) {
18
+ if (min === max) {
19
+ rememberExistingMethod(index, name, min);
20
+ return;
21
+ }
22
+ const key = methodKey(index, name);
23
+ const ranges = index.arityRangesByName.get(key) ?? [];
24
+ ranges.push({ min, max });
25
+ index.arityRangesByName.set(key, ranges);
26
+ }
27
+ export function hasExistingMethod(index, name, arity) {
28
+ const key = methodKey(index, name);
29
+ if (index.aritiesByName.get(key)?.has(arity) === true)
30
+ return true;
31
+ return (index.arityRangesByName.get(key)?.some((range) => range.min <= arity && arity <= range.max) ===
32
+ true);
33
+ }
34
+ export function createJvmAccessorSynthesis(config) {
35
+ return {
36
+ synthesize(tree, filePath, classOwnersById) {
37
+ const result = emptySyntheticAccessorResult();
38
+ for (const owner of config.planOwners(tree.rootNode)) {
39
+ const ownerId = classOwnersById.get(owner.node.id);
40
+ if (!ownerId)
41
+ continue;
42
+ emitPlannedAccessors({
43
+ planned: owner.accessors,
44
+ filePath,
45
+ ownerId,
46
+ idPrefix: ownerIdNamePrefix(ownerId, filePath, owner.name),
47
+ language: config.language,
48
+ synthetic: config.synthetic,
49
+ result,
50
+ });
51
+ }
52
+ return result;
53
+ },
54
+ captures(rootNode) {
55
+ return capturesForPlannedAccessors(config.planOwners(rootNode));
56
+ },
57
+ };
58
+ }
59
+ function emptySyntheticAccessorResult() {
60
+ return { symbols: [], nodes: [], relationships: [] };
61
+ }
62
+ function ownerIdNamePrefix(ownerId, filePath, fallback) {
63
+ const needle = `Class:${filePath}:`;
64
+ if (ownerId.startsWith(needle))
65
+ return ownerId.slice(needle.length);
66
+ const enumNeedle = `Enum:${filePath}:`;
67
+ if (ownerId.startsWith(enumNeedle))
68
+ return ownerId.slice(enumNeedle.length);
69
+ const ifaceNeedle = `Interface:${filePath}:`;
70
+ if (ownerId.startsWith(ifaceNeedle))
71
+ return ownerId.slice(ifaceNeedle.length);
72
+ return fallback;
73
+ }
74
+ export function jvmTypeSimpleName(node) {
75
+ const named = node.childForFieldName('name')?.text;
76
+ if (named)
77
+ return named;
78
+ for (const child of node.namedChildren) {
79
+ if (child.type === 'type_identifier' || child.type === 'simple_identifier')
80
+ return child.text;
81
+ }
82
+ return undefined;
83
+ }
84
+ function emitPlannedAccessors(args) {
85
+ const emittedIds = new Set();
86
+ for (const acc of args.planned) {
87
+ const arity = acc.parameterTypes.length;
88
+ const qualifiedName = `${args.idPrefix}.${acc.name}`;
89
+ const nodeId = `Method:${args.filePath}:${qualifiedName}#${arity}`;
90
+ if (emittedIds.has(nodeId))
91
+ continue;
92
+ emittedIds.add(nodeId);
93
+ args.result.nodes.push({
94
+ id: nodeId,
95
+ label: 'Method',
96
+ properties: {
97
+ name: acc.name,
98
+ filePath: args.filePath,
99
+ startLine: toZeroBasedLine(acc.startLine),
100
+ endLine: toZeroBasedLine(acc.endLine),
101
+ language: args.language,
102
+ isExported: false,
103
+ synthetic: args.synthetic,
104
+ visibility: acc.visibility,
105
+ isStatic: acc.isStatic,
106
+ returnType: acc.returnType,
107
+ parameterTypes: acc.parameterTypes,
108
+ parameterCount: arity,
109
+ qualifiedName,
110
+ },
111
+ });
112
+ args.result.symbols.push({
113
+ filePath: args.filePath,
114
+ name: acc.name,
115
+ nodeId,
116
+ type: 'Method',
117
+ ownerId: args.ownerId,
118
+ qualifiedName,
119
+ parameterCount: arity,
120
+ requiredParameterCount: arity,
121
+ parameterTypes: acc.parameterTypes,
122
+ returnType: acc.returnType,
123
+ visibility: acc.visibility,
124
+ isStatic: acc.isStatic,
125
+ isAbstract: acc.isAbstract,
126
+ isFinal: false,
127
+ });
128
+ args.result.relationships.push({
129
+ id: `HAS_METHOD:${args.ownerId}->${nodeId}`,
130
+ sourceId: args.ownerId,
131
+ targetId: nodeId,
132
+ type: 'HAS_METHOD',
133
+ confidence: 1.0,
134
+ reason: acc.kind === 'getter' ? `${args.synthetic}-getter` : `${args.synthetic}-setter`,
135
+ });
136
+ }
137
+ }
138
+ function accessorCapture(name, acc, text) {
139
+ const node = acc.declaratorNode;
140
+ const startLine = node.startPosition.row + 1;
141
+ const startCol = node.startPosition.column;
142
+ const endLine = node.endPosition.row + 1;
143
+ const endCol = acc.kind === 'getter' ? node.endPosition.column : startCol;
144
+ return { name, range: { startLine, startCol, endLine, endCol }, text };
145
+ }
146
+ function capturesForPlannedAccessors(owners) {
147
+ const captures = [];
148
+ for (const owner of owners) {
149
+ const enclosing = owner.name;
150
+ const emitted = new Set();
151
+ for (const acc of owner.accessors) {
152
+ const arity = String(acc.parameterTypes.length);
153
+ const qualifiedName = `${enclosing}.${acc.name}`;
154
+ const identity = `${qualifiedName}#${arity}`;
155
+ if (emitted.has(identity))
156
+ continue;
157
+ emitted.add(identity);
158
+ captures.push({
159
+ '@scope.function': accessorCapture('@scope.function', acc, acc.name),
160
+ });
161
+ captures.push({
162
+ '@declaration.method': accessorCapture('@declaration.method', acc, acc.name),
163
+ '@declaration.name': accessorCapture('@declaration.name', acc, acc.name),
164
+ '@declaration.qualified_name': accessorCapture('@declaration.qualified_name', acc, qualifiedName),
165
+ '@declaration.parameter-count': accessorCapture('@declaration.parameter-count', acc, arity),
166
+ '@declaration.required-parameter-count': accessorCapture('@declaration.required-parameter-count', acc, arity),
167
+ '@declaration.return-type': accessorCapture('@declaration.return-type', acc, acc.returnType),
168
+ '@declaration.is-synthetic': accessorCapture('@declaration.is-synthetic', acc, 'true'),
169
+ });
170
+ }
171
+ }
172
+ return captures;
173
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Language-neutral JVM JavaBeans naming primitives.
3
+ *
4
+ * Language adapters choose whether to invent/preserve an `is` prefix and
5
+ * which single-character capitalization policy their compiler uses.
6
+ */
7
+ export declare function capitalizeBeanName(s: string): string;
8
+ /**
9
+ * Primitive-boolean / Kotlin `is`-prefix fields whose name already starts with
10
+ * `is` plus a non-lowercase character keep that name for the getter and drop
11
+ * the `is` prefix for the setter base (`isEnabled` → `isEnabled()` /
12
+ * `setEnabled(...)`, `is1` → `is1()` / `set1(...)`). Digits and punctuation
13
+ * count as non-lowercase, matching Lombok `!Character.isLowerCase` and kotlinc.
14
+ */
15
+ export declare function booleanIsPrefixBase(fieldName: string, useIsPrefix: boolean): string | null;
16
+ export declare function jvmGetterName(fieldName: string, useIsPrefix: boolean, capitalize?: (name: string) => string): string;
17
+ export declare function jvmSetterName(fieldName: string, useIsPrefix: boolean, capitalize?: (name: string) => string): string;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Language-neutral JVM JavaBeans naming primitives.
3
+ *
4
+ * Language adapters choose whether to invent/preserve an `is` prefix and
5
+ * which single-character capitalization policy their compiler uses.
6
+ */
7
+ export function capitalizeBeanName(s) {
8
+ if (s.length === 0)
9
+ return s;
10
+ const first = s.charAt(0);
11
+ const upper = first.toUpperCase();
12
+ // Java Character case conversion is one UTF-16 code unit. JavaScript
13
+ // full-case conversion may expand one unit (`ß` → `SS`), which would invent
14
+ // a method name no JVM compiler emits.
15
+ return (upper.length === 1 ? upper : first) + s.slice(1);
16
+ }
17
+ /**
18
+ * Primitive-boolean / Kotlin `is`-prefix fields whose name already starts with
19
+ * `is` plus a non-lowercase character keep that name for the getter and drop
20
+ * the `is` prefix for the setter base (`isEnabled` → `isEnabled()` /
21
+ * `setEnabled(...)`, `is1` → `is1()` / `set1(...)`). Digits and punctuation
22
+ * count as non-lowercase, matching Lombok `!Character.isLowerCase` and kotlinc.
23
+ */
24
+ export function booleanIsPrefixBase(fieldName, useIsPrefix) {
25
+ if (!useIsPrefix || !fieldName.startsWith('is') || fieldName.length < 3)
26
+ return null;
27
+ const third = fieldName.charAt(2);
28
+ return third === third.toUpperCase() ? fieldName.slice(2) : null;
29
+ }
30
+ export function jvmGetterName(fieldName, useIsPrefix, capitalize = capitalizeBeanName) {
31
+ if (booleanIsPrefixBase(fieldName, useIsPrefix) !== null)
32
+ return fieldName;
33
+ if (useIsPrefix)
34
+ return `is${capitalize(fieldName)}`;
35
+ return `get${capitalize(fieldName)}`;
36
+ }
37
+ export function jvmSetterName(fieldName, useIsPrefix, capitalize = capitalizeBeanName) {
38
+ const stripped = booleanIsPrefixBase(fieldName, useIsPrefix);
39
+ if (stripped !== null)
40
+ return `set${stripped}`;
41
+ return `set${capitalize(fieldName)}`;
42
+ }
@@ -39,6 +39,7 @@
39
39
  import type { ParsedFile, ScopeId } from '../../../../_shared/index.js';
40
40
  import { type ClassAnnotationFact } from '../../frameworks/spring/bean-candidates.js';
41
41
  import { type JvmPackageFact } from '../jvm/package-facts.js';
42
+ import type { SpringDynamicLookupFact } from '../../frameworks/spring/dynamic-lookups.js';
42
43
  import type { KotlinSpringAopFact } from './spring-aop.js';
43
44
  import type { KotlinSpringConditionalFact } from './spring-conditionals.js';
44
45
  import type { KotlinSpringDiClassFact } from './spring-di.js';
@@ -63,6 +64,8 @@ export interface KotlinCaptureSideChannel {
63
64
  readonly springConditionalFacts?: readonly KotlinSpringConditionalFact[];
64
65
  /** Constructor, property, and method injection syntax captured per class. */
65
66
  readonly springDiFacts?: readonly KotlinSpringDiClassFact[];
67
+ /** Programmatic Spring bean lookups captured per callable. */
68
+ readonly springDynamicLookupFacts?: readonly SpringDynamicLookupFact[];
66
69
  /** Scheduled, event, messaging, and managed-job handler syntax captured per callable. */
67
70
  readonly springNonHttpHandlerFacts?: readonly KotlinSpringNonHttpHandlerFact[];
68
71
  }
@@ -75,6 +78,8 @@ export declare function setKotlinSpringConditionalFacts(filePath: string, facts:
75
78
  export declare function getKotlinSpringConditionalFacts(filePath: string): readonly KotlinSpringConditionalFact[];
76
79
  export declare function setKotlinSpringDiFacts(filePath: string, facts: readonly KotlinSpringDiClassFact[]): void;
77
80
  export declare function getKotlinSpringDiFacts(filePath: string): readonly KotlinSpringDiClassFact[];
81
+ export declare function setKotlinSpringDynamicLookupFacts(filePath: string, facts: readonly SpringDynamicLookupFact[]): void;
82
+ export declare function getKotlinSpringDynamicLookupFacts(filePath: string): readonly SpringDynamicLookupFact[];
78
83
  export declare function setKotlinSpringNonHttpHandlerFacts(filePath: string, facts: readonly KotlinSpringNonHttpHandlerFact[]): void;
79
84
  export declare function getKotlinSpringNonHttpHandlerFacts(filePath: string): readonly KotlinSpringNonHttpHandlerFact[];
80
85
  /**
@@ -44,12 +44,14 @@ const classAnnotations = createClassAnnotationFactStore();
44
44
  const springAopFacts = new Map();
45
45
  const springConditionalFacts = new Map();
46
46
  const springDiFacts = new Map();
47
+ const springDynamicLookupFacts = new Map();
47
48
  const springNonHttpHandlerFacts = new Map();
48
49
  export function clearKotlinClassAnnotationFacts() {
49
50
  classAnnotations.clear();
50
51
  springAopFacts.clear();
51
52
  springConditionalFacts.clear();
52
53
  springDiFacts.clear();
54
+ springDynamicLookupFacts.clear();
53
55
  springNonHttpHandlerFacts.clear();
54
56
  }
55
57
  export function setKotlinSpringAopFacts(filePath, facts) {
@@ -85,6 +87,15 @@ export function setKotlinSpringDiFacts(filePath, facts) {
85
87
  export function getKotlinSpringDiFacts(filePath) {
86
88
  return springDiFacts.get(filePath) ?? [];
87
89
  }
90
+ export function setKotlinSpringDynamicLookupFacts(filePath, facts) {
91
+ if (facts.length === 0)
92
+ springDynamicLookupFacts.delete(filePath);
93
+ else
94
+ springDynamicLookupFacts.set(filePath, facts);
95
+ }
96
+ export function getKotlinSpringDynamicLookupFacts(filePath) {
97
+ return springDynamicLookupFacts.get(filePath) ?? [];
98
+ }
88
99
  export function setKotlinSpringNonHttpHandlerFacts(filePath, facts) {
89
100
  if (facts.length === 0)
90
101
  springNonHttpHandlerFacts.delete(filePath);
@@ -105,6 +116,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
105
116
  const aopFacts = springAopFacts.get(filePath) ?? [];
106
117
  const conditionFacts = springConditionalFacts.get(filePath) ?? [];
107
118
  const diFacts = springDiFacts.get(filePath) ?? [];
119
+ const dynamicLookupFacts = springDynamicLookupFacts.get(filePath) ?? [];
108
120
  const nonHttpHandlerFacts = springNonHttpHandlerFacts.get(filePath) ?? [];
109
121
  const packageFact = getKotlinPackageFact(filePath);
110
122
  if (companionScopes.length === 0 &&
@@ -112,6 +124,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
112
124
  aopFacts.length === 0 &&
113
125
  conditionFacts.length === 0 &&
114
126
  diFacts.length === 0 &&
127
+ dynamicLookupFacts.length === 0 &&
115
128
  nonHttpHandlerFacts.length === 0 &&
116
129
  packageFact === undefined) {
117
130
  return undefined;
@@ -124,6 +137,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
124
137
  ...(aopFacts.length > 0 ? { springAopFacts: aopFacts } : {}),
125
138
  ...(conditionFacts.length > 0 ? { springConditionalFacts: conditionFacts } : {}),
126
139
  ...(diFacts.length > 0 ? { springDiFacts: diFacts } : {}),
140
+ ...(dynamicLookupFacts.length > 0 ? { springDynamicLookupFacts: dynamicLookupFacts } : {}),
127
141
  ...(nonHttpHandlerFacts.length > 0 ? { springNonHttpHandlerFacts: nonHttpHandlerFacts } : {}),
128
142
  };
129
143
  }
@@ -147,6 +161,7 @@ export function applyKotlinCaptureSideChannel(parsed) {
147
161
  setKotlinSpringAopFacts(parsed.filePath, []);
148
162
  setKotlinSpringConditionalFacts(parsed.filePath, []);
149
163
  setKotlinSpringDiFacts(parsed.filePath, []);
164
+ setKotlinSpringDynamicLookupFacts(parsed.filePath, []);
150
165
  setKotlinSpringNonHttpHandlerFacts(parsed.filePath, []);
151
166
  setKotlinPackageFact(parsed.filePath, UNKNOWN_JVM_PACKAGE_FACT);
152
167
  return;
@@ -158,6 +173,7 @@ export function applyKotlinCaptureSideChannel(parsed) {
158
173
  setKotlinSpringAopFacts(parsed.filePath, Array.isArray(data.springAopFacts) ? data.springAopFacts : []);
159
174
  setKotlinSpringConditionalFacts(parsed.filePath, Array.isArray(data.springConditionalFacts) ? data.springConditionalFacts : []);
160
175
  setKotlinSpringDiFacts(parsed.filePath, Array.isArray(data.springDiFacts) ? data.springDiFacts : []);
176
+ setKotlinSpringDynamicLookupFacts(parsed.filePath, Array.isArray(data.springDynamicLookupFacts) ? data.springDynamicLookupFacts : []);
161
177
  setKotlinSpringNonHttpHandlerFacts(parsed.filePath, Array.isArray(data.springNonHttpHandlerFacts) ? data.springNonHttpHandlerFacts : []);
162
178
  setKotlinPackageFact(parsed.filePath, isJvmPackageFact(data.packageFact) ? data.packageFact : UNKNOWN_JVM_PACKAGE_FACT);
163
179
  }
@@ -10,10 +10,12 @@ import { normalizeKotlinType } from './interpret.js';
10
10
  import { synthesizeKotlinReceiverBinding } from './receiver-binding.js';
11
11
  import { getKotlinParser, getKotlinScopeQuery } from './query.js';
12
12
  import { markCompanionScope } from './companion-scopes.js';
13
- import { setKotlinClassAnnotationFacts, setKotlinSpringAopFacts, setKotlinSpringConditionalFacts, setKotlinSpringDiFacts, setKotlinSpringNonHttpHandlerFacts, } from './capture-side-channel.js';
13
+ import { setKotlinClassAnnotationFacts, setKotlinSpringAopFacts, setKotlinSpringConditionalFacts, setKotlinSpringDiFacts, setKotlinSpringDynamicLookupFacts, setKotlinSpringNonHttpHandlerFacts, } from './capture-side-channel.js';
14
14
  import { captureKotlinPackageFact } from './package-facts.js';
15
15
  import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
16
+ import { synthesizeLombokAccessorCaptures } from './lombok-synthesizer.js';
16
17
  import { captureKotlinSpringDiClassFact } from './spring-di.js';
18
+ import { captureKotlinSpringDynamicLookupFact } from './spring-dynamic-lookup.js';
17
19
  import { synthesizeReceiverChainCapture } from '../../utils/receiver-chain-captures.js';
18
20
  import { captureKotlinSpringAopFacts } from './spring-aop.js';
19
21
  import { captureKotlinSpringConditionalFacts, } from './spring-conditionals.js';
@@ -75,6 +77,8 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
75
77
  const springNonHttpHandlerFacts = [];
76
78
  const springNonHttpHandlerTypeNodeIds = new Set();
77
79
  const springDiClassNodeIds = new Set();
80
+ const springDynamicLookupFacts = [];
81
+ const springDynamicLookupNodeIds = new Set();
78
82
  const returnTypes = collectKotlinReturnTypeTexts(tree.rootNode);
79
83
  out.push(...synthesizeKotlinLocalAssignmentBindings(tree.rootNode, returnTypes));
80
84
  out.push(...synthesizeKotlinLoopBindings(tree.rootNode, returnTypes));
@@ -97,6 +101,13 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
97
101
  }
98
102
  if (Object.keys(grouped).length === 0)
99
103
  continue;
104
+ const dynamicLookupNode = nodeIfType(groupedNodes['@reference.call.member'], 'call_expression');
105
+ if (dynamicLookupNode !== null && !springDynamicLookupNodeIds.has(dynamicLookupNode.id)) {
106
+ springDynamicLookupNodeIds.add(dynamicLookupNode.id);
107
+ const fact = captureKotlinSpringDynamicLookupFact(dynamicLookupNode, filePath);
108
+ if (fact !== null)
109
+ springDynamicLookupFacts.push(fact);
110
+ }
100
111
  // tree-sitter-kotlin represents both classes and interfaces with
101
112
  // `class_declaration`; `object_declaration` is the separate object form.
102
113
  const springAopTypeNode = [
@@ -279,7 +290,9 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
279
290
  setKotlinSpringAopFacts(filePath, springAopFacts);
280
291
  setKotlinSpringConditionalFacts(filePath, springConditionalFacts);
281
292
  setKotlinSpringDiFacts(filePath, springDiFacts);
293
+ setKotlinSpringDynamicLookupFacts(filePath, springDynamicLookupFacts);
282
294
  setKotlinSpringNonHttpHandlerFacts(filePath, springNonHttpHandlerFacts);
295
+ out.push(...synthesizeLombokAccessorCaptures(tree.rootNode));
283
296
  out.push(...synthesizeCallableFlowCaptures(tree.rootNode, KOTLIN_CALLABLE_CAPTURE_OPTIONS));
284
297
  return out;
285
298
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Kotlin accessor synthesizer (same provider-hook role as Java Lombok).
3
+ *
4
+ * kotlinc emits JavaBeans getters/setters for `val`/`var` properties. Those
5
+ * methods are absent from the tree-sitter AST, so Java (and Kotlin) calls
6
+ * like `user.getName()` miss CALLS edges. Planning is Kotlin-specific;
7
+ * naming and Method emission share `jvm/beanspec` + `jvm/accessor-synthesis`.
8
+ *
9
+ * ## Supported subset (v1)
10
+ * - Class / data class / object / companion / interface `val`/`var` properties
11
+ * (interface accessors without a custom body are abstract JVM methods).
12
+ * - Primary-constructor `val`/`var` class parameters.
13
+ * - Names beginning with `is` + a non-lowercase character keep that getter name; all other
14
+ * properties, including `Boolean`, use `get`.
15
+ * - Custom `get()`/`set()` bodies still emit their JVM accessor Methods.
16
+ * - Explicit `fun getX` / `@JvmField` / `const` skip synthesis.
17
+ * - `@JvmName`-renamed accessors are suppressed until custom-name emission lands.
18
+ * Unsupported: `@JvmStatic` renaming, file-facade top-level properties.
19
+ */
20
+ import type Parser from 'tree-sitter';
21
+ import type { CaptureMatch } from '../../../../_shared/index.js';
22
+ import { type SyntheticAccessorResult } from '../jvm/accessor-synthesis.js';
23
+ export declare function kotlinGetterName(propertyName: string): string;
24
+ export declare function kotlinSetterName(propertyName: string): string;
25
+ export declare function synthesizeLombokAccessors(tree: Parser.Tree, filePath: string, classOwnersById: ReadonlyMap<number, string>): SyntheticAccessorResult;
26
+ export declare function synthesizeLombokAccessorCaptures(rootNode: Parser.SyntaxNode): CaptureMatch[];