@lumy-pack/line-lore 0.0.8 → 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/dist/cli.mjs +113 -9
- package/dist/core/blame/blame.d.ts +10 -2
- package/dist/core/blame/index.d.ts +1 -1
- package/dist/index.cjs +112 -8
- package/dist/index.mjs +112 -8
- package/dist/types/blame.d.ts +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/stage.d.ts +4 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -809,7 +809,7 @@ var VERSION;
|
|
|
809
809
|
var init_version = __esm({
|
|
810
810
|
"src/version.ts"() {
|
|
811
811
|
"use strict";
|
|
812
|
-
VERSION = "0.0.
|
|
812
|
+
VERSION = "0.0.9";
|
|
813
813
|
}
|
|
814
814
|
});
|
|
815
815
|
|
|
@@ -2181,8 +2181,108 @@ async function executeBlame(file, lineRange, options) {
|
|
|
2181
2181
|
const result = await gitExec(args, options);
|
|
2182
2182
|
return parsePorcelainOutput(result.stdout);
|
|
2183
2183
|
}
|
|
2184
|
-
async function
|
|
2185
|
-
|
|
2184
|
+
async function executeDualBlame(file, lineRange, options) {
|
|
2185
|
+
if (options?.mode === "change") {
|
|
2186
|
+
const results = await executeBlame(file, lineRange, options);
|
|
2187
|
+
return { blame: results, changeBlame: [] };
|
|
2188
|
+
}
|
|
2189
|
+
const [originResult, changeResult] = await Promise.allSettled([
|
|
2190
|
+
executeBlame(file, lineRange, options),
|
|
2191
|
+
executeBlame(file, lineRange, { ...options, mode: "change" })
|
|
2192
|
+
]);
|
|
2193
|
+
const blame = originResult.status === "fulfilled" ? originResult.value : [];
|
|
2194
|
+
const changeBlame = changeResult.status === "fulfilled" ? changeResult.value : [];
|
|
2195
|
+
if (originResult.status === "rejected") {
|
|
2196
|
+
throw originResult.reason;
|
|
2197
|
+
}
|
|
2198
|
+
return { blame, changeBlame };
|
|
2199
|
+
}
|
|
2200
|
+
async function verifyRename(originalFile, currentFile, options) {
|
|
2201
|
+
try {
|
|
2202
|
+
const result = await gitExec(
|
|
2203
|
+
[
|
|
2204
|
+
"log",
|
|
2205
|
+
"--diff-filter=R",
|
|
2206
|
+
"--find-renames",
|
|
2207
|
+
"--format=%H",
|
|
2208
|
+
"--",
|
|
2209
|
+
originalFile,
|
|
2210
|
+
currentFile
|
|
2211
|
+
],
|
|
2212
|
+
options
|
|
2213
|
+
);
|
|
2214
|
+
return result.stdout.trim().length > 0;
|
|
2215
|
+
} catch {
|
|
2216
|
+
return false;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function crossValidateBlame(originResults, changeResults) {
|
|
2220
|
+
const changeMap = /* @__PURE__ */ new Map();
|
|
2221
|
+
for (const r of changeResults) {
|
|
2222
|
+
changeMap.set(r.finalLine, r);
|
|
2223
|
+
}
|
|
2224
|
+
const validated = [];
|
|
2225
|
+
const crossValidatedFlags = [];
|
|
2226
|
+
const changeFallbackFlags = [];
|
|
2227
|
+
const renameChecks = [];
|
|
2228
|
+
for (let i = 0; i < originResults.length; i++) {
|
|
2229
|
+
const origin = originResults[i];
|
|
2230
|
+
const change = changeMap.get(origin.finalLine);
|
|
2231
|
+
if (!change) {
|
|
2232
|
+
validated.push(origin);
|
|
2233
|
+
crossValidatedFlags.push(false);
|
|
2234
|
+
changeFallbackFlags.push(false);
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
if (origin.commitHash === change.commitHash) {
|
|
2238
|
+
validated.push(origin);
|
|
2239
|
+
crossValidatedFlags.push(true);
|
|
2240
|
+
changeFallbackFlags.push(false);
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
if (origin.originalFile) {
|
|
2244
|
+
renameChecks.push({
|
|
2245
|
+
originalFile: origin.originalFile,
|
|
2246
|
+
lineIndex: i
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
validated.push(change);
|
|
2250
|
+
crossValidatedFlags.push(true);
|
|
2251
|
+
changeFallbackFlags.push(true);
|
|
2252
|
+
}
|
|
2253
|
+
return { validated, renameChecks, crossValidatedFlags, changeFallbackFlags };
|
|
2254
|
+
}
|
|
2255
|
+
async function analyzeBlameResults(results, filePath, options, changeResults) {
|
|
2256
|
+
let effectiveResults = results;
|
|
2257
|
+
let crossValidatedFlags;
|
|
2258
|
+
let changeFallbackFlagsResult;
|
|
2259
|
+
if (changeResults && changeResults.length > 0) {
|
|
2260
|
+
const { validated, renameChecks, crossValidatedFlags: cvFlags, changeFallbackFlags } = crossValidateBlame(results, changeResults);
|
|
2261
|
+
if (renameChecks.length > 0) {
|
|
2262
|
+
const pendingChecks = /* @__PURE__ */ new Map();
|
|
2263
|
+
for (const check of renameChecks) {
|
|
2264
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2265
|
+
if (!pendingChecks.has(cacheKey)) {
|
|
2266
|
+
pendingChecks.set(cacheKey, verifyRename(check.originalFile, filePath, options));
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const renameResults = /* @__PURE__ */ new Map();
|
|
2270
|
+
for (const [key, promise] of pendingChecks) {
|
|
2271
|
+
renameResults.set(key, await promise);
|
|
2272
|
+
}
|
|
2273
|
+
for (const check of renameChecks) {
|
|
2274
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2275
|
+
if (renameResults.get(cacheKey)) {
|
|
2276
|
+
validated[check.lineIndex] = results[check.lineIndex];
|
|
2277
|
+
changeFallbackFlags[check.lineIndex] = false;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
effectiveResults = validated;
|
|
2282
|
+
crossValidatedFlags = cvFlags;
|
|
2283
|
+
changeFallbackFlagsResult = changeFallbackFlags;
|
|
2284
|
+
}
|
|
2285
|
+
const uniqueShas = [...new Set(map6(effectiveResults, (r) => r.commitHash))];
|
|
2186
2286
|
const cosmeticMap = /* @__PURE__ */ new Map();
|
|
2187
2287
|
const zeroSha = "0".repeat(40);
|
|
2188
2288
|
const tasks = [];
|
|
@@ -2191,7 +2291,7 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2191
2291
|
tasks.push(
|
|
2192
2292
|
(async () => {
|
|
2193
2293
|
try {
|
|
2194
|
-
const blameResult =
|
|
2294
|
+
const blameResult = effectiveResults.find((r) => r.commitHash === sha);
|
|
2195
2295
|
if (!blameResult) return;
|
|
2196
2296
|
const file = blameResult.originalFile ?? filePath;
|
|
2197
2297
|
const diff = await getCosmeticDiff(sha, file, options);
|
|
@@ -2203,12 +2303,14 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2203
2303
|
);
|
|
2204
2304
|
});
|
|
2205
2305
|
await Promise.all(tasks);
|
|
2206
|
-
return map6(
|
|
2306
|
+
return map6(effectiveResults, (blame, i) => {
|
|
2207
2307
|
const cosmetic = cosmeticMap.get(blame.commitHash);
|
|
2208
2308
|
return {
|
|
2209
2309
|
blame,
|
|
2210
2310
|
isCosmetic: cosmetic?.isCosmetic ?? false,
|
|
2211
|
-
cosmeticReason: cosmetic?.reason
|
|
2311
|
+
cosmeticReason: cosmetic?.reason,
|
|
2312
|
+
crossValidated: crossValidatedFlags?.[i],
|
|
2313
|
+
usedChangeFallback: changeFallbackFlagsResult?.[i]
|
|
2212
2314
|
};
|
|
2213
2315
|
});
|
|
2214
2316
|
}
|
|
@@ -2385,10 +2487,12 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2385
2487
|
const lineRange = parseLineRange(
|
|
2386
2488
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2387
2489
|
);
|
|
2388
|
-
const blameChain =
|
|
2490
|
+
const blameChain = executeDualBlame(options.file, lineRange, {
|
|
2389
2491
|
...execOptions,
|
|
2390
2492
|
mode: options.mode
|
|
2391
|
-
}).then(
|
|
2493
|
+
}).then(
|
|
2494
|
+
({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
|
|
2495
|
+
);
|
|
2392
2496
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2393
2497
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
2394
2498
|
blameChain
|
|
@@ -2426,7 +2530,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2426
2530
|
const commitNode = {
|
|
2427
2531
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2428
2532
|
sha: entry.blame.commitHash,
|
|
2429
|
-
trackingMethod: traceMode === "change" ? "blame" : "blame-CMw",
|
|
2533
|
+
trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
|
|
2430
2534
|
confidence: "exact",
|
|
2431
2535
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2432
2536
|
};
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import type { BlameExecOptions, BlameResult, BlameStageResult, GitExecOptions, LineRange } from '../../types/index.js';
|
|
1
|
+
import type { BlameExecOptions, BlameResult, BlameStageResult, DualBlameResult, GitExecOptions, LineRange } from '../../types/index.js';
|
|
2
2
|
export declare function executeBlame(file: string, lineRange: LineRange, options?: BlameExecOptions): Promise<BlameResult[]>;
|
|
3
|
-
|
|
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';
|
package/dist/index.cjs
CHANGED
|
@@ -2170,8 +2170,108 @@ async function executeBlame(file, lineRange, options) {
|
|
|
2170
2170
|
const result = await gitExec(args, options);
|
|
2171
2171
|
return parsePorcelainOutput(result.stdout);
|
|
2172
2172
|
}
|
|
2173
|
-
async function
|
|
2174
|
-
|
|
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))];
|
|
2175
2275
|
const cosmeticMap = /* @__PURE__ */ new Map();
|
|
2176
2276
|
const zeroSha = "0".repeat(40);
|
|
2177
2277
|
const tasks = [];
|
|
@@ -2180,7 +2280,7 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2180
2280
|
tasks.push(
|
|
2181
2281
|
(async () => {
|
|
2182
2282
|
try {
|
|
2183
|
-
const blameResult =
|
|
2283
|
+
const blameResult = effectiveResults.find((r) => r.commitHash === sha);
|
|
2184
2284
|
if (!blameResult) return;
|
|
2185
2285
|
const file = blameResult.originalFile ?? filePath;
|
|
2186
2286
|
const diff = await getCosmeticDiff(sha, file, options);
|
|
@@ -2192,12 +2292,14 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2192
2292
|
);
|
|
2193
2293
|
});
|
|
2194
2294
|
await Promise.all(tasks);
|
|
2195
|
-
return (0, import_common_utils7.map)(
|
|
2295
|
+
return (0, import_common_utils7.map)(effectiveResults, (blame, i) => {
|
|
2196
2296
|
const cosmetic = cosmeticMap.get(blame.commitHash);
|
|
2197
2297
|
return {
|
|
2198
2298
|
blame,
|
|
2199
2299
|
isCosmetic: cosmetic?.isCosmetic ?? false,
|
|
2200
|
-
cosmeticReason: cosmetic?.reason
|
|
2300
|
+
cosmeticReason: cosmetic?.reason,
|
|
2301
|
+
crossValidated: crossValidatedFlags?.[i],
|
|
2302
|
+
usedChangeFallback: changeFallbackFlagsResult?.[i]
|
|
2201
2303
|
};
|
|
2202
2304
|
});
|
|
2203
2305
|
}
|
|
@@ -2378,10 +2480,12 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2378
2480
|
const lineRange = parseLineRange(
|
|
2379
2481
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2380
2482
|
);
|
|
2381
|
-
const blameChain =
|
|
2483
|
+
const blameChain = executeDualBlame(options.file, lineRange, {
|
|
2382
2484
|
...execOptions,
|
|
2383
2485
|
mode: options.mode
|
|
2384
|
-
}).then(
|
|
2486
|
+
}).then(
|
|
2487
|
+
({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
|
|
2488
|
+
);
|
|
2385
2489
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2386
2490
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
2387
2491
|
blameChain
|
|
@@ -2419,7 +2523,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2419
2523
|
const commitNode = {
|
|
2420
2524
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2421
2525
|
sha: entry.blame.commitHash,
|
|
2422
|
-
trackingMethod: traceMode === "change" ? "blame" : "blame-CMw",
|
|
2526
|
+
trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
|
|
2423
2527
|
confidence: "exact",
|
|
2424
2528
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2425
2529
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -2145,8 +2145,108 @@ async function executeBlame(file, lineRange, options) {
|
|
|
2145
2145
|
const result = await gitExec(args, options);
|
|
2146
2146
|
return parsePorcelainOutput(result.stdout);
|
|
2147
2147
|
}
|
|
2148
|
-
async function
|
|
2149
|
-
|
|
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))];
|
|
2150
2250
|
const cosmeticMap = /* @__PURE__ */ new Map();
|
|
2151
2251
|
const zeroSha = "0".repeat(40);
|
|
2152
2252
|
const tasks = [];
|
|
@@ -2155,7 +2255,7 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2155
2255
|
tasks.push(
|
|
2156
2256
|
(async () => {
|
|
2157
2257
|
try {
|
|
2158
|
-
const blameResult =
|
|
2258
|
+
const blameResult = effectiveResults.find((r) => r.commitHash === sha);
|
|
2159
2259
|
if (!blameResult) return;
|
|
2160
2260
|
const file = blameResult.originalFile ?? filePath;
|
|
2161
2261
|
const diff = await getCosmeticDiff(sha, file, options);
|
|
@@ -2167,12 +2267,14 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2167
2267
|
);
|
|
2168
2268
|
});
|
|
2169
2269
|
await Promise.all(tasks);
|
|
2170
|
-
return map6(
|
|
2270
|
+
return map6(effectiveResults, (blame, i) => {
|
|
2171
2271
|
const cosmetic = cosmeticMap.get(blame.commitHash);
|
|
2172
2272
|
return {
|
|
2173
2273
|
blame,
|
|
2174
2274
|
isCosmetic: cosmetic?.isCosmetic ?? false,
|
|
2175
|
-
cosmeticReason: cosmetic?.reason
|
|
2275
|
+
cosmeticReason: cosmetic?.reason,
|
|
2276
|
+
crossValidated: crossValidatedFlags?.[i],
|
|
2277
|
+
usedChangeFallback: changeFallbackFlagsResult?.[i]
|
|
2176
2278
|
};
|
|
2177
2279
|
});
|
|
2178
2280
|
}
|
|
@@ -2353,10 +2455,12 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2353
2455
|
const lineRange = parseLineRange(
|
|
2354
2456
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2355
2457
|
);
|
|
2356
|
-
const blameChain =
|
|
2458
|
+
const blameChain = executeDualBlame(options.file, lineRange, {
|
|
2357
2459
|
...execOptions,
|
|
2358
2460
|
mode: options.mode
|
|
2359
|
-
}).then(
|
|
2461
|
+
}).then(
|
|
2462
|
+
({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
|
|
2463
|
+
);
|
|
2360
2464
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2361
2465
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
2362
2466
|
blameChain
|
|
@@ -2394,7 +2498,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2394
2498
|
const commitNode = {
|
|
2395
2499
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2396
2500
|
sha: entry.blame.commitHash,
|
|
2397
|
-
trackingMethod: traceMode === "change" ? "blame" : "blame-CMw",
|
|
2501
|
+
trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
|
|
2398
2502
|
confidence: "exact",
|
|
2399
2503
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2400
2504
|
};
|
package/dist/types/blame.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export interface BlameResult {
|
|
|
19
19
|
/** Original line number before any moves/renames */
|
|
20
20
|
originalLine?: number;
|
|
21
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
|
+
}
|
|
22
32
|
/**
|
|
23
33
|
* Basic commit information from git log.
|
|
24
34
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
4
|
export type { GitExecResult, GitExecOptions, BlameExecOptions, RemoteInfo, HealthReport, CloneStatus, } from './git.js';
|
|
5
5
|
export type { GraphOptions, GraphResult } from './graph.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/version.d.ts
CHANGED