@lumy-pack/line-lore 0.0.5 → 0.0.7
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 +71 -0
- package/dist/cli.mjs +331 -61
- package/dist/core/ancestry/ancestry.d.ts +15 -1
- package/dist/core/ancestry/index.d.ts +1 -1
- package/dist/core/pr-lookup/index.d.ts +1 -0
- package/dist/core/pr-lookup/pr-lookup.d.ts +23 -1
- package/dist/git/health.d.ts +4 -1
- package/dist/git/index.d.ts +1 -1
- package/dist/index.cjs +327 -59
- package/dist/index.mjs +327 -58
- package/dist/platform/github/github-adapter.d.ts +3 -1
- package/dist/platform/gitlab/gitlab-adapter.d.ts +3 -1
- package/dist/types/cache.d.ts +13 -0
- package/dist/types/git.d.ts +8 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/platform.d.ts +3 -1
- package/dist/types/trace.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -370,14 +370,51 @@ var init_executor = __esm({
|
|
|
370
370
|
import { filter as filter4, isTruthy as isTruthy4 } from "@winglet/common-utils";
|
|
371
371
|
async function findMergeCommit(commitSha, options) {
|
|
372
372
|
const ref = options?.ref ?? "HEAD";
|
|
373
|
+
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
374
|
+
const startTime = Date.now();
|
|
373
375
|
const firstParentResult = await findMergeCommitWithArgs(
|
|
374
376
|
commitSha,
|
|
375
377
|
ref,
|
|
376
378
|
["--first-parent"],
|
|
377
|
-
options
|
|
379
|
+
{ ...options, timeout: budget }
|
|
378
380
|
);
|
|
379
381
|
if (firstParentResult) return firstParentResult;
|
|
380
|
-
|
|
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
|
+
async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
|
|
391
|
+
if (mergeResult.parentShas.length < 2) return true;
|
|
392
|
+
const firstParent = mergeResult.parentShas[0];
|
|
393
|
+
const branchParents = mergeResult.parentShas.slice(1);
|
|
394
|
+
const onMainline = await isAncestor(targetSha, firstParent, options);
|
|
395
|
+
if (onMainline === null) return false;
|
|
396
|
+
if (onMainline) return false;
|
|
397
|
+
for (const branchParent of branchParents) {
|
|
398
|
+
const onBranch = await isAncestor(targetSha, branchParent, options);
|
|
399
|
+
if (onBranch === null) return false;
|
|
400
|
+
if (onBranch) return true;
|
|
401
|
+
}
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
async function isAncestor(commitA, commitB, options) {
|
|
405
|
+
try {
|
|
406
|
+
const result = await gitExec(
|
|
407
|
+
["merge-base", "--is-ancestor", commitA, commitB],
|
|
408
|
+
{
|
|
409
|
+
cwd: options?.cwd,
|
|
410
|
+
timeout: options?.timeout ?? 5e3,
|
|
411
|
+
allowExitCodes: [1]
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
return result.exitCode === 0;
|
|
415
|
+
} catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
381
418
|
}
|
|
382
419
|
async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
383
420
|
try {
|
|
@@ -396,7 +433,25 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
|
396
433
|
);
|
|
397
434
|
const lines = filter4(result.stdout.trim().split("\n"), isTruthy4);
|
|
398
435
|
if (lines.length === 0) return null;
|
|
399
|
-
|
|
436
|
+
const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
|
|
437
|
+
let verifiedCount = 0;
|
|
438
|
+
for (let i = 0; i < candidateCount; i++) {
|
|
439
|
+
const candidate = parseMergeLogLine(lines[i]);
|
|
440
|
+
if (!candidate) continue;
|
|
441
|
+
verifiedCount++;
|
|
442
|
+
const verified = await verifyMergeIntroducesCommit(
|
|
443
|
+
commitSha,
|
|
444
|
+
candidate,
|
|
445
|
+
options
|
|
446
|
+
);
|
|
447
|
+
if (verified) return candidate;
|
|
448
|
+
}
|
|
449
|
+
if (verifiedCount > 0 && options?.warnings) {
|
|
450
|
+
options.warnings.push(
|
|
451
|
+
`ancestry: all ${verifiedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
400
455
|
} catch {
|
|
401
456
|
return null;
|
|
402
457
|
}
|
|
@@ -418,20 +473,39 @@ function parseMergeLogLine(line) {
|
|
|
418
473
|
const subject = parts.slice(subjectStart).join(" ");
|
|
419
474
|
return { mergeCommitSha, parentShas, subject };
|
|
420
475
|
}
|
|
421
|
-
function
|
|
476
|
+
async function getCommitSubject(sha, options) {
|
|
477
|
+
try {
|
|
478
|
+
const result = await gitExec(["log", "-1", "--format=%s", sha], {
|
|
479
|
+
cwd: options?.cwd,
|
|
480
|
+
timeout: options?.timeout ?? 5e3
|
|
481
|
+
});
|
|
482
|
+
const subject = result.stdout.trim();
|
|
483
|
+
return subject || null;
|
|
484
|
+
} catch {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function extractPRFromMergeMessage(subject, platform) {
|
|
422
489
|
const ghMatch = /Merge pull request #(\d+)/.exec(subject);
|
|
423
490
|
if (ghMatch) return parseInt(ghMatch[1], 10);
|
|
424
491
|
const squashMatch = /\(#(\d+)\)\s*$/.exec(subject);
|
|
425
492
|
if (squashMatch) return parseInt(squashMatch[1], 10);
|
|
426
|
-
|
|
427
|
-
|
|
493
|
+
if (!platform || platform === "gitlab" || platform === "gitlab-self-hosted") {
|
|
494
|
+
const glMatch = /See merge request\s+\S*!(\d+)\s*$/.exec(subject);
|
|
495
|
+
if (glMatch) return parseInt(glMatch[1], 10);
|
|
496
|
+
}
|
|
497
|
+
const adoMatch = /Merged PR (\d+):/.exec(subject);
|
|
498
|
+
if (adoMatch) return parseInt(adoMatch[1], 10);
|
|
428
499
|
return null;
|
|
429
500
|
}
|
|
501
|
+
var DEFAULT_ANCESTRY_TIMEOUT, MAX_CANDIDATES;
|
|
430
502
|
var init_ancestry = __esm({
|
|
431
503
|
"src/core/ancestry/ancestry.ts"() {
|
|
432
504
|
"use strict";
|
|
433
505
|
init_esm_shims();
|
|
434
506
|
init_executor();
|
|
507
|
+
DEFAULT_ANCESTRY_TIMEOUT = 3e4;
|
|
508
|
+
MAX_CANDIDATES = 10;
|
|
435
509
|
}
|
|
436
510
|
});
|
|
437
511
|
|
|
@@ -554,19 +628,67 @@ function getCache2(repoId, noCache) {
|
|
|
554
628
|
}
|
|
555
629
|
return cache;
|
|
556
630
|
}
|
|
557
|
-
|
|
558
|
-
|
|
631
|
+
function toCachedPR(pr) {
|
|
632
|
+
return {
|
|
633
|
+
number: pr.number,
|
|
634
|
+
title: pr.title,
|
|
635
|
+
author: pr.author,
|
|
636
|
+
url: pr.url,
|
|
637
|
+
mergeCommit: pr.mergeCommit,
|
|
638
|
+
baseBranch: pr.baseBranch,
|
|
639
|
+
mergedAt: pr.mergedAt ? new Date(pr.mergedAt).getTime() : void 0,
|
|
640
|
+
resolvedVia: pr.resolvedVia
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function fromCachedPR(cached) {
|
|
644
|
+
let mergedAt;
|
|
645
|
+
if (cached.mergedAt != null) {
|
|
646
|
+
mergedAt = typeof cached.mergedAt === "number" ? new Date(cached.mergedAt).toISOString() : String(cached.mergedAt);
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
number: cached.number,
|
|
650
|
+
title: cached.title,
|
|
651
|
+
author: cached.author,
|
|
652
|
+
url: cached.url,
|
|
653
|
+
mergeCommit: cached.mergeCommit,
|
|
654
|
+
baseBranch: cached.baseBranch,
|
|
655
|
+
mergedAt,
|
|
656
|
+
// Preserve original resolvedVia; fallback to url heuristic for legacy cache entries
|
|
657
|
+
resolvedVia: cached.resolvedVia ?? (cached.url ? "api" : "message")
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
661
|
+
const cache = getCache2(
|
|
662
|
+
options?.repoId,
|
|
663
|
+
options?.cacheOnly ? false : options?.noCache
|
|
664
|
+
);
|
|
559
665
|
const cached = await cache.get(commitSha);
|
|
560
|
-
if (cached) return cached;
|
|
666
|
+
if (cached) return fromCachedPR(cached);
|
|
667
|
+
if (options?.cacheOnly) return null;
|
|
668
|
+
const prSelectOptions = options?.preferredBase ? { preferredBase: options.preferredBase } : void 0;
|
|
669
|
+
if (adapter) {
|
|
670
|
+
const directPR = await adapter.getPRForCommit(commitSha, prSelectOptions);
|
|
671
|
+
if (directPR?.mergedAt) {
|
|
672
|
+
const result = { ...directPR, resolvedVia: "api" };
|
|
673
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
561
677
|
let mergeBasedPR = null;
|
|
562
678
|
const mergeResult = await findMergeCommit(commitSha, options);
|
|
563
679
|
if (mergeResult) {
|
|
564
|
-
const prNumber = extractPRFromMergeMessage(
|
|
680
|
+
const prNumber = extractPRFromMergeMessage(
|
|
681
|
+
mergeResult.subject,
|
|
682
|
+
options?.platform
|
|
683
|
+
);
|
|
565
684
|
if (prNumber) {
|
|
566
685
|
if (adapter) {
|
|
567
|
-
const prInfo = await adapter.getPRForCommit(
|
|
686
|
+
const prInfo = await adapter.getPRForCommit(
|
|
687
|
+
mergeResult.mergeCommitSha,
|
|
688
|
+
prSelectOptions
|
|
689
|
+
);
|
|
568
690
|
if (prInfo?.mergedAt) {
|
|
569
|
-
mergeBasedPR = prInfo;
|
|
691
|
+
mergeBasedPR = { ...prInfo, resolvedVia: "ancestry" };
|
|
570
692
|
}
|
|
571
693
|
}
|
|
572
694
|
if (!mergeBasedPR) {
|
|
@@ -576,35 +698,56 @@ async function lookupPR(commitSha, adapter, options) {
|
|
|
576
698
|
author: "",
|
|
577
699
|
url: "",
|
|
578
700
|
mergeCommit: mergeResult.mergeCommitSha,
|
|
579
|
-
baseBranch: ""
|
|
701
|
+
baseBranch: "",
|
|
702
|
+
resolvedVia: "ancestry"
|
|
580
703
|
};
|
|
581
704
|
}
|
|
582
705
|
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
583
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
706
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
584
707
|
return mergeBasedPR;
|
|
585
708
|
}
|
|
586
709
|
}
|
|
587
710
|
}
|
|
588
711
|
if (mergeBasedPR) {
|
|
589
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
712
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
590
713
|
return mergeBasedPR;
|
|
591
714
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
715
|
+
const commitSubject = await getCommitSubject(commitSha, options);
|
|
716
|
+
if (commitSubject) {
|
|
717
|
+
const directPrNumber = extractPRFromMergeMessage(
|
|
718
|
+
commitSubject,
|
|
719
|
+
options?.platform
|
|
720
|
+
);
|
|
721
|
+
if (directPrNumber) {
|
|
722
|
+
const subjectPR = {
|
|
723
|
+
number: directPrNumber,
|
|
724
|
+
title: commitSubject,
|
|
725
|
+
author: "",
|
|
726
|
+
url: "",
|
|
727
|
+
mergeCommit: commitSha,
|
|
728
|
+
baseBranch: "",
|
|
729
|
+
resolvedVia: "message"
|
|
730
|
+
};
|
|
731
|
+
await cache.set(commitSha, toCachedPR(subjectPR));
|
|
732
|
+
return subjectPR;
|
|
597
733
|
}
|
|
598
734
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
735
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
736
|
+
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
737
|
+
...options,
|
|
738
|
+
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
739
|
+
});
|
|
740
|
+
if (patchIdMatch) {
|
|
741
|
+
const result = await lookupPR(
|
|
742
|
+
patchIdMatch.matchedSha,
|
|
743
|
+
adapter,
|
|
744
|
+
options,
|
|
745
|
+
_recursionDepth + 1
|
|
746
|
+
);
|
|
747
|
+
if (result) {
|
|
748
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
608
751
|
}
|
|
609
752
|
}
|
|
610
753
|
return null;
|
|
@@ -612,7 +755,7 @@ async function lookupPR(commitSha, adapter, options) {
|
|
|
612
755
|
function resetPRCache() {
|
|
613
756
|
cacheRegistry2.clear();
|
|
614
757
|
}
|
|
615
|
-
var cacheRegistry2, DEEP_SCAN_DEPTH;
|
|
758
|
+
var cacheRegistry2, DEEP_SCAN_DEPTH, MAX_RECURSION_DEPTH;
|
|
616
759
|
var init_pr_lookup = __esm({
|
|
617
760
|
"src/core/pr-lookup/pr-lookup.ts"() {
|
|
618
761
|
"use strict";
|
|
@@ -622,6 +765,7 @@ var init_pr_lookup = __esm({
|
|
|
622
765
|
init_patch_id2();
|
|
623
766
|
cacheRegistry2 = /* @__PURE__ */ new Map();
|
|
624
767
|
DEEP_SCAN_DEPTH = 2e3;
|
|
768
|
+
MAX_RECURSION_DEPTH = 2;
|
|
625
769
|
}
|
|
626
770
|
});
|
|
627
771
|
|
|
@@ -646,6 +790,7 @@ init_errors();
|
|
|
646
790
|
// src/core/core.ts
|
|
647
791
|
init_esm_shims();
|
|
648
792
|
import { createHash as createHash2 } from "crypto";
|
|
793
|
+
import { dirname, isAbsolute, relative } from "path";
|
|
649
794
|
import { map as map8 } from "@winglet/common-utils";
|
|
650
795
|
|
|
651
796
|
// src/ast/index.ts
|
|
@@ -879,6 +1024,27 @@ function isVersionAtLeast(version, minVersion) {
|
|
|
879
1024
|
}
|
|
880
1025
|
return true;
|
|
881
1026
|
}
|
|
1027
|
+
async function checkCloneStatus(options) {
|
|
1028
|
+
let partialClone = false;
|
|
1029
|
+
let shallow = false;
|
|
1030
|
+
try {
|
|
1031
|
+
const shallowResult = await gitExec(
|
|
1032
|
+
["rev-parse", "--is-shallow-repository"],
|
|
1033
|
+
{ cwd: options?.cwd }
|
|
1034
|
+
);
|
|
1035
|
+
shallow = shallowResult.stdout.trim() === "true";
|
|
1036
|
+
} catch {
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
const partialResult = await gitExec(
|
|
1040
|
+
["config", "--get", "extensions.partialclone"],
|
|
1041
|
+
{ cwd: options?.cwd }
|
|
1042
|
+
);
|
|
1043
|
+
partialClone = partialResult.stdout.trim().length > 0;
|
|
1044
|
+
} catch {
|
|
1045
|
+
}
|
|
1046
|
+
return { partialClone, shallow };
|
|
1047
|
+
}
|
|
882
1048
|
async function checkGitHealth(options) {
|
|
883
1049
|
const hints = [];
|
|
884
1050
|
let gitVersion = "0.0.0";
|
|
@@ -905,7 +1071,18 @@ async function checkGitHealth(options) {
|
|
|
905
1071
|
`Upgrade git to ${BLOOM_FILTER_MIN_VERSION.join(".")}+ for bloom filter support (current: ${gitVersion}).`
|
|
906
1072
|
);
|
|
907
1073
|
}
|
|
908
|
-
|
|
1074
|
+
const cloneStatus = await checkCloneStatus({ cwd: options?.cwd });
|
|
1075
|
+
if (cloneStatus.partialClone) {
|
|
1076
|
+
hints.push(
|
|
1077
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (cloneStatus.shallow) {
|
|
1081
|
+
hints.push(
|
|
1082
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
return { commitGraph, bloomFilter, gitVersion, hints, ...cloneStatus };
|
|
909
1086
|
}
|
|
910
1087
|
|
|
911
1088
|
// src/platform/index.ts
|
|
@@ -1013,7 +1190,7 @@ var GitHubAdapter = class {
|
|
|
1013
1190
|
return { authenticated: false, hostname: this.hostname };
|
|
1014
1191
|
}
|
|
1015
1192
|
}
|
|
1016
|
-
async getPRForCommit(sha) {
|
|
1193
|
+
async getPRForCommit(sha, options) {
|
|
1017
1194
|
if (this.scheduler.isRateLimited()) return null;
|
|
1018
1195
|
try {
|
|
1019
1196
|
const result = await shellExec(
|
|
@@ -1030,18 +1207,20 @@ var GitHubAdapter = class {
|
|
|
1030
1207
|
);
|
|
1031
1208
|
const prs = JSON.parse(result.stdout);
|
|
1032
1209
|
if (!isArray(prs) || prs.length === 0) return null;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1210
|
+
let data = prs[0];
|
|
1211
|
+
if (options?.preferredBase) {
|
|
1212
|
+
const preferred = prs.find(
|
|
1213
|
+
(pr) => pr.base === options.preferredBase
|
|
1214
|
+
);
|
|
1215
|
+
if (preferred) data = preferred;
|
|
1216
|
+
}
|
|
1038
1217
|
return {
|
|
1039
1218
|
number: data.number,
|
|
1040
1219
|
title: data.title ?? "",
|
|
1041
1220
|
author: data.user ?? "",
|
|
1042
1221
|
url: data.html_url ?? "",
|
|
1043
1222
|
mergeCommit: data.merge_commit_sha ?? sha,
|
|
1044
|
-
baseBranch: data.base ??
|
|
1223
|
+
baseBranch: data.base ?? "",
|
|
1045
1224
|
mergedAt: data.merged_at
|
|
1046
1225
|
};
|
|
1047
1226
|
} catch {
|
|
@@ -1223,7 +1402,7 @@ var GitLabAdapter = class {
|
|
|
1223
1402
|
return { authenticated: false, hostname: this.hostname };
|
|
1224
1403
|
}
|
|
1225
1404
|
}
|
|
1226
|
-
async getPRForCommit(sha) {
|
|
1405
|
+
async getPRForCommit(sha, options) {
|
|
1227
1406
|
if (this.scheduler.isRateLimited()) return null;
|
|
1228
1407
|
try {
|
|
1229
1408
|
const result = await shellExec(
|
|
@@ -1247,18 +1426,20 @@ var GitLabAdapter = class {
|
|
|
1247
1426
|
return aTime - bTime;
|
|
1248
1427
|
});
|
|
1249
1428
|
if (mergedMRs.length === 0) return null;
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1429
|
+
let mr = mergedMRs[0];
|
|
1430
|
+
if (options?.preferredBase) {
|
|
1431
|
+
const preferred = mergedMRs.find(
|
|
1432
|
+
(m) => m.target_branch === options.preferredBase
|
|
1433
|
+
);
|
|
1434
|
+
if (preferred) mr = preferred;
|
|
1435
|
+
}
|
|
1255
1436
|
return {
|
|
1256
1437
|
number: mr.iid,
|
|
1257
1438
|
title: mr.title ?? "",
|
|
1258
1439
|
author: mr.author?.username ?? "",
|
|
1259
1440
|
url: mr.web_url ?? "",
|
|
1260
1441
|
mergeCommit: mr.merge_commit_sha ?? sha,
|
|
1261
|
-
baseBranch: mr.target_branch ??
|
|
1442
|
+
baseBranch: mr.target_branch ?? "",
|
|
1262
1443
|
mergedAt: mr.merged_at
|
|
1263
1444
|
};
|
|
1264
1445
|
} catch {
|
|
@@ -2066,6 +2247,28 @@ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, vi
|
|
|
2066
2247
|
|
|
2067
2248
|
// src/core/core.ts
|
|
2068
2249
|
init_pr_lookup2();
|
|
2250
|
+
function resolvedViaToTrackingMethod(resolvedVia) {
|
|
2251
|
+
switch (resolvedVia) {
|
|
2252
|
+
case "api":
|
|
2253
|
+
return "api";
|
|
2254
|
+
case "ancestry":
|
|
2255
|
+
return "ancestry-path";
|
|
2256
|
+
case "message":
|
|
2257
|
+
return "message-parse";
|
|
2258
|
+
case "patch-id":
|
|
2259
|
+
return "patch-id";
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
function resolvedViaToConfidence(resolvedVia) {
|
|
2263
|
+
switch (resolvedVia) {
|
|
2264
|
+
case "api":
|
|
2265
|
+
case "ancestry":
|
|
2266
|
+
return "exact";
|
|
2267
|
+
case "message":
|
|
2268
|
+
case "patch-id":
|
|
2269
|
+
return "heuristic";
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2069
2272
|
function computeFeatureFlags(operatingLevel, options) {
|
|
2070
2273
|
return {
|
|
2071
2274
|
astDiff: isAstAvailable() && !options.noAst,
|
|
@@ -2083,6 +2286,22 @@ async function resolveRepoIdentity(cwd) {
|
|
|
2083
2286
|
return { host: "_local", owner: "_", repo: "_unknown" };
|
|
2084
2287
|
}
|
|
2085
2288
|
}
|
|
2289
|
+
async function resolveFileContext(file, cwd) {
|
|
2290
|
+
if (cwd || !isAbsolute(file)) return { file, cwd };
|
|
2291
|
+
const fileDir = dirname(file);
|
|
2292
|
+
try {
|
|
2293
|
+
const result = await gitExec(["rev-parse", "--show-toplevel"], {
|
|
2294
|
+
cwd: fileDir
|
|
2295
|
+
});
|
|
2296
|
+
const repoRoot = result.stdout.trim();
|
|
2297
|
+
return {
|
|
2298
|
+
file: relative(repoRoot, file),
|
|
2299
|
+
cwd: repoRoot
|
|
2300
|
+
};
|
|
2301
|
+
} catch {
|
|
2302
|
+
return { file, cwd };
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2086
2305
|
async function detectPlatform2(options) {
|
|
2087
2306
|
const warnings = [];
|
|
2088
2307
|
let adapter = null;
|
|
@@ -2129,7 +2348,7 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2129
2348
|
}
|
|
2130
2349
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2131
2350
|
}
|
|
2132
|
-
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId) {
|
|
2351
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2133
2352
|
const nodes = [];
|
|
2134
2353
|
const commitNode = {
|
|
2135
2354
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
@@ -2160,15 +2379,19 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2160
2379
|
const prInfo = await lookupPR(targetSha, adapter, {
|
|
2161
2380
|
...execOptions,
|
|
2162
2381
|
noCache: options.noCache,
|
|
2382
|
+
cacheOnly: options.cacheOnly,
|
|
2163
2383
|
deep: featureFlags.deepTrace,
|
|
2164
|
-
repoId
|
|
2384
|
+
repoId,
|
|
2385
|
+
skipPatchIdScan,
|
|
2386
|
+
preferredBase,
|
|
2387
|
+
platform: adapter?.platform
|
|
2165
2388
|
});
|
|
2166
2389
|
if (prInfo) {
|
|
2167
2390
|
nodes.push({
|
|
2168
2391
|
type: "pull_request",
|
|
2169
2392
|
sha: prInfo.mergeCommit,
|
|
2170
|
-
trackingMethod: prInfo.
|
|
2171
|
-
confidence: prInfo.
|
|
2393
|
+
trackingMethod: resolvedViaToTrackingMethod(prInfo.resolvedVia),
|
|
2394
|
+
confidence: resolvedViaToConfidence(prInfo.resolvedVia),
|
|
2172
2395
|
prNumber: prInfo.number,
|
|
2173
2396
|
prUrl: prInfo.url || void 0,
|
|
2174
2397
|
prTitle: prInfo.title || void 0,
|
|
@@ -2178,24 +2401,35 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2178
2401
|
}
|
|
2179
2402
|
return nodes;
|
|
2180
2403
|
}
|
|
2181
|
-
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId) {
|
|
2404
|
+
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2182
2405
|
const results = await Promise.allSettled(
|
|
2183
2406
|
map8(
|
|
2184
2407
|
analyzed,
|
|
2185
|
-
(entry) => processEntry(
|
|
2408
|
+
(entry) => processEntry(
|
|
2409
|
+
entry,
|
|
2410
|
+
featureFlags,
|
|
2411
|
+
adapter,
|
|
2412
|
+
options,
|
|
2413
|
+
execOptions,
|
|
2414
|
+
repoId,
|
|
2415
|
+
skipPatchIdScan,
|
|
2416
|
+
preferredBase
|
|
2417
|
+
)
|
|
2186
2418
|
)
|
|
2187
2419
|
);
|
|
2188
2420
|
return results.flatMap((r) => r.status === "fulfilled" ? r.value : []);
|
|
2189
2421
|
}
|
|
2190
2422
|
var legacyCacheCleaned = false;
|
|
2191
2423
|
async function trace(options) {
|
|
2192
|
-
const
|
|
2424
|
+
const { file, cwd } = await resolveFileContext(options.file, options.cwd);
|
|
2425
|
+
const warnings = [];
|
|
2426
|
+
const execOptions = { cwd, warnings };
|
|
2193
2427
|
if (!legacyCacheCleaned) {
|
|
2194
2428
|
legacyCacheCleaned = true;
|
|
2195
2429
|
cleanupLegacyCache().catch(() => {
|
|
2196
2430
|
});
|
|
2197
2431
|
}
|
|
2198
|
-
const platform = await detectPlatform2(options);
|
|
2432
|
+
const platform = await detectPlatform2({ ...options, cwd });
|
|
2199
2433
|
let repoId;
|
|
2200
2434
|
if (platform.remote) {
|
|
2201
2435
|
repoId = {
|
|
@@ -2204,23 +2438,58 @@ async function trace(options) {
|
|
|
2204
2438
|
repo: platform.remote.repo
|
|
2205
2439
|
};
|
|
2206
2440
|
} else {
|
|
2207
|
-
repoId = await resolveRepoIdentity(
|
|
2441
|
+
repoId = await resolveRepoIdentity(cwd);
|
|
2208
2442
|
}
|
|
2209
2443
|
const blameAuth = await runBlameAndAuth(
|
|
2210
2444
|
platform.adapter,
|
|
2211
|
-
options,
|
|
2445
|
+
{ ...options, file, cwd },
|
|
2212
2446
|
execOptions
|
|
2213
2447
|
);
|
|
2214
2448
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
2215
|
-
|
|
2449
|
+
warnings.push(...platform.warnings, ...blameAuth.warnings);
|
|
2450
|
+
if (options.cacheOnly && options.noCache) {
|
|
2451
|
+
warnings.push(
|
|
2452
|
+
"Both cacheOnly and noCache are set. cacheOnly takes precedence \u2014 cache reads are enabled."
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2216
2455
|
const featureFlags = computeFeatureFlags(operatingLevel, options);
|
|
2456
|
+
let cloneStatus = { partialClone: false, shallow: false };
|
|
2457
|
+
try {
|
|
2458
|
+
const result = await checkCloneStatus({ cwd });
|
|
2459
|
+
if (result) cloneStatus = result;
|
|
2460
|
+
} catch {
|
|
2461
|
+
}
|
|
2462
|
+
if (cloneStatus.partialClone) {
|
|
2463
|
+
warnings.push(
|
|
2464
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
if (cloneStatus.shallow) {
|
|
2468
|
+
warnings.push(
|
|
2469
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
let preferredBase;
|
|
2473
|
+
try {
|
|
2474
|
+
const branchResult = await gitExec(
|
|
2475
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
2476
|
+
execOptions
|
|
2477
|
+
);
|
|
2478
|
+
const branch = branchResult.stdout.trim();
|
|
2479
|
+
if (branch && branch !== "HEAD") {
|
|
2480
|
+
preferredBase = branch;
|
|
2481
|
+
}
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2217
2484
|
const nodes = await buildTraceNodes(
|
|
2218
2485
|
blameAuth.analyzed,
|
|
2219
2486
|
featureFlags,
|
|
2220
2487
|
platform.adapter,
|
|
2221
|
-
options,
|
|
2488
|
+
{ ...options, file, cwd },
|
|
2222
2489
|
execOptions,
|
|
2223
|
-
repoId
|
|
2490
|
+
repoId,
|
|
2491
|
+
cloneStatus.partialClone || void 0,
|
|
2492
|
+
preferredBase
|
|
2224
2493
|
);
|
|
2225
2494
|
return { nodes, operatingLevel, featureFlags, warnings };
|
|
2226
2495
|
}
|
|
@@ -14,7 +14,9 @@ export declare class GitHubAdapter implements PlatformAdapter {
|
|
|
14
14
|
cwd?: string;
|
|
15
15
|
});
|
|
16
16
|
checkAuth(): Promise<AuthStatus>;
|
|
17
|
-
getPRForCommit(sha: string
|
|
17
|
+
getPRForCommit(sha: string, options?: {
|
|
18
|
+
preferredBase?: string;
|
|
19
|
+
}): Promise<PRInfo | null>;
|
|
18
20
|
private detectDefaultBranch;
|
|
19
21
|
getPRCommits(prNumber: number): Promise<string[]>;
|
|
20
22
|
getLinkedIssues(prNumber: number): Promise<IssueInfo[]>;
|
|
@@ -14,7 +14,9 @@ export declare class GitLabAdapter implements PlatformAdapter {
|
|
|
14
14
|
cwd?: string;
|
|
15
15
|
});
|
|
16
16
|
checkAuth(): Promise<AuthStatus>;
|
|
17
|
-
getPRForCommit(sha: string
|
|
17
|
+
getPRForCommit(sha: string, options?: {
|
|
18
|
+
preferredBase?: string;
|
|
19
|
+
}): Promise<PRInfo | null>;
|
|
18
20
|
private detectDefaultBranch;
|
|
19
21
|
getPRCommits(prNumber: number): Promise<string[]>;
|
|
20
22
|
getLinkedIssues(prNumber: number): Promise<IssueInfo[]>;
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -3,3 +3,16 @@ export interface CacheEntry<T> {
|
|
|
3
3
|
value: T;
|
|
4
4
|
createdAt: number;
|
|
5
5
|
}
|
|
6
|
+
/** Disk-serialized PRInfo — date fields stored as numeric timestamps (ms) */
|
|
7
|
+
export interface CachedPRInfo {
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
author: string;
|
|
11
|
+
url: string;
|
|
12
|
+
mergeCommit: string;
|
|
13
|
+
baseBranch: string;
|
|
14
|
+
/** Unix timestamp in milliseconds, NOT ISO 8601 string */
|
|
15
|
+
mergedAt?: number;
|
|
16
|
+
/** Which strategy resolved this PR — absent in legacy cache entries */
|
|
17
|
+
resolvedVia?: string;
|
|
18
|
+
}
|
package/dist/types/git.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface GitExecOptions {
|
|
|
8
8
|
cwd?: string;
|
|
9
9
|
timeout?: number;
|
|
10
10
|
allowExitCodes?: number[];
|
|
11
|
+
/** Mutable array for collecting diagnostic warnings throughout the pipeline */
|
|
12
|
+
warnings?: string[];
|
|
11
13
|
}
|
|
12
14
|
export interface RemoteInfo {
|
|
13
15
|
owner: string;
|
|
@@ -15,9 +17,15 @@ export interface RemoteInfo {
|
|
|
15
17
|
host: string;
|
|
16
18
|
platform: PlatformType | 'unknown';
|
|
17
19
|
}
|
|
20
|
+
export interface CloneStatus {
|
|
21
|
+
partialClone: boolean;
|
|
22
|
+
shallow: boolean;
|
|
23
|
+
}
|
|
18
24
|
export interface HealthReport {
|
|
19
25
|
commitGraph: boolean;
|
|
20
26
|
bloomFilter: boolean;
|
|
21
27
|
gitVersion: string;
|
|
22
28
|
hints: string[];
|
|
29
|
+
partialClone: boolean;
|
|
30
|
+
shallow: boolean;
|
|
23
31
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { SymbolKind, SymbolInfo, ContentHash, ChangeType, ComparisonResult, AstTraceResult, } from './ast.js';
|
|
2
2
|
export type { BlameResult, CommitInfo } from './blame.js';
|
|
3
|
-
export type { CacheEntry } from './cache.js';
|
|
4
|
-
export type { GitExecResult, GitExecOptions, RemoteInfo, HealthReport, } from './git.js';
|
|
3
|
+
export type { CacheEntry, CachedPRInfo } from './cache.js';
|
|
4
|
+
export type { GitExecResult, GitExecOptions, 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';
|
package/dist/types/platform.d.ts
CHANGED
|
@@ -36,7 +36,9 @@ export interface RateLimitInfo {
|
|
|
36
36
|
export interface PlatformAdapter {
|
|
37
37
|
readonly platform: PlatformType;
|
|
38
38
|
checkAuth(): Promise<AuthStatus>;
|
|
39
|
-
getPRForCommit(sha: string
|
|
39
|
+
getPRForCommit(sha: string, options?: {
|
|
40
|
+
preferredBase?: string;
|
|
41
|
+
}): Promise<PRInfo | null>;
|
|
40
42
|
getPRCommits(prNumber: number): Promise<string[]>;
|
|
41
43
|
getLinkedIssues(prNumber: number): Promise<IssueInfo[]>;
|
|
42
44
|
getLinkedPRs(issueNumber: number): Promise<PRInfo[]>;
|