@lumy-pack/line-lore 0.0.7 → 0.0.9

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.
@@ -5,6 +5,11 @@ export interface AncestryResult {
5
5
  subject: string;
6
6
  }
7
7
  export declare const DEFAULT_ANCESTRY_TIMEOUT = 30000;
8
+ /**
9
+ * @deprecated Use {@link findMergeCommits} (plural) instead.
10
+ * Returns only the first verified merge commit. The plural version returns
11
+ * multiple candidates from both first-parent and full ancestry paths.
12
+ */
8
13
  export declare function findMergeCommit(commitSha: string, options?: GitExecOptions & {
9
14
  ref?: string;
10
15
  }): Promise<AncestryResult | null>;
@@ -19,6 +24,19 @@ export declare function findMergeCommit(commitSha: string, options?: GitExecOpti
19
24
  * Returns false on git command failure (fail-skip policy).
20
25
  */
21
26
  export declare function verifyMergeIntroducesCommit(targetSha: string, mergeResult: AncestryResult, options?: GitExecOptions): Promise<boolean>;
27
+ /**
28
+ * Multi-candidate merge commit search.
29
+ * Returns up to MAX_CANDIDATES verified merge commits from both first-parent
30
+ * and full ancestry paths, deduplicated by mergeCommitSha and ordered with
31
+ * first-parent results first.
32
+ *
33
+ * Unlike `findMergeCommit` (singular) which returns only the first verified candidate,
34
+ * this function enables callers to iterate through multiple candidates when the
35
+ * first one doesn't yield a PR (e.g., bulk merge with non-standard message).
36
+ */
37
+ export declare function findMergeCommits(commitSha: string, options?: GitExecOptions & {
38
+ ref?: string;
39
+ }): Promise<AncestryResult[]>;
22
40
  /** Retrieve the subject line of a single commit. Returns null on git failure. */
23
41
  export declare function getCommitSubject(sha: string, options?: GitExecOptions): Promise<string | null>;
24
42
  export declare function extractPRFromMergeMessage(subject: string, platform?: string): number | null;
@@ -1,2 +1,2 @@
1
- export { extractPRFromMergeMessage, findMergeCommit, getCommitSubject, verifyMergeIntroducesCommit, } from './ancestry.js';
1
+ export { extractPRFromMergeMessage, findMergeCommit, findMergeCommits, getCommitSubject, verifyMergeIntroducesCommit, } from './ancestry.js';
2
2
  export type { AncestryResult } from './ancestry.js';
@@ -1,3 +1,11 @@
1
- import type { BlameResult, BlameStageResult, GitExecOptions, LineRange } from '../../types/index.js';
2
- export declare function executeBlame(file: string, lineRange: LineRange, options?: GitExecOptions): Promise<BlameResult[]>;
3
- export declare function analyzeBlameResults(results: BlameResult[], filePath: string, options?: GitExecOptions): Promise<BlameStageResult[]>;
1
+ import type { BlameExecOptions, BlameResult, BlameStageResult, DualBlameResult, GitExecOptions, LineRange } from '../../types/index.js';
2
+ export declare function executeBlame(file: string, lineRange: LineRange, options?: BlameExecOptions): Promise<BlameResult[]>;
3
+ /**
4
+ * Run dual blame for origin mode: origin blame (-C -C -M) and change blame (-w)
5
+ * in parallel. For change mode, only runs the single blame.
6
+ *
7
+ * Assumes both blames on the same -L range produce results in the same
8
+ * finalLine order (guaranteed by git blame line-ordered output).
9
+ */
10
+ export declare function executeDualBlame(file: string, lineRange: LineRange, options?: BlameExecOptions): Promise<DualBlameResult>;
11
+ export declare function analyzeBlameResults(results: BlameResult[], filePath: string, options?: GitExecOptions, changeResults?: BlameResult[]): Promise<BlameStageResult[]>;
@@ -1,4 +1,4 @@
1
- export { analyzeBlameResults, executeBlame } from './blame.js';
1
+ export { analyzeBlameResults, executeBlame, executeDualBlame } from './blame.js';
2
2
  export { getCosmeticDiff, isCosmeticDiff } from './detection/index.js';
3
3
  export type { CosmeticCheckResult } from './detection/index.js';
4
4
  export { parsePorcelainOutput } from './parsing/index.js';
@@ -1,6 +1,6 @@
1
1
  export { analyzeBlameResults, executeBlame, isCosmeticDiff, parsePorcelainOutput, } from './blame/index.js';
2
2
  export { compareSymbolMaps, computeContentHash, computeExactHash, computeStructuralHash, extractSymbols, findContainingSymbol, findMatchAcrossFiles, traceByAst, } from './ast-diff/index.js';
3
- export { extractPRFromMergeMessage, findMergeCommit, } from './ancestry/index.js';
3
+ export { extractPRFromMergeMessage, findMergeCommit, findMergeCommits, } from './ancestry/index.js';
4
4
  export type { AncestryResult } from './ancestry/index.js';
5
5
  export { computePatchId, findPatchIdMatch, resetPatchIdCache, } from './patch-id/index.js';
6
6
  export type { PatchIdResult } from './patch-id/index.js';
package/dist/index.cjs CHANGED
@@ -381,25 +381,6 @@ var init_executor = __esm({
381
381
  });
382
382
 
383
383
  // src/core/ancestry/ancestry.ts
384
- async function findMergeCommit(commitSha, options) {
385
- const ref = options?.ref ?? "HEAD";
386
- const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
387
- const startTime = Date.now();
388
- const firstParentResult = await findMergeCommitWithArgs(
389
- commitSha,
390
- ref,
391
- ["--first-parent"],
392
- { ...options, timeout: budget }
393
- );
394
- if (firstParentResult) return firstParentResult;
395
- const elapsed = Date.now() - startTime;
396
- const remaining = budget - elapsed;
397
- if (remaining <= 0) return null;
398
- return findMergeCommitWithArgs(commitSha, ref, [], {
399
- ...options,
400
- timeout: remaining
401
- });
402
- }
403
384
  async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
404
385
  if (mergeResult.parentShas.length < 2) return true;
405
386
  const firstParent = mergeResult.parentShas[0];
@@ -429,7 +410,7 @@ async function isAncestor(commitA, commitB, options) {
429
410
  return null;
430
411
  }
431
412
  }
