@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.
- package/README.md +44 -20
- package/dist/cli.mjs +228 -68
- package/dist/core/ancestry/ancestry.d.ts +18 -0
- package/dist/core/ancestry/index.d.ts +1 -1
- package/dist/core/blame/blame.d.ts +11 -3
- package/dist/core/blame/index.d.ts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/index.cjs +214 -65
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +214 -65
- package/dist/types/blame.d.ts +12 -0
- package/dist/types/git.d.ts +5 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/stage.d.ts +4 -0
- package/dist/types/trace.d.ts +3 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -368,25 +368,6 @@ var init_executor = __esm({
|
|
|
368
368
|
|
|
369
369
|
// src/core/ancestry/ancestry.ts
|
|
370
370
|
import { filter as filter4, isTruthy as isTruthy4 } from "@winglet/common-utils";
|
|
371
|
-
async function findMergeCommit(commitSha, options) {
|
|
372
|
-
const ref = options?.ref ?? "HEAD";
|
|
373
|
-
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
374
|
-
const startTime = Date.now();
|
|
375
|
-
const firstParentResult = await findMergeCommitWithArgs(
|
|
376
|
-
commitSha,
|
|
377
|
-
ref,
|
|
378
|
-
["--first-parent"],
|
|
379
|
-
{ ...options, timeout: budget }
|
|
380
|
-
);
|
|
381
|
-
if (firstParentResult) return firstParentResult;
|
|
382
|
-
const elapsed = Date.now() - startTime;
|
|
383
|
-
const remaining = budget - elapsed;
|
|
384
|
-
if (remaining <= 0) return null;
|
|
385
|
-
return findMergeCommitWithArgs(commitSha, ref, [], {
|
|
386
|
-
...options,
|
|
387
|
-
timeout: remaining
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
371
|
async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
|
|
391
372
|
if (mergeResult.parentShas.length < 2) return true;
|
|
392
373
|
const firstParent = mergeResult.parentShas[0];
|
|
@@ -416,7 +397,7 @@ async function isAncestor(commitA, commitB, options) {
|
|
|
416
397
|
return null;
|
|
417
398
|
}
|
|
418
399
|
}
|
|
419
|
-
async function
|
|
400
|
+
async function findMergeCommitsWithArgs(commitSha, ref, extraArgs, options) {
|
|
420
401
|
try {
|
|
421
402
|
const result = await gitExec(
|
|
422
403
|
[
|
|
@@ -432,28 +413,29 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
|
432
413
|
{ cwd: options?.cwd, timeout: options?.timeout }
|
|
433
414
|
);
|
|
434
415
|
const lines = filter4(result.stdout.trim().split("\n"), isTruthy4);
|
|
435
|
-
if (lines.length === 0) return
|
|
416
|
+
if (lines.length === 0) return [];
|
|
417
|
+
const verifiedCandidates = [];
|
|
436
418
|
const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
|
|
437
|
-
let
|
|
419
|
+
let attemptedCount = 0;
|
|
438
420
|
for (let i = 0; i < candidateCount; i++) {
|
|
439
421
|
const candidate = parseMergeLogLine(lines[i]);
|
|
440
422
|
if (!candidate) continue;
|
|
441
|
-
|
|
423
|
+
attemptedCount++;
|
|
442
424
|
const verified = await verifyMergeIntroducesCommit(
|
|
443
425
|
commitSha,
|
|
444
426
|
candidate,
|
|
445
427
|
options
|
|
446
428
|
);
|
|
447
|
-
if (verified)
|
|
429
|
+
if (verified) verifiedCandidates.push(candidate);
|
|
448
430
|
}
|
|
449
|
-
if (
|
|
431
|
+
if (attemptedCount > 0 && verifiedCandidates.length === 0 && options?.warnings) {
|
|
450
432
|
options.warnings.push(
|
|
451
|
-
`ancestry: all ${
|
|
433
|
+
`ancestry: all ${attemptedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
|
|
452
434
|
);
|
|
453
435
|
}
|
|
454
|
-
return
|
|
436
|
+
return verifiedCandidates;
|
|
455
437
|
} catch {
|
|
456
|
-
return
|
|
438
|
+
return [];
|
|
457
439
|
}
|
|
458
440
|
}
|
|
459
441
|
function parseMergeLogLine(line) {
|
|
@@ -473,6 +455,38 @@ function parseMergeLogLine(line) {
|
|
|
473
455
|
const subject = parts.slice(subjectStart).join(" ");
|
|
474
456
|
return { mergeCommitSha, parentShas, subject };
|
|
475
457
|
}
|
|
458
|
+
async function findMergeCommits(commitSha, options) {
|
|
459
|
+
const ref = options?.ref ?? "HEAD";
|
|
460
|
+
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
461
|
+
const startTime = Date.now();
|
|
462
|
+
const results = [];
|
|
463
|
+
const seen = /* @__PURE__ */ new Set();
|
|
464
|
+
const pushUnique = (candidates) => {
|
|
465
|
+
for (const candidate of candidates) {
|
|
466
|
+
if (seen.has(candidate.mergeCommitSha)) continue;
|
|
467
|
+
seen.add(candidate.mergeCommitSha);
|
|
468
|
+
results.push(candidate);
|
|
469
|
+
if (results.length >= MAX_CANDIDATES) break;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
const firstParent = await findMergeCommitsWithArgs(
|
|
473
|
+
commitSha,
|
|
474
|
+
ref,
|
|
475
|
+
["--first-parent"],
|
|
476
|
+
{ ...options, timeout: budget }
|
|
477
|
+
);
|
|
478
|
+
pushUnique(firstParent);
|
|
479
|
+
const elapsed = Date.now() - startTime;
|
|
480
|
+
const remaining = budget - elapsed;
|
|
481
|
+
if (remaining > 0 && results.length < MAX_CANDIDATES) {
|
|
482
|
+
const full = await findMergeCommitsWithArgs(commitSha, ref, [], {
|
|
483
|
+
...options,
|
|
484
|
+
timeout: remaining
|
|
485
|
+
});
|
|
486
|
+
pushUnique(full);
|
|
487
|
+
}
|
|
488
|
+
return results;
|
|
489
|
+
}
|
|
476
490
|
async function getCommitSubject(sha, options) {
|
|
477
491
|
try {
|
|
478
492
|
const result = await gitExec(["log", "-1", "--format=%s", sha], {
|
|
@@ -675,16 +689,17 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
675
689
|
}
|
|
676
690
|
}
|
|
677
691
|
let mergeBasedPR = null;
|
|
678
|
-
const
|
|
679
|
-
|
|
692
|
+
const mergeCandidates = await findMergeCommits(commitSha, options);
|
|
693
|
+
const hasAncestryMerges = mergeCandidates.length > 0;
|
|
694
|
+
for (const candidate of mergeCandidates) {
|
|
680
695
|
const prNumber = extractPRFromMergeMessage(
|
|
681
|
-
|
|
696
|
+
candidate.subject,
|
|
682
697
|
options?.platform
|
|
683
698
|
);
|
|
684
699
|
if (prNumber) {
|
|
685
700
|
if (adapter) {
|
|
686
701
|
const prInfo = await adapter.getPRForCommit(
|
|
687
|
-
|
|
702
|
+
candidate.mergeCommitSha,
|
|
688
703
|
prSelectOptions
|
|
689
704
|
);
|
|
690
705
|
if (prInfo?.mergedAt) {
|
|
@@ -694,23 +709,32 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
694
709
|
if (!mergeBasedPR) {
|
|
695
710
|
mergeBasedPR = {
|
|
696
711
|
number: prNumber,
|
|
697
|
-
title:
|
|
712
|
+
title: candidate.subject,
|
|
698
713
|
author: "",
|
|
699
714
|
url: "",
|
|
700
|
-
mergeCommit:
|
|
715
|
+
mergeCommit: candidate.mergeCommitSha,
|
|
701
716
|
baseBranch: "",
|
|
702
717
|
resolvedVia: "ancestry"
|
|
703
718
|
};
|
|
704
719
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
if (adapter) {
|
|
723
|
+
const mergeCommitPR = await adapter.getPRForCommit(
|
|
724
|
+
candidate.mergeCommitSha,
|
|
725
|
+
prSelectOptions
|
|
726
|
+
);
|
|
727
|
+
if (mergeCommitPR?.mergedAt) {
|
|
728
|
+
mergeBasedPR = { ...mergeCommitPR, resolvedVia: "ancestry" };
|
|
729
|
+
break;
|
|
708
730
|
}
|
|
709
731
|
}
|
|
710
732
|
}
|
|
711
733
|
if (mergeBasedPR) {
|
|
712
|
-
|
|
713
|
-
|
|
734
|
+
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
735
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
736
|
+
return mergeBasedPR;
|
|
737
|
+
}
|
|
714
738
|
}
|
|
715
739
|
const commitSubject = await getCommitSubject(commitSha, options);
|
|
716
740
|
if (commitSubject) {
|
|
@@ -732,7 +756,7 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
732
756
|
return subjectPR;
|
|
733
757
|
}
|
|
734
758
|
}
|
|
735
|
-
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
759
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH && (!hasAncestryMerges || options?.deep)) {
|
|
736
760
|
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
737
761
|
...options,
|
|
738
762
|
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
@@ -750,6 +774,10 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
750
774
|
}
|
|
751
775
|
}
|
|
752
776
|
}
|
|
777
|
+
if (mergeBasedPR) {
|
|
778
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
779
|
+
return mergeBasedPR;
|
|
780
|
+
}
|
|
753
781
|
return null;
|
|
754
782
|
}
|
|
755
783
|
function resetPRCache() {
|
|
@@ -2058,6 +2086,7 @@ function parsePorcelainOutput(output) {
|
|
|
2058
2086
|
}
|
|
2059
2087
|
let commitHash = headerMatch[1];
|
|
2060
2088
|
const originalLine = parseInt(headerMatch[2], 10);
|
|
2089
|
+
const finalLine = parseInt(headerMatch[3], 10) || 0;
|
|
2061
2090
|
const isBoundary = commitHash.startsWith("^");
|
|
2062
2091
|
if (isBoundary) {
|
|
2063
2092
|
commitHash = commitHash.slice(1).padStart(40, "0");
|
|
@@ -2101,6 +2130,7 @@ function parsePorcelainOutput(output) {
|
|
|
2101
2130
|
authorEmail: cleanEmail,
|
|
2102
2131
|
date,
|
|
2103
2132
|
lineContent,
|
|
2133
|
+
finalLine,
|
|
2104
2134
|
originalFile,
|
|
2105
2135
|
originalLine: originalFile ? originalLine : void 0
|
|
2106
2136
|
});
|
|
@@ -2111,14 +2141,112 @@ function parsePorcelainOutput(output) {
|
|
|
2111
2141
|
// src/core/blame/blame.ts
|
|
2112
2142
|
async function executeBlame(file, lineRange, options) {
|
|
2113
2143
|
const lineSpec = `${lineRange.start},${lineRange.end}`;
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
options
|
|
2117
|
-
);
|
|
2144
|
+
const args = options?.mode === "change" ? ["blame", "-w", "--porcelain", "-L", lineSpec, file] : ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file];
|
|
2145
|
+
const result = await gitExec(args, options);
|
|
2118
2146
|
return parsePorcelainOutput(result.stdout);
|
|
2119
2147
|
}
|
|
2120
|
-
async function
|
|
2121
|
-
|
|
2148
|
+
async function executeDualBlame(file, lineRange, options) {
|
|
2149
|
+
if (options?.mode === "change") {
|
|
2150
|
+
const results = await executeBlame(file, lineRange, options);
|
|
2151
|
+
return { blame: results, changeBlame: [] };
|
|
2152
|
+
}
|
|
2153
|
+
const [originResult, changeResult] = await Promise.allSettled([
|
|
2154
|
+
executeBlame(file, lineRange, options),
|
|
2155
|
+
executeBlame(file, lineRange, { ...options, mode: "change" })
|
|
2156
|
+
]);
|
|
2157
|
+
const blame = originResult.status === "fulfilled" ? originResult.value : [];
|
|
2158
|
+
const changeBlame = changeResult.status === "fulfilled" ? changeResult.value : [];
|
|
2159
|
+
if (originResult.status === "rejected") {
|
|
2160
|
+
throw originResult.reason;
|
|
2161
|
+
}
|
|
2162
|
+
return { blame, changeBlame };
|
|
2163
|
+
}
|
|
2164
|
+
async function verifyRename(originalFile, currentFile, options) {
|
|
2165
|
+
try {
|
|
2166
|
+
const result = await gitExec(
|
|
2167
|
+
[
|
|
2168
|
+
"log",
|
|
2169
|
+
"--diff-filter=R",
|
|
2170
|
+
"--find-renames",
|
|
2171
|
+
"--format=%H",
|
|
2172
|
+
"--",
|
|
2173
|
+
originalFile,
|
|
2174
|
+
currentFile
|
|
2175
|
+
],
|
|
2176
|
+
options
|
|
2177
|
+
);
|
|
2178
|
+
return result.stdout.trim().length > 0;
|
|
2179
|
+
} catch {
|
|
2180
|
+
return false;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function crossValidateBlame(originResults, changeResults) {
|
|
2184
|
+
const changeMap = /* @__PURE__ */ new Map();
|
|
2185
|
+
for (const r of changeResults) {
|
|
2186
|
+
changeMap.set(r.finalLine, r);
|
|
2187
|
+
}
|
|
2188
|
+
const validated = [];
|
|
2189
|
+
const crossValidatedFlags = [];
|
|
2190
|
+
const changeFallbackFlags = [];
|
|
2191
|
+
const renameChecks = [];
|
|
2192
|
+
for (let i = 0; i < originResults.length; i++) {
|
|
2193
|
+
const origin = originResults[i];
|
|
2194
|
+
const change = changeMap.get(origin.finalLine);
|
|
2195
|
+
if (!change) {
|
|
2196
|
+
validated.push(origin);
|
|
2197
|
+
crossValidatedFlags.push(false);
|
|
2198
|
+
changeFallbackFlags.push(false);
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (origin.commitHash === change.commitHash) {
|
|
2202
|
+
validated.push(origin);
|
|
2203
|
+
crossValidatedFlags.push(true);
|
|
2204
|
+
changeFallbackFlags.push(false);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
if (origin.originalFile) {
|
|
2208
|
+
renameChecks.push({
|
|
2209
|
+
originalFile: origin.originalFile,
|
|
2210
|
+
lineIndex: i
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
validated.push(change);
|
|
2214
|
+
crossValidatedFlags.push(true);
|
|
2215
|
+
changeFallbackFlags.push(true);
|
|
2216
|
+
}
|
|
2217
|
+
return { validated, renameChecks, crossValidatedFlags, changeFallbackFlags };
|
|
2218
|
+
}
|
|
2219
|
+
async function analyzeBlameResults(results, filePath, options, changeResults) {
|
|
2220
|
+
let effectiveResults = results;
|
|
2221
|
+
let crossValidatedFlags;
|
|
2222
|
+
let changeFallbackFlagsResult;
|
|
2223
|
+
if (changeResults && changeResults.length > 0) {
|
|
2224
|
+
const { validated, renameChecks, crossValidatedFlags: cvFlags, changeFallbackFlags } = crossValidateBlame(results, changeResults);
|
|
2225
|
+
if (renameChecks.length > 0) {
|
|
2226
|
+
const pendingChecks = /* @__PURE__ */ new Map();
|
|
2227
|
+
for (const check of renameChecks) {
|
|
2228
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2229
|
+
if (!pendingChecks.has(cacheKey)) {
|
|
2230
|
+
pendingChecks.set(cacheKey, verifyRename(check.originalFile, filePath, options));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
const renameResults = /* @__PURE__ */ new Map();
|
|
2234
|
+
for (const [key, promise] of pendingChecks) {
|
|
2235
|
+
renameResults.set(key, await promise);
|
|
2236
|
+
}
|
|
2237
|
+
for (const check of renameChecks) {
|
|
2238
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2239
|
+
if (renameResults.get(cacheKey)) {
|
|
2240
|
+
validated[check.lineIndex] = results[check.lineIndex];
|
|
2241
|
+
changeFallbackFlags[check.lineIndex] = false;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
effectiveResults = validated;
|
|
2246
|
+
crossValidatedFlags = cvFlags;
|
|
2247
|
+
changeFallbackFlagsResult = changeFallbackFlags;
|
|
2248
|
+
}
|
|
2249
|
+
const uniqueShas = [...new Set(map6(effectiveResults, (r) => r.commitHash))];
|
|
2122
2250
|
const cosmeticMap = /* @__PURE__ */ new Map();
|
|
2123
2251
|
const zeroSha = "0".repeat(40);
|
|
2124
2252
|
const tasks = [];
|
|
@@ -2127,7 +2255,7 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2127
2255
|
tasks.push(
|
|
2128
2256
|
(async () => {
|
|
2129
2257
|
try {
|
|
2130
|
-
const blameResult =
|
|
2258
|
+
const blameResult = effectiveResults.find((r) => r.commitHash === sha);
|
|
2131
2259
|
if (!blameResult) return;
|
|
2132
2260
|
const file = blameResult.originalFile ?? filePath;
|
|
2133
2261
|
const diff = await getCosmeticDiff(sha, file, options);
|
|
@@ -2139,12 +2267,14 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2139
2267
|
);
|
|
2140
2268
|
});
|
|
2141
2269
|
await Promise.all(tasks);
|
|
2142
|
-
return map6(
|
|
2270
|
+
return map6(effectiveResults, (blame, i) => {
|
|
2143
2271
|
const cosmetic = cosmeticMap.get(blame.commitHash);
|
|
2144
2272
|
return {
|
|
2145
2273
|
blame,
|
|
2146
2274
|
isCosmetic: cosmetic?.isCosmetic ?? false,
|
|
2147
|
-
cosmeticReason: cosmetic?.reason
|
|
2275
|
+
cosmeticReason: cosmetic?.reason,
|
|
2276
|
+
crossValidated: crossValidatedFlags?.[i],
|
|
2277
|
+
usedChangeFallback: changeFallbackFlagsResult?.[i]
|
|
2148
2278
|
};
|
|
2149
2279
|
});
|
|
2150
2280
|
}
|
|
@@ -2325,8 +2455,11 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2325
2455
|
const lineRange = parseLineRange(
|
|
2326
2456
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2327
2457
|
);
|
|
2328
|
-
const blameChain =
|
|
2329
|
-
|
|
2458
|
+
const blameChain = executeDualBlame(options.file, lineRange, {
|
|
2459
|
+
...execOptions,
|
|
2460
|
+
mode: options.mode
|
|
2461
|
+
}).then(
|
|
2462
|
+
({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
|
|
2330
2463
|
);
|
|
2331
2464
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2332
2465
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
@@ -2348,12 +2481,24 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2348
2481
|
}
|
|
2349
2482
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2350
2483
|
}
|
|
2351
|
-
|
|
2484
|
+
function resolveTraceMode(mode) {
|
|
2485
|
+
return mode ?? "origin";
|
|
2486
|
+
}
|
|
2487
|
+
function deduplicatedLookupPR(sha, adapter, options, inflight) {
|
|
2488
|
+
const existing = inflight.get(sha);
|
|
2489
|
+
if (existing) return existing;
|
|
2490
|
+
const promise = lookupPR(sha, adapter, options);
|
|
2491
|
+
inflight.set(sha, promise);
|
|
2492
|
+
promise.finally(() => inflight.delete(sha));
|
|
2493
|
+
return promise;
|
|
2494
|
+
}
|
|
2495
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, inflightPR, skipPatchIdScan, preferredBase) {
|
|
2352
2496
|
const nodes = [];
|
|
2497
|
+
const traceMode = resolveTraceMode(options.mode);
|
|
2353
2498
|
const commitNode = {
|
|
2354
2499
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2355
2500
|
sha: entry.blame.commitHash,
|
|
2356
|
-
trackingMethod: "blame-CMw",
|
|
2501
|
+
trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
|
|
2357
2502
|
confidence: "exact",
|
|
2358
2503
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2359
2504
|
};
|
|
@@ -2375,17 +2520,18 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2375
2520
|
}
|
|
2376
2521
|
}
|
|
2377
2522
|
const targetSha = nodes[nodes.length - 1].sha;
|
|
2523
|
+
const prLookupOptions = {
|
|
2524
|
+
...execOptions,
|
|
2525
|
+
noCache: options.noCache,
|
|
2526
|
+
cacheOnly: options.cacheOnly,
|
|
2527
|
+
deep: featureFlags.deepTrace,
|
|
2528
|
+
repoId,
|
|
2529
|
+
skipPatchIdScan,
|
|
2530
|
+
preferredBase,
|
|
2531
|
+
platform: adapter?.platform
|
|
2532
|
+
};
|
|
2378
2533
|
if (targetSha) {
|
|
2379
|
-
const prInfo = await
|
|
2380
|
-
...execOptions,
|
|
2381
|
-
noCache: options.noCache,
|
|
2382
|
-
cacheOnly: options.cacheOnly,
|
|
2383
|
-
deep: featureFlags.deepTrace,
|
|
2384
|
-
repoId,
|
|
2385
|
-
skipPatchIdScan,
|
|
2386
|
-
preferredBase,
|
|
2387
|
-
platform: adapter?.platform
|
|
2388
|
-
});
|
|
2534
|
+
const prInfo = await deduplicatedLookupPR(targetSha, adapter, prLookupOptions, inflightPR);
|
|
2389
2535
|
if (prInfo) {
|
|
2390
2536
|
nodes.push({
|
|
2391
2537
|
type: "pull_request",
|
|
@@ -2402,6 +2548,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2402
2548
|
return nodes;
|
|
2403
2549
|
}
|
|
2404
2550
|
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2551
|
+
const inflightPR = /* @__PURE__ */ new Map();
|
|
2405
2552
|
const results = await Promise.allSettled(
|
|
2406
2553
|
map8(
|
|
2407
2554
|
analyzed,
|
|
@@ -2412,6 +2559,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2412
2559
|
options,
|
|
2413
2560
|
execOptions,
|
|
2414
2561
|
repoId,
|
|
2562
|
+
inflightPR,
|
|
2415
2563
|
skipPatchIdScan,
|
|
2416
2564
|
preferredBase
|
|
2417
2565
|
)
|
|
@@ -2421,6 +2569,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2421
2569
|
}
|
|
2422
2570
|
var legacyCacheCleaned = false;
|
|
2423
2571
|
async function trace(options) {
|
|
2572
|
+
const mode = resolveTraceMode(options.mode);
|
|
2424
2573
|
const { file, cwd } = await resolveFileContext(options.file, options.cwd);
|
|
2425
2574
|
const warnings = [];
|
|
2426
2575
|
const execOptions = { cwd, warnings };
|
|
@@ -2442,7 +2591,7 @@ async function trace(options) {
|
|
|
2442
2591
|
}
|
|
2443
2592
|
const blameAuth = await runBlameAndAuth(
|
|
2444
2593
|
platform.adapter,
|
|
2445
|
-
{ ...options, file, cwd },
|
|
2594
|
+
{ ...options, mode, file, cwd },
|
|
2446
2595
|
execOptions
|
|
2447
2596
|
);
|
|
2448
2597
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
@@ -2485,7 +2634,7 @@ async function trace(options) {
|
|
|
2485
2634
|
blameAuth.analyzed,
|
|
2486
2635
|
featureFlags,
|
|
2487
2636
|
platform.adapter,
|
|
2488
|
-
{ ...options, file, cwd },
|
|
2637
|
+
{ ...options, mode, file, cwd },
|
|
2489
2638
|
execOptions,
|
|
2490
2639
|
repoId,
|
|
2491
2640
|
cloneStatus.partialClone || void 0,
|
package/dist/types/blame.d.ts
CHANGED
|
@@ -12,11 +12,23 @@ export interface BlameResult {
|
|
|
12
12
|
date: string;
|
|
13
13
|
/** The actual content of the blamed line */
|
|
14
14
|
lineContent: string;
|
|
15
|
+
/** Final line number in the current file */
|
|
16
|
+
finalLine: number;
|
|
15
17
|
/** Original filename if the line was moved/renamed */
|
|
16
18
|
originalFile?: string;
|
|
17
19
|
/** Original line number before any moves/renames */
|
|
18
20
|
originalLine?: number;
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Result of running dual blame (origin + change) in parallel.
|
|
24
|
+
* Used by origin mode to cross-validate blame results.
|
|
25
|
+
*/
|
|
26
|
+
export interface DualBlameResult {
|
|
27
|
+
/** Primary blame results for the requested mode */
|
|
28
|
+
blame: BlameResult[];
|
|
29
|
+
/** Change-mode blame for cross-validation (populated only in origin mode) */
|
|
30
|
+
changeBlame: BlameResult[];
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
33
|
* Basic commit information from git log.
|
|
22
34
|
*/
|
package/dist/types/git.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PlatformType } from './platform.js';
|
|
2
|
+
import type { TraceMode } from './trace.js';
|
|
2
3
|
export interface GitExecResult {
|
|
3
4
|
stdout: string;
|
|
4
5
|
stderr: string;
|
|
@@ -11,6 +12,10 @@ export interface GitExecOptions {
|
|
|
11
12
|
/** Mutable array for collecting diagnostic warnings throughout the pipeline */
|
|
12
13
|
warnings?: string[];
|
|
13
14
|
}
|
|
15
|
+
export interface BlameExecOptions extends GitExecOptions {
|
|
16
|
+
/** Blame semantics used by trace mode selection */
|
|
17
|
+
mode?: TraceMode;
|
|
18
|
+
}
|
|
14
19
|
export interface RemoteInfo {
|
|
15
20
|
owner: string;
|
|
16
21
|
repo: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export type { SymbolKind, SymbolInfo, ContentHash, ChangeType, ComparisonResult, AstTraceResult, } from './ast.js';
|
|
2
|
-
export type { BlameResult, CommitInfo } from './blame.js';
|
|
2
|
+
export type { BlameResult, CommitInfo, DualBlameResult } from './blame.js';
|
|
3
3
|
export type { CacheEntry, CachedPRInfo } from './cache.js';
|
|
4
|
-
export type { GitExecResult, GitExecOptions, RemoteInfo, HealthReport, CloneStatus, } from './git.js';
|
|
4
|
+
export type { GitExecResult, GitExecOptions, BlameExecOptions, RemoteInfo, HealthReport, CloneStatus, } from './git.js';
|
|
5
5
|
export type { GraphOptions, GraphResult } from './graph.js';
|
|
6
6
|
export type { NormalizedResponse } from './output.js';
|
|
7
7
|
export type { TraceNodeType, TrackingMethod, Confidence, TraceNode, OperatingLevel, FeatureFlags, } from './pipeline.js';
|
|
8
8
|
export type { PlatformType, AuthStatus, PRInfo, IssueInfo, RateLimitInfo, PlatformAdapter, } from './platform.js';
|
|
9
9
|
export type { CosmeticReason, BlameStageResult, AstDiffStageResult, } from './stage.js';
|
|
10
|
-
export type { TraceResult, TraceOptions } from './trace.js';
|
|
10
|
+
export type { TraceMode, TraceResult, TraceOptions } from './trace.js';
|
|
11
11
|
export type { LineRange } from './util.js';
|
package/dist/types/stage.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export interface BlameStageResult {
|
|
|
6
6
|
blame: BlameResult;
|
|
7
7
|
isCosmetic: boolean;
|
|
8
8
|
cosmeticReason?: CosmeticReason;
|
|
9
|
+
/** Whether this result was cross-validated against change blame (origin mode only) */
|
|
10
|
+
crossValidated?: boolean;
|
|
11
|
+
/** Whether the change blame result was used instead of origin (false positive filtered) */
|
|
12
|
+
usedChangeFallback?: boolean;
|
|
9
13
|
}
|
|
10
14
|
export interface AstDiffStageResult {
|
|
11
15
|
originalSha: string;
|
package/dist/types/trace.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface TraceResult {
|
|
|
9
9
|
/** PR information if found, null if commit is not from a PR */
|
|
10
10
|
pr: PRInfo | null;
|
|
11
11
|
}
|
|
12
|
+
export type TraceMode = 'origin' | 'change';
|
|
12
13
|
/**
|
|
13
14
|
* Options for the trace operation (library API).
|
|
14
15
|
*/
|
|
@@ -31,4 +32,6 @@ export interface TraceOptions {
|
|
|
31
32
|
noCache?: boolean;
|
|
32
33
|
/** Return cached results only — skip API calls, ancestry traversal, and patch-id scan */
|
|
33
34
|
cacheOnly?: boolean;
|
|
35
|
+
/** Trace mode. `origin` follows copy/move history, `change` finds the last meaningful local change. */
|
|
36
|
+
mode?: TraceMode;
|
|
34
37
|
}
|
package/dist/version.d.ts
CHANGED