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,646 @@
1
+ "use strict";
2
+ /**
3
+ * Impact analysis — transitive callers + type references for a method.
4
+ *
5
+ * Spec input (per `docs/features/2026-07-01-impact-command/spec.md`, drafted
6
+ * from clarify-requirements):
7
+ * - Replace the existing indexed-DB `cgraphx impact` command (CLI surface only;
8
+ * the graph API `CodeGraph.getImpactRadius` stays for the MCP server until
9
+ * that's separately deprecated).
10
+ * - Input: `Class#method` or `file:line` (same as `calls --from`).
11
+ * - Output: transitive callers (BFS up to depth N) + type references to the
12
+ * enclosing class (import / extends / implements / field / param / local /
13
+ * cast / annotation / generic). All SNR-ranked, tiered-budget output.
14
+ *
15
+ * Isolation: lives entirely under `src/search/`, imports nothing from
16
+ * `src/graph/`, `src/resolution/`, `src/db/`, `src/mcp/`, or `src/extraction/`
17
+ * (beyond the grammar loader). When the indexed pipeline is eventually removed,
18
+ * this file is untouched.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.searchCallSites = void 0;
55
+ exports.computeImpactCallSiteSnr = computeImpactCallSiteSnr;
56
+ exports.computeImpactSnrBreakdown = computeImpactSnrBreakdown;
57
+ exports.searchImpact = searchImpact;
58
+ exports.applyImpactTieredBudget = applyImpactTieredBudget;
59
+ exports.formatImpactResult = formatImpactResult;
60
+ const fs_1 = require("fs");
61
+ const path = __importStar(require("path"));
62
+ const grammars_1 = require("../extraction/grammars");
63
+ const call_sites_1 = require("./call-sites");
64
+ const ast_helpers_1 = require("./ast-helpers");
65
+ // Re-export for callers-mode reuse
66
+ var call_sites_2 = require("./call-sites");
67
+ Object.defineProperty(exports, "searchCallSites", { enumerable: true, get: function () { return call_sites_2.searchCallSites; } });
68
+ // ---------------------------------------------------------------------------
69
+ // SNR scoring for type references
70
+ // ---------------------------------------------------------------------------
71
+ /**
72
+ * SNR score a type reference. The signal here is "how directly does this
73
+ * reference propagate a change to the method".
74
+ *
75
+ * extends/implements → +50 (subclass MUST implement/inherit the method)
76
+ * field → +35 (field is reached via the method's class)
77
+ * param/local/return → +25 (typed usage; method change might affect callers)
78
+ * annotation → +20 (annotation processor might break)
79
+ * cast → +15 (runtime cast; weakly affected)
80
+ * generic → +10 (e.g. List<Foo>; least affected by method change)
81
+ * import → +5 (might be unused or fully unrelated)
82
+ * other → +10
83
+ */
84
+ function computeTypeRefSnr(ref) {
85
+ switch (ref.kind) {
86
+ case 'extends': return 50;
87
+ case 'implements': return 50;
88
+ case 'field': return 35;
89
+ case 'param': return 25;
90
+ case 'local': return 25;
91
+ case 'return': return 25;
92
+ case 'annotation': return 20;
93
+ case 'cast': return 15;
94
+ case 'generic': return 10;
95
+ case 'import': return 5;
96
+ case 'other': return 10;
97
+ }
98
+ }
99
+ /**
100
+ * SNR score a transitive caller. Reuses computeSnrScore for the call-site
101
+ * itself, then applies hop-distance decay (hop 1 = 1.0, hop 2 = 0.6,
102
+ * hop 3 = 0.36, ...). Direct callers always outrank indirect ones.
103
+ */
104
+ function computeImpactCallSiteSnr(site) {
105
+ const base = (0, call_sites_1.computeSnrScore)(site);
106
+ const decay = Math.pow(0.6, site.hopDistance - 1);
107
+ return Math.round(base * decay);
108
+ }
109
+ function computeImpactSnrBreakdown(site) {
110
+ const baseBd = (0, call_sites_1.computeSnrBreakdown)(site);
111
+ const decayMultiplier = Math.pow(0.6, site.hopDistance - 1);
112
+ return {
113
+ base: baseBd.total,
114
+ decayMultiplier,
115
+ total: Math.round(baseBd.total * decayMultiplier),
116
+ matchedRules: baseBd.matchedRules,
117
+ receiver: baseBd.receiver,
118
+ method: baseBd.method,
119
+ };
120
+ }
121
+ /**
122
+ * Format an impact SNR breakdown for the `--explain` flag. Hop 1 (decay 1.0)
123
+ * collapses to the plain breakdown; deeper hops show `base × decay = total`.
124
+ */
125
+ function formatImpactSnrBreakdown(site) {
126
+ const bd = computeImpactSnrBreakdown(site);
127
+ // At hop 1 (decay 1.0) the base equals the total, so the multiplier is
128
+ // noise — show the plain call-site breakdown instead.
129
+ if (site.hopDistance === 1) {
130
+ return (0, call_sites_1.formatSnrBreakdown)(site);
131
+ }
132
+ // Deeper hops: strip the trailing `= <total>` from the call-site breakdown
133
+ // (that's the pre-decay base) and append the hop multiplier.
134
+ const baseFull = (0, call_sites_1.formatSnrBreakdown)(site);
135
+ const baseStr = baseFull.replace(/\s*=\s*-?\d+$/, '').trim();
136
+ const decayPct = `${Math.round(bd.decayMultiplier * 100)}%`;
137
+ return `${baseStr} × ${decayPct} hop${site.hopDistance} = ${bd.total}`;
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Main entry
141
+ // ---------------------------------------------------------------------------
142
+ async function searchImpact(projectRoot, symbol, options = {}) {
143
+ const { depth = 2, topPerHop = 20, topTypeRefs = 15, budget = 6000, languages = ['java', 'kotlin'], excludeDirs = [], } = options;
144
+ await (0, grammars_1.loadGrammarsForLanguages)(languages);
145
+ const excludeSet = new Set([...call_sites_1.DEFAULT_EXCLUDE_DIRS, ...excludeDirs]);
146
+ // 1. Resolve symbol → method_declaration AST node + enclosing class name
147
+ const resolved = await (0, call_sites_1.resolveMethodSymbol)(projectRoot, symbol, languages);
148
+ const classNode = (0, call_sites_1.findEnclosingClassType)(resolved.methodNode);
149
+ const enclosingClass = classNode?.childForFieldName('name')?.text ??
150
+ resolved.methodQualifiedName.split('#')[0] ??
151
+ null;
152
+ // 2. BFS transitive callers
153
+ const { callers, depthReached } = await findTransitiveCallers(projectRoot, {
154
+ methodName: resolved.methodNode.childForFieldName('name')?.text ?? '',
155
+ className: enclosingClass ?? '',
156
+ filePath: resolved.file,
157
+ methodNode: resolved.methodNode,
158
+ }, depth, { topPerHop, languages, excludeDirs: excludeSet });
159
+ // 3. Type references scan (only if we resolved an enclosing class)
160
+ const typeReferences = enclosingClass
161
+ ? await scanTypeReferences(projectRoot, enclosingClass, languages, excludeSet)
162
+ : [];
163
+ // 4. SNR scoring
164
+ for (const c of callers) {
165
+ c.snrScore = computeImpactCallSiteSnr(c);
166
+ }
167
+ for (const r of typeReferences) {
168
+ r.snrScore = computeTypeRefSnr(r);
169
+ }
170
+ // 5. Sort + top-K
171
+ callers.sort(compareByImpactSnr);
172
+ const callersKept = callers.slice(0, topPerHop * Math.max(depthReached, 1));
173
+ const callersPrunedByTopPerHop = callers.length - callersKept.length;
174
+ typeReferences.sort((a, b) => b.snrScore - a.snrScore || a.line - b.line);
175
+ const typeRefsKept = typeReferences.slice(0, topTypeRefs);
176
+ const typeRefsPrunedByTopK = typeReferences.length - typeRefsKept.length;
177
+ // 6. Tiered budget — two-list allocation: callers first, then type refs
178
+ const { tieredCallers, tieredTypeRefs, truncatedByBudget } = applyImpactTieredBudget(callersKept, typeRefsKept, budget, { forceRecommended: options.withSnippet ?? false, forceOptional: options.full ?? false });
179
+ return {
180
+ symbol,
181
+ methodQualifiedName: resolved.methodQualifiedName,
182
+ enclosingClass,
183
+ filePath: path.relative(projectRoot, resolved.file).replace(/\\/g, '/'),
184
+ startLine: resolved.methodNode.startPosition.row + 1,
185
+ endLine: resolved.methodNode.endPosition.row + 1,
186
+ depthReached,
187
+ totalCallers: callers.length,
188
+ totalTypeRefFiles: new Set(typeReferences.map((r) => r.filePath)).size,
189
+ totalTypeRefs: typeReferences.length,
190
+ callers: tieredCallers,
191
+ typeReferences: tieredTypeRefs,
192
+ omitted: {
193
+ callersPrunedByTopPerHop,
194
+ typeRefsPrunedByTopK,
195
+ truncatedByBudget,
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * BFS over callers. For each method, find all callsites via `searchCallSites`
201
+ * (calls callers mode), then recurse on each unique containing method.
202
+ *
203
+ * Cycle detection: a visited Set<string> keyed `Class#method`. If a caller
204
+ * is already visited, skip — prevents infinite loops on recursion (A calls B,
205
+ * B calls A).
206
+ *
207
+ * The hop-level topK (`topPerHop`) caps how many callers we surface per hop
208
+ * to bound the output. Lower-signal callers (noise) get pruned.
209
+ */
210
+ async function findTransitiveCallers(projectRoot, start, maxDepth, options) {
211
+ // Lazy import — searchCallSites pulls grammars, which we already loaded
212
+ const { searchCallSites } = await Promise.resolve().then(() => __importStar(require('./call-sites')));
213
+ const visited = new Set();
214
+ const visitedCallsites = new Set(); // dedup by `${filePath}:${line}`
215
+ const startKey = `${start.className}#${start.methodName}`;
216
+ visited.add(startKey);
217
+ // Queue carries className + methodName so we can filter searchCallSites
218
+ // results to only those whose containingClass matches — without this filter,
219
+ // common method names like `create` / `get` / `process` pull in hundreds of
220
+ // unrelated callsites and drown the BFS in noise (the method-name-ambiguity
221
+ // problem).
222
+ const queue = [
223
+ { methodName: start.methodName, expectedClass: start.className || null, hop: 0 },
224
+ ];
225
+ const allCallers = [];
226
+ let depthReached = 0;
227
+ while (queue.length > 0) {
228
+ const { methodName, expectedClass, hop } = queue.shift();
229
+ if (hop >= maxDepth)
230
+ continue;
231
+ depthReached = Math.max(depthReached, hop + 1);
232
+ // Find callers of methodName across the project
233
+ const result = await searchCallSites(projectRoot, methodName, {
234
+ languages: options.languages,
235
+ excludeDirs: [...options.excludeDirs],
236
+ topK: options.topPerHop * 2, // pull more, we'll filter + dedupe
237
+ });
238
+ // Filter to callsites whose containingClass matches what we expect — BUT
239
+ // only from hop 2 onwards. At hop 1 we want ALL callers of the start
240
+ // method (the start method's class doesn't constrain who calls it).
241
+ // From hop 2: each queued entry's expectedClass is the prior-hop caller's
242
+ // containingClass, so filtering keeps only callsites that live INSIDE
243
+ // that class — i.e., callers of that specific Class#method, not every
244
+ // method of the same name across the project. Without this, common names
245
+ // (`create`/`get`/`process`) pull in hundreds of unrelated callsites.
246
+ const filtered = (hop >= 1 && expectedClass)
247
+ ? result.callSites.filter((s) => s.containingClass === expectedClass)
248
+ : result.callSites;
249
+ // Pull top-K per hop. Dedupe by (filePath, line) across ALL hops — BFS
250
+ // can reach the same callsite via multiple paths (A→B→C and A→D→C both
251
+ // land on C), but we only want to surface C once at its shallowest hop.
252
+ // Also skip self-calls (callsite inside a method we've already visited
253
+ // from) — these are the method calling its own name on a different
254
+ // receiver (e.g., `orderClient.sceneCommit()` inside
255
+ // `CustomerOrderServiceImpl#sceneCommit`), not a real caller.
256
+ const takenThisHop = filtered.slice(0, options.topPerHop);
257
+ for (const site of takenThisHop) {
258
+ const callsiteKey = `${site.filePath}:${site.line}`;
259
+ if (visitedCallsites.has(callsiteKey))
260
+ continue;
261
+ const callerKey = site.containingClass && site.containingMethod
262
+ ? `${site.containingClass}#${site.containingMethod}`
263
+ : null;
264
+ if (callerKey && visited.has(callerKey))
265
+ continue; // self-call or already-covered
266
+ visitedCallsites.add(callsiteKey);
267
+ const impactSite = {
268
+ ...site,
269
+ hopDistance: hop + 1,
270
+ };
271
+ allCallers.push(impactSite);
272
+ // Recurse on each unique containing method (Class#method)
273
+ if (callerKey) {
274
+ visited.add(callerKey);
275
+ queue.push({
276
+ methodName: site.containingMethod,
277
+ expectedClass: site.containingClass,
278
+ hop: hop + 1,
279
+ });
280
+ }
281
+ }
282
+ }
283
+ return { callers: allCallers, depthReached };
284
+ }
285
+ // ---------------------------------------------------------------------------
286
+ // Type references scan
287
+ // ---------------------------------------------------------------------------
288
+ /**
289
+ * Walk all source files looking for type_identifier nodes matching `className`.
290
+ * For each match, classify the kind (extends/implements/field/param/...) by
291
+ * looking at the AST parent chain.
292
+ *
293
+ * Fast text pre-filter: skip files that don't contain `className` as text.
294
+ */
295
+ async function scanTypeReferences(projectRoot, className, languages, excludeDirs) {
296
+ const refs = [];
297
+ const files = await (0, call_sites_1.listJavaFamilyFiles)(projectRoot, excludeDirs);
298
+ for (const file of files) {
299
+ let content;
300
+ try {
301
+ content = await fs_1.promises.readFile(file, 'utf-8');
302
+ }
303
+ catch {
304
+ continue;
305
+ }
306
+ // Fast text filter — most files won't mention this class
307
+ if (!content.includes(className))
308
+ continue;
309
+ const language = (0, grammars_1.detectLanguage)(file, content);
310
+ if (!languages.includes(language))
311
+ continue;
312
+ const parser = (0, grammars_1.getParser)(language);
313
+ if (!parser)
314
+ continue;
315
+ const tree = parser.parse(content);
316
+ if (!tree)
317
+ continue;
318
+ const relPath = path.relative(projectRoot, file).replace(/\\/g, '/');
319
+ (0, ast_helpers_1.walkTree)(tree.rootNode, (node) => {
320
+ // Java/Kotlin: type_identifier is the typical class-name node.
321
+ // Kotlin also uses 'user_type_name' in some contexts.
322
+ if (node.type !== 'type_identifier' && node.type !== 'user_type_name')
323
+ return;
324
+ if (node.text !== className)
325
+ return;
326
+ const kind = classifyTypeReference(node);
327
+ // For import statements, the type_identifier might be the LAST segment of
328
+ // a scoped_type_identifier — avoid double-counting by checking we're the
329
+ // last identifier in our parent chain.
330
+ if (kind === 'import' && isInnerSegmentOfScopedType(node))
331
+ return;
332
+ const { containingClass, containingMethod } = findEnclosingContext(node);
333
+ refs.push({
334
+ className,
335
+ filePath: relPath,
336
+ line: node.startPosition.row + 1,
337
+ column: node.startPosition.column,
338
+ kind,
339
+ containingClass,
340
+ containingMethod,
341
+ snippet: extractOneLine(content, node.startPosition.row),
342
+ snrScore: 0, // scored later
343
+ });
344
+ });
345
+ }
346
+ return refs;
347
+ }
348
+ /**
349
+ * Classify a type_identifier reference by walking its parent chain.
350
+ *
351
+ * Tree-sitter-java AST shapes (verified empirically):
352
+ * import com.x.Foo; — import_declaration > scoped_type_identifier > type_identifier
353
+ * class Bar extends Foo — class_declaration > super_class > type_identifier
354
+ * class Bar implements F — class_declaration > implements > interface_type_list > type_identifier
355
+ * private Foo foo; — field_declaration > type > type_identifier
356
+ * void m(Foo x) — formal_parameter > type > type_identifier
357
+ * Foo x = ...; — local_variable_declaration > type > type_identifier
358
+ * (Foo) obj — cast_expression > type > type_identifier
359
+ * List<Foo> — generic_type > type_arguments > type_identifier
360
+ * @Foo — annotation > type_identifier (or qualified_name)
361
+ * Foo m() — method_declaration > type > type_identifier (return type)
362
+ */
363
+ function classifyTypeReference(node) {
364
+ // Walk up: look at parent and the closest "interesting" ancestor
365
+ let parent = node.parent;
366
+ // Direct parent checks first
367
+ if (parent?.type === 'annotation')
368
+ return 'annotation';
369
+ if (parent?.type === 'cast_expression')
370
+ return 'cast';
371
+ if (parent?.type === 'generic_type')
372
+ return 'generic';
373
+ // import_declaration ancestor
374
+ let p = node;
375
+ while (p) {
376
+ if (p.type === 'import_declaration')
377
+ return 'import';
378
+ p = p.parent;
379
+ }
380
+ // super_class / implements
381
+ if (parent?.type === 'super_class')
382
+ return 'extends';
383
+ if (parent?.type === 'super_interfaces' || parent?.type === 'interface_type_list')
384
+ return 'implements';
385
+ // type field of various declarations
386
+ if (parent?.type === 'type') {
387
+ const grand = parent.parent;
388
+ if (!grand)
389
+ return 'other';
390
+ switch (grand.type) {
391
+ case 'field_declaration': return 'field';
392
+ case 'formal_parameter': return 'param';
393
+ case 'local_variable_declaration':
394
+ case 'variable_declarator': return 'local';
395
+ case 'method_declaration': return 'return';
396
+ default: return 'other';
397
+ }
398
+ }
399
+ return 'other';
400
+ }
401
+ /**
402
+ * True if this type_identifier is an inner segment of a scoped_type_identifier
403
+ * (e.g., the `http` in `com.example.http.Client`). For import deduplication —
404
+ * we only want the LAST type_identifier (the actual class name).
405
+ */
406
+ function isInnerSegmentOfScopedType(node) {
407
+ return node.parent?.type === 'scoped_type_identifier';
408
+ }
409
+ function findEnclosingContext(node) {
410
+ let containingClass = null;
411
+ let containingMethod = null;
412
+ let cur = node.parent;
413
+ while (cur) {
414
+ if (!containingClass && (cur.type === 'class_declaration' ||
415
+ cur.type === 'interface_declaration' ||
416
+ cur.type === 'enum_declaration' ||
417
+ cur.type === 'record_declaration')) {
418
+ const name = cur.childForFieldName('name')?.text ?? null;
419
+ if (name)
420
+ containingClass = name;
421
+ }
422
+ if (!containingMethod && (cur.type === 'method_declaration' ||
423
+ cur.type === 'constructor_declaration' ||
424
+ cur.type === 'compact_constructor_declaration')) {
425
+ const name = cur.childForFieldName('name')?.text ?? null;
426
+ if (name)
427
+ containingMethod = name;
428
+ }
429
+ if (containingClass && containingMethod)
430
+ break;
431
+ cur = cur.parent;
432
+ }
433
+ return { containingClass, containingMethod };
434
+ }
435
+ function extractOneLine(content, row) {
436
+ const lines = content.split('\n');
437
+ return (lines[row] ?? '').trim();
438
+ }
439
+ // ---------------------------------------------------------------------------
440
+ // Sort + tiered budget
441
+ // ---------------------------------------------------------------------------
442
+ function compareByImpactSnr(a, b) {
443
+ const sa = a.snrScore ?? 0;
444
+ const sb = b.snrScore ?? 0;
445
+ if (sa !== sb)
446
+ return sb - sa;
447
+ // Lower hop distance first on tie (direct caller before indirect)
448
+ if (a.hopDistance !== b.hopDistance)
449
+ return a.hopDistance - b.hopDistance;
450
+ const da = a.filePath.split('/').length;
451
+ const db = b.filePath.split('/').length;
452
+ if (da !== db)
453
+ return da - db;
454
+ if (a.filePath !== b.filePath)
455
+ return a.filePath.localeCompare(b.filePath);
456
+ return a.line - b.line;
457
+ }
458
+ const TIER_ESSENTIAL_COST = 40;
459
+ const TIER_RECOMMENDED_ADDITIONAL = 120;
460
+ const TIER_OPTIONAL_ADDITIONAL = 400;
461
+ /**
462
+ * Two-list tiered budget: allocate budget across callers AND type references.
463
+ * Callers get essential tier first (in SNR order), then type refs get essential
464
+ * tier. If budget remains, recommended tier fills in SNR order across both
465
+ * lists. Same for optional.
466
+ */
467
+ function applyImpactTieredBudget(callers, typeRefs, budget, opts = {}) {
468
+ // Pass 1: how many items fit at essential tier across both lists?
469
+ let count = 0;
470
+ let used = 0;
471
+ const total = callers.length + typeRefs.length;
472
+ for (let i = 0; i < total; i++) {
473
+ if (used + TIER_ESSENTIAL_COST > budget && count > 0)
474
+ break;
475
+ used += TIER_ESSENTIAL_COST;
476
+ count++;
477
+ }
478
+ const truncatedByBudget = total - count;
479
+ // Allocate essential slots in priority order: callers first (in SNR order),
480
+ // then type refs (in SNR order)
481
+ const callerCount = Math.min(callers.length, count);
482
+ const typeRefCount = Math.min(typeRefs.length, count - callerCount);
483
+ const tieredCallers = callers.slice(0, callerCount).map((c) => ({ ...c, tier: 'essential' }));
484
+ const tieredTypeRefs = typeRefs.slice(0, typeRefCount).map((r) => ({ ...r, tier: 'essential' }));
485
+ // Force flags short-circuit
486
+ if (opts.forceOptional) {
487
+ for (const c of tieredCallers)
488
+ c.tier = 'optional';
489
+ for (const r of tieredTypeRefs)
490
+ r.tier = 'optional';
491
+ return { tieredCallers, tieredTypeRefs, truncatedByBudget };
492
+ }
493
+ if (opts.forceRecommended) {
494
+ for (const c of tieredCallers)
495
+ c.tier = 'recommended';
496
+ for (const r of tieredTypeRefs)
497
+ r.tier = 'recommended';
498
+ return { tieredCallers, tieredTypeRefs, truncatedByBudget };
499
+ }
500
+ // Pass 2: upgrade to recommended in SNR order, callers first then type refs
501
+ let remaining = budget - used;
502
+ for (const c of tieredCallers) {
503
+ if (remaining >= TIER_RECOMMENDED_ADDITIONAL) {
504
+ c.tier = 'recommended';
505
+ remaining -= TIER_RECOMMENDED_ADDITIONAL;
506
+ }
507
+ }
508
+ for (const r of tieredTypeRefs) {
509
+ if (remaining >= TIER_RECOMMENDED_ADDITIONAL) {
510
+ r.tier = 'recommended';
511
+ remaining -= TIER_RECOMMENDED_ADDITIONAL;
512
+ }
513
+ }
514
+ // Pass 3: upgrade to optional
515
+ for (const c of tieredCallers) {
516
+ if (c.tier !== 'recommended')
517
+ continue;
518
+ if (remaining >= TIER_OPTIONAL_ADDITIONAL) {
519
+ c.tier = 'optional';
520
+ remaining -= TIER_OPTIONAL_ADDITIONAL;
521
+ }
522
+ }
523
+ for (const r of tieredTypeRefs) {
524
+ if (r.tier !== 'recommended')
525
+ continue;
526
+ if (remaining >= TIER_OPTIONAL_ADDITIONAL) {
527
+ r.tier = 'optional';
528
+ remaining -= TIER_OPTIONAL_ADDITIONAL;
529
+ }
530
+ }
531
+ return { tieredCallers, tieredTypeRefs, truncatedByBudget };
532
+ }
533
+ // ---------------------------------------------------------------------------
534
+ // Formatter (CLI markdown)
535
+ // ---------------------------------------------------------------------------
536
+ function formatImpactResult(result, options) {
537
+ const lines = [];
538
+ lines.push(`**Impact of \`${result.methodQualifiedName}\`** \`${result.filePath}:${result.startLine}-${result.endLine}\``);
539
+ if (result.totalCallers === 0 && result.totalTypeRefs === 0) {
540
+ lines.push('');
541
+ lines.push(`_No callers and no type references found — method appears isolated (entry point or unused)._`);
542
+ return lines.join('\n');
543
+ }
544
+ lines.push(`— ${result.totalCallers} caller${result.totalCallers === 1 ? '' : 's'} (BFS depth ${result.depthReached}) · ${result.totalTypeRefs} type ref${result.totalTypeRefs === 1 ? '' : 's'} to \`${result.enclosingClass ?? '(unknown class)'}\` across ${result.totalTypeRefFiles} file${result.totalTypeRefFiles === 1 ? '' : 's'}`);
545
+ // --- Callers section (grouped by hop) ---
546
+ if (result.callers.length > 0) {
547
+ lines.push('');
548
+ lines.push(`## Callers (transitive, BFS)`);
549
+ // Group by hop, then SNR within hop
550
+ const byHop = new Map();
551
+ for (const c of result.callers) {
552
+ const group = byHop.get(c.hopDistance) ?? [];
553
+ group.push(c);
554
+ byHop.set(c.hopDistance, group);
555
+ }
556
+ const hops = [...byHop.keys()].sort((a, b) => a - b);
557
+ for (const hop of hops) {
558
+ const group = byHop.get(hop).sort(compareByImpactSnr);
559
+ lines.push('');
560
+ lines.push(`### Hop ${hop}${hop === 1 ? ' — direct callers' : ` — indirect (callers of callers, depth ${hop})`}`);
561
+ for (const c of group) {
562
+ const tag = c.crossBoundary
563
+ ? ` **[xBdy:${c.crossBoundary.fieldType}]**`
564
+ : c.sameClassBiz
565
+ ? ' **[sibling]**'
566
+ : '';
567
+ lines.push(`- **${c.methodName}** L${c.line} \`${c.filePath}:${c.line}\`${tag}`);
568
+ if (c.containingClass && c.containingMethod) {
569
+ lines.push(` _in ${c.containingClass}#${c.containingMethod}_`);
570
+ }
571
+ if (c.tier === 'recommended' || c.tier === 'optional') {
572
+ if (c.snippet) {
573
+ const snippetLines = c.snippet.split('\n').slice(0, 2);
574
+ for (const sl of snippetLines) {
575
+ lines.push(` > ${sl}`);
576
+ }
577
+ }
578
+ }
579
+ if (options?.explain) {
580
+ lines.push(` _score: ${formatImpactSnrBreakdown(c)}_`);
581
+ }
582
+ }
583
+ }
584
+ }
585
+ else {
586
+ lines.push('');
587
+ lines.push(`## Callers — none (likely entry point or RMI target)`);
588
+ }
589
+ // --- Type references section ---
590
+ if (result.typeReferences.length > 0) {
591
+ lines.push('');
592
+ lines.push(`## Type references to \`${result.enclosingClass}\` (ranked)`);
593
+ let currentKind = null;
594
+ for (const r of result.typeReferences) {
595
+ if (r.kind !== currentKind) {
596
+ currentKind = r.kind;
597
+ lines.push('');
598
+ lines.push(`### ${kindLabel(r.kind)}`);
599
+ }
600
+ const ctx = r.containingClass
601
+ ? ` _in ${r.containingClass}${r.containingMethod ? `#${r.containingMethod}` : ''}_`
602
+ : r.containingMethod
603
+ ? ` _in #${r.containingMethod}_`
604
+ : '';
605
+ lines.push(`- \`${r.filePath}:${r.line}\`${ctx}`);
606
+ if (r.tier === 'recommended' || r.tier === 'optional') {
607
+ if (r.snippet)
608
+ lines.push(` > ${r.snippet}`);
609
+ }
610
+ if (options?.explain) {
611
+ lines.push(` _score: +${r.snrScore} ${r.kind} = ${r.snrScore}_`);
612
+ }
613
+ }
614
+ }
615
+ // Omitted summary
616
+ const o = result.omitted;
617
+ const omittedParts = [];
618
+ if (o.callersPrunedByTopPerHop > 0)
619
+ omittedParts.push(`${o.callersPrunedByTopPerHop} callers past topPerHop`);
620
+ if (o.typeRefsPrunedByTopK > 0)
621
+ omittedParts.push(`${o.typeRefsPrunedByTopK} type refs past topK`);
622
+ if (o.truncatedByBudget > 0)
623
+ omittedParts.push(`${o.truncatedByBudget} truncated by budget`);
624
+ if (omittedParts.length > 0) {
625
+ const widenHint = o.callersPrunedByTopPerHop > 0 ? '. Use `-k N` to widen' : '';
626
+ lines.push('');
627
+ lines.push(`_Omitted: ${omittedParts.join(' · ')}${widenHint}_`);
628
+ }
629
+ return lines.join('\n');
630
+ }
631
+ function kindLabel(kind) {
632
+ switch (kind) {
633
+ case 'extends': return 'extends (subclass)';
634
+ case 'implements': return 'implements (interface impl)';
635
+ case 'field': return 'field declarations';
636
+ case 'param': return 'method parameters';
637
+ case 'local': return 'local variables';
638
+ case 'return': return 'method return types';
639
+ case 'cast': return 'cast expressions';
640
+ case 'annotation': return 'annotations';
641
+ case 'generic': return 'generic type arguments';
642
+ case 'import': return 'imports';
643
+ case 'other': return 'other';
644
+ }
645
+ }
646
+ //# sourceMappingURL=impact.js.map