@davmash/bygone 0.8.11 → 0.8.13

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.
@@ -4077,16 +4077,41 @@ var require_changeTour = __commonJS({
4077
4077
  }
4078
4078
  validateMultiPanelFiles(value.files, panels.length, index);
4079
4079
  validateMultiPanelSteps(value.steps, panels.length, index, true);
4080
- if (Array.isArray(value.steps) && value.steps.length !== panels.length - 1) {
4081
- throw new Error(`scenes[${index}].steps must match the number of explanation stages.`);
4082
- }
4083
4080
  if (Array.isArray(value.steps)) {
4081
+ let previousPairIndex = -1;
4084
4082
  value.steps.forEach((step, stepIndex) => {
4085
- const panel = panels[stepIndex + 1];
4086
- if (!isRecord2(step) || !isRecord2(panel) || panel.stageId !== step.id) {
4087
- throw new Error(`scenes[${index}].panels must align with explanation stage ids.`);
4083
+ if (!isRecord2(step)) return;
4084
+ const pairIndex = Number(step.pairIndex);
4085
+ if (pairIndex < previousPairIndex) {
4086
+ throw new Error(`scenes[${index}].steps must remain grouped in explanation stage order.`);
4087
+ }
4088
+ previousPairIndex = pairIndex;
4089
+ if (step.stageId !== void 0) requireString(step.stageId, `scenes[${index}].steps[${stepIndex}].stageId`);
4090
+ if (step.stageIndex !== void 0) requireNonNegativeInteger(step.stageIndex, `scenes[${index}].steps[${stepIndex}].stageIndex`);
4091
+ if (step.focusIndex !== void 0) requireNonNegativeInteger(step.focusIndex, `scenes[${index}].steps[${stepIndex}].focusIndex`);
4092
+ if (step.focusCount !== void 0) requirePositiveInteger(step.focusCount, `scenes[${index}].steps[${stepIndex}].focusCount`);
4093
+ if (step.stageIndex !== void 0 && Number(step.stageIndex) !== pairIndex) {
4094
+ throw new Error(`scenes[${index}].steps[${stepIndex}].stageIndex must match pairIndex.`);
4095
+ }
4096
+ if (step.focusIndex !== void 0 && step.focusCount !== void 0 && Number(step.focusIndex) >= Number(step.focusCount)) {
4097
+ throw new Error(`scenes[${index}].steps[${stepIndex}].focusIndex must be less than focusCount.`);
4088
4098
  }
4089
4099
  });
4100
+ for (let stageIndex = 0; stageIndex < panels.length - 1; stageIndex += 1) {
4101
+ const panel = panels[stageIndex + 1];
4102
+ const stageSteps = value.steps.filter((step) => isRecord2(step) && Number(step.pairIndex) === stageIndex);
4103
+ if (!isRecord2(panel) || stageSteps.length === 0) {
4104
+ throw new Error(`scenes[${index}].steps must cover every explanation stage.`);
4105
+ }
4106
+ const firstStep = stageSteps[0];
4107
+ const fallbackStageId = isRecord2(firstStep) ? firstStep.id : void 0;
4108
+ if (stageSteps.some((step) => {
4109
+ if (!isRecord2(step)) return true;
4110
+ return (step.stageId ?? fallbackStageId) !== panel.stageId;
4111
+ })) {
4112
+ throw new Error(`scenes[${index}].panels must align with explanation stage ids.`);
4113
+ }
4114
+ }
4090
4115
  }
4091
4116
  }
4092
4117
  function validateMultiPanelFiles(value, panelCount, index) {
@@ -4115,7 +4140,6 @@ var require_changeTour = __commonJS({
4115
4140
  if (deconstructed) {
4116
4141
  requireStringArray(step.introducedHunks, `scenes[${index}].steps[${stepIndex}].introducedHunks`);
4117
4142
  if (step.introducedHunks.length === 0) throw new Error(`scenes[${index}].steps[${stepIndex}].introducedHunks must be non-empty.`);
4118
- if (Number(step.pairIndex) !== stepIndex) throw new Error(`scenes[${index}].steps[${stepIndex}].pairIndex must match its explanation stage.`);
4119
4143
  }
4120
4144
  if (step.side !== "left" && step.side !== "right") throw new Error(`scenes[${index}].steps[${stepIndex}].side must be left or right.`);
4121
4145
  if (step.startLine !== void 0) requirePositiveInteger(step.startLine, `scenes[${index}].steps[${stepIndex}].startLine`);
@@ -4755,6 +4779,7 @@ var require_changeTour = __commonJS({
4755
4779
  for (const { stage, claims } of stageClaims) {
4756
4780
  const introducedHunks = [];
4757
4781
  const introducedFiles = [];
4782
+ const introducedChanges = [];
4758
4783
  for (const claim of claims) {
4759
4784
  const cumulative = cumulativeByFile.get(claim.file) || /* @__PURE__ */ new Set();
4760
4785
  claim.ids.forEach((id) => {
@@ -4763,6 +4788,12 @@ var require_changeTour = __commonJS({
4763
4788
  });
4764
4789
  cumulativeByFile.set(claim.file, cumulative);
4765
4790
  if (!introducedFiles.includes(claim.file.path)) introducedFiles.push(claim.file.path);
4791
+ const introducedChange = introducedChanges.find((change) => change.file === claim.file.path);
4792
+ if (introducedChange) {
4793
+ introducedChange.hunks.push(...claim.ids);
4794
+ } else {
4795
+ introducedChanges.push({ file: claim.file.path, hunks: [...claim.ids] });
4796
+ }
4766
4797
  }
4767
4798
  stages.push({
4768
4799
  id: stage.id,
@@ -4770,6 +4801,7 @@ var require_changeTour = __commonJS({
4770
4801
  narration: stage.narration,
4771
4802
  introducedHunks,
4772
4803
  introducedFiles,
4804
+ introducedChanges,
4773
4805
  cumulativeHunks: [...cumulativeByFile.values()].flatMap((ids) => [...ids]),
4774
4806
  files: materializableFiles.map((file) => buildFileState(
4775
4807
  file,
@@ -4834,6 +4866,1195 @@ var require_changeTour = __commonJS({
4834
4866
  appliedHunks: [...selected]
4835
4867
  };
4836
4868
  }
4869
+ function Diff2() {
4870
+ }
4871
+ Diff2.prototype = {
4872
+ diff: function diff2(oldString, newString) {
4873
+ var _options$timeout;
4874
+ var options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
4875
+ var callback = options.callback;
4876
+ if (typeof options === "function") {
4877
+ callback = options;
4878
+ options = {};
4879
+ }
4880
+ this.options = options;
4881
+ var self2 = this;
4882
+ function done(value) {
4883
+ if (callback) {
4884
+ setTimeout(function() {
4885
+ callback(void 0, value);
4886
+ }, 0);
4887
+ return true;
4888
+ } else {
4889
+ return value;
4890
+ }
4891
+ }
4892
+ oldString = this.castInput(oldString);
4893
+ newString = this.castInput(newString);
4894
+ oldString = this.removeEmpty(this.tokenize(oldString));
4895
+ newString = this.removeEmpty(this.tokenize(newString));
4896
+ var newLen = newString.length, oldLen = oldString.length;
4897
+ var editLength = 1;
4898
+ var maxEditLength = newLen + oldLen;
4899
+ if (options.maxEditLength) {
4900
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
4901
+ }
4902
+ var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity;
4903
+ var abortAfterTimestamp = Date.now() + maxExecutionTime;
4904
+ var bestPath = [{
4905
+ oldPos: -1,
4906
+ lastComponent: void 0
4907
+ }];
4908
+ var newPos = this.extractCommon(bestPath[0], newString, oldString, 0);
4909
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
4910
+ return done([{
4911
+ value: this.join(newString),
4912
+ count: newString.length
4913
+ }]);
4914
+ }
4915
+ var minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
4916
+ function execEditLength() {
4917
+ for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
4918
+ var basePath = void 0;
4919
+ var removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
4920
+ if (removePath) {
4921
+ bestPath[diagonalPath - 1] = void 0;
4922
+ }
4923
+ var canAdd = false;
4924
+ if (addPath) {
4925
+ var addPathNewPos = addPath.oldPos - diagonalPath;
4926
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
4927
+ }
4928
+ var canRemove = removePath && removePath.oldPos + 1 < oldLen;
4929
+ if (!canAdd && !canRemove) {
4930
+ bestPath[diagonalPath] = void 0;
4931
+ continue;
4932
+ }
4933
+ if (!canRemove || canAdd && removePath.oldPos + 1 < addPath.oldPos) {
4934
+ basePath = self2.addToPath(addPath, true, void 0, 0);
4935
+ } else {
4936
+ basePath = self2.addToPath(removePath, void 0, true, 1);
4937
+ }
4938
+ newPos = self2.extractCommon(basePath, newString, oldString, diagonalPath);
4939
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
4940
+ return done(buildValues2(self2, basePath.lastComponent, newString, oldString, self2.useLongestToken));
4941
+ } else {
4942
+ bestPath[diagonalPath] = basePath;
4943
+ if (basePath.oldPos + 1 >= oldLen) {
4944
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
4945
+ }
4946
+ if (newPos + 1 >= newLen) {
4947
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
4948
+ }
4949
+ }
4950
+ }
4951
+ editLength++;
4952
+ }
4953
+ if (callback) {
4954
+ (function exec() {
4955
+ setTimeout(function() {
4956
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
4957
+ return callback();
4958
+ }
4959
+ if (!execEditLength()) {
4960
+ exec();
4961
+ }
4962
+ }, 0);
4963
+ })();
4964
+ } else {
4965
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
4966
+ var ret = execEditLength();
4967
+ if (ret) {
4968
+ return ret;
4969
+ }
4970
+ }
4971
+ }
4972
+ },
4973
+ addToPath: function addToPath2(path9, added, removed, oldPosInc) {
4974
+ var last = path9.lastComponent;
4975
+ if (last && last.added === added && last.removed === removed) {
4976
+ return {
4977
+ oldPos: path9.oldPos + oldPosInc,
4978
+ lastComponent: {
4979
+ count: last.count + 1,
4980
+ added,
4981
+ removed,
4982
+ previousComponent: last.previousComponent
4983
+ }
4984
+ };
4985
+ } else {
4986
+ return {
4987
+ oldPos: path9.oldPos + oldPosInc,
4988
+ lastComponent: {
4989
+ count: 1,
4990
+ added,
4991
+ removed,
4992
+ previousComponent: last
4993
+ }
4994
+ };
4995
+ }
4996
+ },
4997
+ extractCommon: function extractCommon2(basePath, newString, oldString, diagonalPath) {
4998
+ var newLen = newString.length, oldLen = oldString.length, oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
4999
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
5000
+ newPos++;
5001
+ oldPos++;
5002
+ commonCount++;
5003
+ }
5004
+ if (commonCount) {
5005
+ basePath.lastComponent = {
5006
+ count: commonCount,
5007
+ previousComponent: basePath.lastComponent
5008
+ };
5009
+ }
5010
+ basePath.oldPos = oldPos;
5011
+ return newPos;
5012
+ },
5013
+ equals: function equals2(left, right) {
5014
+ if (this.options.comparator) {
5015
+ return this.options.comparator(left, right);
5016
+ } else {
5017
+ return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
5018
+ }
5019
+ },
5020
+ removeEmpty: function removeEmpty2(array) {
5021
+ var ret = [];
5022
+ for (var i = 0; i < array.length; i++) {
5023
+ if (array[i]) {
5024
+ ret.push(array[i]);
5025
+ }
5026
+ }
5027
+ return ret;
5028
+ },
5029
+ castInput: function castInput2(value) {
5030
+ return value;
5031
+ },
5032
+ tokenize: function tokenize2(value) {
5033
+ return value.split("");
5034
+ },
5035
+ join: function join4(chars) {
5036
+ return chars.join("");
5037
+ }
5038
+ };
5039
+ function buildValues2(diff2, lastComponent, newString, oldString, useLongestToken) {
5040
+ var components = [];
5041
+ var nextComponent;
5042
+ while (lastComponent) {
5043
+ components.push(lastComponent);
5044
+ nextComponent = lastComponent.previousComponent;
5045
+ delete lastComponent.previousComponent;
5046
+ lastComponent = nextComponent;
5047
+ }
5048
+ components.reverse();
5049
+ var componentPos = 0, componentLen = components.length, newPos = 0, oldPos = 0;
5050
+ for (; componentPos < componentLen; componentPos++) {
5051
+ var component = components[componentPos];
5052
+ if (!component.removed) {
5053
+ if (!component.added && useLongestToken) {
5054
+ var value = newString.slice(newPos, newPos + component.count);
5055
+ value = value.map(function(value2, i) {
5056
+ var oldValue = oldString[oldPos + i];
5057
+ return oldValue.length > value2.length ? oldValue : value2;
5058
+ });
5059
+ component.value = diff2.join(value);
5060
+ } else {
5061
+ component.value = diff2.join(newString.slice(newPos, newPos + component.count));
5062
+ }
5063
+ newPos += component.count;
5064
+ if (!component.added) {
5065
+ oldPos += component.count;
5066
+ }
5067
+ } else {
5068
+ component.value = diff2.join(oldString.slice(oldPos, oldPos + component.count));
5069
+ oldPos += component.count;
5070
+ if (componentPos && components[componentPos - 1].added) {
5071
+ var tmp = components[componentPos - 1];
5072
+ components[componentPos - 1] = components[componentPos];
5073
+ components[componentPos] = tmp;
5074
+ }
5075
+ }
5076
+ }
5077
+ var finalComponent = components[componentLen - 1];
5078
+ if (componentLen > 1 && typeof finalComponent.value === "string" && (finalComponent.added || finalComponent.removed) && diff2.equals("", finalComponent.value)) {
5079
+ components[componentLen - 2].value += finalComponent.value;
5080
+ components.pop();
5081
+ }
5082
+ return components;
5083
+ }
5084
+ var characterDiff2 = new Diff2();
5085
+ function diffChars2(oldStr, newStr, options) {
5086
+ return characterDiff2.diff(oldStr, newStr, options);
5087
+ }
5088
+ var extendedWordChars2 = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/;
5089
+ var reWhitespace2 = /\S/;
5090
+ var wordDiff2 = new Diff2();
5091
+ wordDiff2.equals = function(left, right) {
5092
+ if (this.options.ignoreCase) {
5093
+ left = left.toLowerCase();
5094
+ right = right.toLowerCase();
5095
+ }
5096
+ return left === right || this.options.ignoreWhitespace && !reWhitespace2.test(left) && !reWhitespace2.test(right);
5097
+ };
5098
+ wordDiff2.tokenize = function(value) {
5099
+ var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
5100
+ for (var i = 0; i < tokens.length - 1; i++) {
5101
+ if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars2.test(tokens[i]) && extendedWordChars2.test(tokens[i + 2])) {
5102
+ tokens[i] += tokens[i + 2];
5103
+ tokens.splice(i + 1, 2);
5104
+ i--;
5105
+ }
5106
+ }
5107
+ return tokens;
5108
+ };
5109
+ function diffWordsWithSpace2(oldStr, newStr, options) {
5110
+ return wordDiff2.diff(oldStr, newStr, options);
5111
+ }
5112
+ var lineDiff2 = new Diff2();
5113
+ lineDiff2.tokenize = function(value) {
5114
+ if (this.options.stripTrailingCr) {
5115
+ value = value.replace(/\r\n/g, "\n");
5116
+ }
5117
+ var retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
5118
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
5119
+ linesAndNewlines.pop();
5120
+ }
5121
+ for (var i = 0; i < linesAndNewlines.length; i++) {
5122
+ var line = linesAndNewlines[i];
5123
+ if (i % 2 && !this.options.newlineIsToken) {
5124
+ retLines[retLines.length - 1] += line;
5125
+ } else {
5126
+ if (this.options.ignoreWhitespace) {
5127
+ line = line.trim();
5128
+ }
5129
+ retLines.push(line);
5130
+ }
5131
+ }
5132
+ return retLines;
5133
+ };
5134
+ var sentenceDiff2 = new Diff2();
5135
+ sentenceDiff2.tokenize = function(value) {
5136
+ return value.split(/(\S.+?[.!?])(?=\s+|$)/);
5137
+ };
5138
+ var cssDiff2 = new Diff2();
5139
+ cssDiff2.tokenize = function(value) {
5140
+ return value.split(/([{}:;,]|\s+)/);
5141
+ };
5142
+ function _typeof2(obj) {
5143
+ "@babel/helpers - typeof";
5144
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
5145
+ _typeof2 = function(obj2) {
5146
+ return typeof obj2;
5147
+ };
5148
+ } else {
5149
+ _typeof2 = function(obj2) {
5150
+ return obj2 && typeof Symbol === "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
5151
+ };
5152
+ }
5153
+ return _typeof2(obj);
5154
+ }
5155
+ var objectPrototypeToString2 = Object.prototype.toString;
5156
+ var jsonDiff2 = new Diff2();
5157
+ jsonDiff2.useLongestToken = true;
5158
+ jsonDiff2.tokenize = lineDiff2.tokenize;
5159
+ jsonDiff2.castInput = function(value) {
5160
+ var _this$options = this.options, undefinedReplacement = _this$options.undefinedReplacement, _this$options$stringi = _this$options.stringifyReplacer, stringifyReplacer = _this$options$stringi === void 0 ? function(k, v) {
5161
+ return typeof v === "undefined" ? undefinedReplacement : v;
5162
+ } : _this$options$stringi;
5163
+ return typeof value === "string" ? value : JSON.stringify(canonicalize2(value, null, null, stringifyReplacer), stringifyReplacer, " ");
5164
+ };
5165
+ jsonDiff2.equals = function(left, right) {
5166
+ return Diff2.prototype.equals.call(jsonDiff2, left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"));
5167
+ };
5168
+ function canonicalize2(obj, stack, replacementStack, replacer, key) {
5169
+ stack = stack || [];
5170
+ replacementStack = replacementStack || [];
5171
+ if (replacer) {
5172
+ obj = replacer(key, obj);
5173
+ }
5174
+ var i;
5175
+ for (i = 0; i < stack.length; i += 1) {
5176
+ if (stack[i] === obj) {
5177
+ return replacementStack[i];
5178
+ }
5179
+ }
5180
+ var canonicalizedObj;
5181
+ if ("[object Array]" === objectPrototypeToString2.call(obj)) {
5182
+ stack.push(obj);
5183
+ canonicalizedObj = new Array(obj.length);
5184
+ replacementStack.push(canonicalizedObj);
5185
+ for (i = 0; i < obj.length; i += 1) {
5186
+ canonicalizedObj[i] = canonicalize2(obj[i], stack, replacementStack, replacer, key);
5187
+ }
5188
+ stack.pop();
5189
+ replacementStack.pop();
5190
+ return canonicalizedObj;
5191
+ }
5192
+ if (obj && obj.toJSON) {
5193
+ obj = obj.toJSON();
5194
+ }
5195
+ if (_typeof2(obj) === "object" && obj !== null) {
5196
+ stack.push(obj);
5197
+ canonicalizedObj = {};
5198
+ replacementStack.push(canonicalizedObj);
5199
+ var sortedKeys = [], _key;
5200
+ for (_key in obj) {
5201
+ if (obj.hasOwnProperty(_key)) {
5202
+ sortedKeys.push(_key);
5203
+ }
5204
+ }
5205
+ sortedKeys.sort();
5206
+ for (i = 0; i < sortedKeys.length; i += 1) {
5207
+ _key = sortedKeys[i];
5208
+ canonicalizedObj[_key] = canonicalize2(obj[_key], stack, replacementStack, replacer, _key);
5209
+ }
5210
+ stack.pop();
5211
+ replacementStack.pop();
5212
+ } else {
5213
+ canonicalizedObj = obj;
5214
+ }
5215
+ return canonicalizedObj;
5216
+ }
5217
+ var arrayDiff2 = new Diff2();
5218
+ arrayDiff2.tokenize = function(value) {
5219
+ return value.slice();
5220
+ };
5221
+ arrayDiff2.join = arrayDiff2.removeEmpty = function(value) {
5222
+ return value;
5223
+ };
5224
+ function diffArrays2(oldArr, newArr, callback) {
5225
+ return arrayDiff2.diff(oldArr, newArr, callback);
5226
+ }
5227
+ var DEFAULT_MAX_INLINE_HIGHLIGHT_LINE_LENGTH2 = 500;
5228
+ var DEFAULT_DIFF_TIMEOUT_MS2 = 100;
5229
+ var MAX_INLINE_HIGHLIGHT_LINE_LENGTH2 = readPositiveIntegerEnv4(
5230
+ "BYGONE_MAX_INLINE_HIGHLIGHT_LINE_LENGTH",
5231
+ DEFAULT_MAX_INLINE_HIGHLIGHT_LINE_LENGTH2
5232
+ );
5233
+ var DIFF_TIMEOUT_MS2 = readPositiveIntegerEnv4("BYGONE_DIFF_TIMEOUT_MS", DEFAULT_DIFF_TIMEOUT_MS2);
5234
+ function buildTwoWayDiffModel3(leftContent, rightContent, options = {}) {
5235
+ const leftLines = normalizeLines2(leftContent);
5236
+ const rightLines = normalizeLines2(rightContent);
5237
+ const changes = diffArrays2(leftLines, rightLines, {
5238
+ timeout: options.timeoutMs ?? DIFF_TIMEOUT_MS2
5239
+ });
5240
+ if (!changes) {
5241
+ return buildConservativeTwoWayDiffModel2(leftLines, rightLines);
5242
+ }
5243
+ const rows = [];
5244
+ const renderedLeftLines = [];
5245
+ const renderedRightLines = [];
5246
+ const blocks = [];
5247
+ let leftLineNumber = 1;
5248
+ let rightLineNumber = 1;
5249
+ for (let index = 0; index < changes.length; index++) {
5250
+ const change = changes[index];
5251
+ const removedLines = change.removed ? change.value : [];
5252
+ const addedLines = change.added ? change.value : [];
5253
+ if (!change.added && !change.removed) {
5254
+ for (const line of change.value) {
5255
+ renderedLeftLines.push(makeDiffLine2("context", line, leftLineNumber));
5256
+ renderedRightLines.push(makeDiffLine2("context", line, rightLineNumber));
5257
+ rows.push(makeDiffRow2(
5258
+ makeDiffCell2("context", line, leftLineNumber++),
5259
+ makeDiffCell2("context", line, rightLineNumber++)
5260
+ ));
5261
+ }
5262
+ continue;
5263
+ }
5264
+ if (change.removed && index + 1 < changes.length && changes[index + 1].added) {
5265
+ const nextChange = changes[index + 1];
5266
+ const leftStart = renderedLeftLines.length;
5267
+ const rightStart = renderedRightLines.length;
5268
+ const alignedLines = alignReplacementLines2(removedLines, nextChange.value);
5269
+ for (const { left: removedLine, right: addedLine } of alignedLines) {
5270
+ let renderedLeftLine;
5271
+ let renderedRightLine;
5272
+ if (removedLine !== void 0) {
5273
+ renderedLeftLine = makeDiffLine2("removed", removedLine, leftLineNumber);
5274
+ renderedLeftLines.push(renderedLeftLine);
5275
+ }
5276
+ if (addedLine !== void 0) {
5277
+ renderedRightLine = makeDiffLine2("added", addedLine, rightLineNumber);
5278
+ renderedRightLines.push(renderedRightLine);
5279
+ }
5280
+ if (renderedLeftLine && renderedRightLine) {
5281
+ applyInlineHighlightPair2(renderedLeftLine, renderedRightLine);
5282
+ }
5283
+ rows.push(makeDiffRow2(
5284
+ removedLine === void 0 ? makePlaceholder2() : makeDiffCell2("removed", removedLine, leftLineNumber++),
5285
+ addedLine === void 0 ? makePlaceholder2() : makeDiffCell2("added", addedLine, rightLineNumber++)
5286
+ ));
5287
+ }
5288
+ blocks.push(makeDiffBlock2("replace", leftStart, renderedLeftLines.length, rightStart, renderedRightLines.length));
5289
+ index++;
5290
+ continue;
5291
+ }
5292
+ if (change.removed) {
5293
+ const leftStart = renderedLeftLines.length;
5294
+ const rightStart = renderedRightLines.length;
5295
+ for (const line of removedLines) {
5296
+ renderedLeftLines.push(makeDiffLine2("removed", line, leftLineNumber));
5297
+ rows.push(makeDiffRow2(
5298
+ makeDiffCell2("removed", line, leftLineNumber++),
5299
+ makePlaceholder2()
5300
+ ));
5301
+ }
5302
+ blocks.push(makeDiffBlock2("delete", leftStart, renderedLeftLines.length, rightStart, renderedRightLines.length));
5303
+ continue;
5304
+ }
5305
+ if (change.added) {
5306
+ const leftStart = renderedLeftLines.length;
5307
+ const rightStart = renderedRightLines.length;
5308
+ for (const line of addedLines) {
5309
+ renderedRightLines.push(makeDiffLine2("added", line, rightLineNumber));
5310
+ rows.push(makeDiffRow2(
5311
+ makePlaceholder2(),
5312
+ makeDiffCell2("added", line, rightLineNumber++)
5313
+ ));
5314
+ }
5315
+ blocks.push(makeDiffBlock2("insert", leftStart, renderedLeftLines.length, rightStart, renderedRightLines.length));
5316
+ }
5317
+ }
5318
+ const hasChanges = rows.some((row) => row.left.kind !== "context" || row.right.kind !== "context");
5319
+ return {
5320
+ rows,
5321
+ leftLines: renderedLeftLines,
5322
+ rightLines: renderedRightLines,
5323
+ blocks,
5324
+ hasChanges
5325
+ };
5326
+ }
5327
+ function buildConservativeTwoWayDiffModel2(leftLines, rightLines) {
5328
+ const renderedLeftLines = leftLines.map((line, index) => makeDiffLine2("removed", line, index + 1));
5329
+ const renderedRightLines = rightLines.map((line, index) => makeDiffLine2("added", line, index + 1));
5330
+ const rows = [
5331
+ ...leftLines.map((line, index) => makeDiffRow2(
5332
+ makeDiffCell2("removed", line, index + 1),
5333
+ makePlaceholder2()
5334
+ )),
5335
+ ...rightLines.map((line, index) => makeDiffRow2(
5336
+ makePlaceholder2(),
5337
+ makeDiffCell2("added", line, index + 1)
5338
+ ))
5339
+ ];
5340
+ let blockKind = "replace";
5341
+ if (leftLines.length === 0) {
5342
+ blockKind = "insert";
5343
+ } else if (rightLines.length === 0) {
5344
+ blockKind = "delete";
5345
+ }
5346
+ return {
5347
+ rows,
5348
+ leftLines: renderedLeftLines,
5349
+ rightLines: renderedRightLines,
5350
+ blocks: rows.length === 0 ? [] : [makeDiffBlock2(blockKind, 0, leftLines.length, 0, rightLines.length)],
5351
+ hasChanges: rows.length > 0
5352
+ };
5353
+ }
5354
+ function normalizeLines2(content) {
5355
+ if (content.length === 0) {
5356
+ return [];
5357
+ }
5358
+ const lines = content.replace(/\r\n/g, "\n").split("\n");
5359
+ if (lines[lines.length - 1] === "") {
5360
+ lines.pop();
5361
+ }
5362
+ return lines;
5363
+ }
5364
+ var MAX_ALIGNMENT_CELLS2 = 1e4;
5365
+ var MAX_SCORING_LINE_LENGTH2 = 2e3;
5366
+ var MINIMUM_MATCH_SCORE2 = 0.52;
5367
+ var MINIMUM_SINGLE_PAIR_SCORE2 = 0.35;
5368
+ var MINIMUM_CONTEXTUAL_MATCH_SCORE2 = 0.38;
5369
+ var MINIMUM_CONTEXTUAL_RUN_SCORE2 = 0.33;
5370
+ var HIGH_CONFIDENCE_MATCH_SCORE2 = 0.86;
5371
+ var AMBIGUITY_MARGIN2 = 0.08;
5372
+ var CONTEXTUAL_AMBIGUITY_MARGIN2 = 0.05;
5373
+ var GAP_PENALTY2 = -0.12;
5374
+ var MAX_BOUNDED_CANDIDATES2 = 5e3;
5375
+ var MAX_LARGE_HUNK_ANCHORS2 = 2e3;
5376
+ var MAX_RARE_TOKEN_OCCURRENCES2 = 4;
5377
+ var MAX_CONTEXTUAL_HUNK_LINES2 = 8;
5378
+ var MAX_CONTEXTUAL_LENGTH_DELTA2 = 4;
5379
+ function alignReplacementLines2(leftLines, rightLines) {
5380
+ if (leftLines.length === 0) {
5381
+ return rightLines.map((right) => ({ right }));
5382
+ }
5383
+ if (rightLines.length === 0) {
5384
+ return leftLines.map((left) => ({ left }));
5385
+ }
5386
+ if (leftLines.length === 1 && rightLines.length === 1) {
5387
+ const singletonScore = scoreReplacementLinePair2(leftLines[0], rightLines[0]);
5388
+ if ((singletonScore.eligible || singletonScore.score >= MINIMUM_SINGLE_PAIR_SCORE2) && haveCompatibleLineRoles2(leftLines[0], rightLines[0]) && isInformativeLine2(normalizeMatchingContent2(leftLines[0])) && isInformativeLine2(normalizeMatchingContent2(rightLines[0]))) {
5389
+ return [{ left: leftLines[0], right: rightLines[0] }];
5390
+ }
5391
+ return [{ left: leftLines[0] }, { right: rightLines[0] }];
5392
+ }
5393
+ if (leftLines.length * rightLines.length > MAX_ALIGNMENT_CELLS2) {
5394
+ return alignLargeReplacementLines2(leftLines, rightLines);
5395
+ }
5396
+ const pairScores = buildPairScores2(leftLines, rightLines);
5397
+ const scores = Array.from(
5398
+ { length: leftLines.length + 1 },
5399
+ () => new Array(rightLines.length + 1).fill(Number.NEGATIVE_INFINITY)
5400
+ );
5401
+ const moves = Array.from(
5402
+ { length: leftLines.length + 1 },
5403
+ () => new Array(rightLines.length + 1).fill(null)
5404
+ );
5405
+ scores[0][0] = 0;
5406
+ for (let leftIndex2 = 1; leftIndex2 <= leftLines.length; leftIndex2++) {
5407
+ scores[leftIndex2][0] = scores[leftIndex2 - 1][0] + GAP_PENALTY2;
5408
+ moves[leftIndex2][0] = "left";
5409
+ }
5410
+ for (let rightIndex2 = 1; rightIndex2 <= rightLines.length; rightIndex2++) {
5411
+ scores[0][rightIndex2] = scores[0][rightIndex2 - 1] + GAP_PENALTY2;
5412
+ moves[0][rightIndex2] = "right";
5413
+ }
5414
+ for (let leftIndex2 = 1; leftIndex2 <= leftLines.length; leftIndex2++) {
5415
+ for (let rightIndex2 = 1; rightIndex2 <= rightLines.length; rightIndex2++) {
5416
+ const pairScore = pairScores[leftIndex2 - 1][rightIndex2 - 1];
5417
+ const candidates = [
5418
+ { move: "left", score: scores[leftIndex2 - 1][rightIndex2] + GAP_PENALTY2 },
5419
+ { move: "right", score: scores[leftIndex2][rightIndex2 - 1] + GAP_PENALTY2 },
5420
+ {
5421
+ move: "match",
5422
+ score: pairScore === null ? Number.NEGATIVE_INFINITY : scores[leftIndex2 - 1][rightIndex2 - 1] + pairScore
5423
+ }
5424
+ ];
5425
+ const best = candidates.reduce((current, candidate) => candidate.score > current.score ? candidate : current);
5426
+ scores[leftIndex2][rightIndex2] = best.score;
5427
+ moves[leftIndex2][rightIndex2] = best.move;
5428
+ }
5429
+ }
5430
+ const aligned = [];
5431
+ let leftIndex = leftLines.length;
5432
+ let rightIndex = rightLines.length;
5433
+ while (leftIndex > 0 || rightIndex > 0) {
5434
+ const move = moves[leftIndex][rightIndex];
5435
+ if (move === "match") {
5436
+ aligned.push({ left: leftLines[--leftIndex], right: rightLines[--rightIndex] });
5437
+ } else if (move === "left") {
5438
+ aligned.push({ left: leftLines[--leftIndex] });
5439
+ } else {
5440
+ aligned.push({ right: rightLines[--rightIndex] });
5441
+ }
5442
+ }
5443
+ return aligned.reverse();
5444
+ }
5445
+ function scoreReplacementLinePair2(left, right) {
5446
+ const normalizedLeft = normalizeMatchingContent2(left);
5447
+ const normalizedRight = normalizeMatchingContent2(right);
5448
+ const leftInformative = isInformativeLine2(normalizedLeft);
5449
+ const rightInformative = isInformativeLine2(normalizedRight);
5450
+ if (!leftInformative || !rightInformative || normalizedLeft.length > MAX_SCORING_LINE_LENGTH2 || normalizedRight.length > MAX_SCORING_LINE_LENGTH2) {
5451
+ return {
5452
+ score: 0,
5453
+ eligible: false,
5454
+ characterSimilarity: 0,
5455
+ tokenSimilarity: 0,
5456
+ lengthSimilarity: 0
5457
+ };
5458
+ }
5459
+ if (normalizedLeft === normalizedRight) {
5460
+ return {
5461
+ score: 1,
5462
+ eligible: true,
5463
+ characterSimilarity: 1,
5464
+ tokenSimilarity: 1,
5465
+ lengthSimilarity: 1
5466
+ };
5467
+ }
5468
+ const leftTokens = tokenizeMatchingContent2(normalizedLeft);
5469
+ const rightTokens = tokenizeMatchingContent2(normalizedRight);
5470
+ const characterSimilarity = lineSimilarity2(normalizedLeft, normalizedRight);
5471
+ const tokenSimilarity = multisetDiceSimilarity2(leftTokens, rightTokens);
5472
+ const lengthSimilarity = Math.min(normalizedLeft.length, normalizedRight.length) / Math.max(normalizedLeft.length, normalizedRight.length);
5473
+ const boundarySimilarity = tokenBoundarySimilarity2(leftTokens, rightTokens);
5474
+ const score = characterSimilarity * 0.52 + tokenSimilarity * 0.32 + lengthSimilarity * 0.1 + boundarySimilarity * 0.06;
5475
+ const hasContentEvidence = characterSimilarity >= 0.52 || tokenSimilarity >= 0.45 || characterSimilarity >= 0.48 && tokenSimilarity >= 0.28;
5476
+ return {
5477
+ score,
5478
+ eligible: hasContentEvidence && score >= MINIMUM_MATCH_SCORE2,
5479
+ characterSimilarity,
5480
+ tokenSimilarity,
5481
+ lengthSimilarity
5482
+ };
5483
+ }
5484
+ function buildPairScores2(leftLines, rightLines) {
5485
+ const scored = leftLines.map((left) => rightLines.map((right) => scoreReplacementLinePair2(left, right)));
5486
+ const structuralPairs = collectUniqueStructuralPairs2(leftLines, rightLines);
5487
+ const leftRankings = scored.map((row) => rankEligibleScores2(row));
5488
+ const rightRankings = rightLines.map((_right, rightIndex) => rankEligibleScores2(scored.map((row) => row[rightIndex])));
5489
+ const accepted = scored.map((row, leftIndex) => row.map((candidate, rightIndex) => {
5490
+ if (structuralPairs.has(`${leftIndex}:${rightIndex}`)) {
5491
+ return Math.max(candidate.score, 1);
5492
+ }
5493
+ if (!candidate.eligible) {
5494
+ return null;
5495
+ }
5496
+ const leftRanking = leftRankings[leftIndex];
5497
+ const rightRanking = rightRankings[rightIndex];
5498
+ const isBestOnBothSides = candidate.score === leftRanking.best && candidate.score === rightRanking.best;
5499
+ const requiredMargin = candidate.score >= HIGH_CONFIDENCE_MATCH_SCORE2 ? AMBIGUITY_MARGIN2 / 2 : AMBIGUITY_MARGIN2;
5500
+ const hasLeftMargin = leftRanking.best - leftRanking.second >= requiredMargin;
5501
+ const hasRightMargin = rightRanking.best - rightRanking.second >= requiredMargin;
5502
+ return isBestOnBothSides && hasLeftMargin && hasRightMargin ? candidate.score : null;
5503
+ }));
5504
+ if (Math.max(leftLines.length, rightLines.length) > MAX_CONTEXTUAL_HUNK_LINES2 || Math.abs(leftLines.length - rightLines.length) > MAX_CONTEXTUAL_LENGTH_DELTA2) {
5505
+ return accepted;
5506
+ }
5507
+ const isContextualCandidate = (candidate, leftIndex, rightIndex) => candidate.score >= MINIMUM_CONTEXTUAL_MATCH_SCORE2 && haveCompatibleLineRoles2(leftLines[leftIndex], rightLines[rightIndex]) && Math.abs(leftIndex - rightIndex) <= MAX_CONTEXTUAL_LENGTH_DELTA2;
5508
+ const leftContextRankings = scored.map((row, leftIndex) => rankScores2(row, (candidate, rightIndex) => isContextualCandidate(candidate, leftIndex, rightIndex)));
5509
+ const rightContextRankings = rightLines.map((_right, rightIndex) => rankScores2(
5510
+ scored.map((row) => row[rightIndex]),
5511
+ (candidate, leftIndex) => isContextualCandidate(candidate, leftIndex, rightIndex)
5512
+ ));
5513
+ const isWeakRunCandidate = (candidate, leftIndex, rightIndex) => candidate.score >= MINIMUM_CONTEXTUAL_RUN_SCORE2 && haveCompatibleLineRoles2(leftLines[leftIndex], rightLines[rightIndex]) && Math.abs(leftIndex - rightIndex) <= MAX_CONTEXTUAL_LENGTH_DELTA2;
5514
+ const leftWeakRankings = scored.map((row, leftIndex) => rankScores2(row, (candidate, rightIndex) => isWeakRunCandidate(candidate, leftIndex, rightIndex)));
5515
+ const rightWeakRankings = rightLines.map((_right, rightIndex) => rankScores2(
5516
+ scored.map((row) => row[rightIndex]),
5517
+ (candidate, leftIndex) => isWeakRunCandidate(candidate, leftIndex, rightIndex)
5518
+ ));
5519
+ const isUnambiguousContextualCandidate = (leftIndex, rightIndex) => {
5520
+ const candidate = scored[leftIndex][rightIndex];
5521
+ if (!isContextualCandidate(candidate, leftIndex, rightIndex)) {
5522
+ return false;
5523
+ }
5524
+ const leftRanking = leftContextRankings[leftIndex];
5525
+ const rightRanking = rightContextRankings[rightIndex];
5526
+ return candidate.score === leftRanking.best && candidate.score === rightRanking.best && leftRanking.best - leftRanking.second >= CONTEXTUAL_AMBIGUITY_MARGIN2 && rightRanking.best - rightRanking.second >= CONTEXTUAL_AMBIGUITY_MARGIN2;
5527
+ };
5528
+ const isUnambiguousWeakRunAnchor = (leftIndex, rightIndex) => {
5529
+ const candidate = scored[leftIndex][rightIndex];
5530
+ if (!isWeakRunCandidate(candidate, leftIndex, rightIndex)) {
5531
+ return false;
5532
+ }
5533
+ const leftRanking = leftWeakRankings[leftIndex];
5534
+ const rightRanking = rightWeakRankings[rightIndex];
5535
+ return candidate.score === leftRanking.best && candidate.score === rightRanking.best && leftRanking.best - leftRanking.second >= CONTEXTUAL_AMBIGUITY_MARGIN2 && rightRanking.best - rightRanking.second >= CONTEXTUAL_AMBIGUITY_MARGIN2;
5536
+ };
5537
+ return accepted.map((row, leftIndex) => row.map((acceptedScore, rightIndex) => {
5538
+ if (acceptedScore !== null) {
5539
+ return acceptedScore;
5540
+ }
5541
+ const hasLocalSupport = isUnambiguousContextualCandidate(leftIndex, rightIndex) && (hasAcceptedDiagonalNeighbor2(accepted, leftIndex, rightIndex) || hasContextualDiagonalNeighbor2(
5542
+ scored,
5543
+ leftIndex,
5544
+ rightIndex,
5545
+ isUnambiguousContextualCandidate
5546
+ ));
5547
+ const hasRunSupport = hasAnchoredWeakDiagonalRun2(
5548
+ scored,
5549
+ leftIndex,
5550
+ rightIndex,
5551
+ (candidateLeftIndex, candidateRightIndex) => isWeakRunCandidate(
5552
+ scored[candidateLeftIndex][candidateRightIndex],
5553
+ candidateLeftIndex,
5554
+ candidateRightIndex
5555
+ ),
5556
+ isUnambiguousWeakRunAnchor
5557
+ );
5558
+ if (!hasLocalSupport && !hasRunSupport) {
5559
+ return null;
5560
+ }
5561
+ return scored[leftIndex][rightIndex].score;
5562
+ }));
5563
+ }
5564
+ function hasAcceptedDiagonalNeighbor2(accepted, leftIndex, rightIndex) {
5565
+ return leftIndex > 0 && rightIndex > 0 && accepted[leftIndex - 1][rightIndex - 1] !== null || leftIndex + 1 < accepted.length && rightIndex + 1 < accepted[leftIndex + 1].length && accepted[leftIndex + 1][rightIndex + 1] !== null;
5566
+ }
5567
+ function hasContextualDiagonalNeighbor2(scored, leftIndex, rightIndex, isCandidate) {
5568
+ return leftIndex > 0 && rightIndex > 0 && isCandidate(leftIndex - 1, rightIndex - 1) || leftIndex + 1 < scored.length && rightIndex + 1 < scored[leftIndex + 1].length && isCandidate(leftIndex + 1, rightIndex + 1);
5569
+ }
5570
+ function hasAnchoredWeakDiagonalRun2(scored, leftIndex, rightIndex, isCandidate, isAnchor) {
5571
+ let startLeft = leftIndex;
5572
+ let startRight = rightIndex;
5573
+ while (startLeft > 0 && startRight > 0 && isCandidate(startLeft - 1, startRight - 1)) {
5574
+ startLeft--;
5575
+ startRight--;
5576
+ }
5577
+ let endLeft = leftIndex;
5578
+ let endRight = rightIndex;
5579
+ let scoreTotal = scored[leftIndex][rightIndex].score;
5580
+ let length = 1;
5581
+ while (endLeft + 1 < scored.length && endRight + 1 < scored[endLeft + 1].length && isCandidate(endLeft + 1, endRight + 1)) {
5582
+ endLeft++;
5583
+ endRight++;
5584
+ scoreTotal += scored[endLeft][endRight].score;
5585
+ length++;
5586
+ }
5587
+ for (let scanLeft = leftIndex - 1, scanRight = rightIndex - 1; scanLeft >= startLeft && scanRight >= startRight; scanLeft--, scanRight--) {
5588
+ scoreTotal += scored[scanLeft][scanRight].score;
5589
+ length++;
5590
+ }
5591
+ return length >= 3 && scoreTotal / length >= MINIMUM_CONTEXTUAL_MATCH_SCORE2 && isAnchor(startLeft, startRight) && isAnchor(endLeft, endRight);
5592
+ }
5593
+ function rankScores2(scores, include) {
5594
+ let best = Number.NEGATIVE_INFINITY;
5595
+ let second = Number.NEGATIVE_INFINITY;
5596
+ scores.forEach((candidate, index) => {
5597
+ if (!include(candidate, index)) {
5598
+ return;
5599
+ }
5600
+ if (candidate.score > best) {
5601
+ second = best;
5602
+ best = candidate.score;
5603
+ } else if (candidate.score > second) {
5604
+ second = candidate.score;
5605
+ }
5606
+ });
5607
+ return { best, second };
5608
+ }
5609
+ function rankEligibleScores2(scores) {
5610
+ let best = Number.NEGATIVE_INFINITY;
5611
+ let second = Number.NEGATIVE_INFINITY;
5612
+ for (const candidate of scores) {
5613
+ if (!candidate.eligible) {
5614
+ continue;
5615
+ }
5616
+ if (candidate.score > best) {
5617
+ second = best;
5618
+ best = candidate.score;
5619
+ } else if (candidate.score > second) {
5620
+ second = candidate.score;
5621
+ }
5622
+ }
5623
+ return { best, second };
5624
+ }
5625
+ function alignLargeReplacementLines2(leftLines, rightLines) {
5626
+ const anchors = collectLargeHunkAnchors2(leftLines, rightLines);
5627
+ const aligned = [];
5628
+ let leftStart = 0;
5629
+ let rightStart = 0;
5630
+ for (const anchor of anchors) {
5631
+ appendBoundedSegment2(
5632
+ aligned,
5633
+ leftLines.slice(leftStart, anchor.leftIndex),
5634
+ rightLines.slice(rightStart, anchor.rightIndex)
5635
+ );
5636
+ aligned.push({ left: leftLines[anchor.leftIndex], right: rightLines[anchor.rightIndex] });
5637
+ leftStart = anchor.leftIndex + 1;
5638
+ rightStart = anchor.rightIndex + 1;
5639
+ }
5640
+ appendBoundedSegment2(aligned, leftLines.slice(leftStart), rightLines.slice(rightStart));
5641
+ return aligned;
5642
+ }
5643
+ function collectLargeHunkAnchors2(leftLines, rightLines) {
5644
+ const candidatePairs = collectRareTokenCandidatePairs2(leftLines, rightLines);
5645
+ if (candidatePairs === null) {
5646
+ return [];
5647
+ }
5648
+ const structuralPairs = collectUniqueStructuralPairs2(leftLines, rightLines);
5649
+ const scored = candidatePairs.map(({ leftIndex, rightIndex, exact }) => ({
5650
+ leftIndex,
5651
+ rightIndex,
5652
+ exact,
5653
+ structural: structuralPairs.has(`${leftIndex}:${rightIndex}`),
5654
+ match: scoreReplacementLinePair2(leftLines[leftIndex], rightLines[rightIndex])
5655
+ })).filter((candidate) => candidate.exact || candidate.structural || candidate.match.eligible);
5656
+ const leftRankings = rankAnchorCandidates2(scored, "leftIndex");
5657
+ const rightRankings = rankAnchorCandidates2(scored, "rightIndex");
5658
+ const confident = scored.filter((candidate) => {
5659
+ if (candidate.exact || candidate.structural) {
5660
+ return true;
5661
+ }
5662
+ const leftRanking = leftRankings.get(candidate.leftIndex);
5663
+ const rightRanking = rightRankings.get(candidate.rightIndex);
5664
+ if (!leftRanking || !rightRanking || candidate.match.score !== leftRanking.best || candidate.match.score !== rightRanking.best) {
5665
+ return false;
5666
+ }
5667
+ const requiredMargin = candidate.match.score >= HIGH_CONFIDENCE_MATCH_SCORE2 ? AMBIGUITY_MARGIN2 / 2 : AMBIGUITY_MARGIN2;
5668
+ return leftRanking.best - leftRanking.second >= requiredMargin && rightRanking.best - rightRanking.second >= requiredMargin;
5669
+ });
5670
+ const anchors = selectMaximumWeightMonotonicAnchors2(confident, rightLines.length);
5671
+ return anchors.length <= MAX_LARGE_HUNK_ANCHORS2 ? anchors : [];
5672
+ }
5673
+ function collectRareTokenCandidatePairs2(leftLines, rightLines) {
5674
+ const leftExact = buildLinePostings2(leftLines, normalizeMatchingContent2);
5675
+ const rightExact = buildLinePostings2(rightLines, normalizeMatchingContent2);
5676
+ const pairs = /* @__PURE__ */ new Map();
5677
+ for (const [line, leftIndices] of leftExact) {
5678
+ const rightIndices = rightExact.get(line);
5679
+ if (!isInformativeLine2(line) || leftIndices.length !== 1 || (rightIndices == null ? void 0 : rightIndices.length) !== 1) {
5680
+ continue;
5681
+ }
5682
+ const leftIndex = leftIndices[0];
5683
+ const rightIndex = rightIndices[0];
5684
+ pairs.set(`${leftIndex}:${rightIndex}`, { leftIndex, rightIndex, exact: true });
5685
+ if (pairs.size > MAX_BOUNDED_CANDIDATES2) {
5686
+ return null;
5687
+ }
5688
+ }
5689
+ const leftTokens = buildTokenPostings2(leftLines);
5690
+ const rightTokens = buildTokenPostings2(rightLines);
5691
+ for (const [token, leftIndices] of leftTokens) {
5692
+ const rightIndices = rightTokens.get(token);
5693
+ if (!rightIndices || leftIndices.length > MAX_RARE_TOKEN_OCCURRENCES2 || rightIndices.length > MAX_RARE_TOKEN_OCCURRENCES2) {
5694
+ continue;
5695
+ }
5696
+ for (const leftIndex of leftIndices) {
5697
+ for (const rightIndex of rightIndices) {
5698
+ const key = `${leftIndex}:${rightIndex}`;
5699
+ if (!pairs.has(key)) {
5700
+ pairs.set(key, { leftIndex, rightIndex, exact: false });
5701
+ if (pairs.size > MAX_BOUNDED_CANDIDATES2) {
5702
+ return null;
5703
+ }
5704
+ }
5705
+ }
5706
+ }
5707
+ }
5708
+ return [...pairs.values()];
5709
+ }
5710
+ function buildLinePostings2(lines, keyForLine) {
5711
+ const postings = /* @__PURE__ */ new Map();
5712
+ lines.forEach((line, index) => {
5713
+ const key = keyForLine(line);
5714
+ const indices = postings.get(key) ?? [];
5715
+ indices.push(index);
5716
+ postings.set(key, indices);
5717
+ });
5718
+ return postings;
5719
+ }
5720
+ function buildTokenPostings2(lines) {
5721
+ const postings = /* @__PURE__ */ new Map();
5722
+ lines.forEach((line, index) => {
5723
+ const tokens = new Set(tokenizeMatchingContent2(normalizeMatchingContent2(line)).filter(isMeaningfulAnchorToken2));
5724
+ for (const token of tokens) {
5725
+ const indices = postings.get(token) ?? [];
5726
+ indices.push(index);
5727
+ postings.set(token, indices);
5728
+ }
5729
+ });
5730
+ return postings;
5731
+ }
5732
+ function isMeaningfulAnchorToken2(token) {
5733
+ return token.length >= 2 && /[\p{L}\p{N}_$]/u.test(token);
5734
+ }
5735
+ function rankAnchorCandidates2(candidates, indexKey) {
5736
+ const grouped = /* @__PURE__ */ new Map();
5737
+ for (const candidate of candidates) {
5738
+ const scores = grouped.get(candidate[indexKey]) ?? [];
5739
+ scores.push(candidate.match);
5740
+ grouped.set(candidate[indexKey], scores);
5741
+ }
5742
+ return new Map([...grouped].map(([index, scores]) => [index, rankEligibleScores2(scores)]));
5743
+ }
5744
+ function selectMaximumWeightMonotonicAnchors2(candidates, rightLength) {
5745
+ const ordered = [...candidates].sort((left, right) => left.leftIndex - right.leftIndex || left.rightIndex - right.rightIndex);
5746
+ const treeScores = new Array(rightLength + 2).fill(0);
5747
+ const treeCandidates = new Array(rightLength + 2).fill(-1);
5748
+ const bestScores = new Array(ordered.length).fill(0);
5749
+ const previous = new Array(ordered.length).fill(-1);
5750
+ for (let start = 0; start < ordered.length; ) {
5751
+ let end = start + 1;
5752
+ while (end < ordered.length && ordered[end].leftIndex === ordered[start].leftIndex) {
5753
+ end++;
5754
+ }
5755
+ for (let index = start; index < end; index++) {
5756
+ const prior = queryAnchorTree2(treeScores, treeCandidates, ordered[index].rightIndex);
5757
+ const anchorBonus = ordered[index].exact || ordered[index].structural ? 0.5 : 0;
5758
+ bestScores[index] = prior.score + ordered[index].match.score + anchorBonus;
5759
+ previous[index] = prior.candidateIndex;
5760
+ }
5761
+ for (let index = start; index < end; index++) {
5762
+ updateAnchorTree2(
5763
+ treeScores,
5764
+ treeCandidates,
5765
+ ordered[index].rightIndex + 1,
5766
+ bestScores[index],
5767
+ index
5768
+ );
5769
+ }
5770
+ start = end;
5771
+ }
5772
+ const best = queryAnchorTree2(treeScores, treeCandidates, rightLength + 1);
5773
+ const anchors = [];
5774
+ for (let index = best.candidateIndex; index >= 0; index = previous[index]) {
5775
+ anchors.push({ leftIndex: ordered[index].leftIndex, rightIndex: ordered[index].rightIndex });
5776
+ }
5777
+ return anchors.reverse();
5778
+ }
5779
+ function queryAnchorTree2(scores, candidates, position) {
5780
+ let score = 0;
5781
+ let candidateIndex = -1;
5782
+ for (let index = position; index > 0; index -= index & -index) {
5783
+ if (scores[index] > score) {
5784
+ score = scores[index];
5785
+ candidateIndex = candidates[index];
5786
+ }
5787
+ }
5788
+ return { score, candidateIndex };
5789
+ }
5790
+ function updateAnchorTree2(scores, candidates, position, score, candidateIndex) {
5791
+ for (let index = position; index < scores.length; index += index & -index) {
5792
+ if (score > scores[index]) {
5793
+ scores[index] = score;
5794
+ candidates[index] = candidateIndex;
5795
+ }
5796
+ }
5797
+ }
5798
+ function appendBoundedSegment2(aligned, leftLines, rightLines) {
5799
+ if (leftLines.length * rightLines.length <= MAX_ALIGNMENT_CELLS2) {
5800
+ aligned.push(...alignReplacementLines2(leftLines, rightLines));
5801
+ return;
5802
+ }
5803
+ aligned.push(...leftLines.map((left) => ({ left })));
5804
+ aligned.push(...rightLines.map((right) => ({ right })));
5805
+ }
5806
+ function normalizeMatchingContent2(line) {
5807
+ return line.trim().replace(/\s+/g, " ");
5808
+ }
5809
+ function collectUniqueStructuralPairs2(leftLines, rightLines) {
5810
+ const leftPostings = buildStructuralPostings2(leftLines);
5811
+ const rightPostings = buildStructuralPostings2(rightLines);
5812
+ const pairs = /* @__PURE__ */ new Set();
5813
+ for (const [key, leftIndices] of leftPostings) {
5814
+ const rightIndices = rightPostings.get(key);
5815
+ if (leftIndices.length === 1 && (rightIndices == null ? void 0 : rightIndices.length) === 1) {
5816
+ pairs.add(`${leftIndices[0]}:${rightIndices[0]}`);
5817
+ }
5818
+ }
5819
+ return pairs;
5820
+ }
5821
+ function buildStructuralPostings2(lines) {
5822
+ const postings = /* @__PURE__ */ new Map();
5823
+ lines.forEach((line, index) => {
5824
+ const key = declarationAnchorKey2(line);
5825
+ if (!key) {
5826
+ return;
5827
+ }
5828
+ const indices = postings.get(key) ?? [];
5829
+ indices.push(index);
5830
+ postings.set(key, indices);
5831
+ });
5832
+ return postings;
5833
+ }
5834
+ function haveCompatibleLineRoles2(left, right) {
5835
+ const leftKey = declarationAnchorKey2(left);
5836
+ const rightKey = declarationAnchorKey2(right);
5837
+ if (!leftKey || !rightKey) {
5838
+ return leftKey === rightKey;
5839
+ }
5840
+ return leftKey.split(":", 1)[0] === rightKey.split(":", 1)[0];
5841
+ }
5842
+ function declarationAnchorKey2(line) {
5843
+ const normalized = normalizeMatchingContent2(line);
5844
+ const patterns = [
5845
+ ["function", /^(?:(?:export|default)\s+)*(?:async\s+)?function\s*\*?\s*([\p{L}_$][\p{L}\p{N}_$]*)\s*\(/u],
5846
+ ["function", /^(?:async\s+)?def\s+([\p{L}_][\p{L}\p{N}_]*)\s*\(/u],
5847
+ ["function", /^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([\p{L}_][\p{L}\p{N}_]*)\s*(?:<[^>]*>)?\s*\(/u],
5848
+ ["function", /^func\s+(?:\([^)]*\)\s*)?([\p{L}_][\p{L}\p{N}_]*)\s*\(/u],
5849
+ ["class", /^(?:(?:export|default|abstract|public)\s+)*class\s+([\p{L}_$][\p{L}\p{N}_$]*)\b/u]
5850
+ ];
5851
+ for (const [kind, pattern] of patterns) {
5852
+ const match = normalized.match(pattern);
5853
+ if (match) {
5854
+ return `${kind}:${match[1]}`;
5855
+ }
5856
+ }
5857
+ return null;
5858
+ }
5859
+ function isInformativeLine2(line) {
5860
+ if (line.length < 3) {
5861
+ return false;
5862
+ }
5863
+ return /[\p{L}\p{N}_$]/u.test(line) && !/^[\s()[\]{};,.:+\-*/=>|&!?'"`]+$/.test(line);
5864
+ }
5865
+ function tokenizeMatchingContent2(line) {
5866
+ return line.toLowerCase().match(new RegExp("[\\p{L}_$][\\p{L}\\p{N}_$]*|\\p{N}+(?:\\.\\p{N}+)?|[^\\s\\p{L}\\p{N}_$]", "gu")) ?? [];
5867
+ }
5868
+ function multisetDiceSimilarity2(leftTokens, rightTokens) {
5869
+ if (leftTokens.length + rightTokens.length === 0) {
5870
+ return 1;
5871
+ }
5872
+ const remaining = /* @__PURE__ */ new Map();
5873
+ leftTokens.forEach((token) => remaining.set(token, (remaining.get(token) ?? 0) + 1));
5874
+ let common = 0;
5875
+ for (const token of rightTokens) {
5876
+ const count = remaining.get(token) ?? 0;
5877
+ if (count > 0) {
5878
+ common++;
5879
+ remaining.set(token, count - 1);
5880
+ }
5881
+ }
5882
+ return 2 * common / (leftTokens.length + rightTokens.length);
5883
+ }
5884
+ function tokenBoundarySimilarity2(leftTokens, rightTokens) {
5885
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
5886
+ return 0;
5887
+ }
5888
+ const sameStart = leftTokens[0] === rightTokens[0] ? 0.5 : 0;
5889
+ const sameEnd = leftTokens[leftTokens.length - 1] === rightTokens[rightTokens.length - 1] ? 0.5 : 0;
5890
+ return sameStart + sameEnd;
5891
+ }
5892
+ function lineSimilarity2(left, right) {
5893
+ const maxLength = left.length + right.length;
5894
+ if (maxLength === 0) {
5895
+ return 1;
5896
+ }
5897
+ const commonLength = diffChars2(left, right).filter((change) => !change.added && !change.removed).reduce((total, change) => total + change.value.length, 0);
5898
+ return 2 * commonLength / maxLength;
5899
+ }
5900
+ function applyInlineHighlightPair2(leftLine, rightLine) {
5901
+ if (leftLine.content.length > MAX_INLINE_HIGHLIGHT_LINE_LENGTH2 || rightLine.content.length > MAX_INLINE_HIGHLIGHT_LINE_LENGTH2) {
5902
+ return;
5903
+ }
5904
+ const { leftSegments, rightSegments, hasInlineChanges } = buildInlineSegments2(leftLine.content, rightLine.content);
5905
+ if (hasInlineChanges) {
5906
+ leftLine.segments = leftSegments;
5907
+ rightLine.segments = rightSegments;
5908
+ }
5909
+ }
5910
+ function buildInlineSegments2(leftContent, rightContent) {
5911
+ const changes = diffWordsWithSpace2(leftContent, rightContent);
5912
+ const leftSegments = [];
5913
+ const rightSegments = [];
5914
+ let hasInlineChanges = false;
5915
+ for (const change of changes) {
5916
+ const value = change.value;
5917
+ if (!change.added && !change.removed) {
5918
+ const contextSegment = {
5919
+ kind: "context",
5920
+ text: value,
5921
+ emphasis: false
5922
+ };
5923
+ leftSegments.push(contextSegment);
5924
+ rightSegments.push(contextSegment);
5925
+ continue;
5926
+ }
5927
+ const emphasis = /[^\s]/.test(value);
5928
+ hasInlineChanges = hasInlineChanges || emphasis;
5929
+ if (change.removed) {
5930
+ leftSegments.push({
5931
+ kind: "removed",
5932
+ text: value,
5933
+ emphasis
5934
+ });
5935
+ }
5936
+ if (change.added) {
5937
+ rightSegments.push({
5938
+ kind: "added",
5939
+ text: value,
5940
+ emphasis
5941
+ });
5942
+ }
5943
+ }
5944
+ return {
5945
+ leftSegments,
5946
+ rightSegments,
5947
+ hasInlineChanges
5948
+ };
5949
+ }
5950
+ function makePlaceholder2() {
5951
+ return {
5952
+ kind: "placeholder",
5953
+ content: "",
5954
+ lineNumber: null
5955
+ };
5956
+ }
5957
+ function makeDiffCell2(kind, content, lineNumber) {
5958
+ return { kind, content, lineNumber };
5959
+ }
5960
+ function makeDiffLine2(kind, content, lineNumber) {
5961
+ return { kind, content, lineNumber };
5962
+ }
5963
+ function makeDiffRow2(left, right) {
5964
+ return { left, right };
5965
+ }
5966
+ function makeDiffBlock2(kind, leftStart, leftEnd, rightStart, rightEnd) {
5967
+ return { kind, leftStart, leftEnd, rightStart, rightEnd };
5968
+ }
5969
+ function readPositiveIntegerEnv4(name, fallback) {
5970
+ var _a;
5971
+ const value = typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[name] : void 0;
5972
+ const parsed = Number.parseInt(value ?? "", 10);
5973
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
5974
+ }
5975
+ var MAX_AUTOMATIC_TOUR_FOCUS_RANGES = 4;
5976
+ var NEARBY_TOUR_FOCUS_LINE_GAP = 8;
5977
+ var LARGE_TOUR_FOCUS_RANGE_LINES = 120;
5978
+ var TOUR_FOCUS_BOUNDARY_SEARCH_LINES = 12;
5979
+ function getChangeSourceRange(diffModel, blockIndex, side) {
5980
+ var _a, _b, _c;
5981
+ const block = diffModel == null ? void 0 : diffModel.blocks[blockIndex];
5982
+ const lines = side === "left" ? diffModel == null ? void 0 : diffModel.leftLines : diffModel == null ? void 0 : diffModel.rightLines;
5983
+ if (!block || !(lines == null ? void 0 : lines.length)) {
5984
+ return void 0;
5985
+ }
5986
+ const start = side === "left" ? block.leftStart : block.rightStart;
5987
+ const end = side === "left" ? block.leftEnd : block.rightEnd;
5988
+ const changedLines = lines.slice(start, end);
5989
+ const startLine = ((_a = changedLines[0]) == null ? void 0 : _a.lineNumber) ?? ((_b = lines[Math.min(start, lines.length - 1)]) == null ? void 0 : _b.lineNumber);
5990
+ if (!Number.isInteger(startLine)) {
5991
+ return void 0;
5992
+ }
5993
+ return {
5994
+ startLine,
5995
+ endLine: ((_c = changedLines.at(-1)) == null ? void 0 : _c.lineNumber) ?? startLine
5996
+ };
5997
+ }
5998
+ function buildTourFocusRanges(diffModel, side) {
5999
+ const ranges = diffModel.blocks.map((_, blockIndex) => getChangeSourceRange(diffModel, blockIndex, side)).filter((range) => Boolean(range));
6000
+ if (ranges.length === 0) return [];
6001
+ const nearby = ranges.reduce((grouped, range) => {
6002
+ const previous = grouped.at(-1);
6003
+ if (previous && range.startLine - previous.endLine <= NEARBY_TOUR_FOCUS_LINE_GAP) {
6004
+ previous.endLine = Math.max(previous.endLine, range.endLine);
6005
+ } else {
6006
+ grouped.push({ ...range });
6007
+ }
6008
+ return grouped;
6009
+ }, []);
6010
+ const capped = capFocusRanges(nearby, MAX_AUTOMATIC_TOUR_FOCUS_RANGES);
6011
+ if (capped.length !== 1) return capped;
6012
+ const [only] = capped;
6013
+ const lineCount = only.endLine - only.startLine + 1;
6014
+ if (lineCount < LARGE_TOUR_FOCUS_RANGE_LINES) return capped;
6015
+ const lines = side === "left" ? diffModel.leftLines : diffModel.rightLines;
6016
+ const partCount = Math.min(
6017
+ MAX_AUTOMATIC_TOUR_FOCUS_RANGES,
6018
+ Math.ceil(lineCount / LARGE_TOUR_FOCUS_RANGE_LINES)
6019
+ );
6020
+ const boundaries = [];
6021
+ for (let partIndex = 1; partIndex < partCount; partIndex += 1) {
6022
+ const target = only.startLine + Math.round(lineCount * partIndex / partCount) - 1;
6023
+ boundaries.push(findBlankLineBoundary(lines, target, only.startLine, only.endLine));
6024
+ }
6025
+ const uniqueBoundaries = [...new Set(boundaries)].filter((line) => line >= only.startLine && line < only.endLine).sort((left, right) => left - right);
6026
+ const split = [];
6027
+ let startLine = only.startLine;
6028
+ for (const endLine of uniqueBoundaries) {
6029
+ split.push({ startLine, endLine });
6030
+ startLine = endLine + 1;
6031
+ }
6032
+ split.push({ startLine, endLine: only.endLine });
6033
+ return split;
6034
+ }
6035
+ function capFocusRanges(ranges, maximum) {
6036
+ if (ranges.length <= maximum) return ranges.map((range) => ({ ...range }));
6037
+ const capped = [];
6038
+ for (let index = 0; index < maximum; index += 1) {
6039
+ const startIndex = Math.floor(index * ranges.length / maximum);
6040
+ const endIndex = Math.floor((index + 1) * ranges.length / maximum) - 1;
6041
+ capped.push({
6042
+ startLine: ranges[startIndex].startLine,
6043
+ endLine: ranges[endIndex].endLine
6044
+ });
6045
+ }
6046
+ return capped;
6047
+ }
6048
+ function findBlankLineBoundary(lines, target, minimum, maximum) {
6049
+ for (let distance = 0; distance <= TOUR_FOCUS_BOUNDARY_SEARCH_LINES; distance += 1) {
6050
+ for (const candidate of distance === 0 ? [target] : [target + distance, target - distance]) {
6051
+ if (candidate < minimum || candidate >= maximum) continue;
6052
+ const line = lines.find((entry) => entry.lineNumber === candidate);
6053
+ if ((line == null ? void 0 : line.content.trim()) === "") return candidate;
6054
+ }
6055
+ }
6056
+ return Math.min(Math.max(target, minimum), maximum - 1);
6057
+ }
4837
6058
  var import_child_process32 = require("child_process");
4838
6059
  var CHANGE_TOUR_CONTEXT_VERSION = 1;
4839
6060
  var DEFAULT_MAX_PATCH_BYTES = 128 * 1024;
@@ -5162,7 +6383,8 @@ var require_changeTour = __commonJS({
5162
6383
  repoRoot,
5163
6384
  authoredScene,
5164
6385
  authoredScene.base || ((_a = source.range) == null ? void 0 : _a.base),
5165
- authoredScene.target || ((_b = source.range) == null ? void 0 : _b.head)
6386
+ authoredScene.target || ((_b = source.range) == null ? void 0 : _b.head),
6387
+ [...available.keys()]
5166
6388
  );
5167
6389
  scenes.push(deconstructedScene);
5168
6390
  sceneIds.push(deconstructedScene.id);
@@ -5208,7 +6430,7 @@ var require_changeTour = __commonJS({
5208
6430
  }
5209
6431
  return { scenes, chapters };
5210
6432
  }
5211
- function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef) {
6433
+ function buildDeconstructedManifestScene(repoRoot, source, baseRef, targetRef, availableFilePaths = []) {
5212
6434
  const compiled = buildDeconstructedScene(repoRoot, source, { baseRef, headRef: targetRef });
5213
6435
  const panels = [{
5214
6436
  id: "explanation-baseline",
@@ -5220,9 +6442,15 @@ var require_changeTour = __commonJS({
5220
6442
  role: "stage",
5221
6443
  stageId: stage.id
5222
6444
  }))];
5223
- const includedPaths = [...new Set(compiled.stages.flatMap((stage) => stage.introducedFiles))];
6445
+ const materializablePaths = new Set(compiled.baselineFiles.map((file) => file.path));
6446
+ const includedPaths = availableFilePaths.length > 0 ? availableFilePaths.filter((filePath) => materializablePaths.has(filePath)).sort() : [...materializablePaths].sort();
6447
+ const missingIntroducedPath = compiled.stages.flatMap((stage) => stage.introducedFiles).find((filePath) => !includedPaths.includes(filePath));
6448
+ if (missingIntroducedPath) {
6449
+ throw new Error(`Deconstructed tour file is not renderable: ${missingIntroducedPath}`);
6450
+ }
6451
+ const introducedPaths = new Set(compiled.stages.flatMap((stage) => stage.introducedFiles));
5224
6452
  let totalContentBytes = 0;
5225
- const files = includedPaths.map((filePath) => {
6453
+ const files = includedPaths.flatMap((filePath) => {
5226
6454
  const states = [
5227
6455
  compiled.baselineFiles.find((file) => file.path === filePath),
5228
6456
  ...compiled.stages.map((stage) => stage.files.find((file) => file.path === filePath))
@@ -5230,18 +6458,23 @@ var require_changeTour = __commonJS({
5230
6458
  if (states.some((state) => !state)) {
5231
6459
  throw new Error(`Deconstructed scene ${source.id} is missing virtual state for ${filePath}.`);
5232
6460
  }
5233
- return {
6461
+ const fileContentBytes = states.reduce((total, state) => total + Buffer.byteLength((state == null ? void 0 : state.content) || "", "utf8"), 0);
6462
+ const exceedsFileLimit = states.some((state) => {
6463
+ const content = Buffer.from((state == null ? void 0 : state.content) || "", "utf8");
6464
+ return content.length > DEFAULT_MAX_TOUR_FILE_BYTES || hasLineLongerThan(content, DEFAULT_MAX_TOUR_LINE_BYTES);
6465
+ });
6466
+ const exceedsSceneLimit = totalContentBytes + fileContentBytes > DEFAULT_MAX_STACK_CONTENT_BYTES;
6467
+ if (exceedsFileLimit || exceedsSceneLimit) {
6468
+ if (introducedPaths.has(filePath)) {
6469
+ throw new Error(exceedsFileLimit ? `Deconstructed tour file is too large to present: ${filePath}` : `Deconstructed tour content exceeds ${DEFAULT_MAX_STACK_CONTENT_BYTES} bytes.`);
6470
+ }
6471
+ return [];
6472
+ }
6473
+ totalContentBytes += fileContentBytes;
6474
+ return [{
5234
6475
  path: filePath,
5235
6476
  panels: states.map((state, index) => {
5236
6477
  if (!state) throw new Error(`Missing virtual state for ${filePath}.`);
5237
- const content = Buffer.from(state.content, "utf8");
5238
- if (content.length > DEFAULT_MAX_TOUR_FILE_BYTES || hasLineLongerThan(content, DEFAULT_MAX_TOUR_LINE_BYTES)) {
5239
- throw new Error(`Deconstructed tour file is too large to present: ${filePath}`);
5240
- }
5241
- totalContentBytes += content.length;
5242
- if (totalContentBytes > DEFAULT_MAX_STACK_CONTENT_BYTES) {
5243
- throw new Error(`Deconstructed tour content exceeds ${DEFAULT_MAX_STACK_CONTENT_BYTES} bytes.`);
5244
- }
5245
6478
  return {
5246
6479
  id: panels[index].id,
5247
6480
  label: `${panels[index].label} / ${filePath}${state.exists ? "" : " (absent)"}`,
@@ -5250,9 +6483,10 @@ var require_changeTour = __commonJS({
5250
6483
  exists: state.exists
5251
6484
  };
5252
6485
  })
5253
- };
6486
+ }];
5254
6487
  });
5255
6488
  if (files.length === 0) throw new Error(`Deconstructed scene ${source.id} has no staged files.`);
6489
+ const steps = buildDeconstructedFocusSteps(source.id, compiled.stages, files);
5256
6490
  return {
5257
6491
  id: source.id,
5258
6492
  kind: "deconstructed-diff",
@@ -5270,17 +6504,63 @@ var require_changeTour = __commonJS({
5270
6504
  },
5271
6505
  panels,
5272
6506
  files,
5273
- steps: compiled.stages.map((stage, index) => ({
5274
- id: stage.id,
5275
- title: stage.title,
5276
- body: stage.narration,
5277
- file: stage.introducedFiles[0],
5278
- pairIndex: index,
5279
- side: "right",
5280
- introducedHunks: stage.introducedHunks
5281
- }))
6507
+ steps
5282
6508
  };
5283
6509
  }
6510
+ function buildDeconstructedFocusSteps(sceneId, stages, files) {
6511
+ const reservedIds = new Set(stages.map((stage) => stage.id));
6512
+ const usedIds = /* @__PURE__ */ new Set();
6513
+ return stages.flatMap((stage, stageIndex) => {
6514
+ const focuses = stage.introducedChanges.flatMap((change) => {
6515
+ const file = files.find((candidate) => candidate.path === change.file);
6516
+ const left = file == null ? void 0 : file.panels[stageIndex];
6517
+ const right = file == null ? void 0 : file.panels[stageIndex + 1];
6518
+ if (!file || !left || !right) {
6519
+ throw new Error(`Deconstructed scene ${sceneId} is missing comparison state for ${change.file}.`);
6520
+ }
6521
+ const diffModel = buildTwoWayDiffModel3(left.content, right.content);
6522
+ const side = left.exists && !right.exists ? "left" : "right";
6523
+ const ranges = buildTourFocusRanges(diffModel, side);
6524
+ if (ranges.length === 0) {
6525
+ throw new Error(`Deconstructed stage ${stage.id} has no visible change for ${change.file}.`);
6526
+ }
6527
+ return ranges.map((range) => ({
6528
+ file: change.file,
6529
+ hunks: change.hunks,
6530
+ side,
6531
+ ...range
6532
+ }));
6533
+ });
6534
+ return focuses.map((focus, focusIndex) => ({
6535
+ id: focusIndex === 0 ? claimDeconstructedStageId(stage.id, usedIds) : claimDeconstructedFocusId(`${stage.id}-focus-${focusIndex + 1}`, reservedIds, usedIds),
6536
+ title: focuses.length === 1 ? stage.title : `${stage.title} \xB7 ${focus.file} \xB7 Focus ${focusIndex + 1}/${focuses.length}`,
6537
+ body: stage.narration,
6538
+ file: focus.file,
6539
+ pairIndex: stageIndex,
6540
+ side: focus.side,
6541
+ startLine: focus.startLine,
6542
+ endLine: focus.endLine,
6543
+ introducedHunks: [...focus.hunks],
6544
+ stageId: stage.id,
6545
+ stageIndex,
6546
+ focusIndex,
6547
+ focusCount: focuses.length
6548
+ }));
6549
+ });
6550
+ }
6551
+ function claimDeconstructedStageId(stageId, usedIds) {
6552
+ usedIds.add(stageId);
6553
+ return stageId;
6554
+ }
6555
+ function claimDeconstructedFocusId(preferredId, reservedIds, usedIds) {
6556
+ let candidate = preferredId;
6557
+ let suffix = 2;
6558
+ while (usedIds.has(candidate) || reservedIds.has(candidate)) {
6559
+ candidate = `${preferredId}-${suffix++}`;
6560
+ }
6561
+ usedIds.add(candidate);
6562
+ return candidate;
6563
+ }
5284
6564
  function buildStackedScene(repoRoot, source, availableFilePaths) {
5285
6565
  const stack = source.stack.map((entry) => ({
5286
6566
  id: entry.id,
@@ -9297,7 +10577,7 @@ var require_package = __commonJS({
9297
10577
  displayName: "Bygone",
9298
10578
  desktopName: "com.davidmashburn.bygone.desktop",
9299
10579
  description: "See how change happened. Visual diff and file history for VS Code.",
9300
- version: "0.8.11",
10580
+ version: "0.8.13",
9301
10581
  author: "David Mashburn",
9302
10582
  license: "MIT",
9303
10583
  private: false,