cgraphx 1.1.0 → 1.3.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 (201) hide show
  1. package/README.md +0 -1
  2. package/dist/.claude-template/hooks/precommit-check/precommit-check.cjs +90 -0
  3. package/dist/.claude-template/skills/cgraphx/SKILL.md +3 -3
  4. package/dist/.claude-template/skills/cgraphx/agent-prompt.md +1 -1
  5. package/dist/.claude-template/skills/cgraphx-guide/SKILL.md +94 -0
  6. package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +424 -0
  7. package/dist/.claude-template/skills/clarify-requirements/SKILL.md +19 -8
  8. package/dist/.claude-template/skills/code-impact-docgen/SKILL.md +186 -176
  9. package/dist/.claude-template/skills/code-impact-docgen/template-design-html.md +357 -0
  10. package/dist/.claude-template/skills/code-impact-docgen/template-design-md.md +164 -0
  11. package/dist/.claude-template/skills/code-impact-init/SKILL.md +47 -47
  12. package/dist/.claude-template/skills/developer-timeline/SKILL.md +9 -0
  13. package/dist/.claude-template/skills/precommit-review/SKILL.md +50 -0
  14. package/dist/.claude-template/skills/run-api-test/SKILL.md +187 -0
  15. package/dist/.claude-template/skills/run-api-test/assets/template-test-report.md +103 -0
  16. package/dist/.claude-template/skills/run-api-test/assets/template-test-verify.jsonl +5 -0
  17. package/dist/.claude-template/skills/run-api-test/references/bru-run.md +60 -0
  18. package/dist/.claude-template/skills/run-api-test/references/db-verification.md +81 -0
  19. package/dist/.claude-template/skills/run-api-test/references/report-format.md +104 -0
  20. package/dist/.claude-template/skills/run-api-test/references/service-readiness.md +61 -0
  21. package/dist/.claude-template/skills/run-api-test/references/test-scope.md +64 -0
  22. package/dist/.claude-template/skills/write-api/SKILL.md +150 -0
  23. package/dist/.claude-template/skills/write-api/assets/template-api-spec.md +112 -0
  24. package/dist/.claude-template/skills/write-api/assets/template-request.bru +75 -0
  25. package/dist/.claude-template/skills/write-api/references/ai-prompts.md +133 -0
  26. package/dist/.claude-template/skills/write-api/references/api-spec-format.md +108 -0
  27. package/dist/.claude-template/skills/write-api/references/bru-format.md +144 -0
  28. package/dist/.claude-template/skills/write-api/references/collection-layout.md +81 -0
  29. package/dist/.claude-template/skills/write-api/references/environment-setup.md +105 -0
  30. package/dist/.claude-template/skills/write-api/references/interface-scope.md +74 -0
  31. package/dist/.claude-template/skills/write-api-doc/SKILL.md +317 -0
  32. package/dist/.claude-template/skills/write-api-doc/template-api-html.md +422 -0
  33. package/dist/.claude-template/skills/write-plan/SKILL.md +38 -16
  34. package/dist/.claude-template/skills/write-prd/SKILL.md +32 -8
  35. package/dist/.claude-template/skills/write-spec/SKILL.md +34 -9
  36. package/dist/api-test/ai-fields.d.ts +37 -0
  37. package/dist/api-test/ai-fields.d.ts.map +1 -0
  38. package/dist/api-test/ai-fields.js +114 -0
  39. package/dist/api-test/ai-fields.js.map +1 -0
  40. package/dist/api-test/assemble.d.ts +76 -0
  41. package/dist/api-test/assemble.d.ts.map +1 -0
  42. package/dist/api-test/assemble.js +185 -0
  43. package/dist/api-test/assemble.js.map +1 -0
  44. package/dist/api-test/bru-cli-invoker.d.ts +72 -0
  45. package/dist/api-test/bru-cli-invoker.d.ts.map +1 -0
  46. package/dist/api-test/bru-cli-invoker.js +169 -0
  47. package/dist/api-test/bru-cli-invoker.js.map +1 -0
  48. package/dist/api-test/bru-report-parser.d.ts +24 -0
  49. package/dist/api-test/bru-report-parser.d.ts.map +1 -0
  50. package/dist/api-test/bru-report-parser.js +110 -0
  51. package/dist/api-test/bru-report-parser.js.map +1 -0
  52. package/dist/api-test/bru-runner.d.ts +101 -0
  53. package/dist/api-test/bru-runner.d.ts.map +1 -0
  54. package/dist/api-test/bru-runner.js +316 -0
  55. package/dist/api-test/bru-runner.js.map +1 -0
  56. package/dist/api-test/bru-writer.d.ts +52 -0
  57. package/dist/api-test/bru-writer.d.ts.map +1 -0
  58. package/dist/api-test/bru-writer.js +159 -0
  59. package/dist/api-test/bru-writer.js.map +1 -0
  60. package/dist/api-test/call-chain-extractor.d.ts +80 -0
  61. package/dist/api-test/call-chain-extractor.d.ts.map +1 -0
  62. package/dist/api-test/call-chain-extractor.js +179 -0
  63. package/dist/api-test/call-chain-extractor.js.map +1 -0
  64. package/dist/api-test/cli.d.ts +133 -0
  65. package/dist/api-test/cli.d.ts.map +1 -0
  66. package/dist/api-test/cli.js +1009 -0
  67. package/dist/api-test/cli.js.map +1 -0
  68. package/dist/api-test/config.d.ts +75 -0
  69. package/dist/api-test/config.d.ts.map +1 -0
  70. package/dist/api-test/config.js +406 -0
  71. package/dist/api-test/config.js.map +1 -0
  72. package/dist/api-test/db-query-cli.d.ts +51 -0
  73. package/dist/api-test/db-query-cli.d.ts.map +1 -0
  74. package/dist/api-test/db-query-cli.js +119 -0
  75. package/dist/api-test/db-query-cli.js.map +1 -0
  76. package/dist/api-test/enhance-prepare.d.ts +111 -0
  77. package/dist/api-test/enhance-prepare.d.ts.map +1 -0
  78. package/dist/api-test/enhance-prepare.js +425 -0
  79. package/dist/api-test/enhance-prepare.js.map +1 -0
  80. package/dist/api-test/enhance-write.d.ts +28 -0
  81. package/dist/api-test/enhance-write.d.ts.map +1 -0
  82. package/dist/api-test/enhance-write.js +145 -0
  83. package/dist/api-test/enhance-write.js.map +1 -0
  84. package/dist/api-test/errors.d.ts +48 -0
  85. package/dist/api-test/errors.d.ts.map +1 -0
  86. package/dist/api-test/errors.js +76 -0
  87. package/dist/api-test/errors.js.map +1 -0
  88. package/dist/api-test/field-extractor.d.ts +98 -0
  89. package/dist/api-test/field-extractor.d.ts.map +1 -0
  90. package/dist/api-test/field-extractor.js +327 -0
  91. package/dist/api-test/field-extractor.js.map +1 -0
  92. package/dist/api-test/impl-finder.d.ts +37 -0
  93. package/dist/api-test/impl-finder.d.ts.map +1 -0
  94. package/dist/api-test/impl-finder.js +54 -0
  95. package/dist/api-test/impl-finder.js.map +1 -0
  96. package/dist/api-test/index.d.ts +41 -0
  97. package/dist/api-test/index.d.ts.map +1 -0
  98. package/dist/api-test/index.js +124 -0
  99. package/dist/api-test/index.js.map +1 -0
  100. package/dist/api-test/java-parser.d.ts +89 -0
  101. package/dist/api-test/java-parser.d.ts.map +1 -0
  102. package/dist/api-test/java-parser.js +508 -0
  103. package/dist/api-test/java-parser.js.map +1 -0
  104. package/dist/api-test/md-writer.d.ts +49 -0
  105. package/dist/api-test/md-writer.d.ts.map +1 -0
  106. package/dist/api-test/md-writer.js +202 -0
  107. package/dist/api-test/md-writer.js.map +1 -0
  108. package/dist/api-test/parser-httpservice.d.ts +91 -0
  109. package/dist/api-test/parser-httpservice.d.ts.map +1 -0
  110. package/dist/api-test/parser-httpservice.js +271 -0
  111. package/dist/api-test/parser-httpservice.js.map +1 -0
  112. package/dist/api-test/report.d.ts +188 -0
  113. package/dist/api-test/report.d.ts.map +1 -0
  114. package/dist/api-test/report.js +522 -0
  115. package/dist/api-test/report.js.map +1 -0
  116. package/dist/api-test/snapshot.d.ts +26 -0
  117. package/dist/api-test/snapshot.d.ts.map +1 -0
  118. package/dist/api-test/snapshot.js +150 -0
  119. package/dist/api-test/snapshot.js.map +1 -0
  120. package/dist/api-test/test-history.d.ts +48 -0
  121. package/dist/api-test/test-history.d.ts.map +1 -0
  122. package/dist/api-test/test-history.js +122 -0
  123. package/dist/api-test/test-history.js.map +1 -0
  124. package/dist/api-test/types.d.ts +174 -0
  125. package/dist/api-test/types.d.ts.map +1 -0
  126. package/dist/api-test/types.js +13 -0
  127. package/dist/api-test/types.js.map +1 -0
  128. package/dist/api-test/verify-prepare.d.ts +30 -0
  129. package/dist/api-test/verify-prepare.d.ts.map +1 -0
  130. package/dist/api-test/verify-prepare.js +150 -0
  131. package/dist/api-test/verify-prepare.js.map +1 -0
  132. package/dist/api-test/verify-write.d.ts +31 -0
  133. package/dist/api-test/verify-write.d.ts.map +1 -0
  134. package/dist/api-test/verify-write.js +159 -0
  135. package/dist/api-test/verify-write.js.map +1 -0
  136. package/dist/bin/codegraph.js +0 -100
  137. package/dist/bin/codegraph.js.map +1 -1
  138. package/dist/dbquery/dump-schema.d.ts +46 -0
  139. package/dist/dbquery/dump-schema.d.ts.map +1 -0
  140. package/dist/dbquery/dump-schema.js +379 -0
  141. package/dist/dbquery/dump-schema.js.map +1 -0
  142. package/dist/installer/targets/claude.d.ts +15 -0
  143. package/dist/installer/targets/claude.d.ts.map +1 -1
  144. package/dist/installer/targets/claude.js +53 -0
  145. package/dist/installer/targets/claude.js.map +1 -1
  146. package/dist/resolution/index.d.ts.map +1 -1
  147. package/dist/resolution/index.js +13 -0
  148. package/dist/resolution/index.js.map +1 -1
  149. package/dist/resolution/scope-index.d.ts +86 -0
  150. package/dist/resolution/scope-index.d.ts.map +1 -0
  151. package/dist/resolution/scope-index.js +143 -0
  152. package/dist/resolution/scope-index.js.map +1 -0
  153. package/dist/resolution/stdlib-blocklist.d.ts +53 -0
  154. package/dist/resolution/stdlib-blocklist.d.ts.map +1 -0
  155. package/dist/resolution/stdlib-blocklist.js +143 -0
  156. package/dist/resolution/stdlib-blocklist.js.map +1 -0
  157. package/dist/search/ast-helpers.d.ts +42 -0
  158. package/dist/search/ast-helpers.d.ts.map +1 -0
  159. package/dist/search/ast-helpers.js +106 -0
  160. package/dist/search/ast-helpers.js.map +1 -0
  161. package/dist/search/call-sites.d.ts +398 -0
  162. package/dist/search/call-sites.d.ts.map +1 -0
  163. package/dist/search/call-sites.js +1433 -0
  164. package/dist/search/call-sites.js.map +1 -0
  165. package/dist/search/context.d.ts +134 -0
  166. package/dist/search/context.d.ts.map +1 -0
  167. package/dist/search/context.js +575 -0
  168. package/dist/search/context.js.map +1 -0
  169. package/dist/search/impact.d.ts +139 -0
  170. package/dist/search/impact.d.ts.map +1 -0
  171. package/dist/search/impact.js +646 -0
  172. package/dist/search/impact.js.map +1 -0
  173. package/dist/search/related.d.ts +178 -0
  174. package/dist/search/related.d.ts.map +1 -0
  175. package/dist/search/related.js +667 -0
  176. package/dist/search/related.js.map +1 -0
  177. package/dist/search/slice.d.ts +148 -0
  178. package/dist/search/slice.d.ts.map +1 -0
  179. package/dist/search/slice.js +460 -0
  180. package/dist/search/slice.js.map +1 -0
  181. package/dist/search/snr-constants.d.ts +41 -0
  182. package/dist/search/snr-constants.d.ts.map +1 -0
  183. package/dist/search/snr-constants.js +44 -0
  184. package/dist/search/snr-constants.js.map +1 -0
  185. package/dist/search/types.d.ts +28 -0
  186. package/dist/search/types.d.ts.map +1 -0
  187. package/dist/search/types.js +12 -0
  188. package/dist/search/types.js.map +1 -0
  189. package/dist/timeline/cli.d.ts.map +1 -1
  190. package/dist/timeline/cli.js +22 -3
  191. package/dist/timeline/cli.js.map +1 -1
  192. package/dist/timeline/store.d.ts +5 -0
  193. package/dist/timeline/store.d.ts.map +1 -1
  194. package/dist/timeline/store.js +23 -3
  195. package/dist/timeline/store.js.map +1 -1
  196. package/package.json +1 -1
  197. package/scripts/agent-eval/subagent-token-cost.py +188 -0
  198. package/dist/.claude-template/skills/code-impact-docgen/template-business-html.md +0 -242
  199. package/dist/.claude-template/skills/code-impact-docgen/template-business-md.md +0 -107
  200. package/dist/.claude-template/skills/code-impact-docgen/template-technical-html.md +0 -205
  201. package/dist/.claude-template/skills/code-impact-docgen/template-technical-md.md +0 -155