432
- async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
413
+ async function findMergeCommitsWithArgs(commitSha, ref, extraArgs, options) {
433
414
  try {
434
415
  const result = await gitExec(
435
416
  [
@@ -445,28 +426,29 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
445
426
  { cwd: options?.cwd, timeout: options?.timeout }
446
427
  );
447
428
  const lines = (0, import_common_utils9.filter)(result.stdout.trim().split("\n"), import_common_utils9.isTruthy);
448
- if (lines.length === 0) return null;
429
+ if (lines.length === 0) return [];
430
+ const verifiedCandidates = [];
449
431
  const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
450
- let verifiedCount = 0;
432
+ let attemptedCount = 0;
451
433
  for (let i = 0; i < candidateCount; i++) {
452
434
  const candidate = parseMergeLogLine(lines[i]);
453
435
  if (!candidate) continue;
454
- verifiedCount++;
436
+ attemptedCount++;
455
437
  const verified = await verifyMergeIntroducesCommit(
456
438
  commitSha,
457
439
  candidate,
458
440
  options
459
441
  );
460
- if (verified) return candidate;
442
+ if (verified) verifiedCandidates.push(candidate);
461
443
  }
462
- if (verifiedCount > 0 && options?.warnings) {
444
+ if (attemptedCount > 0 && verifiedCandidates.length === 0 && options?.warnings) {
463
445
  options.warnings.push(
464
- `ancestry: all ${verifiedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
446
+ `ancestry: all ${attemptedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
465
447
  );
466
448
  }
467
- return null;
449
+ return verifiedCandidates;
468
450
  } catch {
469
- return null;
451
+ return [];
470
452
  }
471
453
  }
472
454
  function parseMergeLogLine(line) {
@@ -486,6 +468,38 @@ function parseMergeLogLine(line) {
486
468
  const subject = parts.slice(subjectStart).join(" ");
487
469
  return { mergeCommitSha, parentShas, subject };
488
470
  }
471
+ async function findMergeCommits(commitSha, options) {
472
+ const ref = options?.ref ?? "HEAD";
473
+ const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
474
+ const startTime = Date.now();
475
+ const results = [];
476
+ const seen = /* @__PURE__ */ new Set();
477
+ const pushUnique = (candidates) => {
478
+ for (const candidate of candidates) {
479
+ if (seen.has(candidate.mergeCommitSha)) continue;
480
+ seen.add(candidate.mergeCommitSha);
481
+ results.push(candidate);
482
+ if (results.length >= MAX_CANDIDATES) break;
483
+ }
484
+ };
485
+ const firstParent = await findMergeCommitsWithArgs(
486
+ commitSha,
487
+ ref,
488
+ ["--first-parent"],
489
+ { ...options, timeout: budget }
490
+ );
491
+ pushUnique(firstParent);
492
+ const elapsed = Date.now() - startTime;
493
+ const remaining = budget - elapsed;
494
+ if (remaining > 0 && results.length < MAX_CANDIDATES) {
495
+ const full = await findMergeCommitsWithArgs(commitSha, ref, [], {
496
+ ...options,
497
+ timeout: remaining
498
+ });
499
+ pushUnique(full);
500
+ }
501
+ return results;
502
+ }
489
503
  async function getCommitSubject(sha, options) {
490
504
  try {
491
505
  const result = await gitExec(["log", "-1", "--format=%s", sha], {
@@ -689,16 +703,17 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
689
703
  }
690
704
  }
691
705
  let mergeBasedPR = null;
692
- const mergeResult = await findMergeCommit(commitSha, options);
693
- if (mergeResult) {
706
+ const mergeCandidates = await findMergeCommits(commitSha, options);
707
+ const hasAncestryMerges = mergeCandidates.length > 0;
708
+ for (const candidate of mergeCandidates) {
694
709
  const prNumber = extractPRFromMergeMessage(
695
- mergeResult.subject,
710
+ candidate.subject,
696
711
  options?.platform
697
712
  );
698
713
  if (prNumber) {
699
714
  if (adapter) {
700
715
  const prInfo = await adapter.getPRForCommit(
701
- mergeResult.mergeCommitSha,
716
+ candidate.mergeCommitSha,
702
717
  prSelectOptions
703
718
  );
704
719
  if (prInfo?.mergedAt) {
@@ -708,23 +723,32 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
708
723
  if (!mergeBasedPR) {
709
724
  mergeBasedPR = {
710
725
  number: prNumber,
711
- title: mergeResult.subject,
726
+ title: candidate.subject,
712
727
  author: "",
713
728
  url: "",
714
- mergeCommit: mergeResult.mergeCommitSha,
729
+ mergeCommit: candidate.mergeCommitSha,
715
730
  baseBranch: "",
716
731
  resolvedVia: "ancestry"
717
732
  };
718
733
  }
719
- if (!options?.deep || mergeBasedPR.mergedAt) {
720
- await cache.set(commitSha, toCachedPR(mergeBasedPR));
721
- return mergeBasedPR;
734
+ break;
735
+ }
736
+ if (adapter) {
737
+ const mergeCommitPR = await adapter.getPRForCommit(
738
+ candidate.mergeCommitSha,
739
+ prSelectOptions
740
+ );
741
+ if (mergeCommitPR?.mergedAt) {
742
+ mergeBasedPR = { ...mergeCommitPR, resolvedVia: "ancestry" };
743
+ break;
722
744
  }
723
745
  }
724
746
  }
725
747
  if (mergeBasedPR) {
726
- await cache.set(commitSha, toCachedPR(mergeBasedPR));
727
- return mergeBasedPR;
748
+ if (!options?.deep || mergeBasedPR.mergedAt) {
749
+ await cache.set(commitSha, toCachedPR(mergeBasedPR));
750
+ return mergeBasedPR;
751
+ }
728
752
  }
729
753
  const commitSubject = await getCommitSubject(commitSha, options);
730
754
  if (commitSubject) {
@@ -746,7 +770,7 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
746
770
  return subjectPR;
747
771
  }
748
772
  }
749
- if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
773
+ if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH && (!hasAncestryMerges || options?.deep)) {
750
774
  const patchIdMatch = await findPatchIdMatch(commitSha, {
751
775
  ...options,
752
776
  scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
@@ -764,6 +788,10 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
764
788
  }
765
789
  }
766
790
  }
791
+ if (mergeBasedPR) {
792
+ await cache.set(commitSha, toCachedPR(mergeBasedPR));
793
+ return mergeBasedPR;
794
+ }
767
795
  return null;
768
796
  }
769
797
  function resetPRCache() {
@@ -2083,6 +2111,7 @@ function parsePorcelainOutput(output) {
2083
2111
  }
2084
2112
  let commitHash = headerMatch[1];
2085
2113
  const originalLine = parseInt(headerMatch[2], 10);
2114
+ const finalLine = parseInt(headerMatch[3], 10) || 0;
2086
2115
  const isBoundary = commitHash.startsWith("^");
2087
2116
  if (isBoundary) {
2088
2117
  commitHash = commitHash.slice(1).padStart(40, "0");
@@ -2126,6 +2155,7 @@ function parsePorcelainOutput(output) {
2126
2155
  authorEmail: cleanEmail,
2127
2156
  date,
2128
2157
  lineContent,
2158
+ finalLine,
2129
2159
  originalFile,
2130
2160
  originalLine: originalFile ? originalLine : void 0
2131
2161
  });
@@ -2136,14 +2166,112 @@ function parsePorcelainOutput(output) {
2136
2166
  // src/core/blame/blame.ts
2137
2167
  async function executeBlame(file, lineRange, options) {
2138
2168
  const lineSpec = `${lineRange.start},${lineRange.end}`;
2139
- const result = await gitExec(
2140
- ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file],
2141
- options
2142
- );
2169
+ const args = options?.mode === "change" ? ["blame", "-w", "--porcelain", "-L", lineSpec, file] : ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file];
2170
+ const result = await gitExec(args, options);
2143
2171
  return parsePorcelainOutput(result.stdout);
2144
2172
  }
2145
- async function analyzeBlameResults(results, filePath, options) {
2146
- const uniqueShas = [...new Set((0, import_common_utils7.map)(results, (r) => r.commitHash))];
2173
+ async function executeDualBlame(file, lineRange, options) {
2174
+ if (options?.mode === "change") {
2175
+ const results = await executeBlame(file, lineRange, options);
2176
+ return { blame: results, changeBlame: [] };
2177
+ }
2178
+ const [originResult, changeResult] = await Promise.allSettled([
2179
+ executeBlame(file, lineRange, options),
2180
+ executeBlame(file, lineRange, { ...options, mode: "change" })
2181
+ ]);
2182
+ const blame = originResult.status === "fulfilled" ? originResult.value : [];
2183
+ const changeBlame = changeResult.status === "fulfilled" ? changeResult.value : [];
2184
+ if (originResult.status === "rejected") {
2185
+ throw originResult.reason;
2186
+ }
2187
+ return { blame, changeBlame };
2188
+ }
2189
+ async function verifyRename(originalFile, currentFile, options) {
2190
+ try {
2191
+ const result = await gitExec(
2192
+ [
2193
+ "log",
2194
+ "--diff-filter=R",
2195
+ "--find-renames",
2196
+ "--format=%H",
2197
+ "--",
2198
+ originalFile,
2199
+ currentFile
2200
+ ],
2201
+ options
2202
+ );
2203
+ return result.stdout.trim().length > 0;
2204
+ } catch {
2205
+ return false;
2206
+ }
2207
+ }
2208
+ function crossValidateBlame(originResults, changeResults) {
2209
+ const changeMap = /* @__PURE__ */ new Map();
2210
+ for (const r of changeResults) {
2211
+ changeMap.set(r.finalLine, r);
2212
+ }
2213
+ const validated = [];
2214
+ const crossValidatedFlags = [];
2215
+ const changeFallbackFlags = [];
2216
+ const renameChecks = [];
2217
+ for (let i = 0; i < originResults.length; i++) {
2218
+ const origin = originResults[i];
2219
+ const change = changeMap.get(origin.finalLine);
2220
+ if (!change) {
2221
+ validated.push(origin);
2222
+ crossValidatedFlags.push(false);
2223
+ changeFallbackFlags.push(false);
2224
+ continue;
2225
+ }
2226
+ if (origin.commitHash === change.commitHash) {
2227
+ validated.push(origin);
2228
+ crossValidatedFlags.push(true);
2229
+ changeFallbackFlags.push(false);
2230
+ continue;
2231
+ }
2232
+ if (origin.originalFile) {
2233
+ renameChecks.push({
2234
+ originalFile: origin.originalFile,
2235
+ lineIndex: i
2236
+ });
2237
+ }
2238
+ validated.push(change);
2239
+ crossValidatedFlags.push(true);
2240
+ changeFallbackFlags.push(true);
2241
+ }
2242
+ return { validated, renameChecks, crossValidatedFlags, changeFallbackFlags };
2243
+ }
2244
+ async function analyzeBlameResults(results, filePath, options, changeResults) {
2245
+ let effectiveResults = results;
2246
+ let crossValidatedFlags;
2247
+ let changeFallbackFlagsResult;
2248
+ if (changeResults && changeResults.length > 0) {
2249
+ const { validated, renameChecks, crossValidatedFlags: cvFlags, changeFallbackFlags } = crossValidateBlame(results, changeResults);
2250
+ if (renameChecks.length > 0) {
2251
+ const pendingChecks = /* @__PURE__ */ new Map();
2252
+ for (const check of renameChecks) {
2253
+ const cacheKey = `${check.originalFile}:${filePath}`;
2254
+ if (!pendingChecks.has(cacheKey)) {
2255
+ pendingChecks.set(cacheKey, verifyRename(check.originalFile, filePath, options));
2256
+ }
2257
+ }
2258
+ const renameResults = /* @__PURE__ */ new Map();
2259
+ for (const [key, promise] of pendingChecks) {
2260
+ renameResults.set(key, await promise);
2261
+ }
2262
+ for (const check of renameChecks) {
2263
+ const cacheKey = `${check.originalFile}:${filePath}`;
2264
+ if (renameResults.get(cacheKey)) {
2265
+ validated[check.lineIndex] = results[check.lineIndex];
2266
+ changeFallbackFlags[check.lineIndex] = false;
2267
+ }
2268
+ }
2269
+ }
2270
+ effectiveResults = validated;
2271
+ crossValidatedFlags = cvFlags;
2272
+ changeFallbackFlagsResult = changeFallbackFlags;
2273
+ }
2274
+ const uniqueShas = [...new Set((0, import_common_utils7.map)(effectiveResults, (r) => r.commitHash))];
2147
2275
  const cosmeticMap = /* @__PURE__ */ new Map();
2148
2276
  const zeroSha = "0".repeat(40);
2149
2277
  const tasks = [];
@@ -2152,7 +2280,7 @@ async function analyzeBlameResults(results, filePath, options) {
2152
2280
  tasks.push(
2153
2281
  (async () => {
2154
2282
  try {
2155
- const blameResult = results.find((r) => r.commitHash === sha);
2283
+ const blameResult = effectiveResults.find((r) => r.commitHash === sha);
2156
2284
  if (!blameResult) return;
2157
2285
  const file = blameResult.originalFile ?? filePath;
2158
2286
  const diff = await getCosmeticDiff(sha, file, options);
@@ -2164,12 +2292,14 @@ async function analyzeBlameResults(results, filePath, options) {
2164
2292
  );
2165
2293
  });
2166
2294
  await Promise.all(tasks);
2167
- return (0, import_common_utils7.map)(results, (blame) => {
2295
+ return (0, import_common_utils7.map)(effectiveResults, (blame, i) => {
2168
2296
  const cosmetic = cosmeticMap.get(blame.commitHash);
2169
2297
  return {
2170
2298
  blame,
2171
2299
  isCosmetic: cosmetic?.isCosmetic ?? false,
2172
- cosmeticReason: cosmetic?.reason
2300
+ cosmeticReason: cosmetic?.reason,
2301
+ crossValidated: crossValidatedFlags?.[i],
2302
+ usedChangeFallback: changeFallbackFlagsResult?.[i]
2173
2303
  };
2174
2304
  });
2175
2305
  }
@@ -2350,8 +2480,11 @@ async function runBlameAndAuth(adapter, options, execOptions) {
2350
2480
  const lineRange = parseLineRange(
2351
2481
  options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
2352
2482
  );
2353
- const blameChain = executeBlame(options.file, lineRange, execOptions).then(
2354
- (results) => analyzeBlameResults(results, options.file, execOptions)
2483
+ const blameChain = executeDualBlame(options.file, lineRange, {
2484
+ ...execOptions,
2485
+ mode: options.mode
2486
+ }).then(
2487
+ ({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
2355
2488
  );
2356
2489
  const [authResult, blameResult] = await Promise.allSettled([
2357
2490
  adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
@@ -2373,12 +2506,24 @@ async function runBlameAndAuth(adapter, options, execOptions) {
2373
2506
  }
2374
2507
  return { analyzed: blameResult.value, operatingLevel, warnings };
2375
2508
  }
2376
- async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
2509
+ function resolveTraceMode(mode) {
2510
+ return mode ?? "origin";
2511
+ }
2512
+ function deduplicatedLookupPR(sha, adapter, options, inflight) {
2513
+ const existing = inflight.get(sha);
2514
+ if (existing) return existing;
2515
+ const promise = lookupPR(sha, adapter, options);
2516
+ inflight.set(sha, promise);
2517
+ promise.finally(() => inflight.delete(sha));
2518
+ return promise;
2519
+ }
2520
+ async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, inflightPR, skipPatchIdScan, preferredBase) {
2377
2521
  const nodes = [];
2522
+ const traceMode = resolveTraceMode(options.mode);
2378
2523
  const commitNode = {
2379
2524
  type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
2380
2525
  sha: entry.blame.commitHash,
2381
- trackingMethod: "blame-CMw",
2526
+ trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
2382
2527
  confidence: "exact",
2383
2528
  note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
2384
2529
  };
@@ -2400,17 +2545,18 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
2400
2545
  }
2401
2546
  }
2402
2547
  const targetSha = nodes[nodes.length - 1].sha;
2548
+ const prLookupOptions = {
2549
+ ...execOptions,
2550
+ noCache: options.noCache,
2551
+ cacheOnly: options.cacheOnly,
2552
+ deep: featureFlags.deepTrace,
2553
+ repoId,
2554
+ skipPatchIdScan,
2555
+ preferredBase,
2556
+ platform: adapter?.platform
2557
+ };
2403
2558
  if (targetSha) {
2404
- const prInfo = await lookupPR(targetSha, adapter, {
2405
- ...execOptions,
2406
- noCache: options.noCache,
2407
- cacheOnly: options.cacheOnly,
2408
- deep: featureFlags.deepTrace,
2409
- repoId,
2410
- skipPatchIdScan,
2411
- preferredBase,
2412
- platform: adapter?.platform
2413
- });
2559
+ const prInfo = await deduplicatedLookupPR(targetSha, adapter, prLookupOptions, inflightPR);
2414
2560
  if (prInfo) {
2415
2561
  nodes.push({
2416
2562
  type: "pull_request",
@@ -2427,6 +2573,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
2427
2573
  return nodes;
2428
2574
  }
2429
2575
  async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
2576
+ const inflightPR = /* @__PURE__ */ new Map();
2430
2577
  const results = await Promise.allSettled(
2431
2578
  (0, import_common_utils11.map)(
2432
2579
  analyzed,
@@ -2437,6 +2584,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
2437
2584
  options,
2438
2585
  execOptions,
2439
2586
  repoId,
2587
+ inflightPR,
2440
2588
  skipPatchIdScan,
2441
2589
  preferredBase
2442
2590
  )
@@ -2446,6 +2594,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
2446
2594
  }
2447
2595
  var legacyCacheCleaned = false;
2448
2596
  async function trace(options) {
2597
+ const mode = resolveTraceMode(options.mode);
2449
2598
  const { file, cwd } = await resolveFileContext(options.file, options.cwd);
2450
2599
  const warnings = [];
2451
2600
  const execOptions = { cwd, warnings };
@@ -2467,7 +2616,7 @@ async function trace(options) {
2467
2616
  }
2468
2617
  const blameAuth = await runBlameAndAuth(
2469
2618
  platform.adapter,
2470
- { ...options, file, cwd },
2619
+ { ...options, mode, file, cwd },
2471
2620
  execOptions
2472
2621
  );
2473
2622
  const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
@@ -2510,7 +2659,7 @@ async function trace(options) {
2510
2659
  blameAuth.analyzed,
2511
2660
  featureFlags,
2512
2661
  platform.adapter,
2513
- { ...options, file, cwd },
2662
+ { ...options, mode, file, cwd },
2514
2663
  execOptions,
2515
2664
  repoId,
2516
2665
  cloneStatus.partialClone || void 0,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { AstDiffStageResult, AstTraceResult, AuthStatus, BlameResult, BlameStageResult, CacheEntry, ChangeType, CommitInfo, ComparisonResult, Confidence, ContentHash, CosmeticReason, FeatureFlags, GraphOptions, GraphResult, HealthReport, IssueInfo, LineRange, NormalizedResponse, OperatingLevel, PlatformAdapter, PlatformType, PRInfo, RateLimitInfo, RemoteInfo, SymbolInfo, SymbolKind, TraceNode, TraceNodeType, TraceOptions, TraceResult, TrackingMethod, } from './types/index.js';
1
+ export type { AstDiffStageResult, AstTraceResult, AuthStatus, BlameResult, BlameStageResult, CacheEntry, ChangeType, CommitInfo, ComparisonResult, Confidence, ContentHash, CosmeticReason, FeatureFlags, GraphOptions, GraphResult, HealthReport, IssueInfo, LineRange, NormalizedResponse, OperatingLevel, PlatformAdapter, PlatformType, PRInfo, RateLimitInfo, RemoteInfo, SymbolInfo, SymbolKind, TraceNode, TraceNodeType, TraceMode, TraceOptions, TraceResult, TrackingMethod, } from './types/index.js';
2
2
  export { LineLoreError, LineLoreErrorCode } from './errors.js';
3
3
  export { clearCache, graph, health, trace } from './core/core.js';
4
4
  export type { TraceFullResult } from './core/core.js';