@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/changeTour.js
CHANGED
|
@@ -235,17 +235,29 @@ function readGitMaxBuffer() {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// src/changeTourSource.ts
|
|
238
|
-
var CHANGE_TOUR_SOURCE_VERSION =
|
|
238
|
+
var CHANGE_TOUR_SOURCE_VERSION = 3;
|
|
239
239
|
function parseChangeTourSource(value) {
|
|
240
240
|
if (isRecord(value) && Number.isInteger(value.version) && Number(value.version) > CHANGE_TOUR_SOURCE_VERSION) {
|
|
241
241
|
throw new Error(
|
|
242
242
|
`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.`
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
|
-
if (!isRecord(value) || value.version !== 1 && value.version !== CHANGE_TOUR_SOURCE_VERSION) {
|
|
245
|
+
if (!isRecord(value) || value.version !== 1 && value.version !== 2 && value.version !== CHANGE_TOUR_SOURCE_VERSION) {
|
|
246
246
|
throw new Error("Unsupported or missing change-tour source version.");
|
|
247
247
|
}
|
|
248
|
-
requireOnlyKeys(value, [
|
|
248
|
+
requireOnlyKeys(value, [
|
|
249
|
+
"version",
|
|
250
|
+
"title",
|
|
251
|
+
"windowTitle",
|
|
252
|
+
"sourceUrl",
|
|
253
|
+
"range",
|
|
254
|
+
"anchors",
|
|
255
|
+
"connections",
|
|
256
|
+
"chapters",
|
|
257
|
+
"tours",
|
|
258
|
+
"coverage",
|
|
259
|
+
"review"
|
|
260
|
+
], "source");
|
|
249
261
|
optionalString(value.title, "title");
|
|
250
262
|
optionalString(value.windowTitle, "windowTitle");
|
|
251
263
|
optionalString(value.sourceUrl, "sourceUrl");
|
|
@@ -309,30 +321,81 @@ function parseChangeTourSource(value) {
|
|
|
309
321
|
}
|
|
310
322
|
connectionIds.add(id);
|
|
311
323
|
}
|
|
324
|
+
const authoredWalkthroughSteps = validateSourceChapters(
|
|
325
|
+
value.chapters,
|
|
326
|
+
"chapters",
|
|
327
|
+
value.version,
|
|
328
|
+
value.anchors,
|
|
329
|
+
connectionIds,
|
|
330
|
+
"root"
|
|
331
|
+
);
|
|
332
|
+
if (value.tours !== void 0) {
|
|
333
|
+
if (value.version !== 3) throw new Error("Independent tours require version 3.");
|
|
334
|
+
if (!isRecord(value.tours)) throw new Error("tours must be an object.");
|
|
335
|
+
requireOnlyKeys(value.tours, ["historical", "deconstructed"], "tours");
|
|
336
|
+
if (value.tours.historical === void 0 && value.tours.deconstructed === void 0) {
|
|
337
|
+
throw new Error("tours must contain a historical or deconstructed tour.");
|
|
338
|
+
}
|
|
339
|
+
for (const mode of ["historical", "deconstructed"]) {
|
|
340
|
+
const tour = value.tours[mode];
|
|
341
|
+
if (tour === void 0) continue;
|
|
342
|
+
const path = `tours.${mode}`;
|
|
343
|
+
if (!isRecord(tour)) throw new Error(`${path} must be an object.`);
|
|
344
|
+
requireOnlyKeys(tour, ["chapters"], path);
|
|
345
|
+
validateSourceChapters(tour.chapters, `${path}.chapters`, value.version, value.anchors, connectionIds, mode);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (value.review !== void 0) {
|
|
349
|
+
if (value.version !== 3) throw new Error("Review notes require version 3.");
|
|
350
|
+
validateChangeTourReview(value.review, { authoredWalkthroughSteps });
|
|
351
|
+
}
|
|
352
|
+
return { ...value, connections: rawConnections };
|
|
353
|
+
}
|
|
354
|
+
function validateSourceChapters(chaptersValue, chaptersPath, version, anchors, connectionIds, mode) {
|
|
355
|
+
if (!Array.isArray(chaptersValue) || chaptersValue.length === 0) {
|
|
356
|
+
throw new Error(`${chaptersPath} must be a non-empty array.`);
|
|
357
|
+
}
|
|
312
358
|
const chapterIds = /* @__PURE__ */ new Set();
|
|
313
359
|
const sceneIds = /* @__PURE__ */ new Set();
|
|
314
|
-
|
|
360
|
+
const authoredWalkthroughSteps = /* @__PURE__ */ new Map();
|
|
361
|
+
for (const [chapterIndex, chapter] of chaptersValue.entries()) {
|
|
362
|
+
const chapterPath = `${chaptersPath}[${chapterIndex}]`;
|
|
315
363
|
if (!isRecord(chapter) || !Array.isArray(chapter.scenes) || chapter.scenes.length === 0) {
|
|
316
|
-
throw new Error(
|
|
364
|
+
throw new Error(`${chapterPath} must contain a non-empty scenes array.`);
|
|
317
365
|
}
|
|
318
|
-
requireString(chapter.id,
|
|
319
|
-
requireString(chapter.title,
|
|
320
|
-
requireOnlyKeys(chapter, ["id", "title", "scenes"],
|
|
366
|
+
requireString(chapter.id, `${chapterPath}.id`);
|
|
367
|
+
requireString(chapter.title, `${chapterPath}.title`);
|
|
368
|
+
requireOnlyKeys(chapter, ["id", "title", "scenes"], chapterPath);
|
|
321
369
|
if (chapterIds.has(chapter.id)) throw new Error(`Duplicate chapter id: ${chapter.id}`);
|
|
322
370
|
chapterIds.add(chapter.id);
|
|
323
371
|
for (const [sceneIndex, scene] of chapter.scenes.entries()) {
|
|
324
|
-
const path =
|
|
372
|
+
const path = `${chapterPath}.scenes[${sceneIndex}]`;
|
|
325
373
|
if (!isRecord(scene)) throw new Error(`${path} must be an object.`);
|
|
326
374
|
requireString(scene.id, `${path}.id`);
|
|
327
375
|
requireString(scene.title, `${path}.title`);
|
|
328
376
|
if (sceneIds.has(scene.id)) throw new Error(`Duplicate scene id: ${scene.id}`);
|
|
329
377
|
sceneIds.add(scene.id);
|
|
378
|
+
if (scene.overview !== void 0 && version !== 3) {
|
|
379
|
+
throw new Error(`${path}.overview requires version 3.`);
|
|
380
|
+
}
|
|
330
381
|
validateNarrative(scene, path);
|
|
331
382
|
if (scene.kind === "deconstructed-diff") {
|
|
383
|
+
if (mode === "historical") {
|
|
384
|
+
throw new Error(`${path} cannot contain a deconstructed-diff scene in the historical tour.`);
|
|
385
|
+
}
|
|
332
386
|
validateDeconstructedScene(scene, path);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
387
|
+
validateSceneOverview(scene.overview, `${path}.overview`, {
|
|
388
|
+
endpointIds: deconstructedOverviewEndpointIds(scene, path)
|
|
389
|
+
});
|
|
390
|
+
const requireRealEvidence = mode === "root" && version >= 2;
|
|
391
|
+
if (requireRealEvidence || scene.stack !== void 0) {
|
|
392
|
+
validateRealStack(scene.stack, `${path}.stack`, 2);
|
|
393
|
+
}
|
|
394
|
+
if (requireRealEvidence || scene.steps !== void 0) {
|
|
395
|
+
validateWalkthroughSteps(scene, path, anchors, connectionIds);
|
|
396
|
+
if (scene.steps !== void 0) {
|
|
397
|
+
authoredWalkthroughSteps.set(scene.id, collectStepIds(scene.steps));
|
|
398
|
+
}
|
|
336
399
|
}
|
|
337
400
|
continue;
|
|
338
401
|
}
|
|
@@ -340,20 +403,110 @@ function parseChangeTourSource(value) {
|
|
|
340
403
|
throw new Error(`${path} must contain a non-empty steps array.`);
|
|
341
404
|
}
|
|
342
405
|
if (scene.kind === "stacked-diff") {
|
|
343
|
-
validateStackedScene(scene, path,
|
|
406
|
+
validateStackedScene(scene, path, mode === "root" ? version >= 2 ? 2 : 3 : 2);
|
|
407
|
+
validateSceneOverview(scene.overview, `${path}.overview`, {
|
|
408
|
+
endpointIds: stackedOverviewEndpointIds(scene, path)
|
|
409
|
+
});
|
|
344
410
|
continue;
|
|
345
411
|
}
|
|
346
412
|
if (scene.kind !== void 0 && scene.kind !== "walkthrough") {
|
|
347
413
|
throw new Error(`${path}.kind must be walkthrough, stacked-diff, or deconstructed-diff.`);
|
|
348
414
|
}
|
|
349
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "steps"], path);
|
|
350
|
-
|
|
415
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "steps"], path);
|
|
416
|
+
validateSceneOverview(scene.overview, `${path}.overview`, { allowComparison: false });
|
|
417
|
+
validateWalkthroughSteps(scene, path, anchors, connectionIds);
|
|
418
|
+
authoredWalkthroughSteps.set(scene.id, collectStepIds(scene.steps));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (mode === "deconstructed" && !chaptersValue.some((chapter) => isRecord(chapter) && Array.isArray(chapter.scenes) && chapter.scenes.some((scene) => isRecord(scene) && scene.kind === "deconstructed-diff"))) {
|
|
422
|
+
throw new Error(`${chaptersPath} must contain at least one deconstructed-diff scene.`);
|
|
423
|
+
}
|
|
424
|
+
return authoredWalkthroughSteps;
|
|
425
|
+
}
|
|
426
|
+
function stackedOverviewEndpointIds(scene, path) {
|
|
427
|
+
if (!Array.isArray(scene.stack)) {
|
|
428
|
+
throw new Error(`${path}.stack must be an array.`);
|
|
429
|
+
}
|
|
430
|
+
return scene.stack.filter(isRecord).map((entry) => typeof entry.id === "string" ? entry.id : "").filter((id) => id.length > 0);
|
|
431
|
+
}
|
|
432
|
+
function deconstructedOverviewEndpointIds(scene, path) {
|
|
433
|
+
if (!Array.isArray(scene.stages)) {
|
|
434
|
+
throw new Error(`${path}.stages must be an array.`);
|
|
435
|
+
}
|
|
436
|
+
return [
|
|
437
|
+
"explanation-baseline",
|
|
438
|
+
...scene.stages.filter(isRecord).map((stage) => typeof stage.id === "string" ? `explanation-stage-${stage.id}` : "").filter((id) => id.length > 0)
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
function validateChangeTourReview(value, context = {}) {
|
|
442
|
+
if (!isRecord(value)) {
|
|
443
|
+
throw new Error("review must be an object.");
|
|
444
|
+
}
|
|
445
|
+
requireOnlyKeys(value, ["baseOid", "headOid", "items"], "review");
|
|
446
|
+
requireFullGitOid(value.baseOid, "review.baseOid");
|
|
447
|
+
requireFullGitOid(value.headOid, "review.headOid");
|
|
448
|
+
if (!Array.isArray(value.items) || value.items.length === 0) {
|
|
449
|
+
throw new Error("review.items must be a non-empty array.");
|
|
450
|
+
}
|
|
451
|
+
if (context.expectedRange) {
|
|
452
|
+
if (normalizeGitOid(value.baseOid) !== normalizeGitOid(context.expectedRange.baseOid)) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Stale review: review.baseOid ${value.baseOid} does not match the resolved range.mergeBaseOid ${context.expectedRange.baseOid}.`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (normalizeGitOid(value.headOid) !== normalizeGitOid(context.expectedRange.headOid)) {
|
|
458
|
+
throw new Error(
|
|
459
|
+
`Stale review: review.headOid ${value.headOid} does not match the resolved range.headOid ${context.expectedRange.headOid}.`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
464
|
+
for (const [index, item] of value.items.entries()) {
|
|
465
|
+
const itemPath = `review.items[${index}]`;
|
|
466
|
+
if (!isRecord(item)) throw new Error(`${itemPath} must be an object.`);
|
|
467
|
+
requireOnlyKeys(item, ["id", "kind", "title", "body", "evidence", "nextCheck"], itemPath);
|
|
468
|
+
requireReviewString(item.id, `${itemPath}.id`);
|
|
469
|
+
if (itemIds.has(item.id)) throw new Error(`Duplicate review item id: ${item.id}`);
|
|
470
|
+
itemIds.add(item.id);
|
|
471
|
+
if (typeof item.kind !== "string" || !["concept", "boundary", "tradeoff", "question"].includes(item.kind)) {
|
|
472
|
+
throw new Error(`${itemPath}.kind must be concept, boundary, tradeoff, or question.`);
|
|
473
|
+
}
|
|
474
|
+
requireReviewString(item.title, `${itemPath}.title`);
|
|
475
|
+
requireReviewString(item.body, `${itemPath}.body`);
|
|
476
|
+
if (!Array.isArray(item.evidence)) {
|
|
477
|
+
throw new Error(`${itemPath}.evidence must be an array.`);
|
|
478
|
+
}
|
|
479
|
+
if (item.kind !== "question" && item.evidence.length === 0) {
|
|
480
|
+
throw new Error(`${itemPath}.evidence must contain at least one reference for claims.`);
|
|
481
|
+
}
|
|
482
|
+
if (item.kind === "question" && item.nextCheck === void 0) {
|
|
483
|
+
throw new Error(`${itemPath}.nextCheck must be a non-empty string for question items.`);
|
|
484
|
+
}
|
|
485
|
+
if (item.nextCheck !== void 0) requireReviewString(item.nextCheck, `${itemPath}.nextCheck`);
|
|
486
|
+
for (const [evidenceIndex, evidence] of item.evidence.entries()) {
|
|
487
|
+
const evidencePath = `${itemPath}.evidence[${evidenceIndex}]`;
|
|
488
|
+
if (!isRecord(evidence)) throw new Error(`${evidencePath} must be an object.`);
|
|
489
|
+
requireOnlyKeys(evidence, ["sceneId", "stepId"], evidencePath);
|
|
490
|
+
requireReviewString(evidence.sceneId, `${evidencePath}.sceneId`);
|
|
491
|
+
requireReviewString(evidence.stepId, `${evidencePath}.stepId`);
|
|
492
|
+
if (context.authoredWalkthroughSteps) {
|
|
493
|
+
const stepIds = context.authoredWalkthroughSteps.get(evidence.sceneId);
|
|
494
|
+
if (!stepIds) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`${evidencePath} references unknown authored walkthrough scene ${evidence.sceneId}.`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (!stepIds.has(evidence.stepId)) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`${evidencePath} references unknown authored walkthrough step ${evidence.sceneId}/${evidence.stepId}.`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
351
505
|
}
|
|
352
506
|
}
|
|
353
|
-
return { ...value, connections: rawConnections };
|
|
354
507
|
}
|
|
355
508
|
function validateDeconstructedScene(scene, path) {
|
|
356
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "base", "target", "stack", "steps", "stages", "exclusions"], path);
|
|
509
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "base", "target", "stack", "steps", "stages", "exclusions"], path);
|
|
357
510
|
optionalString(scene.base, `${path}.base`);
|
|
358
511
|
optionalString(scene.target, `${path}.target`);
|
|
359
512
|
if (!Array.isArray(scene.stages) || scene.stages.length === 0 || scene.stages.length > 12) {
|
|
@@ -439,7 +592,7 @@ function validateRealStack(stack, path, minimum) {
|
|
|
439
592
|
}
|
|
440
593
|
}
|
|
441
594
|
function validateStackedScene(scene, path, minimum = 3) {
|
|
442
|
-
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "stack", "files", "steps"], path);
|
|
595
|
+
requireOnlyKeys(scene, ["id", "kind", "title", "summary", "bullets", "tags", "takeaway", "overview", "stack", "files", "steps"], path);
|
|
443
596
|
if (!Array.isArray(scene.stack) || scene.stack.length < minimum || scene.stack.length > 6) {
|
|
444
597
|
throw new Error(`${path}.stack must contain between ${minimum} and 6 revisions.`);
|
|
445
598
|
}
|
|
@@ -495,6 +648,53 @@ function validateStepRequirement(value, path) {
|
|
|
495
648
|
throw new Error(`${path}.confidence must be high, medium, or low.`);
|
|
496
649
|
}
|
|
497
650
|
}
|
|
651
|
+
function validateSceneOverview(value, path, options = {}) {
|
|
652
|
+
if (value === void 0) return;
|
|
653
|
+
if (!isRecord(value)) throw new Error(`${path} must be an object.`);
|
|
654
|
+
requireOnlyKeys(value, ["kind", "path", "comparison"], path);
|
|
655
|
+
if (value.kind !== "directory-diff") {
|
|
656
|
+
throw new Error(`${path}.kind must be directory-diff.`);
|
|
657
|
+
}
|
|
658
|
+
if (value.path !== void 0) validateDirectoryOverviewPath(value.path, `${path}.path`);
|
|
659
|
+
if (value.comparison === void 0) return;
|
|
660
|
+
if (options.allowComparison === false) {
|
|
661
|
+
throw new Error(`${path}.comparison is not supported for walkthrough scenes; use the fixed base-to-head comparison.`);
|
|
662
|
+
}
|
|
663
|
+
if (!isRecord(value.comparison)) throw new Error(`${path}.comparison must be an object.`);
|
|
664
|
+
requireOnlyKeys(value.comparison, ["from", "to"], `${path}.comparison`);
|
|
665
|
+
requireString(value.comparison.from, `${path}.comparison.from`);
|
|
666
|
+
requireString(value.comparison.to, `${path}.comparison.to`);
|
|
667
|
+
if (value.comparison.from === value.comparison.to) {
|
|
668
|
+
throw new Error(`${path}.comparison.from and to must identify distinct endpoints.`);
|
|
669
|
+
}
|
|
670
|
+
if (options.endpointIds !== void 0) {
|
|
671
|
+
for (const endpoint of [value.comparison.from, value.comparison.to]) {
|
|
672
|
+
if (!options.endpointIds.includes(endpoint)) {
|
|
673
|
+
throw new Error(`${path}.comparison endpoint ${endpoint} is not defined by this scene.`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function validateDirectoryOverviewPath(value, path) {
|
|
679
|
+
requireString(value, path);
|
|
680
|
+
if (value.includes("\\")) {
|
|
681
|
+
throw new Error(`${path} must use a relative POSIX directory path without backslashes.`);
|
|
682
|
+
}
|
|
683
|
+
if (value.startsWith("/") || /^[A-Za-z]:/.test(value)) {
|
|
684
|
+
throw new Error(`${path} must be relative to the repository root.`);
|
|
685
|
+
}
|
|
686
|
+
if (value.split("/").some((segment) => segment === "..")) {
|
|
687
|
+
throw new Error(`${path} must not contain parent-directory traversal.`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function collectStepIds(value) {
|
|
691
|
+
const ids = /* @__PURE__ */ new Set();
|
|
692
|
+
if (!Array.isArray(value)) return ids;
|
|
693
|
+
for (const step of value) {
|
|
694
|
+
if (isRecord(step) && typeof step.id === "string") ids.add(step.id);
|
|
695
|
+
}
|
|
696
|
+
return ids;
|
|
697
|
+
}
|
|
498
698
|
function validateNarrative(value, path) {
|
|
499
699
|
requireString(value.summary, `${path}.summary`);
|
|
500
700
|
requireStringArray(value.bullets, `${path}.bullets`);
|
|
@@ -509,6 +709,20 @@ function requireString(value, path) {
|
|
|
509
709
|
throw new Error(`${path} must be a non-empty string.`);
|
|
510
710
|
}
|
|
511
711
|
}
|
|
712
|
+
function requireFullGitOid(value, path) {
|
|
713
|
+
requireString(value, path);
|
|
714
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value)) {
|
|
715
|
+
throw new Error(`${path} must be a full 40- or 64-character hexadecimal Git OID.`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function requireReviewString(value, path) {
|
|
719
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
720
|
+
throw new Error(`${path} must be a non-empty string.`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function normalizeGitOid(value) {
|
|
724
|
+
return value.toLowerCase();
|
|
725
|
+
}
|
|
512
726
|
function optionalString(value, path) {
|
|
513
727
|
if (value !== void 0) {
|
|
514
728
|
requireString(value, path);
|
|
@@ -527,14 +741,14 @@ function requireOnlyKeys(value, allowed, path) {
|
|
|
527
741
|
}
|
|
528
742
|
|
|
529
743
|
// src/changeTourManifest.ts
|
|
530
|
-
var CHANGE_TOUR_MANIFEST_VERSION =
|
|
744
|
+
var CHANGE_TOUR_MANIFEST_VERSION = 3;
|
|
531
745
|
function parseChangeTourManifest(value) {
|
|
532
746
|
if (isRecord2(value) && Number.isInteger(value.version) && Number(value.version) > CHANGE_TOUR_MANIFEST_VERSION) {
|
|
533
747
|
throw new Error(
|
|
534
748
|
`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.`
|
|
535
749
|
);
|
|
536
750
|
}
|
|
537
|
-
if (!isRecord2(value) || value.version !== 1 && value.version !== CHANGE_TOUR_MANIFEST_VERSION) {
|
|
751
|
+
if (!isRecord2(value) || value.version !== 1 && value.version !== 2 && value.version !== CHANGE_TOUR_MANIFEST_VERSION) {
|
|
538
752
|
throw new Error(`Unsupported or missing change-tour manifest version.`);
|
|
539
753
|
}
|
|
540
754
|
requireString2(value.title, "title");
|
|
@@ -566,7 +780,7 @@ function parseChangeTourManifest(value) {
|
|
|
566
780
|
}
|
|
567
781
|
const sceneIds = /* @__PURE__ */ new Set();
|
|
568
782
|
for (const [index, candidate] of value.scenes.entries()) {
|
|
569
|
-
validateScene(candidate, index);
|
|
783
|
+
validateScene(candidate, index, value.version);
|
|
570
784
|
if (sceneIds.has(candidate.id)) {
|
|
571
785
|
throw new Error(`Duplicate change-tour scene id: ${candidate.id}`);
|
|
572
786
|
}
|
|
@@ -598,14 +812,44 @@ function parseChangeTourManifest(value) {
|
|
|
598
812
|
if (value.authoringCoverage !== void 0) {
|
|
599
813
|
validateAuthoringCoverage(value.authoringCoverage, value.version);
|
|
600
814
|
}
|
|
601
|
-
if (value.
|
|
815
|
+
if (value.tours !== void 0) {
|
|
816
|
+
if (value.version !== 3) throw new Error("Independent tours require manifest version 3.");
|
|
817
|
+
validateModeTours(value.tours, value.version);
|
|
818
|
+
}
|
|
819
|
+
if (value.version !== 1) validateZoom(value, value.version);
|
|
820
|
+
if (value.review !== void 0) {
|
|
821
|
+
if (value.version !== 3) throw new Error("Review notes require manifest version 3.");
|
|
822
|
+
const evidenceScenes = value.version >= 2 && isRecord2(value.zoom) && isRecord2(value.zoom.final) ? value.zoom.final.scenes : value.scenes;
|
|
823
|
+
validateChangeTourReview(value.review, {
|
|
824
|
+
expectedRange: {
|
|
825
|
+
baseOid: String(value.range.mergeBaseOid),
|
|
826
|
+
headOid: String(value.range.headOid)
|
|
827
|
+
},
|
|
828
|
+
authoredWalkthroughSteps: collectAuthoredWalkthroughStepIds(evidenceScenes)
|
|
829
|
+
});
|
|
830
|
+
}
|
|
602
831
|
return { ...value, files };
|
|
603
832
|
}
|
|
833
|
+
function collectAuthoredWalkthroughStepIds(value) {
|
|
834
|
+
const scenes = /* @__PURE__ */ new Map();
|
|
835
|
+
if (!Array.isArray(value)) return scenes;
|
|
836
|
+
for (const scene of value) {
|
|
837
|
+
if (!isRecord2(scene) || scene.kind !== "walkthrough" || !Array.isArray(scene.steps) || typeof scene.id !== "string") {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
841
|
+
for (const step of scene.steps) {
|
|
842
|
+
if (isRecord2(step) && typeof step.id === "string") stepIds.add(step.id);
|
|
843
|
+
}
|
|
844
|
+
scenes.set(scene.id, stepIds);
|
|
845
|
+
}
|
|
846
|
+
return scenes;
|
|
847
|
+
}
|
|
604
848
|
function validateAuthoringCoverage(value, version) {
|
|
605
849
|
if (!isRecord2(value) || !isRecord2(value.walkthrough) || !Array.isArray(value.explanationAssignments)) {
|
|
606
850
|
throw new Error("authoringCoverage must contain walkthrough and explanationAssignments.");
|
|
607
851
|
}
|
|
608
|
-
const expectedScope = version
|
|
852
|
+
const expectedScope = version >= 2 ? "final" : "tour";
|
|
609
853
|
if (value.walkthrough.scope !== expectedScope) {
|
|
610
854
|
throw new Error(`authoringCoverage.walkthrough.scope must be ${expectedScope}.`);
|
|
611
855
|
}
|
|
@@ -629,6 +873,48 @@ function validateAuthoringCoverage(value, version) {
|
|
|
629
873
|
}
|
|
630
874
|
}
|
|
631
875
|
}
|
|
876
|
+
function validateModeTours(value, version) {
|
|
877
|
+
if (!isRecord2(value)) throw new Error("Change-tour manifest tours must be an object.");
|
|
878
|
+
if (value.historical === void 0 && value.deconstructed === void 0) {
|
|
879
|
+
throw new Error("Change-tour manifest tours must contain a historical or deconstructed tour.");
|
|
880
|
+
}
|
|
881
|
+
for (const mode of ["historical", "deconstructed"]) {
|
|
882
|
+
const tour = value[mode];
|
|
883
|
+
if (tour === void 0) continue;
|
|
884
|
+
if (!isRecord2(tour) || !Array.isArray(tour.chapters) || !Array.isArray(tour.scenes) || tour.chapters.length === 0 || tour.scenes.length === 0) {
|
|
885
|
+
throw new Error(`Change-tour manifest tours.${mode} must contain non-empty chapters and scenes arrays.`);
|
|
886
|
+
}
|
|
887
|
+
const sceneIds = /* @__PURE__ */ new Set();
|
|
888
|
+
let hasDeconstructed = false;
|
|
889
|
+
for (const [index, scene] of tour.scenes.entries()) {
|
|
890
|
+
validateScene(scene, index, version);
|
|
891
|
+
if (sceneIds.has(scene.id)) {
|
|
892
|
+
throw new Error(`Duplicate ${mode} tour scene id: ${scene.id}`);
|
|
893
|
+
}
|
|
894
|
+
sceneIds.add(scene.id);
|
|
895
|
+
if (scene.kind === "deconstructed-diff") hasDeconstructed = true;
|
|
896
|
+
if (mode === "historical" && scene.kind === "deconstructed-diff") {
|
|
897
|
+
throw new Error("Historical tours cannot contain synthetic deconstructed scenes.");
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (mode === "deconstructed" && !hasDeconstructed) {
|
|
901
|
+
throw new Error("Deconstructed tours must contain a constructed deconstructed-diff scene.");
|
|
902
|
+
}
|
|
903
|
+
for (const [index, chapter] of tour.chapters.entries()) {
|
|
904
|
+
if (!isRecord2(chapter)) {
|
|
905
|
+
throw new Error(`tours.${mode}.chapters[${index}] must be an object.`);
|
|
906
|
+
}
|
|
907
|
+
requireString2(chapter.id, `tours.${mode}.chapters[${index}].id`);
|
|
908
|
+
requireString2(chapter.title, `tours.${mode}.chapters[${index}].title`);
|
|
909
|
+
requireStringArray2(chapter.sceneIds, `tours.${mode}.chapters[${index}].sceneIds`);
|
|
910
|
+
for (const sceneId of chapter.sceneIds) {
|
|
911
|
+
if (!sceneIds.has(sceneId)) {
|
|
912
|
+
throw new Error(`tours.${mode} chapter references unknown scene id: ${sceneId}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
632
918
|
function parseChangeTourStory(value) {
|
|
633
919
|
if (!isRecord2(value) || !Array.isArray(value.scenes)) {
|
|
634
920
|
throw new Error("Change-tour story must contain a scenes array.");
|
|
@@ -660,7 +946,7 @@ function parseChangeTourStory(value) {
|
|
|
660
946
|
}
|
|
661
947
|
return value;
|
|
662
948
|
}
|
|
663
|
-
function validateZoom(value) {
|
|
949
|
+
function validateZoom(value, version) {
|
|
664
950
|
if (!isRecord2(value.repository) || typeof value.repository.root !== "string" || !value.repository.root) {
|
|
665
951
|
throw new Error("A v2 tour requires its originating repository.root.");
|
|
666
952
|
}
|
|
@@ -671,7 +957,7 @@ function validateZoom(value) {
|
|
|
671
957
|
const revisions = value.zoom.revisions;
|
|
672
958
|
const ids = /* @__PURE__ */ new Set();
|
|
673
959
|
for (const [index, scene] of revisions.entries()) {
|
|
674
|
-
validateScene(scene, index);
|
|
960
|
+
validateScene(scene, index, version);
|
|
675
961
|
if (scene.kind !== "stacked-diff") throw new Error("zoom.revisions must contain real stacked-diff scenes.");
|
|
676
962
|
if (ids.has(scene.id)) throw new Error(`Duplicate zoom revision scene: ${scene.id}`);
|
|
677
963
|
ids.add(scene.id);
|
|
@@ -701,7 +987,7 @@ function validateZoom(value) {
|
|
|
701
987
|
}
|
|
702
988
|
const finalSceneIds = /* @__PURE__ */ new Set();
|
|
703
989
|
for (const [index, scene] of value.zoom.final.scenes.entries()) {
|
|
704
|
-
validateScene(scene, index);
|
|
990
|
+
validateScene(scene, index, version);
|
|
705
991
|
if (scene.kind === "stacked-diff" || scene.kind === "deconstructed-diff") {
|
|
706
992
|
throw new Error("zoom.final must contain only non-stacked scenes.");
|
|
707
993
|
}
|
|
@@ -720,18 +1006,23 @@ function validateZoom(value) {
|
|
|
720
1006
|
}
|
|
721
1007
|
}
|
|
722
1008
|
}
|
|
723
|
-
function validateScene(value, index) {
|
|
1009
|
+
function validateScene(value, index, version) {
|
|
724
1010
|
if (!isRecord2(value) || !["text-diff", "discussion", "walkthrough", "stacked-diff", "deconstructed-diff"].includes(String(value.kind))) {
|
|
725
1011
|
throw new Error(`scenes[${index}] must be a text-diff, discussion, walkthrough, stacked-diff, or deconstructed-diff scene.`);
|
|
726
1012
|
}
|
|
727
1013
|
for (const key of ["id", "title"]) {
|
|
728
1014
|
requireString2(value[key], `scenes[${index}].${key}`);
|
|
729
1015
|
}
|
|
1016
|
+
if (value.overview !== void 0 && version !== 3) {
|
|
1017
|
+
throw new Error(`scenes[${index}].overview requires manifest version 3.`);
|
|
1018
|
+
}
|
|
730
1019
|
validateNarrative2(value, `scenes[${index}]`);
|
|
731
1020
|
if (value.kind === "discussion") {
|
|
1021
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
732
1022
|
return;
|
|
733
1023
|
}
|
|
734
1024
|
if (value.kind === "walkthrough") {
|
|
1025
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
735
1026
|
if (!Array.isArray(value.steps) || value.steps.length === 0) {
|
|
736
1027
|
throw new Error(`scenes[${index}].steps must be a non-empty array.`);
|
|
737
1028
|
}
|
|
@@ -746,7 +1037,7 @@ function validateScene(value, index) {
|
|
|
746
1037
|
}
|
|
747
1038
|
validateStepRequirement(step.requirement, `${path}.requirement`);
|
|
748
1039
|
validateResolvedAnchor(step.focus, `${path}.focus`);
|
|
749
|
-
validateScene(step.diff, index);
|
|
1040
|
+
validateScene(step.diff, index, version);
|
|
750
1041
|
if (step.diff.kind !== "text-diff") throw new Error(`${path}.diff must be a text-diff scene.`);
|
|
751
1042
|
if (step.connection !== void 0) {
|
|
752
1043
|
if (!isRecord2(step.connection)) throw new Error(`${path}.connection must be an object.`);
|
|
@@ -760,12 +1051,19 @@ function validateScene(value, index) {
|
|
|
760
1051
|
}
|
|
761
1052
|
if (value.kind === "stacked-diff") {
|
|
762
1053
|
validateStackedScene2(value, index);
|
|
1054
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, {
|
|
1055
|
+
endpointIds: value.stack.map((panel) => panel.id)
|
|
1056
|
+
});
|
|
763
1057
|
return;
|
|
764
1058
|
}
|
|
765
1059
|
if (value.kind === "deconstructed-diff") {
|
|
766
1060
|
validateDeconstructedScene2(value, index);
|
|
1061
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, {
|
|
1062
|
+
endpointIds: value.panels.map((panel) => panel.id)
|
|
1063
|
+
});
|
|
767
1064
|
return;
|
|
768
1065
|
}
|
|
1066
|
+
validateSceneOverview(value.overview, `scenes[${index}].overview`, { allowComparison: false });
|
|
769
1067
|
for (const key of ["path", "leftLabel", "rightLabel", "leftContent", "rightContent"]) {
|
|
770
1068
|
requireString2(value[key], `scenes[${index}].${key}`);
|
|
771
1069
|
}
|
|
@@ -883,7 +1181,7 @@ function validateMultiPanelSteps(value, panelCount, index, deconstructed) {
|
|
|
883
1181
|
function validateTourFile(value, index) {
|
|
884
1182
|
if (!isRecord2(value)) throw new Error(`files[${index}] must be an object.`);
|
|
885
1183
|
if (value.kind === "text-diff") {
|
|
886
|
-
validateScene(value, index);
|
|
1184
|
+
validateScene(value, index, 1);
|
|
887
1185
|
return;
|
|
888
1186
|
}
|
|
889
1187
|
if (value.kind !== "omitted") {
|
|
@@ -3010,12 +3308,21 @@ var CHAPTERS = {
|
|
|
3010
3308
|
packaging: { id: "packaging", title: "Packaging and dependencies", priority: 4 }
|
|
3011
3309
|
};
|
|
3012
3310
|
function buildChangeTourManifest(startPath, options = {}) {
|
|
3013
|
-
var _a, _b, _c, _d
|
|
3311
|
+
var _a, _b, _c, _d;
|
|
3312
|
+
const source = options.source ? parseChangeTourSource(options.source) : void 0;
|
|
3014
3313
|
const range = resolveBranchReviewRange(
|
|
3015
3314
|
startPath,
|
|
3016
|
-
options.headRef ?? ((
|
|
3017
|
-
options.baseRef ?? ((
|
|
3315
|
+
options.headRef ?? ((_a = source == null ? void 0 : source.range) == null ? void 0 : _a.head),
|
|
3316
|
+
options.baseRef ?? ((_b = source == null ? void 0 : source.range) == null ? void 0 : _b.base)
|
|
3018
3317
|
);
|
|
3318
|
+
if (source == null ? void 0 : source.review) {
|
|
3319
|
+
validateChangeTourReview(source.review, {
|
|
3320
|
+
expectedRange: {
|
|
3321
|
+
baseOid: range.mergeBaseOid,
|
|
3322
|
+
headOid: range.headOid
|
|
3323
|
+
}
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3019
3326
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_TOUR_FILE_BYTES;
|
|
3020
3327
|
const maxLineBytes = options.maxLineBytes ?? DEFAULT_MAX_TOUR_LINE_BYTES;
|
|
3021
3328
|
const omittedFiles = [];
|
|
@@ -3076,7 +3383,7 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
3076
3383
|
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
3077
3384
|
sceneRecords.sort((left, right) => left.chapter.priority - right.chapter.priority || left.scene.path.localeCompare(right.scene.path));
|
|
3078
3385
|
let defaultScenes = sceneRecords.map(({ scene }, index) => ({ ...scene, id: `file-${index + 1}` }));
|
|
3079
|
-
if (options.story &&
|
|
3386
|
+
if (options.story && source) {
|
|
3080
3387
|
throw new Error("A change tour can use either a legacy story or a source file, not both.");
|
|
3081
3388
|
}
|
|
3082
3389
|
const attention = buildChangeAttention(files.map((file) => ({
|
|
@@ -3096,7 +3403,7 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
3096
3403
|
};
|
|
3097
3404
|
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));
|
|
3098
3405
|
}
|
|
3099
|
-
const authored =
|
|
3406
|
+
const authored = source ? applySource(source, defaultScenes, range.repoRoot) : options.story ? applyStory(options.story, defaultScenes) : buildGeneratedTourLanding(
|
|
3100
3407
|
defaultScenes,
|
|
3101
3408
|
buildChapters(sceneRecords, new Map(defaultScenes.map((scene) => [scene.path, scene.id]))),
|
|
3102
3409
|
attention,
|
|
@@ -3113,12 +3420,12 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
3113
3420
|
const finalScenes = "finalScenes" in authored ? authored.finalScenes : scenes;
|
|
3114
3421
|
const finalChapters = "finalChapters" in authored ? authored.finalChapters : chapters;
|
|
3115
3422
|
const manifest = {
|
|
3116
|
-
// Only authored v2 files opt into repository-bound zoom modes. Generated
|
|
3423
|
+
// Only authored v2+ files opt into repository-bound zoom modes. Generated
|
|
3117
3424
|
// and legacy/story tours retain the portable v1 manifest contract.
|
|
3118
|
-
version: (
|
|
3119
|
-
title: (
|
|
3120
|
-
windowTitle:
|
|
3121
|
-
sourceUrl: (
|
|
3425
|
+
version: (source == null ? void 0 : source.version) ?? 1,
|
|
3426
|
+
title: (source == null ? void 0 : source.title) || ((_c = options.story) == null ? void 0 : _c.title) || options.title || `${range.headRef} against ${range.baseRef}`,
|
|
3427
|
+
windowTitle: source == null ? void 0 : source.windowTitle,
|
|
3428
|
+
sourceUrl: (source == null ? void 0 : source.sourceUrl) || ((_d = options.story) == null ? void 0 : _d.sourceUrl) || options.sourceUrl,
|
|
3122
3429
|
generatedAt: options.generatedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3123
3430
|
range: {
|
|
3124
3431
|
baseRef: range.baseRef,
|
|
@@ -3137,22 +3444,33 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
3137
3444
|
commits: range.commits,
|
|
3138
3445
|
files,
|
|
3139
3446
|
chapters,
|
|
3140
|
-
scenes
|
|
3447
|
+
scenes,
|
|
3448
|
+
...(source == null ? void 0 : source.review) ? { review: source.review } : {}
|
|
3141
3449
|
};
|
|
3142
|
-
|
|
3450
|
+
const authoredModeTours = (source == null ? void 0 : source.version) === 3 ? buildAuthoredModeTours(
|
|
3451
|
+
source,
|
|
3452
|
+
authored,
|
|
3453
|
+
defaultScenes,
|
|
3454
|
+
range.repoRoot,
|
|
3455
|
+
range.mergeBaseOid,
|
|
3456
|
+
range.headOid
|
|
3457
|
+
) : void 0;
|
|
3458
|
+
if (authoredModeTours) manifest.tours = authoredModeTours;
|
|
3459
|
+
if (manifest.version >= 2) {
|
|
3143
3460
|
manifest.repository = { root: range.repoRoot };
|
|
3144
3461
|
const revisions = [];
|
|
3145
3462
|
for (const scene of scenes) {
|
|
3146
3463
|
let realStack;
|
|
3147
3464
|
if (scene.kind === "stacked-diff") realStack = scene;
|
|
3148
3465
|
if (scene.kind === "deconstructed-diff") {
|
|
3149
|
-
const authoredScene =
|
|
3466
|
+
const authoredScene = source == null ? void 0 : source.chapters.flatMap((chapter) => chapter.scenes).find((candidate) => candidate.id === scene.id);
|
|
3150
3467
|
if ((authoredScene == null ? void 0 : authoredScene.kind) !== "deconstructed-diff" || !authoredScene.stack) {
|
|
3151
|
-
throw new Error(`Deconstructed scene ${scene.id} requires an explicit real revision stack in v2
|
|
3468
|
+
throw new Error(`Deconstructed scene ${scene.id} requires an explicit real revision stack in v2+.`);
|
|
3152
3469
|
}
|
|
3153
3470
|
realStack = buildStackedScene(range.repoRoot, {
|
|
3154
3471
|
...authoredScene,
|
|
3155
3472
|
kind: "stacked-diff",
|
|
3473
|
+
overview: compileSceneOverview(authoredScene.overview, false),
|
|
3156
3474
|
stack: authoredScene.stack,
|
|
3157
3475
|
files: scene.files.map((file) => file.path),
|
|
3158
3476
|
steps: []
|
|
@@ -3185,6 +3503,9 @@ function buildChangeTourManifest(startPath, options = {}) {
|
|
|
3185
3503
|
return parseChangeTourManifest(manifest);
|
|
3186
3504
|
}
|
|
3187
3505
|
function applySource(source, defaultScenes, repoRoot) {
|
|
3506
|
+
return applySourceChapters(source, source.chapters, defaultScenes, repoRoot);
|
|
3507
|
+
}
|
|
3508
|
+
function applySourceChapters(source, chaptersToCompile, defaultScenes, repoRoot) {
|
|
3188
3509
|
var _a, _b;
|
|
3189
3510
|
const available = new Map(defaultScenes.map((scene) => [scene.path, scene]));
|
|
3190
3511
|
const resolvedAnchors = /* @__PURE__ */ new Map();
|
|
@@ -3206,7 +3527,7 @@ function applySource(source, defaultScenes, repoRoot) {
|
|
|
3206
3527
|
const chapters = [];
|
|
3207
3528
|
const finalScenes = [];
|
|
3208
3529
|
const finalChapters = [];
|
|
3209
|
-
for (const chapter of
|
|
3530
|
+
for (const chapter of chaptersToCompile) {
|
|
3210
3531
|
const sceneIds = [];
|
|
3211
3532
|
const finalSceneIds = [];
|
|
3212
3533
|
for (const authoredScene of chapter.scenes) {
|
|
@@ -3260,7 +3581,60 @@ function applySource(source, defaultScenes, repoRoot) {
|
|
|
3260
3581
|
}
|
|
3261
3582
|
return { scenes, chapters, finalScenes, finalChapters };
|
|
3262
3583
|
}
|
|
3584
|
+
function buildAuthoredModeTours(source, root, defaultScenes, repoRoot, mergeBaseOid, headOid) {
|
|
3585
|
+
var _a, _b;
|
|
3586
|
+
const tours = {};
|
|
3587
|
+
if ((_a = source.tours) == null ? void 0 : _a.historical) {
|
|
3588
|
+
const compiled = applySourceChapters(source, source.tours.historical.chapters, defaultScenes, repoRoot);
|
|
3589
|
+
validateHistoricalStackEndpoints(compiled.scenes, mergeBaseOid, headOid);
|
|
3590
|
+
tours.historical = { chapters: compiled.chapters, scenes: compiled.scenes };
|
|
3591
|
+
} else if (source.version === 3) {
|
|
3592
|
+
const fallback = buildHistoricalFallback(source.chapters, root);
|
|
3593
|
+
if (fallback) tours.historical = fallback;
|
|
3594
|
+
}
|
|
3595
|
+
if ((_b = source.tours) == null ? void 0 : _b.deconstructed) {
|
|
3596
|
+
const compiled = applySourceChapters(source, source.tours.deconstructed.chapters, defaultScenes, repoRoot);
|
|
3597
|
+
tours.deconstructed = { chapters: compiled.chapters, scenes: compiled.scenes };
|
|
3598
|
+
} else if (source.version === 3 && source.chapters.some((chapter) => chapter.scenes.some((scene) => scene.kind === "deconstructed-diff"))) {
|
|
3599
|
+
tours.deconstructed = { chapters: root.chapters, scenes: root.scenes };
|
|
3600
|
+
}
|
|
3601
|
+
return tours.historical || tours.deconstructed ? tours : void 0;
|
|
3602
|
+
}
|
|
3603
|
+
function validateHistoricalStackEndpoints(scenes, mergeBaseOid, headOid) {
|
|
3604
|
+
for (const scene of scenes) {
|
|
3605
|
+
if (scene.kind !== "stacked-diff") continue;
|
|
3606
|
+
if (scene.stack[0].oid !== mergeBaseOid || scene.stack[scene.stack.length - 1].oid !== headOid) {
|
|
3607
|
+
throw new Error(`Historical scene ${scene.id} real stack endpoints must match the tour's final diff range.`);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
function buildHistoricalFallback(sourceChapters, root) {
|
|
3612
|
+
const regularScenes = new Map(root.scenes.map((scene) => [scene.id, scene]));
|
|
3613
|
+
const finalScenes = new Map(root.finalScenes.map((scene) => [scene.id, scene]));
|
|
3614
|
+
const scenes = [];
|
|
3615
|
+
const chapters = [];
|
|
3616
|
+
for (const chapter of sourceChapters) {
|
|
3617
|
+
const sceneIds = [];
|
|
3618
|
+
for (const authoredScene of chapter.scenes) {
|
|
3619
|
+
const scene = authoredScene.kind === "deconstructed-diff" ? authoredScene.steps ? finalScenes.get(authoredScene.id) : void 0 : regularScenes.get(authoredScene.id);
|
|
3620
|
+
if (!scene) continue;
|
|
3621
|
+
scenes.push(scene);
|
|
3622
|
+
sceneIds.push(scene.id);
|
|
3623
|
+
}
|
|
3624
|
+
if (sceneIds.length > 0) chapters.push({ id: chapter.id, title: chapter.title, sceneIds });
|
|
3625
|
+
}
|
|
3626
|
+
return scenes.length > 0 ? { chapters, scenes } : void 0;
|
|
3627
|
+
}
|
|
3628
|
+
function compileSceneOverview(overview, includeComparison = true) {
|
|
3629
|
+
if (!overview) return void 0;
|
|
3630
|
+
return {
|
|
3631
|
+
kind: overview.kind,
|
|
3632
|
+
...overview.path === void 0 ? {} : { path: overview.path },
|
|
3633
|
+
...includeComparison && overview.comparison ? { comparison: { ...overview.comparison } } : {}
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3263
3636
|
function buildWalkthroughManifestScene(authoredScene, available, resolvedAnchors, connections) {
|
|
3637
|
+
const overview = compileSceneOverview(authoredScene.overview, false);
|
|
3264
3638
|
return {
|
|
3265
3639
|
id: authoredScene.id,
|
|
3266
3640
|
kind: "walkthrough",
|
|
@@ -3269,6 +3643,7 @@ function buildWalkthroughManifestScene(authoredScene, available, resolvedAnchors
|
|
|
3269
3643
|
bullets: authoredScene.bullets,
|
|
3270
3644
|
tags: authoredScene.tags,
|
|
3271
3645
|
takeaway: authoredScene.takeaway,
|
|
3646
|
+
...overview ? { overview } : {},
|
|
3272
3647
|
steps: authoredScene.steps.map((step) => {
|
|
3273
3648
|
const focus = requireResolvedAnchor(resolvedAnchors, step.focus);
|
|
3274
3649
|
const diff2 = available.get(focus.path);
|
|
@@ -3343,6 +3718,7 @@ function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef, a
|
|
|
3343
3718
|
});
|
|
3344
3719
|
if (files.length === 0) throw new Error(`Deconstructed scene ${source.id} has no staged files.`);
|
|
3345
3720
|
const steps = buildDeconstructedFocusSteps(source.id, compiled.stages, files);
|
|
3721
|
+
const overview = compileSceneOverview(source.overview);
|
|
3346
3722
|
return {
|
|
3347
3723
|
id: source.id,
|
|
3348
3724
|
kind: "deconstructed-diff",
|
|
@@ -3351,6 +3727,7 @@ function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef, a
|
|
|
3351
3727
|
bullets: source.bullets,
|
|
3352
3728
|
tags: source.tags,
|
|
3353
3729
|
takeaway: source.takeaway,
|
|
3730
|
+
...overview ? { overview } : {},
|
|
3354
3731
|
stageLabel: "Explanation stages",
|
|
3355
3732
|
realRange: {
|
|
3356
3733
|
baseRef: baseRef || compiled.baseOid,
|
|
@@ -3492,6 +3869,7 @@ function buildStackedScene(repoRoot, source, availableFilePaths) {
|
|
|
3492
3869
|
});
|
|
3493
3870
|
if (files.length === 0) throw new Error(`Stacked scene ${source.id} has no materializable text files.`);
|
|
3494
3871
|
const stackIds = stack.map((entry) => entry.id);
|
|
3872
|
+
const overview = compileSceneOverview(source.overview);
|
|
3495
3873
|
return {
|
|
3496
3874
|
id: source.id,
|
|
3497
3875
|
kind: "stacked-diff",
|
|
@@ -3500,6 +3878,7 @@ function buildStackedScene(repoRoot, source, availableFilePaths) {
|
|
|
3500
3878
|
bullets: source.bullets,
|
|
3501
3879
|
tags: source.tags,
|
|
3502
3880
|
takeaway: source.takeaway,
|
|
3881
|
+
...overview ? { overview } : {},
|
|
3503
3882
|
stack,
|
|
3504
3883
|
files,
|
|
3505
3884
|
steps: source.steps.length === 0 ? stack.slice(0, -1).map((_, pairIndex) => ({
|