@@ -0,0 +1,667 @@
1
+ "use strict";
2
+ /**
3
+ * Related-code search — find methods / classes / files with similar names
4
+ * across the project. Cross-class retrieval: where `context` shows you a
5
+ * method's siblings in the same class, `related` shows you its peers across
6
+ * the whole codebase.
7
+ *
8
+ * MVP similarity metric: name-based (camelCase token overlap + Levenshtein
9
+ * distance + substring match). No AST signature matching, no body-shape
10
+ * analysis — those are explicit non-goals per clarify-requirements.
11
+ *
12
+ * Input is multi-granular:
13
+ * - `Class#method` — method-level: find similar methods project-wide
14
+ * - `Class` — class-level: find similar class/interface names
15
+ * - `path/to/file.java` — file-level: find files with similar basenames
16
+ * - bare `<name>` — fuzzy method search (like calls callers mode)
17
+ *
18
+ * Output: top-K matches, similarity-ranked. Same essential/recommended/optional
19
+ * tiered budget as `calls` / `impact`.
20
+ *
21
+ * Isolation: lives under `src/search/`, imports nothing from the indexed
22
+ * pipeline. Reuses call-sites helpers (listJavaFamilyFiles, DEFAULT_EXCLUDE_DIRS).
23
+ */
24
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ var desc = Object.getOwnPropertyDescriptor(m, k);
27
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
+ desc = { enumerable: true, get: function() { return m[k]; } };
29
+ }
30
+ Object.defineProperty(o, k2, desc);
31
+ }) : (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ o[k2] = m[k];
34
+ }));
35
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
37
+ }) : function(o, v) {
38
+ o["default"] = v;
39
+ });
40
+ var __importStar = (this && this.__importStar) || (function () {
41
+ var ownKeys = function(o) {
42
+ ownKeys = Object.getOwnPropertyNames || function (o) {
43
+ var ar = [];
44
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
+ return ar;
46
+ };
47
+ return ownKeys(o);
48
+ };
49
+ return function (mod) {
50
+ if (mod && mod.__esModule) return mod;
51
+ var result = {};
52
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
+ __setModuleDefault(result, mod);
54
+ return result;
55
+ };
56
+ })();
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.PENALTY_GETTER_MISMATCH = exports.PENALTY_POJO_VS_DOMAIN = exports.GETTER_PREFIX = exports.POJO_RETURN_TYPES = void 0;
59
+ exports.searchRelated = searchRelated;
60
+ exports.nameSimilarity = nameSimilarity;
61
+ exports.camelSplit = camelSplit;
62
+ exports.levenshtein = levenshtein;
63
+ exports.semanticPenalty = semanticPenalty;
64
+ exports.extractMethodReturnType = extractMethodReturnType;
65
+ exports.deriveQueryReturnType = deriveQueryReturnType;
66
+ exports.applyRelatedTieredBudget = applyRelatedTieredBudget;
67
+ exports.formatRelatedResult = formatRelatedResult;
68
+ const fs_1 = require("fs");
69
+ const path = __importStar(require("path"));
70
+ const grammars_1 = require("../extraction/grammars");
71
+ const call_sites_1 = require("./call-sites");
72
+ const ast_helpers_1 = require("./ast-helpers");
73
+ // ---------------------------------------------------------------------------
74
+ // Main entry
75
+ // ---------------------------------------------------------------------------
76
+ async function searchRelated(projectRoot, query, options = {}) {
77
+ const { topK = 20, budget = 4000, languages = ['java', 'kotlin'], excludeDirs = [], minScore = 20, } = options;
78
+ await (0, grammars_1.loadGrammarsForLanguages)(languages);
79
+ const excludeSet = new Set([...call_sites_1.DEFAULT_EXCLUDE_DIRS, ...excludeDirs]);
80
+ // 1. Detect query kind + extract normalized name
81
+ const { kind, queryName } = detectQueryKind(query);
82
+ // 2. Walk project files
83
+ const allFiles = await (0, call_sites_1.listJavaFamilyFiles)(projectRoot, excludeSet);
84
+ // 3. Collect candidates
85
+ const candidates = [];
86
+ for (const file of allFiles) {
87
+ let content;
88
+ try {
89
+ content = await fs_1.promises.readFile(file, 'utf-8');
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ const relPath = path.relative(projectRoot, file).replace(/\\/g, '/');
95
+ if (kind === 'file') {
96
+ // File-level: compare basenames
97
+ const baseName = path.basename(file).replace(/\.(java|kt|kts)$/, '');
98
+ const ext = path.extname(file);
99
+ const score = nameSimilarity(queryName, baseName);
100
+ if (score.score >= minScore) {
101
+ candidates.push({
102
+ kind: 'file',
103
+ name: baseName + ext,
104
+ qualifiedName: relPath,
105
+ filePath: relPath,
106
+ line: 1,
107
+ enclosingClass: null,
108
+ similarity: score.score,
109
+ reason: score.reason,
110
+ snippet: '',
111
+ });
112
+ }
113
+ continue;
114
+ }
115
+ // Method / class level — parse and walk AST
116
+ if (!content.includes(queryName.split(/(?=[A-Z])/)[0] ?? queryName))
117
+ continue;
118
+ const language = (0, grammars_1.detectLanguage)(file, content);
119
+ if (!languages.includes(language))
120
+ continue;
121
+ const parser = (0, grammars_1.getParser)(language);
122
+ if (!parser)
123
+ continue;
124
+ const tree = parser.parse(content);
125
+ if (!tree)
126
+ continue;
127
+ (0, ast_helpers_1.walkTree)(tree.rootNode, (node) => {
128
+ if (kind === 'method') {
129
+ if (node.type !== 'method_declaration' &&
130
+ node.type !== 'constructor_declaration')
131
+ return;
132
+ const nameNode = node.childForFieldName('name');
133
+ if (!nameNode)
134
+ return;
135
+ const name = nameNode.text;
136
+ const score = nameSimilarity(queryName, name);
137
+ if (score.score < minScore)
138
+ return;
139
+ const enclosing = findEnclosingClassName(node);
140
+ const enclosingReturnType = extractMethodReturnType(node, language);
141
+ candidates.push({
142
+ kind: 'method',
143
+ name,
144
+ qualifiedName: enclosing ? `${enclosing}#${name}` : name,
145
+ filePath: relPath,
146
+ line: node.startPosition.row + 1,
147
+ enclosingClass: enclosing,
148
+ similarity: score.score,
149
+ reason: score.reason,
150
+ snippet: extractDeclarationSnippet(content, node),
151
+ enclosingReturnType,
152
+ });
153
+ }
154
+ else if (kind === 'class') {
155
+ if (node.type !== 'class_declaration' &&
156
+ node.type !== 'interface_declaration' &&
157
+ node.type !== 'enum_declaration' &&
158
+ node.type !== 'record_declaration')
159
+ return;
160
+ const nameNode = node.childForFieldName('name');
161
+ if (!nameNode)
162
+ return;
163
+ const name = nameNode.text;
164
+ const score = nameSimilarity(queryName, name);
165
+ if (score.score < minScore)
166
+ return;
167
+ const kindMap = {
168
+ class_declaration: 'class',
169
+ interface_declaration: 'interface',
170
+ enum_declaration: 'enum',
171
+ record_declaration: 'record',
172
+ };
173
+ candidates.push({
174
+ kind: kindMap[node.type] ?? 'class',
175
+ name,
176
+ qualifiedName: name,
177
+ filePath: relPath,
178
+ line: node.startPosition.row + 1,
179
+ enclosingClass: null,
180
+ similarity: score.score,
181
+ reason: score.reason,
182
+ snippet: extractDeclarationSnippet(content, node),
183
+ });
184
+ }
185
+ });
186
+ }
187
+ // 4. Apply semantic penalty (iteration-3 B-6 #6).
188
+ // For method queries: derive the query's likely return type from same-name
189
+ // candidates (most frequent), then penalize candidates whose return type
190
+ // or name shape indicates a different intent (e.g. a String-returning getter
191
+ // `getSceneNameWithCommit` shouldn't outrank a real domain-object method
192
+ // `sceneCommitWithXxx`).
193
+ if (kind === 'method') {
194
+ const queryMethodReturnType = deriveQueryReturnType(candidates, queryName);
195
+ for (const c of candidates) {
196
+ const baseScore = c.similarity;
197
+ const penalty = semanticPenalty({ name: c.name, enclosingReturnType: c.enclosingReturnType }, { name: queryName, enclosingReturnType: queryMethodReturnType });
198
+ if (penalty < 0) {
199
+ const finalScore = Math.max(0, baseScore + penalty);
200
+ c.similarity = finalScore;
201
+ const penaltyParts = [];
202
+ if (c.enclosingReturnType &&
203
+ exports.POJO_RETURN_TYPES.has(c.enclosingReturnType) &&
204
+ queryMethodReturnType &&
205
+ !exports.POJO_RETURN_TYPES.has(queryMethodReturnType)) {
206
+ penaltyParts.push(`-${exports.PENALTY_POJO_VS_DOMAIN} POJO return (${c.enclosingReturnType}) vs domain (${queryMethodReturnType})`);
207
+ }
208
+ if (exports.GETTER_PREFIX.test(c.name) && !exports.GETTER_PREFIX.test(queryName)) {
209
+ penaltyParts.push(`-${exports.PENALTY_GETTER_MISMATCH} getter prefix mismatch`);
210
+ }
211
+ c.reason = `${c.reason} · semantic penalty ${penalty} [${penaltyParts.join(', ')}] → ${finalScore}`;
212
+ }
213
+ }
214
+ }
215
+ // 5. Filter exact-self matches (the query itself shouldn't appear)
216
+ // If query was `Class#method`, filter out the exact `Class#method` candidate.
217
+ const filtered = candidates.filter((c) => {
218
+ if (kind === 'method' && query.includes('#')) {
219
+ return c.qualifiedName !== query;
220
+ }
221
+ if (kind === 'class') {
222
+ // Don't return the class that matches the query exactly if there's only one
223
+ return true;
224
+ }
225
+ return true;
226
+ });
227
+ // 6. Sort by similarity desc, then by path depth asc, then by line
228
+ filtered.sort((a, b) => {
229
+ if (a.similarity !== b.similarity)
230
+ return b.similarity - a.similarity;
231
+ const da = a.filePath.split('/').length;
232
+ const db = b.filePath.split('/').length;
233
+ if (da !== db)
234
+ return da - db;
235
+ if (a.filePath !== b.filePath)
236
+ return a.filePath.localeCompare(b.filePath);
237
+ return a.line - b.line;
238
+ });
239
+ const belowMinScore = candidates.length - filtered.length;
240
+ const topMatches = filtered.slice(0, topK);
241
+ const pastTopK = filtered.length - topMatches.length;
242
+ // 7. Tiered budget
243
+ const { tiered, truncatedByBudget } = applyRelatedTieredBudget(topMatches, budget, { forceRecommended: options.withSnippet ?? false, forceOptional: options.full ?? false });
244
+ return {
245
+ query,
246
+ queryKind: kind,
247
+ queryName,
248
+ totalFound: filtered.length,
249
+ returned: tiered.length,
250
+ matches: tiered,
251
+ omitted: {
252
+ belowMinScore,
253
+ pastTopK,
254
+ truncatedByBudget,
255
+ },
256
+ };
257
+ }
258
+ // ---------------------------------------------------------------------------
259
+ // Query kind detection
260
+ // ---------------------------------------------------------------------------
261
+ function detectQueryKind(query) {
262
+ // File path: contains `/` or `\` and ends with .java/.kt
263
+ if (/[/\\]/.test(query) && /\.(java|kt|kts)$/i.test(query)) {
264
+ return { kind: 'file', queryName: path.basename(query).replace(/\.(java|kt|kts)$/i, '') };
265
+ }
266
+ // Method: `Class#method` or `pkg.Class#method`
267
+ const hashIdx = query.indexOf('#');
268
+ if (hashIdx >= 0) {
269
+ return { kind: 'method', queryName: query.slice(hashIdx + 1) };
270
+ }
271
+ // File basename without path
272
+ if (/\.(java|kt|kts)$/i.test(query)) {
273
+ return { kind: 'file', queryName: query.replace(/\.(java|kt|kts)$/i, '') };
274
+ }
275
+ // Class: starts with uppercase letter, single token (no dots)
276
+ if (/^[A-Z][a-zA-Z0-9_]*$/.test(query)) {
277
+ return { kind: 'class', queryName: query };
278
+ }
279
+ // Default: method (bare name search)
280
+ return { kind: 'method', queryName: query };
281
+ }
282
+ /**
283
+ * Score name similarity 0-100. Higher = more similar.
284
+ *
285
+ * Heuristics (first match wins):
286
+ * exact match (different class) → 100
287
+ * query is exact prefix of candidate → 85 (createOrder vs createOrderAsync)
288
+ * candidate is exact prefix of query → 75
289
+ * all query tokens appear in candidate → 75 (camelCase token overlap)
290
+ * some query tokens match exactly → 30 + 10 * overlap
291
+ * Levenshtein distance ≤ 2 → 60 - dist * 10
292
+ * substring match (one contains the other) → 40
293
+ * Levenshtein distance ≤ 5 → 25
294
+ * else → 0
295
+ */
296
+ function nameSimilarity(query, candidate) {
297
+ if (!query || !candidate)
298
+ return { score: 0, reason: 'empty' };
299
+ const q = query.toLowerCase();
300
+ const c = candidate.toLowerCase();
301
+ // Exact match
302
+ if (q === c)
303
+ return { score: 100, reason: 'exact name match' };
304
+ // Prefix
305
+ if (c.startsWith(q)) {
306
+ const extra = candidate.length - query.length;
307
+ return { score: Math.max(70, 95 - extra * 2), reason: `candidate extends query (+${extra} chars)` };
308
+ }
309
+ if (q.startsWith(c)) {
310
+ const extra = query.length - candidate.length;
311
+ return { score: Math.max(60, 85 - extra * 2), reason: `query extends candidate (+${extra} chars)` };
312
+ }
313
+ // Token overlap (camelCase split)
314
+ const qTokens = camelSplit(query);
315
+ const cTokens = camelSplit(candidate);
316
+ if (qTokens.length > 0 && cTokens.length > 0) {
317
+ const qSet = new Set(qTokens.map((t) => t.toLowerCase()));
318
+ const cSet = new Set(cTokens.map((t) => t.toLowerCase()));
319
+ const intersection = [...qSet].filter((t) => cSet.has(t));
320
+ if (intersection.length === qSet.size && qSet.size > 0) {
321
+ // All query tokens are in candidate
322
+ return {
323
+ score: 70,
324
+ reason: `all ${qSet.size} query token${qSet.size === 1 ? '' : 's'} in candidate`,
325
+ };
326
+ }
327
+ if (intersection.length > 0) {
328
+ return {
329
+ score: 30 + intersection.length * 10,
330
+ reason: `${intersection.length}/${qSet.size} query tokens match (${intersection.join(',')})`,
331
+ };
332
+ }
333
+ }
334
+ // Levenshtein distance
335
+ const dist = levenshtein(q, c);
336
+ if (dist <= 2 && Math.max(q.length, c.length) >= 4) {
337
+ return { score: 60 - dist * 10, reason: `Levenshtein distance ${dist}` };
338
+ }
339
+ // Substring
340
+ if (c.includes(q) || q.includes(c)) {
341
+ return { score: 40, reason: 'substring match' };
342
+ }
343
+ if (dist <= 5) {
344
+ return { score: 25, reason: `Levenshtein distance ${dist}` };
345
+ }
346
+ return { score: 0, reason: 'no similarity' };
347
+ }
348
+ /**
349
+ * Split camelCase / PascalCase identifier into lowercase tokens.
350
+ * `createOrderAsync` → ['create', 'Order', 'Async']
351
+ * `OrderService` → ['Order', 'Service']
352
+ * `ICustomerOrderService` → ['ICustomer', 'Order', 'Service']
353
+ * (note: leading `I` is preserved; we don't strip interface-prefix here)
354
+ */
355
+ function camelSplit(s) {
356
+ // Split at uppercase letters (camelCase boundaries), keep the uppercase letter
357
+ const parts = s.split(/(?=[A-Z])/).filter((p) => p.length > 0);
358
+ return parts;
359
+ }
360
+ /**
361
+ * Standard Levenshtein distance (no early termination — names are short).
362
+ */
363
+ function levenshtein(a, b) {
364
+ if (a === b)
365
+ return 0;
366
+ if (a.length === 0)
367
+ return b.length;
368
+ if (b.length === 0)
369
+ return a.length;
370
+ const prev = new Array(b.length + 1);
371
+ const curr = new Array(b.length + 1);
372
+ for (let j = 0; j <= b.length; j++)
373
+ prev[j] = j;
374
+ for (let i = 1; i <= a.length; i++) {
375
+ curr[0] = i;
376
+ for (let j = 1; j <= b.length; j++) {
377
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
378
+ const insertCost = (curr[j - 1] ?? 0) + 1;
379
+ const deleteCost = (prev[j] ?? 0) + 1;
380
+ const substCost = (prev[j - 1] ?? 0) + cost;
381
+ curr[j] = Math.min(insertCost, deleteCost, substCost);
382
+ }
383
+ for (let j = 0; j <= b.length; j++)
384
+ prev[j] = curr[j] ?? 0;
385
+ }
386
+ return prev[b.length] ?? 0;
387
+ }
388
+ // ---------------------------------------------------------------------------
389
+ // Semantic penalty (iteration-3 B-6 #6)
390
+ // ---------------------------------------------------------------------------
391
+ //
392
+ // `nameSimilarity` ranks methods by name shape alone — so a getter that returns
393
+ // a String (`getSceneNameWithCommit`) can tie a real domain method that returns
394
+ // a business object (`sceneGroupCommit` → `CreateCustomerOrderRspVo`) when both
395
+ // share the query's tokens. `semanticPenalty` adds a non-positive adjustment on
396
+ // top of the base nameSimilarity score to push those shape-matched-but-semantically
397
+ // different candidates below real variants.
398
+ //
399
+ // Two independent rules, applied additively:
400
+ // 1. POJO-vs-domain mismatch: candidate returns a POJO (String/Integer/…)
401
+ // but the query returns a domain object → -PENALTY_POJO_VS_DOMAIN
402
+ // 2. Getter-prefix mismatch: candidate name starts with get/is/has but the
403
+ // query doesn't → -PENALTY_GETTER_MISMATCH
404
+ //
405
+ // Worst case both rules fire (-35 with default values). Final score is always
406
+ // clamped to >= 0 at the call site.
407
+ /**
408
+ * POJO / primitive-ish return types. A candidate whose enclosing method returns
409
+ * one of these is likely a simple accessor/converter, not the business method
410
+ * the query is after. Add more here if probe shows new patterns.
411
+ */
412
+ exports.POJO_RETURN_TYPES = new Set([
413
+ 'String', 'Integer', 'Long', 'Boolean', 'void', 'Float', 'Double',
414
+ 'Object', 'Optional', 'int', 'long', 'boolean', 'float', 'double',
415
+ ]);
416
+ /** Candidate name starts with `get`/`is`/`has` followed by an uppercase letter. */
417
+ exports.GETTER_PREFIX = /^(get|is|has)[A-Z]/;
418
+ /** Penalty values. Tunable range per plan: -20/-15 (initial) → -25/-20 (max). */
419
+ exports.PENALTY_POJO_VS_DOMAIN = -20;
420
+ exports.PENALTY_GETTER_MISMATCH = -15;
421
+ /**
422
+ * Returns a non-positive penalty. Higher absolute value = more penalty.
423
+ *
424
+ * Rule 1: candidate returns POJO but query returns domain object → -20
425
+ * Rule 2: candidate starts with get/is/has but query doesn't → -15
426
+ * Rules stack (worst case -35 with defaults).
427
+ *
428
+ * Pure function — no I/O, no AST. Safe to unit-test directly.
429
+ */
430
+ function semanticPenalty(candidate, queryMethod) {
431
+ let penalty = 0;
432
+ // Rule 1: POJO vs domain mismatch.
433
+ if (candidate.enclosingReturnType &&
434
+ exports.POJO_RETURN_TYPES.has(candidate.enclosingReturnType) &&
435
+ queryMethod.enclosingReturnType &&
436
+ !exports.POJO_RETURN_TYPES.has(queryMethod.enclosingReturnType)) {
437
+ penalty += exports.PENALTY_POJO_VS_DOMAIN;
438
+ }
439
+ // Rule 2: getter prefix on candidate but not on query.
440
+ if (exports.GETTER_PREFIX.test(candidate.name) &&
441
+ !exports.GETTER_PREFIX.test(queryMethod.name)) {
442
+ penalty += exports.PENALTY_GETTER_MISMATCH;
443
+ }
444
+ return penalty;
445
+ }
446
+ /**
447
+ * Extract a method's declared return type from its AST node.
448
+ *
449
+ * Java `method_declaration`: read the `type` field (tree-sitter-java exposes
450
+ * this for explicit return types). Constructors have no return type → undefined.
451
+ * Kotlin `function_declaration`: scan children for the `:` token, then take the
452
+ * following type node's text. Omitted return type (Unit inferred) → undefined.
453
+ *
454
+ * Returns undefined for unknown node shapes (defensive — caller skips penalty).
455
+ */
456
+ function extractMethodReturnType(node, language) {
457
+ if (language === 'java') {
458
+ if (node.type !== 'method_declaration')
459
+ return undefined;
460
+ // tree-sitter-java exposes the return type via the `type` field for explicit
461
+ // types. Constructors (`constructor_declaration`) have no `type` field.
462
+ const typeNode = node.childForFieldName('type');
463
+ if (!typeNode)
464
+ return undefined;
465
+ // For generic types like `List<String>`, normalize to the raw type name.
466
+ return normalizeReturnType(typeNode.text);
467
+ }
468
+ if (language === 'kotlin') {
469
+ if (node.type !== 'function_declaration')
470
+ return undefined;
471
+ // Kotlin function_declaration children: fun, simple_identifier (name),
472
+ // function_value_parameters, [:], [user_type], function_body.
473
+ // Scan for the `:` separator; the return type follows it.
474
+ const children = node.children;
475
+ for (let i = 0; i < children.length - 1; i++) {
476
+ const c = children[i];
477
+ if (c && c.type === ':' && children[i + 1]) {
478
+ return normalizeReturnType(children[i + 1].text);
479
+ }
480
+ }
481
+ return undefined; // No explicit return type (Unit inferred).
482
+ }
483
+ return undefined;
484
+ }
485
+ /**
486
+ * Normalize a raw return-type string: strip generics (`List<String>` → `List`),
487
+ * strip array brackets (`String[]` → `String`), trim whitespace.
488
+ */
489
+ function normalizeReturnType(raw) {
490
+ let t = raw.trim();
491
+ // Strip generics.
492
+ const genIdx = t.indexOf('<');
493
+ if (genIdx > 0)
494
+ t = t.slice(0, genIdx);
495
+ // Strip array brackets.
496
+ t = t.replace(/[\[\]]/g, '').trim();
497
+ return t;
498
+ }
499
+ /**
500
+ * Derive the query method's likely return type from collected candidates.
501
+ *
502
+ * Strategy: among same-name candidates (name === queryName), pick the most
503
+ * frequent non-undefined return type. This is a heuristic — we assume the
504
+ * query method exists in the codebase and its return type appears in the
505
+ * candidate set. Returns undefined if no same-name candidate has a return type.
506
+ */
507
+ function deriveQueryReturnType(candidates, queryName) {
508
+ const counts = new Map();
509
+ for (const c of candidates) {
510
+ if (c.name === queryName && c.enclosingReturnType) {
511
+ counts.set(c.enclosingReturnType, (counts.get(c.enclosingReturnType) ?? 0) + 1);
512
+ }
513
+ }
514
+ if (counts.size === 0)
515
+ return undefined;
516
+ let best;
517
+ let bestCount = -1;
518
+ for (const [rt, count] of counts) {
519
+ if (count > bestCount) {
520
+ best = rt;
521
+ bestCount = count;
522
+ }
523
+ }
524
+ return best;
525
+ }
526
+ // ---------------------------------------------------------------------------
527
+ // AST helpers
528
+ // ---------------------------------------------------------------------------
529
+ function findEnclosingClassName(node) {
530
+ let cur = node.parent;
531
+ while (cur) {
532
+ if (cur.type === 'class_declaration' ||
533
+ cur.type === 'interface_declaration' ||
534
+ cur.type === 'enum_declaration' ||
535
+ cur.type === 'record_declaration') {
536
+ const name = cur.childForFieldName('name')?.text;
537
+ if (name)
538
+ return name;
539
+ }
540
+ cur = cur.parent;
541
+ }
542
+ return null;
543
+ }
544
+ function extractDeclarationSnippet(content, node) {
545
+ const startRow = node.startPosition.row;
546
+ const lines = content.split('\n');
547
+ // Take the declaration start + 1-2 lines (signature usually fits in 1-3 lines)
548
+ const end = Math.min(startRow + 3, lines.length);
549
+ return lines.slice(startRow, end).join('\n').trim();
550
+ }
551
+ // ---------------------------------------------------------------------------
552
+ // Tiered budget (same shape as calls — essential / recommended / optional)
553
+ // ---------------------------------------------------------------------------
554
+ const TIER_ESSENTIAL_COST = 40;
555
+ const TIER_RECOMMENDED_ADDITIONAL = 120;
556
+ const TIER_OPTIONAL_ADDITIONAL = 400;
557
+ function applyRelatedTieredBudget(matches, budget, opts = {}) {
558
+ let count = 0;
559
+ let used = 0;
560
+ for (let i = 0; i < matches.length; i++) {
561
+ if (used + TIER_ESSENTIAL_COST > budget && count > 0)
562
+ break;
563
+ used += TIER_ESSENTIAL_COST;
564
+ count++;
565
+ }
566
+ const truncatedByBudget = matches.length - count;
567
+ const tiered = matches.slice(0, count).map((m) => ({ ...m, tier: 'essential' }));
568
+ if (opts.forceOptional) {
569
+ for (const m of tiered)
570
+ m.tier = 'optional';
571
+ return { tiered, truncatedByBudget };
572
+ }
573
+ if (opts.forceRecommended) {
574
+ for (const m of tiered)
575
+ m.tier = 'recommended';
576
+ return { tiered, truncatedByBudget };
577
+ }
578
+ let remaining = budget - used;
579
+ for (const m of tiered) {
580
+ if (remaining >= TIER_RECOMMENDED_ADDITIONAL) {
581
+ m.tier = 'recommended';
582
+ remaining -= TIER_RECOMMENDED_ADDITIONAL;
583
+ }
584
+ }
585
+ for (const m of tiered) {
586
+ if (m.tier !== 'recommended')
587
+ continue;
588
+ if (remaining >= TIER_OPTIONAL_ADDITIONAL) {
589
+ m.tier = 'optional';
590
+ remaining -= TIER_OPTIONAL_ADDITIONAL;
591
+ }
592
+ }
593
+ return { tiered, truncatedByBudget };
594
+ }
595
+ // ---------------------------------------------------------------------------
596
+ // Formatter (CLI markdown)
597
+ // ---------------------------------------------------------------------------
598
+ function formatRelatedResult(result, options) {
599
+ const lines = [];
600
+ lines.push(`**Related to \`${result.queryName}\` (${result.queryKind})** — ${result.totalFound} match${result.totalFound === 1 ? '' : 'es'}, returning ${result.returned}`);
601
+ if (result.matches.length === 0) {
602
+ lines.push('');
603
+ lines.push(`_No matches above the similarity threshold. Try a broader query, or lower the threshold with \`--min-score 10\`._`);
604
+ return lines.join('\n');
605
+ }
606
+ // Group by similarity bucket for scanability
607
+ const buckets = new Map();
608
+ for (const m of result.matches) {
609
+ let bucket;
610
+ if (m.similarity >= 80)
611
+ bucket = '🟢 Strong match (≥80)';
612
+ else if (m.similarity >= 50)
613
+ bucket = '🟡 Moderate match (50-79)';
614
+ else if (m.similarity >= 30)
615
+ bucket = '⚪ Weak match (30-49)';
616
+ else
617
+ bucket = '🗄️ Fuzzy match (<30)';
618
+ const arr = buckets.get(bucket) ?? [];
619
+ arr.push(m);
620
+ buckets.set(bucket, arr);
621
+ }
622
+ const bucketOrder = [
623
+ '🟢 Strong match (≥80)',
624
+ '🟡 Moderate match (50-79)',
625
+ '⚪ Weak match (30-49)',
626
+ '🗄️ Fuzzy match (<30)',
627
+ ];
628
+ for (const bucket of bucketOrder) {
629
+ const items = buckets.get(bucket);
630
+ if (!items || items.length === 0)
631
+ continue;
632
+ lines.push('');
633
+ lines.push(`### ${bucket}`);
634
+ for (const m of items) {
635
+ const kindTag = m.kind === 'method' ? '' : ` [${m.kind}]`;
636
+ const enclosingTag = m.enclosingClass ? ` _in ${m.enclosingClass}_` : '';
637
+ lines.push(`- **${m.name}**${kindTag} (score ${m.similarity}) \`${m.filePath}:${m.line}\`${enclosingTag}`);
638
+ lines.push(` _${m.reason}_`);
639
+ if (m.tier === 'recommended' || m.tier === 'optional') {
640
+ if (m.snippet) {
641
+ const snippetLines = m.snippet.split('\n').slice(0, 3);
642
+ for (const sl of snippetLines) {
643
+ lines.push(` > ${sl}`);
644
+ }
645
+ }
646
+ }
647
+ if (options?.explain) {
648
+ lines.push(` _score: +${m.similarity} similarity (${m.reason}) = ${m.similarity}_`);
649
+ }
650
+ }
651
+ }
652
+ // Omitted summary
653
+ const o = result.omitted;
654
+ const omittedParts = [];
655
+ if (o.belowMinScore > 0)
656
+ omittedParts.push(`${o.belowMinScore} below min-score`);
657
+ if (o.pastTopK > 0)
658
+ omittedParts.push(`${o.pastTopK} past topK`);
659
+ if (o.truncatedByBudget > 0)
660
+ omittedParts.push(`${o.truncatedByBudget} truncated by budget`);
661
+ if (omittedParts.length > 0) {
662
+ lines.push('');
663
+ lines.push(`_Omitted: ${omittedParts.join(' · ')}_`);
664
+ }
665
+ return lines.join('\n');
666
+ }
667
+ //# sourceMappingURL=related.js.map