@davmash/bygone 0.8.16 → 0.9.4
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/CHANGELOG.md +47 -0
- package/cli/present.js +50 -9
- package/cli/tourFile.js +9 -0
- package/cli/tourHistory.js +359 -16
- package/docs/change-tour-format.md +168 -16
- package/docs/generating-change-tours.md +70 -17
- package/docs/present.md +126 -5
- package/docs/product-surface.md +56 -4
- package/examples/bygone-history.bygone +52 -10
- package/examples/navigation-lab.bygone.yaml +420 -0
- package/media/webview.css +1 -1
- package/media/webview.js +557 -186
- package/out/changeTour.js +423 -44
- package/out/standalone-main.js +5167 -1156
- package/out/tourCoverage.js +432 -49
- package/package.json +2 -2
- package/schemas/change-tour-source.schema.json +166 -2
- package/skills/pr-tour-guide/SKILL.md +104 -13
- package/standalone/index.html +5 -5
- package/web/index.html +111 -84
- package/web/presenter.css +564 -231
- package/web/web-host.js +374 -5
package/out/tourCoverage.js
CHANGED
|
@@ -475,17 +475,29 @@ function runGit(repoRoot, args) {
|
|
|
475
475
|
var import_child_process3 = require("child_process");
|
|
476
476
|
|
|
477
477
|
// src/changeTourSource.ts
|
|
478
|
-
var CHANGE_TOUR_SOURCE_VERSION =
|
|
478
|
+
var CHANGE_TOUR_SOURCE_VERSION = 3;
|
|
479
479
|
function parseChangeTourSource(value) {
|
|
480
480
|
if (isRecord(value) && Number.isInteger(value.version) && Number(value.version) > CHANGE_TOUR_SOURCE_VERSION) {
|
|
481
481
|
throw new Error(
|
|
482
482
|
`This tour uses source format version ${value.version}, but this version of Bygone supports up to version ${CHANGE_TOUR_SOURCE_VERSION}. Upgrade Bygone to open it.`
|
|
483
483
|
);
|
|
484
484
|
}
|
|
485
|
-
if (!isRecord(value) || value.version !== 1 && value.version !== CHANGE_TOUR_SOURCE_VERSION) {
|
|
485
|
+
if (!isRecord(value) || value.version !== 1 && value.version !== 2 && value.version !== CHANGE_TOUR_SOURCE_VERSION) {
|
|
486
486
|
throw new Error("Unsupported or missing change-tour source version.");
|
|
487
487
|
}
|
|
488
|
-
requireOnlyKeys(value, [
|
|
488
|
+
requireOnlyKeys(value, [
|
|
489
|
+
"version",
|
|
490
|
+
"title",
|
|
491
|
+
"windowTitle",
|
|
492
|
+
"sourceUrl",
|
|
493
|
+
"range",
|
|
494
|
+
"anchors",
|
|
495
|
+
"connections",
|
|
496
|
+
"chapters",
|
|
497
|
+
"tours",
|
|
498
|
+
"coverage",
|
|
499
|
+
"review"
|
|
500
|
+
], "source");
|
|
489
501
|
optionalString(value.title, "title");
|
|
490
502
|
optionalString(value.windowTitle, "windowTitle");
|
|
491
503
|
optionalString(value.sourceUrl, "sourceUrl");
|
|
@@ -549,30 +561,81 @@ function parseChangeTourSource(value) {
|
|
|
549
561
|
}
|
|
550
562
|
connectionIds.add(id);
|
|
551
563
|
}
|
|
564
|
+
const authoredWalkthroughSteps = validateSourceChapters(
|
|
565
|
+
value.chapters,
|
|
566
|
+
"chapters",
|
|
567
|
+
value.version,
|
|
568
|
+
value.anchors,
|
|
569
|
+
connectionIds,
|
|
570
|
+
"root"
|
|
571
|
+
);
|
|
572
|
+
if (value.tours !== void 0) {
|
|
573
|
+
if (value.version !== 3) throw new Error("Independent tours require version 3.");
|
|
574
|
+
if (!isRecord(value.tours)) throw new Error("tours must be an object.");
|
|
575
|
+
requireOnlyKeys(value.tours, ["historical", "deconstructed"], "tours");
|
|
576
|
+
if (value.tours.historical === void 0 && value.tours.deconstructed === void 0) {
|
|
577
|
+
throw new Error("tours must contain a historical or deconstructed tour.");
|
|
578
|
+
}
|
|
579
|
+
for (const mode of ["historical", "deconstructed"]) {
|
|
580
|
+
const tour = value.tours[mode];
|
|
581
|
+
if (tour === void 0) continue;
|
|
582
|
+
const path = `tours.${mode}`;
|
|
583
|
+
if (!isRecord(tour)) throw new Error(`${path} must be an object.`);
|
|
584
|
+
requireOnlyKeys(tour, ["chapters"], path);
|
|
585
|
+
validateSourceChapters(tour.chapters, `${path}.chapters`, value.version, value.anchors, connectionIds, mode);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (value.review !== void 0) {
|
|
589
|
+
if (value.version !== 3) throw new Error("Review notes require version 3.");
|
|
590
|
+
validateChangeTourReview(value.review, { authoredWalkthroughSteps });
|
|
591
|
+
}
|
|
592
|
+
return { ...value, connections: rawConnections };
|
|
593
|
+
}
|
|
594
|
+
function validateSourceChapters(chaptersValue, chaptersPath, version, anchors, connectionIds, mode) {
|
|
595
|
+
if (!Array.isArray(chaptersValue) || chaptersValue.length === 0) {
|
|
596
|
+
throw new Error(`${chaptersPath} must be a non-empty array.`);
|
|
597
|
+
}
|
|
552
598
|
const chapterIds = /* @__PURE__ */ new Set();
|
|
553
599
|
const sceneIds = /* @__PURE__ */ new Set();
|
|
554
|
-
|
|
600
|
+
const authoredWalkthroughSteps = /* @__PURE__ */ new Map();
|
|
601
|
+
for (const [chapterIndex, chapter] of chaptersValue.entries()) {
|
|
602
|
+
const chapterPath = `${chaptersPath}[${chapterIndex}]`;
|
|
555
603
|
if (!isRecord(chapter) || !Array.isArray(chapter.scenes) || chapter.scenes.length === 0) {
|
|
556
|
-
throw new Error(
|
|
604
|
+
throw new Error(`${chapterPath} must contain a non-empty scenes array.`);
|
|
557
605
|
}
|
|
558
|
-
requireString(chapter.id,
|
|
559
|
-
requireString(chapter.title,
|
|
560
|
-
requireOnlyKeys(chapter, ["id", "title", "scenes"],
|
|
606
|
+
requireString(chapter.id, `${chapterPath}.id`);
|
|
607
|
+
requireString(chapter.title, `${chapterPath}.title`);
|
|
608
|
+
requireOnlyKeys(chapter, ["id", "title", "scenes"], chapterPath);
|
|
561
609
|
if (chapterIds.has(chapter.id)) throw new Error(`Duplicate chapter id: ${chapter.id}`);
|
|
562
610
|
chapterIds.add(chapter.id);
|
|
563
611
|
for (const [sceneIndex, scene] of chapter.scenes.entries()) {
|
|
564
|
-
const path =
|
|
612
|
+
const path = `${chapterPath}.scenes[${sceneIndex}]`;
|
|
565
613
|
if (!isRecord(scene)) throw new Error(`${path} must be an object.`);
|
|
566
614
|
requireString(scene.id, `${path}.id`);
|
|
567
615
|
requireString(scene.title, `${path}.title`);
|
|
568
616
|
if (sceneIds.has(scene.id)) throw new Error(`Duplicate scene id: ${scene.id}`);
|
|
569
617
|
sceneIds.add(scene.id);
|
|
618
|
+
if (scene.overview !== void 0 && version !== 3) {
|
|
619
|
+
throw new Error(`${path}.overview requires version 3.`);
|
|
620
|
+
}
|
|
570
621
|
validateNarrative(scene, path);
|
|
571
622
|
if (scene.kind === "deconstructed-diff") {
|
|
623
|
+
if (mode === "historical") {
|
|
624
|
+
throw new Error(`${path} cannot contain a deconstructed-diff scene in the historical tour.`);
|
|
625
|
+
}
|
|
572
626
|
validateDeconstructedScene(scene, path);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
627
|
+
validateSceneOverview(scene.overview, `${path}.overview`, {
|
|
628
|
+
endpointIds: deconstructedOverviewEndpointIds(scene, path)
|
|
629
|
+
});
|
|
630
|
+
const requireRealEvidence = mode === "root" && version >= 2;
|
|
631
|
+
if (requireRealEvidence || scene.stack !== void 0) {
|
|
632
|
+
validateRealStack(scene.stack, `${path}.stack`, 2);
|
|
633
|
+
}
|
|
634
|
+
if (requireRealEvidence || scene.steps !== void 0) {
|
|
635
|
+
validateWalkthroughSteps(scene, path, anchors, connectionIds);
|
|
636
|
+
if (scene.steps !== void 0) {
|
|
637
|
+
authoredWalkthroughSteps.set(scene.id, collectStepIds(scene.steps));
|
|
638
|
+
}
|
|
576
639
|
}
|
|
577
640
|
continue;
|
|
578
641
|
}
|
|
@@ -580,20 +643,110 @@ function parseChangeTourSource(value) {
|
|
|
580
643
|
throw new Error(`${path} must contain a non-empty steps array.`);
|
|
581
644
|
}
|
|
582
645
|
if (scene.kind === "stacked-diff") {
|
|
583
|
-
validateStackedScene(scene, path,
|
|
646
|
+
validateStackedScene(scene, path, mode === "root" ? version >= 2 ? 2 : 3 : 2);
|
|
647
|
+
validateSceneOverview(scene.overview, `${path}.overview`, {
|
|
648
|
+
endpointIds: stackedOverviewEndpointIds(scene, path)
|
|
649
|
+
});
|
|
584
650
|
continue;
|
|
585
651
|
}
|
|
586
652
|
if (scene.kind !== void 0 && scene.kind !== "walkthrough") {
|
|
587
653
|
throw new Error(`${path}.kind must be walkthrough, stacked-diff, or deconstructed-diff.`);
|
|
588
654
|
}
|
|
589
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "steps"], path);
|
|
590
|
-
|
|
655
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "steps"], path);
|
|
656
|
+
validateSceneOverview(scene.overview, `${path}.overview`, { allowComparison: false });
|
|
657
|
+
validateWalkthroughSteps(scene, path, anchors, connectionIds);
|
|
658
|
+
authoredWalkthroughSteps.set(scene.id, collectStepIds(scene.steps));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (mode === "deconstructed" && !chaptersValue.some((chapter) => isRecord(chapter) && Array.isArray(chapter.scenes) && chapter.scenes.some((scene) => isRecord(scene) && scene.kind === "deconstructed-diff"))) {
|
|
662
|
+
throw new Error(`${chaptersPath} must contain at least one deconstructed-diff scene.`);
|
|
663
|
+
}
|
|
664
|
+
return authoredWalkthroughSteps;
|
|
665
|
+
}
|
|
666
|
+
function stackedOverviewEndpointIds(scene, path) {
|
|
667
|
+
if (!Array.isArray(scene.stack)) {
|
|
668
|
+
throw new Error(`${path}.stack must be an array.`);
|
|
669
|
+
}
|
|
670
|
+
return scene.stack.filter(isRecord).map((entry) => typeof entry.id === "string" ? entry.id : "").filter((id) => id.length > 0);
|
|
671
|
+
}
|
|
672
|
+
function deconstructedOverviewEndpointIds(scene, path) {
|
|
673
|
+
if (!Array.isArray(scene.stages)) {
|
|
674
|
+
throw new Error(`${path}.stages must be an array.`);
|
|
675
|
+
}
|
|
676
|
+
return [
|
|
677
|
+
"explanation-baseline",
|
|
678
|
+
...scene.stages.filter(isRecord).map((stage) => typeof stage.id === "string" ? `explanation-stage-${stage.id}` : "").filter((id) => id.length > 0)
|
|
679
|
+
];
|
|
680
|
+
}
|
|
681
|
+
function validateChangeTourReview(value, context = {}) {
|
|
682
|
+
if (!isRecord(value)) {
|
|
683
|
+
throw new Error("review must be an object.");
|
|
684
|
+
}
|
|
685
|
+
requireOnlyKeys(value, ["baseOid", "headOid", "items"], "review");
|
|
686
|
+
requireFullGitOid(value.baseOid, "review.baseOid");
|
|
687
|
+
requireFullGitOid(value.headOid, "review.headOid");
|
|
688
|
+
if (!Array.isArray(value.items) || value.items.length === 0) {
|
|
689
|
+
throw new Error("review.items must be a non-empty array.");
|
|
690
|
+
}
|
|
691
|
+
if (context.expectedRange) {
|
|
692
|
+
if (normalizeGitOid(value.baseOid) !== normalizeGitOid(context.expectedRange.baseOid)) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Stale review: review.baseOid ${value.baseOid} does not match the resolved range.mergeBaseOid ${context.expectedRange.baseOid}.`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
if (normalizeGitOid(value.headOid) !== normalizeGitOid(context.expectedRange.headOid)) {
|
|
698
|
+
throw new Error(
|
|
699
|
+
`Stale review: review.headOid ${value.headOid} does not match the resolved range.headOid ${context.expectedRange.headOid}.`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
704
|
+
for (const [index, item] of value.items.entries()) {
|
|
705
|
+
const itemPath = `review.items[${index}]`;
|
|
706
|
+
if (!isRecord(item)) throw new Error(`${itemPath} must be an object.`);
|
|
707
|
+
requireOnlyKeys(item, ["id", "kind", "title", "body", "evidence", "nextCheck"], itemPath);
|
|
708
|
+
requireReviewString(item.id, `${itemPath}.id`);
|
|
709
|
+
if (itemIds.has(item.id)) throw new Error(`Duplicate review item id: ${item.id}`);
|
|
710
|
+
itemIds.add(item.id);
|
|
711
|
+
if (typeof item.kind !== "string" || !["concept", "boundary", "tradeoff", "question"].includes(item.kind)) {
|
|
712
|
+
throw new Error(`${itemPath}.kind must be concept, boundary, tradeoff, or question.`);
|
|
713
|
+
}
|
|
714
|
+
requireReviewString(item.title, `${itemPath}.title`);
|
|
715
|
+
requireReviewString(item.body, `${itemPath}.body`);
|
|
716
|
+
if (!Array.isArray(item.evidence)) {
|
|
717
|
+
throw new Error(`${itemPath}.evidence must be an array.`);
|
|
718
|
+
}
|
|
719
|
+
if (item.kind !== "question" && item.evidence.length === 0) {
|
|
720
|
+
throw new Error(`${itemPath}.evidence must contain at least one reference for claims.`);
|
|
721
|
+
}
|
|
722
|
+
if (item.kind === "question" && item.nextCheck === void 0) {
|
|
723
|
+
throw new Error(`${itemPath}.nextCheck must be a non-empty string for question items.`);
|
|
724
|
+
}
|
|
725
|
+
if (item.nextCheck !== void 0) requireReviewString(item.nextCheck, `${itemPath}.nextCheck`);
|
|
726
|
+
for (const [evidenceIndex, evidence] of item.evidence.entries()) {
|
|
727
|
+
const evidencePath = `${itemPath}.evidence[${evidenceIndex}]`;
|
|
728
|
+
if (!isRecord(evidence)) throw new Error(`${evidencePath} must be an object.`);
|
|
729
|
+
requireOnlyKeys(evidence, ["sceneId", "stepId"], evidencePath);
|
|
730
|
+
requireReviewString(evidence.sceneId, `${evidencePath}.sceneId`);
|
|
731
|
+
requireReviewString(evidence.stepId, `${evidencePath}.stepId`);
|
|
732
|
+
if (context.authoredWalkthroughSteps) {
|
|
733
|
+
const stepIds = context.authoredWalkthroughSteps.get(evidence.sceneId);
|
|
734
|
+
if (!stepIds) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`${evidencePath} references unknown authored walkthrough scene ${evidence.sceneId}.`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (!stepIds.has(evidence.stepId)) {
|
|
740
|
+
throw new Error(
|
|
741
|
+
`${evidencePath} references unknown authored walkthrough step ${evidence.sceneId}/${evidence.stepId}.`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
591
745
|
}
|
|
592
746
|
}
|
|
593
|
-
return { ...value, connections: rawConnections };
|
|
594
747
|
}
|
|
595
748
|
function validateDeconstructedScene(scene, path) {
|
|
596
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "base", "target", "stack", "steps", "stages", "exclusions"], path);
|
|
749
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "base", "target", "stack", "steps", "stages", "exclusions"], path);
|
|
597
750
|
optionalString(scene.base, `${path}.base`);
|
|
598
751
|
optionalString(scene.target, `${path}.target`);
|
|
599
752
|
if (!Array.isArray(scene.stages) || scene.stages.length === 0 || scene.stages.length > 12) {
|
|
@@ -679,7 +832,7 @@ function validateRealStack(stack, path, minimum) {
|
|
|
679
832
|
}
|
|
680
833
|
}
|
|
681
834
|
function validateStackedScene(scene, path, minimum = 3) {
|
|
682
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "stack", "files", "steps"], path);
|
|
835
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "stack", "files", "steps"], path);
|
|
683
836
|
if (!Array.isArray(scene.stack) || scene.stack.length < minimum || scene.stack.length > 6) {
|
|
684
837
|
throw new Error(`${path}.stack must contain between ${minimum} and 6 revisions.`);
|
|
685
838
|
}
|
|
@@ -735,6 +888,53 @@ function validateStepRequirement(value, path) {
|
|
|
735
888
|
throw new Error(`${path}.confidence must be high, medium, or low.`);
|
|
736
889
|
}
|
|
737
890
|
}
|
|
891
|
+
function validateSceneOverview(value, path, options = {}) {
|
|
892
|
+
if (value === void 0) return;
|
|
893
|
+
if (!isRecord(value)) throw new Error(`${path} must be an object.`);
|
|
894
|
+
requireOnlyKeys(value, ["kind", "path", "comparison"], path);
|
|
895
|
+
if (value.kind !== "directory-diff") {
|
|
896
|
+
throw new Error(`${path}.kind must be directory-diff.`);
|
|
897
|
+
}
|
|
898
|
+
if (value.path !== void 0) validateDirectoryOverviewPath(value.path, `${path}.path`);
|
|
899
|
+
if (value.comparison === void 0) return;
|
|
900
|
+
if (options.allowComparison === false) {
|
|
901
|
+
throw new Error(`${path}.comparison is not supported for walkthrough scenes; use the fixed base-to-head comparison.`);
|
|
902
|
+
}
|
|
903
|
+
if (!isRecord(value.comparison)) throw new Error(`${path}.comparison must be an object.`);
|
|
904
|
+
requireOnlyKeys(value.comparison, ["from", "to"], `${path}.comparison`);
|
|
905
|
+
requireString(value.comparison.from, `${path}.comparison.from`);
|
|
906
|
+
requireString(value.comparison.to, `${path}.comparison.to`);
|
|
907
|
+
if (value.comparison.from === value.comparison.to) {
|
|
908
|
+
throw new Error(`${path}.comparison.from and to must identify distinct endpoints.`);
|
|
909
|
+
}
|
|
910
|
+
if (options.endpointIds !== void 0) {
|
|
911
|
+
for (const endpoint of [value.comparison.from, value.comparison.to]) {
|
|
912
|
+
if (!options.endpointIds.includes(endpoint)) {
|
|
913
|
+
throw new Error(`${path}.comparison endpoint ${endpoint} is not defined by this scene.`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function validateDirectoryOverviewPath(value, path) {
|
|
919
|
+
requireString(value, path);
|
|
920
|
+
if (value.includes("\\")) {
|
|
921
|
+
throw new Error(`${path} must use a relative POSIX directory path without backslashes.`);
|
|
922
|
+
}
|
|
923
|
+
if (value.startsWith("/") || /^[A-Za-z]:/.test(value)) {
|
|
924
|
+
throw new Error(`${path} must be relative to the repository root.`);
|
|
925
|
+
}
|
|
926
|
+
if (value.split("/").some((segment) => segment === "..")) {
|
|
927
|
+
throw new Error(`${path} must not contain parent-directory traversal.`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function collectStepIds(value) {
|
|
931
|
+
const ids = /* @__PURE__ */ new Set();
|
|
932
|
+
if (!Array.isArray(value)) return ids;
|
|
933
|
+
for (const step of value) {
|
|
934
|
+
if (isRecord(step) && typeof step.id === "string") ids.add(step.id);
|
|
935
|
+
}
|
|
936
|
+
return ids;
|
|
937
|
+
}
|
|
738
938
|
function validateNarrative(value, path) {
|
|
739
939
|
requireString(value.summary, `${path}.summary`);
|
|
740
940
|
requireStringArray(value.bullets, `${path}.bullets`);
|
|
@@ -749,6 +949,20 @@ function requireString(value, path) {
|
|
|
749
949
|
throw new Error(`${path} must be a non-empty string.`);
|
|
750
950
|
}
|
|
751
951
|
}
|
|
952
|
+
function requireFullGitOid(value, path) {
|
|
953
|
+
requireString(value, path);
|
|
954
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value)) {
|
|
955
|
+
throw new Error(`${path} must be a full 40- or 64-character hexadecimal Git OID.`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function requireReviewString(value, path) {
|
|
959
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
960
|
+
throw new Error(`${path} must be a non-empty string.`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function normalizeGitOid(value) {
|
|
964
|
+
return value.toLowerCase();
|
|
965
|
+
}
|
|
752
966
|
function optionalString(value, path) {
|
|
753
967
|
if (value !== void 0) {
|
|
754
968
|
requireString(value, path);
|
|
@@ -767,14 +981,14 @@ function requireOnlyKeys(value, allowed, path) {
|
|
|
767
981
|
}
|
|
768
982
|
|
|
769
983
|
// src/changeTourManifest.ts
|
|
770
|
-
var CHANGE_TOUR_MANIFEST_VERSION =
|
|
984
|
+
var CHANGE_TOUR_MANIFEST_VERSION = 3;
|
|
771
985
|
function parseChangeTourManifest(value) {
|
|
772
986
|
if (isRecord2(value) && Number.isInteger(value.version) && Number(value.version) > CHANGE_TOUR_MANIFEST_VERSION) {
|
|
773
987
|
throw new Error(
|
|
774
988
|
`This tour uses manifest format version ${value.version}, but this version of Bygone supports up to version ${CHANGE_TOUR_MANIFEST_VERSION}. Upgrade Bygone to open it.`
|
|
775
989
|
);
|
|
776
990
|
}
|
|
777
|
-
if (!isRecord2(value) || value.version !== 1 && value.version !== CHANGE_TOUR_MANIFEST_VERSION) {
|
|
991
|
+
if (!isRecord2(value) || value.version !== 1 && value.version !== 2 && value.version !== CHANGE_TOUR_MANIFEST_VERSION) {
|
|
778
992
|
throw new Error(`Unsupported or missing change-tour manifest version.`);
|
|
779
993
|
}
|
|
780
994
|
requireString2(value.title, "title");
|
|
@@ -806,7 +1020,7 @@ function parseChangeTourManifest(value) {
|
|
|
806
1020
|
}
|
|
807
1021
|
const sceneIds = /* @__PURE__ */ new Set();
|
|
808
1022
|
for (const [index, candidate] of value.scenes.entries()) {
|
|
809
|
-
validateScene(candidate, index);
|
|
1023
|
+
validateScene(candidate, index, value.version);
|
|
810
1024
|
if (sceneIds.has(candidate.id)) {
|
|
811
1025
|
throw new Error(`Duplicate change-tour scene id: ${candidate.id}`);
|
|
812
1026
|
}
|
|
@@ -838,14 +1052,44 @@ function parseChangeTourManifest(value) {
|
|
|
838
1052
|
if (value.authoringCoverage !== void 0) {
|
|
839
1053
|
validateAuthoringCoverage(value.authoringCoverage, value.version);
|
|
840
1054
|
}
|
|
841
|
-
if (value.
|
|
1055
|
+
if (value.tours !== void 0) {
|
|
1056
|
+
if (value.version !== 3) throw new Error("Independent tours require manifest version 3.");
|
|
1057
|
+
validateModeTours(value.tours, value.version);
|
|
1058
|
+
}
|
|
1059
|
+
if (value.version !== 1) validateZoom(value, value.version);
|
|
1060
|
+
if (value.review !== void 0) {
|
|
1061
|
+
if (value.version !== 3) throw new Error("Review notes require manifest version 3.");
|
|
1062
|
+
const evidenceScenes = value.version >= 2 && isRecord2(value.zoom) && isRecord2(value.zoom.final) ? value.zoom.final.scenes : value.scenes;
|
|
1063
|
+
validateChangeTourReview(value.review, {
|
|
1064
|
+
expectedRange: {
|
|
1065
|
+
baseOid: String(value.range.mergeBaseOid),
|
|
1066
|
+
headOid: String(value.range.headOid)
|
|
1067
|
+
},
|
|
1068
|
+
authoredWalkthroughSteps: collectAuthoredWalkthroughStepIds(evidenceScenes)
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
842
1071
|
return { ...value, files };
|
|
843
1072
|
}
|
|
1073
|
+
function collectAuthoredWalkthroughStepIds(value) {
|
|
1074
|
+
const scenes = /* @__PURE__ */ new Map();
|
|
1075
|
+
if (!Array.isArray(value)) return scenes;
|
|
1076
|
+
for (const scene of value) {
|
|
1077
|
+
if (!isRecord2(scene) || scene.kind !== "walkthrough" || !Array.isArray(scene.steps) || typeof scene.id !== "string") {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
1081
|
+
for (const step of scene.steps) {
|
|
1082
|
+
if (isRecord2(step) && typeof step.id === "string") stepIds.add(step.id);
|
|
1083
|
+
}
|
|
1084
|
+
scenes.set(scene.id, stepIds);
|
|
1085
|
+
}
|
|
1086
|
+
return scenes;
|
|
1087
|
+
}
|
|
844
1088
|
function validateAuthoringCoverage(value, version) {
|
|
845
1089
|
if (!isRecord2(value) || !isRecord2(value.walkthrough) || !Array.isArray(value.explanationAssignments)) {
|
|
846
1090
|
throw new Error("authoringCoverage must contain walkthrough and explanationAssignments.");
|
|
847
1091
|
}
|
|
848
|
-
const expectedScope = version
|
|
1092
|
+
const expectedScope = version >= 2 ? "final" : "tour";
|
|
849
1093
|
if (value.walkthrough.scope !== expectedScope) {
|
|
850
1094
|
throw new Error(`authoringCoverage.walkthrough.scope must be ${expectedScope}.`);
|
|
851
1095
|
}
|
|
@@ -869,7 +1113,49 @@ function validateAuthoringCoverage(value, version) {
|
|
|
869
1113
|
}
|
|
870
1114
|
}
|
|
871
1115
|
}
|
|
872
|
-
function
|
|
1116
|
+
function validateModeTours(value, version) {
|
|
1117
|
+
if (!isRecord2(value)) throw new Error("Change-tour manifest tours must be an object.");
|
|
1118
|
+
if (value.historical === void 0 && value.deconstructed === void 0) {
|
|
1119
|
+
throw new Error("Change-tour manifest tours must contain a historical or deconstructed tour.");
|
|
1120
|
+
}
|
|
1121
|
+
for (const mode of ["historical", "deconstructed"]) {
|
|
1122
|
+
const tour = value[mode];
|
|
1123
|
+
if (tour === void 0) continue;
|
|
1124
|
+
if (!isRecord2(tour) || !Array.isArray(tour.chapters) || !Array.isArray(tour.scenes) || tour.chapters.length === 0 || tour.scenes.length === 0) {
|
|
1125
|
+
throw new Error(`Change-tour manifest tours.${mode} must contain non-empty chapters and scenes arrays.`);
|
|
1126
|
+
}
|
|
1127
|
+
const sceneIds = /* @__PURE__ */ new Set();
|
|
1128
|
+
let hasDeconstructed = false;
|
|
1129
|
+
for (const [index, scene] of tour.scenes.entries()) {
|
|
1130
|
+
validateScene(scene, index, version);
|
|
1131
|
+
if (sceneIds.has(scene.id)) {
|
|
1132
|
+
throw new Error(`Duplicate ${mode} tour scene id: ${scene.id}`);
|
|
1133
|
+
}
|
|
1134
|
+
sceneIds.add(scene.id);
|
|
1135
|
+
if (scene.kind === "deconstructed-diff") hasDeconstructed = true;
|
|
1136
|
+
if (mode === "historical" && scene.kind === "deconstructed-diff") {
|
|
1137
|
+
throw new Error("Historical tours cannot contain synthetic deconstructed scenes.");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (mode === "deconstructed" && !hasDeconstructed) {
|
|
1141
|
+
throw new Error("Deconstructed tours must contain a constructed deconstructed-diff scene.");
|
|
1142
|
+
}
|
|
1143
|
+
for (const [index, chapter] of tour.chapters.entries()) {
|
|
1144
|
+
if (!isRecord2(chapter)) {
|
|
1145
|
+
throw new Error(`tours.${mode}.chapters[${index}] must be an object.`);
|
|
1146
|
+
}
|
|
1147
|
+
requireString2(chapter.id, `tours.${mode}.chapters[${index}].id`);
|
|
1148
|
+
requireString2(chapter.title, `tours.${mode}.chapters[${index}].title`);
|
|
1149
|
+
requireStringArray2(chapter.sceneIds, `tours.${mode}.chapters[${index}].sceneIds`);
|
|
1150
|
+
for (const sceneId of chapter.sceneIds) {
|
|
1151
|
+
if (!sceneIds.has(sceneId)) {
|
|
1152
|
+
throw new Error(`tours.${mode} chapter references unknown scene id: ${sceneId}`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function validateZoom(value, version) {
|
|
873
1159
|
if (!isRecord2(value.repository) || typeof value.repository.root !== "string" || !value.repository.root) {
|
|
874
1160
|
throw new Error("A v2 tour requires its originating repository.root.");
|
|
875
1161
|
}
|
|
@@ -880,7 +1166,7 @@ function validateZoom(value) {
|
|
|
880
1166
|
const revisions = value.zoom.revisions;
|
|
881
1167
|
const ids = /* @__PURE__ */ new Set();
|
|
882
1168
|
for (const [index, scene] of revisions.entries()) {
|
|
883
|
-
validateScene(scene, index);
|
|
1169
|
+
validateScene(scene, index, version);
|
|
884
1170
|
if (scene.kind !== "stacked-diff") throw new Error("zoom.revisions must contain real stacked-diff scenes.");
|
|
885
1171
|
if (ids.has(scene.id)) throw new Error(`Duplicate zoom revision scene: ${scene.id}`);
|
|
886
1172
|
ids.add(scene.id);
|
|
@@ -910,7 +1196,7 @@ function validateZoom(value) {
|
|
|
910
1196
|
}
|
|
911
1197
|
const finalSceneIds = /* @__PURE__ */ new Set();
|
|
912
1198
|
for (const [index, scene] of value.zoom.final.scenes.entries()) {
|
|
913
|
-
validateScene(scene, index);
|
|
1199
|
+
validateScene(scene, index, version);
|
|
914
1200
|
if (scene.kind === "stacked-diff" || scene.kind === "deconstructed-diff") {
|
|
915
1201
|
throw new Error("zoom.final must contain only non-stacked scenes.");
|
|
916
1202
|
}
|
|
@@ -929,18 +1215,23 @@ function validateZoom(value) {
|
|
|
929
1215
|
}
|
|
930
1216
|
}
|
|
931
1217
|
}
|
|
932
|
-
function validateScene(value, index) {
|
|
1218
|
+
function validateScene(value, index, version) {
|
|
933
1219
|
if (!isRecord2(value) || !["text-diff", "discussion", "walkthrough", "stacked-diff", "deconstructed-diff"].includes(String(value.kind))) {
|
|
934
1220
|
throw new Error(`scenes[${index}] must be a text-diff, discussion, walkthrough, stacked-diff, or deconstructed-diff scene.`);
|
|
935
1221
|
}
|
|
936
1222
|
for (const key of ["id", "title"]) {
|
|
937
1223
|
requireString2(value[key], `scenes[${index}].${key}`);
|
|
938
1224
|
}
|
|
1225
|
+
if (value.overview !== void 0 && version !== 3) {
|
|
1226
|
+
throw new Error(`scenes[${index}].overview requires manifest version 3.`);
|
|
1227
|
+
}
|
|
939
1228
|
validateNarrative2(value, `scenes[${index}]`);
|
|
940
1229
|
if (value.kind === "discussion") {
|
|
1230
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
941
1231
|
return;
|
|
942
1232
|
}
|
|
943
1233
|
if (value.kind === "walkthrough") {
|
|
1234
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
944
1235
|
if (!Array.isArray(value.steps) || value.steps.length === 0) {
|
|
945
1236
|
throw new Error(`scenes[${index}].steps must be a non-empty array.`);
|
|
946
1237
|
}
|
|
@@ -955,7 +1246,7 @@ function validateScene(value, index) {
|
|
|
955
1246
|
}
|
|
956
1247
|
validateStepRequirement(step.requirement, `${path}.requirement`);
|
|
957
1248
|
validateResolvedAnchor(step.focus, `${path}.focus`);
|
|
958
|
-
validateScene(step.diff, index);
|
|
1249
|
+
validateScene(step.diff, index, version);
|
|
959
1250
|
if (step.diff.kind !== "text-diff") throw new Error(`${path}.diff must be a text-diff scene.`);
|
|
960
1251
|
if (step.connection !== void 0) {
|
|
961
1252
|
if (!isRecord2(step.connection)) throw new Error(`${path}.connection must be an object.`);
|
|
@@ -969,12 +1260,19 @@ function validateScene(value, index) {
|
|
|
969
1260
|
}
|
|
970
1261
|
if (value.kind === "stacked-diff") {
|
|
971
1262
|
validateStackedScene2(value, index);
|
|
1263
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, {
|
|
1264
|
+
endpointIds: value.stack.map((panel) => panel.id)
|
|
1265
|
+
});
|
|
972
1266
|
return;
|
|
973
1267
|
}
|
|
974
1268
|
if (value.kind === "deconstructed-diff") {
|
|
975
1269
|
validateDeconstructedScene2(value, index);
|
|
1270
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, {
|
|
1271
|
+
endpointIds: value.panels.map((panel) => panel.id)
|
|
1272
|
+
});
|
|
976
1273
|
return;
|
|
977
1274
|
}
|
|
1275
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
978
1276
|
for (const key of ["path", "leftLabel", "rightLabel", "leftContent", "rightContent"]) {
|
|
979
1277
|
requireString2(value[key], `scenes[${index}].${key}`);
|
|
980
1278
|
}
|
|
@@ -1092,7 +1390,7 @@ function validateMultiPanelSteps(value, panelCount, index, deconstructed) {
|
|
|
1092
1390
|
function validateTourFile(value, index) {
|
|
1093
1391
|
if (!isRecord2(value)) throw new Error(`files[${index}] must be an object.`);
|
|
1094
1392
|
if (value.kind === "text-diff") {
|
|
1095
|
-
validateScene(value, index);
|
|
1393
|
+
validateScene(value, index, 1);
|
|
1096
1394
|
return;
|
|
1097
1395
|
}
|
|
1098
1396
|
if (value.kind !== "omitted") {
|
|
@@ -2794,12 +3092,21 @@ var CHAPTERS = {
|
|
|
2794
3092
|
packaging: { id: "packaging", title: "Packaging and dependencies", priority: 4 }
|
|
2795
3093
|
};
|
|
2796
3094
|
function buildChangeTourManifest(startPath, options = {}) {
|
|
2797
|
-
var _a, _b, _c, _d
|
|
3095
|
+
var _a, _b, _c, _d;
|
|
3096
|
+
const source = options.source ? parseChangeTourSource(options.source) : void 0;
|
|
2798
3097
|
const range = resolveBranchReviewRange(
|
|
2799
3098
|
startPath,
|
|
2800
|
-
options.headRef ?? ((
|
|
2801
|
-
options.baseRef ?? ((
|
|
3099
|
+
options.headRef ?? ((_a = source == null ? void 0 : source.range) == null ? void 0 : _a.head),
|
|
3100
|
+
options.baseRef ?? ((_b = source == null ? void 0 : source.range) == null ? void 0 : _b.base)
|
|
2802
3101
|
);
|
|
3102
|
+
if (source == null ? void 0 : source.review) {
|
|
3103
|
+
validateChangeTourReview(source.review, {
|
|
3104
|
+
expectedRange: {
|
|
3105
|
+
baseOid: range.mergeBaseOid,
|
|
3106
|
+
headOid: range.headOid
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
2803
3110
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_TOUR_FILE_BYTES;
|
|
2804
3111
|
const maxLineBytes = options.maxLineBytes ?? DEFAULT_MAX_TOUR_LINE_BYTES;
|
|
2805
3112
|
const omittedFiles = [];
|
|
@@ -2860,7 +3167,7 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
2860
3167
|
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
2861
3168
|
sceneRecords.sort((left, right) => left.chapter.priority - right.chapter.priority || left.scene.path.localeCompare(right.scene.path));
|
|
2862
3169
|
let defaultScenes = sceneRecords.map(({ scene }, index) => ({ ...scene, id: `file-${index + 1}` }));
|
|
2863
|
-
if (options.story &&
|
|
3170
|
+
if (options.story && source) {
|
|
2864
3171
|
throw new Error("A change tour can use either a legacy story or a source file, not both.");
|
|
2865
3172
|
}
|
|
2866
3173
|
const attention = buildChangeAttention(files.map((file) => ({
|
|
@@ -2880,7 +3187,7 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
2880
3187
|
};
|
|
2881
3188
|
defaultScenes = [...defaultScenes].sort((left, right) => Number(right.path === attention.entryPath) - Number(left.path === attention.entryPath) || focusRank(left.path) - focusRank(right.path) || left.id.localeCompare(right.id));
|
|
2882
3189
|
}
|
|
2883
|
-
const authored =
|
|
3190
|
+
const authored = source ? applySource(source, defaultScenes, range.repoRoot) : options.story ? applyStory(options.story, defaultScenes) : buildGeneratedTourLanding(
|
|
2884
3191
|
defaultScenes,
|
|
2885
3192
|
buildChapters(sceneRecords, new Map(defaultScenes.map((scene) => [scene.path, scene.id]))),
|
|
2886
3193
|
attention,
|
|
@@ -2897,12 +3204,12 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
2897
3204
|
const finalScenes = "finalScenes" in authored ? authored.finalScenes : scenes;
|
|
2898
3205
|
const finalChapters = "finalChapters" in authored ? authored.finalChapters : chapters;
|
|
2899
3206
|
const manifest = {
|
|
2900
|
-
// Only authored v2 files opt into repository-bound zoom modes. Generated
|
|
3207
|
+
// Only authored v2+ files opt into repository-bound zoom modes. Generated
|
|
2901
3208
|
// and legacy/story tours retain the portable v1 manifest contract.
|
|
2902
|
-
version: (
|
|
2903
|
-
title: (
|
|
2904
|
-
windowTitle:
|
|
2905
|
-
sourceUrl: (
|
|
3209
|
+
version: (source == null ? void 0 : source.version) ?? 1,
|
|
3210
|
+
title: (source == null ? void 0 : source.title) || ((_c = options.story) == null ? void 0 : _c.title) || options.title || `${range.headRef} against ${range.baseRef}`,
|
|
3211
|
+
windowTitle: source == null ? void 0 : source.windowTitle,
|
|
3212
|
+
sourceUrl: (source == null ? void 0 : source.sourceUrl) || ((_d = options.story) == null ? void 0 : _d.sourceUrl) || options.sourceUrl,
|
|
2906
3213
|
generatedAt: options.generatedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2907
3214
|
range: {
|
|
2908
3215
|
baseRef: range.baseRef,
|
|
@@ -2921,22 +3228,33 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
2921
3228
|
commits: range.commits,
|
|
2922
3229
|
files,
|
|
2923
3230
|
chapters,
|
|
2924
|
-
scenes
|
|
3231
|
+
scenes,
|
|
3232
|
+
...(source == null ? void 0 : source.review) ? { review: source.review } : {}
|
|
2925
3233
|
};
|
|
2926
|
-
|
|
3234
|
+
const authoredModeTours = (source == null ? void 0 : source.version) === 3 ? buildAuthoredModeTours(
|
|
3235
|
+
source,
|
|
3236
|
+
authored,
|
|
3237
|
+
defaultScenes,
|
|
3238
|
+
range.repoRoot,
|
|
3239
|
+
range.mergeBaseOid,
|
|
3240
|
+
range.headOid
|
|
3241
|
+
) : void 0;
|
|
3242
|
+
if (authoredModeTours) manifest.tours = authoredModeTours;
|
|
3243
|
+
if (manifest.version >= 2) {
|
|
2927
3244
|
manifest.repository = { root: range.repoRoot };
|
|
2928
3245
|
const revisions = [];
|
|
2929
3246
|
for (const scene of scenes) {
|
|
2930
3247
|
let realStack;
|
|
2931
3248
|
if (scene.kind === "stacked-diff") realStack = scene;
|
|
2932
3249
|
if (scene.kind === "deconstructed-diff") {
|
|
2933
|
-
const authoredScene =
|
|
3250
|
+
const authoredScene = source == null ? void 0 : source.chapters.flatMap((chapter) => chapter.scenes).find((candidate) => candidate.id === scene.id);
|
|
2934
3251
|
if ((authoredScene == null ? void 0 : authoredScene.kind) !== "deconstructed-diff" || !authoredScene.stack) {
|
|
2935
|
-
throw new Error(`Deconstructed scene ${scene.id} requires an explicit real revision stack in v2
|
|
3252
|
+
throw new Error(`Deconstructed scene ${scene.id} requires an explicit real revision stack in v2+.`);
|
|
2936
3253
|
}
|
|
2937
3254
|
realStack = buildStackedScene(range.repoRoot, {
|
|
2938
3255
|
...authoredScene,
|
|
2939
3256
|
kind: "stacked-diff",
|
|
3257
|
+
overview: compileSceneOverview(authoredScene.overview, false),
|
|
2940
3258
|
stack: authoredScene.stack,
|
|
2941
3259
|
files: scene.files.map((file) => file.path),
|
|
2942
3260
|
steps: []
|
|
@@ -2969,6 +3287,9 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
2969
3287
|
return parseChangeTourManifest(manifest);
|
|
2970
3288
|
}
|
|
2971
3289
|
function applySource(source, defaultScenes, repoRoot) {
|
|
3290
|
+
return applySourceChapters(source, source.chapters, defaultScenes, repoRoot);
|
|
3291
|
+
}
|
|
3292
|
+
function applySourceChapters(source, chaptersToCompile, defaultScenes, repoRoot) {
|
|
2972
3293
|
var _a, _b;
|
|
2973
3294
|
const available = new Map(defaultScenes.map((scene) => [scene.path, scene]));
|
|
2974
3295
|
const resolvedAnchors = /* @__PURE__ */ new Map();
|
|
@@ -2990,7 +3311,7 @@ function applySource(source, defaultScenes, repoRoot) {
|
|
|
2990
3311
|
const chapters = [];
|
|
2991
3312
|
const finalScenes = [];
|
|
2992
3313
|
const finalChapters = [];
|
|
2993
|
-
for (const chapter of
|
|
3314
|
+
for (const chapter of chaptersToCompile) {
|
|
2994
3315
|
const sceneIds = [];
|
|
2995
3316
|
const finalSceneIds = [];
|
|
2996
3317
|
for (const authoredScene of chapter.scenes) {
|
|
@@ -3044,7 +3365,60 @@ function applySource(source, defaultScenes, repoRoot) {
|
|
|
3044
3365
|
}
|
|
3045
3366
|
return { scenes, chapters, finalScenes, finalChapters };
|
|
3046
3367
|
}
|
|
3368
|
+
function buildAuthoredModeTours(source, root, defaultScenes, repoRoot, mergeBaseOid, headOid) {
|
|
3369
|
+
var _a, _b;
|
|
3370
|
+
const tours = {};
|
|
3371
|
+
if ((_a = source.tours) == null ? void 0 : _a.historical) {
|
|
3372
|
+
const compiled = applySourceChapters(source, source.tours.historical.chapters, defaultScenes, repoRoot);
|
|
3373
|
+
validateHistoricalStackEndpoints(compiled.scenes, mergeBaseOid, headOid);
|
|
3374
|
+
tours.historical = { chapters: compiled.chapters, scenes: compiled.scenes };
|
|
3375
|
+
} else if (source.version === 3) {
|
|
3376
|
+
const fallback = buildHistoricalFallback(source.chapters, root);
|
|
3377
|
+
if (fallback) tours.historical = fallback;
|
|
3378
|
+
}
|
|
3379
|
+
if ((_b = source.tours) == null ? void 0 : _b.deconstructed) {
|
|
3380
|
+
const compiled = applySourceChapters(source, source.tours.deconstructed.chapters, defaultScenes, repoRoot);
|
|
3381
|
+
tours.deconstructed = { chapters: compiled.chapters, scenes: compiled.scenes };
|
|
3382
|
+
} else if (source.version === 3 && source.chapters.some((chapter) => chapter.scenes.some((scene) => scene.kind === "deconstructed-diff"))) {
|
|
3383
|
+
tours.deconstructed = { chapters: root.chapters, scenes: root.scenes };
|
|
3384
|
+
}
|
|
3385
|
+
return tours.historical || tours.deconstructed ? tours : void 0;
|
|
3386
|
+
}
|
|
3387
|
+
function validateHistoricalStackEndpoints(scenes, mergeBaseOid, headOid) {
|
|
3388
|
+
for (const scene of scenes) {
|
|
3389
|
+
if (scene.kind !== "stacked-diff") continue;
|
|
3390
|
+
if (scene.stack[0].oid !== mergeBaseOid || scene.stack[scene.stack.length - 1].oid !== headOid) {
|
|
3391
|
+
throw new Error(`Historical scene ${scene.id} real stack endpoints must match the tour's final diff range.`);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
function buildHistoricalFallback(sourceChapters, root) {
|
|
3396
|
+
const regularScenes = new Map(root.scenes.map((scene) => [scene.id, scene]));
|
|
3397
|
+
const finalScenes = new Map(root.finalScenes.map((scene) => [scene.id, scene]));
|
|
3398
|
+
const scenes = [];
|
|
3399
|
+
const chapters = [];
|
|
3400
|
+
for (const chapter of sourceChapters) {
|
|
3401
|
+
const sceneIds = [];
|
|
3402
|
+
for (const authoredScene of chapter.scenes) {
|
|
3403
|
+
const scene = authoredScene.kind === "deconstructed-diff" ? authoredScene.steps ? finalScenes.get(authoredScene.id) : void 0 : regularScenes.get(authoredScene.id);
|
|
3404
|
+
if (!scene) continue;
|
|
3405
|
+
scenes.push(scene);
|
|
3406
|
+
sceneIds.push(scene.id);
|
|
3407
|
+
}
|
|
3408
|
+
if (sceneIds.length > 0) chapters.push({ id: chapter.id, title: chapter.title, sceneIds });
|
|
3409
|
+
}
|
|
3410
|
+
return scenes.length > 0 ? { chapters, scenes } : void 0;
|
|
3411
|
+
}
|
|
3412
|
+
function compileSceneOverview(overview, includeComparison = true) {
|
|
3413
|
+
if (!overview) return void 0;
|
|
3414
|
+
return {
|
|
3415
|
+
kind: overview.kind,
|
|
3416
|
+
...overview.path === void 0 ? {} : { path: overview.path },
|
|
3417
|
+
...includeComparison && overview.comparison ? { comparison: { ...overview.comparison } } : {}
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3047
3420
|
function buildWalkthroughManifestScene(authoredScene, available, resolvedAnchors, connections) {
|
|
3421
|
+
const overview = compileSceneOverview(authoredScene.overview, false);
|
|
3048
3422
|
return {
|
|
3049
3423
|
id: authoredScene.id,
|
|
3050
3424
|
kind: "walkthrough",
|
|
@@ -3053,6 +3427,7 @@ function buildWalkthroughManifestScene(authoredScene, available, resolvedAnchors
|
|
|
3053
3427
|
bullets: authoredScene.bullets,
|
|
3054
3428
|
tags: authoredScene.tags,
|
|
3055
3429
|
takeaway: authoredScene.takeaway,
|
|
3430
|
+
...overview ? { overview } : {},
|
|
3056
3431
|
steps: authoredScene.steps.map((step) => {
|
|
3057
3432
|
const focus = requireResolvedAnchor(resolvedAnchors, step.focus);
|
|
3058
3433
|
const diff2 = available.get(focus.path);
|
|
@@ -3127,6 +3502,7 @@ function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef, a
|
|
|
3127
3502
|
});
|
|
3128
3503
|
if (files.length === 0) throw new Error(`Deconstructed scene ${source.id} has no staged files.`);
|
|
3129
3504
|
const steps = buildDeconstructedFocusSteps(source.id, compiled.stages, files);
|
|
3505
|
+
const overview = compileSceneOverview(source.overview);
|
|
3130
3506
|
return {
|
|
3131
3507
|
id: source.id,
|
|
3132
3508
|
kind: "deconstructed-diff",
|
|
@@ -3135,6 +3511,7 @@ function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef, a
|
|
|
3135
3511
|
bullets: source.bullets,
|
|
3136
3512
|
tags: source.tags,
|
|
3137
3513
|
takeaway: source.takeaway,
|
|
3514
|
+
...overview ? { overview } : {},
|
|
3138
3515
|
stageLabel: "Explanation stages",
|
|
3139
3516
|
realRange: {
|
|
3140
3517
|
baseRef: baseRef || compiled.baseOid,
|
|
@@ -3276,6 +3653,7 @@ function buildStackedScene(repoRoot, source, availableFilePaths) {
|
|
|
3276
3653
|
});
|
|
3277
3654
|
if (files.length === 0) throw new Error(`Stacked scene ${source.id} has no materializable text files.`);
|
|
3278
3655
|
const stackIds = stack.map((entry) => entry.id);
|
|
3656
|
+
const overview = compileSceneOverview(source.overview);
|
|
3279
3657
|
return {
|
|
3280
3658
|
id: source.id,
|
|
3281
3659
|
kind: "stacked-diff",
|
|
@@ -3284,6 +3662,7 @@ function buildStackedScene(repoRoot, source, availableFilePaths) {
|
|
|
3284
3662
|
bullets: source.bullets,
|
|
3285
3663
|
tags: source.tags,
|
|
3286
3664
|
takeaway: source.takeaway,
|
|
3665
|
+
...overview ? { overview } : {},
|
|
3287
3666
|
stack,
|
|
3288
3667
|
files,
|
|
3289
3668
|
steps: source.steps.length === 0 ? stack.slice(0, -1).map((_, pairIndex) => ({
|
|
@@ -3536,7 +3915,7 @@ var DEPTH_ORDER = {
|
|
|
3536
3915
|
contextualized: 2
|
|
3537
3916
|
};
|
|
3538
3917
|
function buildTourCoverageReport(startPath, sourceValue) {
|
|
3539
|
-
var _a, _b, _c, _d, _e, _f;
|
|
3918
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3540
3919
|
const source = parseChangeTourSource(sourceValue);
|
|
3541
3920
|
const inventory = buildChangeInventory(startPath, {
|
|
3542
3921
|
headRef: (_a = source.range) == null ? void 0 : _a.head,
|
|
@@ -3552,8 +3931,10 @@ function buildTourCoverageReport(startPath, sourceValue) {
|
|
|
3552
3931
|
const includedUnits = textualUnits.filter((unit) => !excludedIds.has(unit.id));
|
|
3553
3932
|
const coverage = /* @__PURE__ */ new Map();
|
|
3554
3933
|
const contextualOnlyEvidence = [];
|
|
3555
|
-
const percentageDecimalPlaces = manifest.version
|
|
3556
|
-
const
|
|
3934
|
+
const percentageDecimalPlaces = manifest.version >= 2 ? 2 : 0;
|
|
3935
|
+
const authoredHistoricalScenes = (_f = (_e = manifest.tours) == null ? void 0 : _e.historical) == null ? void 0 : _f.scenes;
|
|
3936
|
+
const historicalHasWalkthrough = (authoredHistoricalScenes == null ? void 0 : authoredHistoricalScenes.some((scene) => scene.kind === "walkthrough")) === true;
|
|
3937
|
+
const walkthroughScenes = manifest.version >= 2 ? requireFinalScenes(historicalHasWalkthrough ? authoredHistoricalScenes : (_g = manifest.zoom) == null ? void 0 : _g.final.scenes) : historicalHasWalkthrough ? authoredHistoricalScenes : manifest.scenes;
|
|
3557
3938
|
for (const scene of walkthroughScenes) {
|
|
3558
3939
|
if (scene.kind !== "walkthrough") continue;
|
|
3559
3940
|
for (const step of scene.steps) {
|
|
@@ -3597,7 +3978,7 @@ function buildTourCoverageReport(startPath, sourceValue) {
|
|
|
3597
3978
|
},
|
|
3598
3979
|
depth,
|
|
3599
3980
|
files,
|
|
3600
|
-
exclusions: (((
|
|
3981
|
+
exclusions: (((_h = source.coverage) == null ? void 0 : _h.exclusions) || []).map((exclusion) => {
|
|
3601
3982
|
var _a2;
|
|
3602
3983
|
return {
|
|
3603
3984
|
path: exclusion.path,
|
|
@@ -3621,9 +4002,11 @@ function requireFinalScenes(scenes) {
|
|
|
3621
4002
|
return scenes;
|
|
3622
4003
|
}
|
|
3623
4004
|
function buildExplanationAssignments(source, files) {
|
|
4005
|
+
var _a, _b;
|
|
3624
4006
|
const materializableFiles = files.filter((file) => file.material === "text" && file.changeKind !== "renamed" && file.baseContent !== void 0 && file.headContent !== void 0);
|
|
3625
4007
|
const totalUnits = materializableFiles.reduce((total, file) => total + file.units.length, 0);
|
|
3626
|
-
|
|
4008
|
+
const deconstructedChapters = ((_b = (_a = source.tours) == null ? void 0 : _a.deconstructed) == null ? void 0 : _b.chapters) || source.chapters;
|
|
4009
|
+
return deconstructedChapters.flatMap((chapter) => chapter.scenes).filter((scene) => scene.kind === "deconstructed-diff").map((scene) => {
|
|
3627
4010
|
const assignedUnits = scene.stages.reduce((stageTotal, stage) => stageTotal + stage.changes.reduce((changeTotal, change) => changeTotal + change.hunks.length, 0), 0);
|
|
3628
4011
|
const excludedUnits = totalUnits - assignedUnits;
|
|
3629
4012
|
const completeUnits = assignedUnits + excludedUnits;
|