@colbymchenry/codegraph-darwin-x64 1.1.3 → 1.1.5

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.
@@ -2435,11 +2435,24 @@ class ToolHandler {
2435
2435
  // trace endpoint picker uses) and inject it as an entry, so every symbol the
2436
2436
  // agent explicitly named is in the subgraph and its file is scored.
2437
2437
  const namedSeedIds = new Set();
2438
+ // The subset of named seeds that earns the named-FIRST sort tier. We still
2439
+ // SEED every ≤3-def name (so RWR / flow ranking is unchanged), but only the
2440
+ // most-substantive def is tiered — a bare name's unrelated namesakes (Go's
2441
+ // `NewClient` = real client + test fake + xds pool) must not fill the tier
2442
+ // and crowd out the real answer file (grpc's `dialoptions.go`). Corroborated
2443
+ // overloads (the query also named the type) all earn it. (#1064)
2444
+ const tierSeedIds = new Set();
2438
2445
  {
2439
2446
  const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro)$/i;
2440
2447
  const CALLABLE = new Set(['method', 'function', 'component', 'constructor']);
2441
2448
  const isTestPath = (p) => /(^|\/)(tests?|specs?|__tests__|testdata|mocks?|fixtures?)\//i.test(p) || /\.(test|spec)\.[a-z]+$/i.test(p);
2442
2449
  const bodyLines = (n) => Math.max(0, (n.endLine ?? n.startLine) - n.startLine);
2450
+ const callerCount = (n) => { try {
2451
+ return cg.getCallers(n.id).length;
2452
+ }
2453
+ catch {
2454
+ return 0;
2455
+ } };
2443
2456
  const tokens = [...new Set(query.split(/[\s,()[\]]+/)
2444
2457
  .map((t) => t.replace(FILE_EXT, '').trim())
2445
2458
  .filter((t) => t.length >= 3 && /^[A-Za-z_$][\w$]*(?:(?:::|\.)[\w$]+)*$/.test(t)))].slice(0, 16);
@@ -2475,12 +2488,23 @@ class ToolHandler {
2475
2488
  // capped; else fall back to the single most-substantive def. This is the
2476
2489
  // explore-side mirror of codegraph_node's overload disambiguation.
2477
2490
  let picks;
2491
+ let tierPicks; // subset that earns the named-first tier (#1064)
2478
2492
  if (cands.length <= 3) {
2479
2493
  picks = cands;
2494
+ // Centrality de-noise: tier the most-substantive def PLUS any co-named
2495
+ // def of comparable centrality (a real overload/wrapper — excalidraw's
2496
+ // `mutateElement` lives in mutateElement.ts, App.tsx AND Scene.ts, all
2497
+ // within ~2x callers). EXCLUDE a vastly-less-central namesake (Go's
2498
+ // `NewClient`: real client 492 callers vs xds-pool 11, test-fake 3 →
2499
+ // ratio <0.025) so it doesn't fill the tier and crowd out the answer.
2500
+ const counts = new Map(cands.map((c) => [c.id, callerCount(c)]));
2501
+ const maxCallers = Math.max(1, ...counts.values());
2502
+ tierPicks = cands.filter((c, i) => i === 0 || (counts.get(c.id) ?? 0) >= maxCallers * 0.25);
2480
2503
  }
2481
2504
  else {
2482
2505
  const ctx = cands.filter(inNamedContext);
2483
2506
  picks = ctx.length > 0 ? ctx.slice(0, 4) : cands.slice(0, 1);
2507
+ tierPicks = picks; // corroborated overloads (or the single fallback) all earn it
2484
2508
  }
2485
2509
  for (const n of picks) {
2486
2510
  if (!subgraph.nodes.has(n.id))
@@ -2492,6 +2516,8 @@ class ToolHandler {
2492
2516
  // so a named symbol FTS already gathered never sorted to the top.)
2493
2517
  namedSeedIds.add(n.id);
2494
2518
  }
2519
+ for (const n of tierPicks)
2520
+ tierSeedIds.add(n.id);
2495
2521
  }
2496
2522
  }
2497
2523
  // Step 2: Group nodes by file, score by relevance
@@ -2505,6 +2531,46 @@ class ToolHandler {
2505
2531
  if (entryNodeIds.has(edge.target))
2506
2532
  connectedToEntry.add(edge.source);
2507
2533
  }
2534
+ // CHANGE SURFACE (#1064): a named method's signature types — its parameter
2535
+ // and return types — are part of what you'd edit to "add a parameter to X",
2536
+ // yet they can be lexically dissimilar to the query ("add a parameter to
2537
+ // NewClient" shares no words with `dialoptions.go`, which defines NewClient's
2538
+ // `DialOption`) and sit a hop away. COLLECT them here from each named-seed
2539
+ // callable's outgoing signature edges (full graph — the type is often not in
2540
+ // the subgraph); the decision to surface one is DEFERRED to the buried-rescue
2541
+ // pass below, which fires only when the type's file would otherwise be
2542
+ // dropped — so a well-connected type (excalidraw's element types, Alamofire's
2543
+ // `DataRequest` on a flow query) is left to rank on its own and never
2544
+ // displaces a flow-central file. Bounded: only the few named seeds, only the
2545
+ // types in their signatures.
2546
+ const CALLABLE_KINDS = new Set(['method', 'function', 'component', 'constructor']);
2547
+ const TYPE_KINDS = new Set(['class', 'struct', 'interface', 'trait', 'protocol', 'enum', 'type_alias']);
2548
+ const SIG_EDGE = new Set(['references', 'type_of', 'returns']);
2549
+ const changeSurfaceCandidates = [];
2550
+ const seenChangeSurface = new Set();
2551
+ for (const seedId of tierSeedIds) {
2552
+ const seedNode = subgraph.nodes.get(seedId);
2553
+ if (!seedNode || !CALLABLE_KINDS.has(seedNode.kind))
2554
+ continue;
2555
+ let outs = [];
2556
+ try {
2557
+ outs = cg.getOutgoingEdges(seedId);
2558
+ }
2559
+ catch {
2560
+ continue;
2561
+ }
2562
+ for (const e of outs) {
2563
+ if (!SIG_EDGE.has(e.kind))
2564
+ continue;
2565
+ const tgt = cg.getNode(e.target);
2566
+ if (!tgt || !TYPE_KINDS.has(tgt.kind) || namedSeedIds.has(tgt.id))
2567
+ continue;
2568
+ if (seenChangeSurface.has(tgt.id))
2569
+ continue;
2570
+ seenChangeSurface.add(tgt.id);
2571
+ changeSurfaceCandidates.push(tgt);
2572
+ }
2573
+ }
2508
2574
  for (const node of subgraph.nodes.values()) {
2509
2575
  // Skip import/export nodes — they add noise without information
2510
2576
  if (node.kind === 'import' || node.kind === 'export')
@@ -2624,6 +2690,36 @@ class ToolHandler {
2624
2690
  if (n)
2625
2691
  entryFiles.add(n.filePath);
2626
2692
  }
2693
+ // Buried-rescue pass (#1064): surface a named method's signature type ONLY
2694
+ // when its file is genuinely buried — near-zero graph mass AND not lexically
2695
+ // matched. That is the invisible case (grpc's `DialOption` → `dialoptions.go`,
2696
+ // g≈0, 0 term hits): reachable but ranked nowhere, so the agent greps. A
2697
+ // well-connected type file (excalidraw element types, Alamofire `DataRequest`)
2698
+ // is NOT buried and is left alone — rescuing it would displace a flow-central
2699
+ // file (App.tsx, Validation.swift). Buried is judged on the PRE-rescue graph,
2700
+ // so injecting the type below can't make it look connected. A rescued file is
2701
+ // injected (so it renders), force-kept (gate + relevantFiles), and tiered.
2702
+ const changeSurfaceFiles = new Set();
2703
+ for (const t of changeSurfaceCandidates) {
2704
+ const fp = t.filePath;
2705
+ const buried = (fileGraphScore.get(fp) ?? 0) < maxGraph * 0.06
2706
+ && (fileTermHits.get(fp) ?? 0) < 2;
2707
+ if (!buried)
2708
+ continue;
2709
+ changeSurfaceFiles.add(fp);
2710
+ if (!subgraph.nodes.has(t.id))
2711
+ subgraph.nodes.set(t.id, t);
2712
+ let group = fileGroups.get(fp);
2713
+ if (!group) {
2714
+ group = { nodes: [], score: 0 };
2715
+ fileGroups.set(fp, group);
2716
+ }
2717
+ if (!group.nodes.some((n) => n.id === t.id))
2718
+ group.nodes.push(t);
2719
+ group.score = Math.max(group.score, 45);
2720
+ if (!relevantFiles.some(([f]) => f === fp))
2721
+ relevantFiles.push([fp, group]);
2722
+ }
2627
2723
  // Relevance gate (so the generous budget is a CEILING, not a target): keep a
2628
2724
  // file only if it is STRUCTURALLY relevant by ANY of:
2629
2725
  // - graph score within a fraction of the top (it's on/near the flow), OR
@@ -2640,6 +2736,7 @@ class ToolHandler {
2640
2736
  const gated = relevantFiles.filter(([fp]) => (fileGraphScore.get(fp) ?? 0) >= maxGraph * 0.06
2641
2737
  || centralFiles.has(fp)
2642
2738
  || entryFiles.has(fp)
2739
+ || changeSurfaceFiles.has(fp)
2643
2740
  || (fileTermHits.get(fp) ?? 0) >= 2);
2644
2741
  if (gated.length >= 2)
2645
2742
  relevantFiles = gated;
@@ -2654,11 +2751,16 @@ class ToolHandler {
2654
2751
  // in other files (`Validation.swift`), falls outside the budget, and the
2655
2752
  // agent Reads it. The named file is the answer — rank it at the top.
2656
2753
  const namedSeedFiles = new Set();
2657
- for (const id of namedSeedIds) {
2754
+ for (const id of tierSeedIds) {
2658
2755
  const n = subgraph.nodes.get(id);
2659
2756
  if (n)
2660
2757
  namedSeedFiles.add(n.filePath);
2661
2758
  }
2759
+ // A rescued change-surface file (only the genuinely-buried ones — see the
2760
+ // buried-rescue pass) is the lexically-dissimilar answer; give it the named
2761
+ // tier so it isn't buried under files that merely share surface words (#1064).
2762
+ for (const fp of changeSurfaceFiles)
2763
+ namedSeedFiles.add(fp);
2662
2764
  // Multi-term corroboration tier: a file that is BOTH (a) an entry/central file
2663
2765
  // (a search root, named seed, or graph-central hub — i.e. structurally part of
2664
2766
  // the answer) AND (b) matched by ≥2 DISTINCT query terms must not be buried by