polite-crawler 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1087 -15
- package/build/browser-CKRNbIDT.mjs +62 -0
- package/build/browser-CKRNbIDT.mjs.map +1 -0
- package/build/cli-daemon--cTmq0Z5.mjs +38 -0
- package/build/cli-daemon--cTmq0Z5.mjs.map +1 -0
- package/build/cli.mjs +1065 -123
- package/build/cli.mjs.map +1 -1
- package/build/daemon-DP4EqjFL.d.mts +3772 -0
- package/build/daemon-fZTE24P5.mjs +515 -0
- package/build/daemon-fZTE24P5.mjs.map +1 -0
- package/build/diff-NQcQqKe9.mjs +648 -0
- package/build/diff-NQcQqKe9.mjs.map +1 -0
- package/build/extract-ByFfc8Zk.mjs +1793 -0
- package/build/extract-ByFfc8Zk.mjs.map +1 -0
- package/build/hours.d.mts +26 -0
- package/build/hours.mjs +48 -1
- package/build/hours.mjs.map +1 -1
- package/build/{html-nWYFVsRU.d.mts → html-BACljlkf.d.mts} +8 -9
- package/build/{html-oNpGAHwi.mjs → html-DkQUAE_d.mjs} +18 -39
- package/build/html-DkQUAE_d.mjs.map +1 -0
- package/build/html.d.mts +2 -1
- package/build/html.mjs +3 -1
- package/build/index.d.mts +582 -12
- package/build/index.mjs +903 -6
- package/build/index.mjs.map +1 -0
- package/build/log-Dx-wXjJl.mjs +342 -0
- package/build/log-Dx-wXjJl.mjs.map +1 -0
- package/build/loopback-C_QYowSf.mjs +22 -0
- package/build/loopback-C_QYowSf.mjs.map +1 -0
- package/build/mcp.d.mts +164 -5
- package/build/mcp.mjs +1115 -42
- package/build/mcp.mjs.map +1 -1
- package/build/ollama-BJxkjny4.mjs +160 -0
- package/build/ollama-BJxkjny4.mjs.map +1 -0
- package/build/open-C4fhXJgD.mjs +4544 -0
- package/build/open-C4fhXJgD.mjs.map +1 -0
- package/build/{browser-gJ_h4AhB.mjs → render-C1CUraWF.mjs} +31 -74
- package/build/render-C1CUraWF.mjs.map +1 -0
- package/build/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/build/{open-DYZ7KL2A.mjs → runtime-JToRO63o.mjs} +986 -1657
- package/build/runtime-JToRO63o.mjs.map +1 -0
- package/build/transformers-BQMlaGj4.mjs +133 -0
- package/build/transformers-BQMlaGj4.mjs.map +1 -0
- package/build/urls-BHFeSyEK.mjs +62 -0
- package/build/urls-BHFeSyEK.mjs.map +1 -0
- package/build/urls-BTDSgDLC.d.mts +16 -0
- package/build/version-ntbY-6wh.mjs +38 -0
- package/build/version-ntbY-6wh.mjs.map +1 -0
- package/build/wacz-BFNG42Kt.mjs +275 -0
- package/build/wacz-BFNG42Kt.mjs.map +1 -0
- package/build/warc-CVS0t1fW.mjs +728 -0
- package/build/warc-CVS0t1fW.mjs.map +1 -0
- package/build/webbotauth-CjJdTAfE.mjs +568 -0
- package/build/webbotauth-CjJdTAfE.mjs.map +1 -0
- package/package.json +10 -2
- package/record.schema.json +15 -1
- package/build/browser-gJ_h4AhB.mjs.map +0 -1
- package/build/html-oNpGAHwi.mjs.map +0 -1
- package/build/open-DYZ7KL2A.mjs.map +0 -1
- package/build/open-eHcriTds.d.mts +0 -1499
- package/build/version-no12qgos.mjs +0 -11
- package/build/version-no12qgos.mjs.map +0 -1
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { R as stableStringify, _ as versionAtTime, c as isHeldBack, l as isoTime, n as HELD_BACK, s as heldBackMeta } from "./runtime-JToRO63o.mjs";
|
|
3
|
+
//#region src/diff.ts
|
|
4
|
+
var diff_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
DIFF_LIMITS: () => DIFF_LIMITS,
|
|
6
|
+
diffDocument: () => diffDocument,
|
|
7
|
+
diffTexts: () => diffTexts,
|
|
8
|
+
editScript: () => editScript,
|
|
9
|
+
formatDiff: () => formatDiff
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* A diff compares two versions of a stored document. It gives the changes of the text as a line
|
|
13
|
+
* diff, with a word diff inside each changed line. It gives the changes of the title, the URL and
|
|
14
|
+
* the metadata (the JSON-LD too) as changes at a path.
|
|
15
|
+
*
|
|
16
|
+
* The line diff is the algorithm of Myers (1986). Its time is linear in the length of the texts
|
|
17
|
+
* when the change is small. A budget of steps (`DIFF_LIMITS`) bounds the time of a large change.
|
|
18
|
+
* After the budget, the diff gives the rest of the change as one removal and one addition. Then
|
|
19
|
+
* the result says that it is not minimal. A huge text gives its first part, and the result says so.
|
|
20
|
+
*
|
|
21
|
+
* A held-back version gives no text and no title to a diff, and only its structural metadata.
|
|
22
|
+
*/
|
|
23
|
+
/** These are the limits of a diff. Each limit bounds the time or the size of a result. */
|
|
24
|
+
const DIFF_LIMITS = {
|
|
25
|
+
/** The characters of each text that a diff compares. A longer text gives its first part. */
|
|
26
|
+
maxChars: 1e6,
|
|
27
|
+
/** The lines of each text that a diff compares. A longer text gives its first lines. */
|
|
28
|
+
maxLines: 5e4,
|
|
29
|
+
/** The steps of the line diff. After them, the rest of the change is one removal and one addition. */
|
|
30
|
+
maxSteps: 1e6,
|
|
31
|
+
/** The steps of the word diffs of all the changed lines together. */
|
|
32
|
+
maxWordSteps: 1e6,
|
|
33
|
+
/** The words of one line that a word diff compares. A longer line gets no word diff. */
|
|
34
|
+
maxLineWords: 5e3,
|
|
35
|
+
/** The lines of context before and after each change. */
|
|
36
|
+
context: 2,
|
|
37
|
+
/** The lines that the hunks of a result show. The summary counts each line. */
|
|
38
|
+
maxHunkLines: 2e3,
|
|
39
|
+
/** The changes of the metadata that a result shows. The summary counts each change. */
|
|
40
|
+
maxMetaChanges: 200,
|
|
41
|
+
/** The depth of the metadata that a diff compares. A deeper change is a change of the whole value. */
|
|
42
|
+
maxMetaDepth: 16,
|
|
43
|
+
/** The characters of the JSON of each value of a change of the metadata. */
|
|
44
|
+
maxValueChars: 300
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* This function gives an edit script from `a` to `b`, with the algorithm of Myers. It first
|
|
48
|
+
* removes the common start and the common end. When the budget ends, it keeps the furthest path
|
|
49
|
+
* that it found, and the rest is one removal and one addition. `exact` is then false.
|
|
50
|
+
*/
|
|
51
|
+
const editScript = (a, b, budget) => {
|
|
52
|
+
const n = a.length;
|
|
53
|
+
const m = b.length;
|
|
54
|
+
let head = 0;
|
|
55
|
+
while (head < n && head < m && a[head] === b[head]) head += 1;
|
|
56
|
+
let endA = n;
|
|
57
|
+
let endB = m;
|
|
58
|
+
while (endA > head && endB > head && a[endA - 1] === b[endB - 1]) {
|
|
59
|
+
endA -= 1;
|
|
60
|
+
endB -= 1;
|
|
61
|
+
}
|
|
62
|
+
budget.steps += head + (n - endA) + 1;
|
|
63
|
+
const reversed = [];
|
|
64
|
+
const width = endA - head;
|
|
65
|
+
const height = endB - head;
|
|
66
|
+
let exact = true;
|
|
67
|
+
reversed.push([0, n - endA]);
|
|
68
|
+
if (width === 0 || height === 0) reversed.push([2, height], [1, width]);
|
|
69
|
+
else {
|
|
70
|
+
const at = (x) => a[head + x];
|
|
71
|
+
const bt = (y) => b[head + y];
|
|
72
|
+
const max = width + height;
|
|
73
|
+
const offset = max + 1;
|
|
74
|
+
const v = new Int32Array(2 * max + 3);
|
|
75
|
+
const trace = [];
|
|
76
|
+
let end;
|
|
77
|
+
rounds: for (let d = 0; d <= max; d += 1) {
|
|
78
|
+
for (let k = -d; k <= d; k += 2) {
|
|
79
|
+
budget.steps += 1;
|
|
80
|
+
if (budget.steps > budget.max) break rounds;
|
|
81
|
+
let x = k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1] ? v[offset + k + 1] : v[offset + k - 1] + 1;
|
|
82
|
+
let y = x - k;
|
|
83
|
+
while (x < width && y < height && at(x) === bt(y)) {
|
|
84
|
+
x += 1;
|
|
85
|
+
y += 1;
|
|
86
|
+
budget.steps += 1;
|
|
87
|
+
}
|
|
88
|
+
v[offset + k] = x;
|
|
89
|
+
if (x >= width && y >= height) {
|
|
90
|
+
end = {
|
|
91
|
+
x,
|
|
92
|
+
y,
|
|
93
|
+
d
|
|
94
|
+
};
|
|
95
|
+
break rounds;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
trace.push(v.slice(offset - d, offset + d + 1));
|
|
99
|
+
}
|
|
100
|
+
if (end === void 0) {
|
|
101
|
+
exact = false;
|
|
102
|
+
const d = trace.length - 1;
|
|
103
|
+
let x = 0;
|
|
104
|
+
let y = 0;
|
|
105
|
+
if (d >= 0) {
|
|
106
|
+
const band = trace[d];
|
|
107
|
+
for (let k = -d; k <= d; k += 2) {
|
|
108
|
+
const fx = band[k + d];
|
|
109
|
+
const fy = fx - k;
|
|
110
|
+
if (fx <= width && fy >= 0 && fy <= height && fx + fy > x + y) {
|
|
111
|
+
x = fx;
|
|
112
|
+
y = fy;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
reversed.push([2, height - y], [1, width - x]);
|
|
117
|
+
end = {
|
|
118
|
+
x,
|
|
119
|
+
y,
|
|
120
|
+
d: Math.max(0, d)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
let { x, y } = end;
|
|
124
|
+
for (let d = end.d; d > 0; d -= 1) {
|
|
125
|
+
const band = trace[d - 1];
|
|
126
|
+
const k = x - y;
|
|
127
|
+
const before = (kk) => band[kk + d - 1];
|
|
128
|
+
const down = k === -d || k !== d && before(k - 1) < before(k + 1);
|
|
129
|
+
const fromK = down ? k + 1 : k - 1;
|
|
130
|
+
const fromX = before(fromK);
|
|
131
|
+
const fromY = fromX - fromK;
|
|
132
|
+
const moved = down ? fromX : fromX + 1;
|
|
133
|
+
reversed.push([0, x - moved], down ? [2, 1] : [1, 1]);
|
|
134
|
+
x = fromX;
|
|
135
|
+
y = fromY;
|
|
136
|
+
}
|
|
137
|
+
reversed.push([0, x]);
|
|
138
|
+
}
|
|
139
|
+
reversed.push([0, head]);
|
|
140
|
+
const runs = [];
|
|
141
|
+
for (let at = reversed.length - 1; at >= 0; at -= 1) {
|
|
142
|
+
const [op, count] = reversed[at];
|
|
143
|
+
if (count <= 0) continue;
|
|
144
|
+
const last = runs.at(-1);
|
|
145
|
+
if (last !== void 0 && last.op === op) runs[runs.length - 1] = {
|
|
146
|
+
op,
|
|
147
|
+
count: last.count + count
|
|
148
|
+
};
|
|
149
|
+
else runs.push({
|
|
150
|
+
op,
|
|
151
|
+
count
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
runs,
|
|
156
|
+
exact
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
/** This function gives a number for each value, equal numbers for equal values. */
|
|
160
|
+
const numberer = () => {
|
|
161
|
+
const ids = /* @__PURE__ */ new Map();
|
|
162
|
+
return (value) => {
|
|
163
|
+
let id = ids.get(value);
|
|
164
|
+
if (id === void 0) {
|
|
165
|
+
id = ids.size;
|
|
166
|
+
ids.set(value, id);
|
|
167
|
+
}
|
|
168
|
+
return id;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
/** A token is a word, a run of spaces, or one sign. */
|
|
172
|
+
const TOKEN = /\s+|[\p{L}\p{N}\p{M}_]+|[^\s\p{L}\p{N}\p{M}_]/gu;
|
|
173
|
+
const tokensOf = (line) => line.match(TOKEN) ?? [];
|
|
174
|
+
/** This function counts the words of a text: each token that is not a space. */
|
|
175
|
+
const wordsOf = (text) => {
|
|
176
|
+
let count = 0;
|
|
177
|
+
for (const token of tokensOf(text)) if (!/^\s+$/.test(token)) count += 1;
|
|
178
|
+
return count;
|
|
179
|
+
};
|
|
180
|
+
/** This function gives the word diff of a changed line, or null when the line is too long or the budget ended. */
|
|
181
|
+
const wordDiff = (before, after, budget, limits) => {
|
|
182
|
+
if (budget.steps >= budget.max) return null;
|
|
183
|
+
const a = tokensOf(before);
|
|
184
|
+
const b = tokensOf(after);
|
|
185
|
+
if (a.length > limits.maxLineWords || b.length > limits.maxLineWords) return null;
|
|
186
|
+
const id = numberer();
|
|
187
|
+
const script = editScript(a.map((token) => id(token)), b.map((token) => id(token)), budget);
|
|
188
|
+
const parts = [];
|
|
189
|
+
const push = (op, text) => {
|
|
190
|
+
const last = parts.at(-1);
|
|
191
|
+
if (last !== void 0 && last.op === op) parts[parts.length - 1] = {
|
|
192
|
+
op,
|
|
193
|
+
text: last.text + text
|
|
194
|
+
};
|
|
195
|
+
else parts.push({
|
|
196
|
+
op,
|
|
197
|
+
text
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
let i = 0;
|
|
201
|
+
let j = 0;
|
|
202
|
+
for (const run of script.runs) if (run.op === 0) {
|
|
203
|
+
push("same", a.slice(i, i + run.count).join(""));
|
|
204
|
+
i += run.count;
|
|
205
|
+
j += run.count;
|
|
206
|
+
} else if (run.op === 1) {
|
|
207
|
+
push("removed", a.slice(i, i + run.count).join(""));
|
|
208
|
+
i += run.count;
|
|
209
|
+
} else {
|
|
210
|
+
push("added", b.slice(j, j + run.count).join(""));
|
|
211
|
+
j += run.count;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
parts,
|
|
215
|
+
exact: script.exact
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
219
|
+
const keyPath = (path, key) => IDENTIFIER.test(key) ? path === "" ? key : `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
|
|
220
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
221
|
+
/** This function gives the changes at paths between two values, to the limits. */
|
|
222
|
+
const metaChanges = (before, after, limits) => {
|
|
223
|
+
const changes = [];
|
|
224
|
+
let count = 0;
|
|
225
|
+
const show = (value) => {
|
|
226
|
+
const text = stableStringify(value);
|
|
227
|
+
return text.length > limits.maxValueChars ? `${text.slice(0, limits.maxValueChars)}…` : text;
|
|
228
|
+
};
|
|
229
|
+
const note = (path, change, a, b) => {
|
|
230
|
+
count += 1;
|
|
231
|
+
if (changes.length < limits.maxMetaChanges) changes.push({
|
|
232
|
+
path,
|
|
233
|
+
change,
|
|
234
|
+
before: change === "added" ? null : show(a),
|
|
235
|
+
after: change === "removed" ? null : show(b)
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
const walk = (a, b, path, depth) => {
|
|
239
|
+
if (a === b) return;
|
|
240
|
+
if (depth >= limits.maxMetaDepth) {
|
|
241
|
+
if (stableStringify(a) !== stableStringify(b)) note(path, "changed", a, b);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (isRecord(a) && isRecord(b)) {
|
|
245
|
+
const keys = [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].sort();
|
|
246
|
+
for (const key of keys) {
|
|
247
|
+
const inA = Object.hasOwn(a, key) && a[key] !== void 0;
|
|
248
|
+
const inB = Object.hasOwn(b, key) && b[key] !== void 0;
|
|
249
|
+
const at = keyPath(path, key);
|
|
250
|
+
if (inA && !inB) note(at, "removed", a[key], void 0);
|
|
251
|
+
else if (!inA && inB) note(at, "added", void 0, b[key]);
|
|
252
|
+
else if (inA && inB) walk(a[key], b[key], at, depth + 1);
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
257
|
+
const id = numberer();
|
|
258
|
+
const script = editScript(a.map((item) => id(stableStringify(item))), b.map((item) => id(stableStringify(item))), {
|
|
259
|
+
steps: 0,
|
|
260
|
+
max: 1e5
|
|
261
|
+
});
|
|
262
|
+
let i = 0;
|
|
263
|
+
let j = 0;
|
|
264
|
+
const removed = [];
|
|
265
|
+
const added = [];
|
|
266
|
+
const flush = () => {
|
|
267
|
+
const pairs = Math.min(removed.length, added.length);
|
|
268
|
+
for (let at = 0; at < pairs; at += 1) walk(a[removed[at]], b[added[at]], `${path}[${String(added[at])}]`, depth + 1);
|
|
269
|
+
for (const at of removed.slice(pairs)) note(`${path}[${String(at)}]`, "removed", a[at], void 0);
|
|
270
|
+
for (const at of added.slice(pairs)) note(`${path}[${String(at)}]`, "added", void 0, b[at]);
|
|
271
|
+
removed.length = 0;
|
|
272
|
+
added.length = 0;
|
|
273
|
+
};
|
|
274
|
+
for (const run of script.runs) if (run.op === 0) {
|
|
275
|
+
flush();
|
|
276
|
+
i += run.count;
|
|
277
|
+
j += run.count;
|
|
278
|
+
} else if (run.op === 1) {
|
|
279
|
+
for (let at = 0; at < run.count; at += 1) removed.push(i + at);
|
|
280
|
+
i += run.count;
|
|
281
|
+
} else {
|
|
282
|
+
for (let at = 0; at < run.count; at += 1) added.push(j + at);
|
|
283
|
+
j += run.count;
|
|
284
|
+
}
|
|
285
|
+
flush();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (stableStringify(a) !== stableStringify(b)) note(path, "changed", a, b);
|
|
289
|
+
};
|
|
290
|
+
walk(before, after, "", 0);
|
|
291
|
+
return {
|
|
292
|
+
changes,
|
|
293
|
+
count
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
/** This function gives the limits of a diff, with the defaults of `DIFF_LIMITS` for the limits that it does not get. */
|
|
297
|
+
const limitsOf = (given) => {
|
|
298
|
+
const limits = { ...DIFF_LIMITS };
|
|
299
|
+
for (const [name, value] of Object.entries(given)) {
|
|
300
|
+
if (value === void 0) continue;
|
|
301
|
+
if (!Number.isInteger(value) || value < 0) throw new RangeError(`the diff limit ${name} must be an integer of 0 or more`);
|
|
302
|
+
limits[name] = value;
|
|
303
|
+
}
|
|
304
|
+
return limits;
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* This function compares two versions: the lines of the text, the words of each changed line, and
|
|
308
|
+
* the fields (the title, the URL and the metadata) at their paths. It writes nothing, and its time
|
|
309
|
+
* and its result stay inside the limits.
|
|
310
|
+
*/
|
|
311
|
+
const diffTexts = (before, after, given = {}) => {
|
|
312
|
+
const limits = limitsOf(given);
|
|
313
|
+
const notes = [];
|
|
314
|
+
let truncated = false;
|
|
315
|
+
const cut = (text, side) => {
|
|
316
|
+
let kept = text;
|
|
317
|
+
if (kept.length > limits.maxChars) {
|
|
318
|
+
kept = kept.slice(0, limits.maxChars);
|
|
319
|
+
truncated = true;
|
|
320
|
+
notes.push(`the ${side} text is longer than ${String(limits.maxChars)} characters, so the diff compares its first part`);
|
|
321
|
+
}
|
|
322
|
+
const lines = kept.length === 0 ? [] : kept.split("\n");
|
|
323
|
+
if (lines.length > limits.maxLines) {
|
|
324
|
+
truncated = true;
|
|
325
|
+
notes.push(`the ${side} text has more than ${String(limits.maxLines)} lines, so the diff compares its first lines`);
|
|
326
|
+
return lines.slice(0, limits.maxLines);
|
|
327
|
+
}
|
|
328
|
+
return lines;
|
|
329
|
+
};
|
|
330
|
+
const a = cut(before.text, "old");
|
|
331
|
+
const b = cut(after.text, "new");
|
|
332
|
+
const id = numberer();
|
|
333
|
+
const lineBudget = {
|
|
334
|
+
steps: 0,
|
|
335
|
+
max: limits.maxSteps
|
|
336
|
+
};
|
|
337
|
+
const script = editScript(a.map((line) => id(line)), b.map((line) => id(line)), lineBudget);
|
|
338
|
+
let exact = script.exact;
|
|
339
|
+
if (!script.exact) notes.push(`the change is too large for a minimal diff in ${String(limits.maxSteps)} steps, so the diff shows its last part as one removal and one addition`);
|
|
340
|
+
const wordBudget = {
|
|
341
|
+
steps: 0,
|
|
342
|
+
max: limits.maxWordSteps
|
|
343
|
+
};
|
|
344
|
+
const entries = [];
|
|
345
|
+
let linesAdded = 0;
|
|
346
|
+
let linesRemoved = 0;
|
|
347
|
+
let linesChanged = 0;
|
|
348
|
+
let wordsAdded = 0;
|
|
349
|
+
let wordsRemoved = 0;
|
|
350
|
+
let wordsCut = false;
|
|
351
|
+
let i = 0;
|
|
352
|
+
let j = 0;
|
|
353
|
+
const removed = [];
|
|
354
|
+
const added = [];
|
|
355
|
+
const flush = () => {
|
|
356
|
+
const pairs = Math.min(removed.length, added.length);
|
|
357
|
+
for (let at = 0; at < pairs; at += 1) {
|
|
358
|
+
const from = removed[at];
|
|
359
|
+
const to = added[at];
|
|
360
|
+
const words = wordDiff(a[from], b[to], wordBudget, limits);
|
|
361
|
+
if (words === null) {
|
|
362
|
+
wordsCut = true;
|
|
363
|
+
wordsRemoved += wordsOf(a[from]);
|
|
364
|
+
wordsAdded += wordsOf(b[to]);
|
|
365
|
+
} else {
|
|
366
|
+
if (!words.exact) exact = false;
|
|
367
|
+
for (const part of words.parts) if (part.op === "added") wordsAdded += wordsOf(part.text);
|
|
368
|
+
else if (part.op === "removed") wordsRemoved += wordsOf(part.text);
|
|
369
|
+
}
|
|
370
|
+
entries.push({
|
|
371
|
+
op: "changed",
|
|
372
|
+
a: from,
|
|
373
|
+
b: to,
|
|
374
|
+
words: words?.parts ?? null
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
for (const from of removed.slice(pairs)) {
|
|
378
|
+
wordsRemoved += wordsOf(a[from]);
|
|
379
|
+
entries.push({
|
|
380
|
+
op: "removed",
|
|
381
|
+
a: from
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
for (const to of added.slice(pairs)) {
|
|
385
|
+
wordsAdded += wordsOf(b[to]);
|
|
386
|
+
entries.push({
|
|
387
|
+
op: "added",
|
|
388
|
+
b: to
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
linesChanged += pairs;
|
|
392
|
+
removed.length = 0;
|
|
393
|
+
added.length = 0;
|
|
394
|
+
};
|
|
395
|
+
for (const run of script.runs) if (run.op === 0) {
|
|
396
|
+
flush();
|
|
397
|
+
for (let at = 0; at < run.count; at += 1) entries.push({
|
|
398
|
+
op: "same",
|
|
399
|
+
a: i + at,
|
|
400
|
+
b: j + at
|
|
401
|
+
});
|
|
402
|
+
i += run.count;
|
|
403
|
+
j += run.count;
|
|
404
|
+
} else if (run.op === 1) {
|
|
405
|
+
for (let at = 0; at < run.count; at += 1) removed.push(i + at);
|
|
406
|
+
linesRemoved += run.count;
|
|
407
|
+
i += run.count;
|
|
408
|
+
} else {
|
|
409
|
+
for (let at = 0; at < run.count; at += 1) added.push(j + at);
|
|
410
|
+
linesAdded += run.count;
|
|
411
|
+
j += run.count;
|
|
412
|
+
}
|
|
413
|
+
flush();
|
|
414
|
+
if (wordsCut) {
|
|
415
|
+
exact = false;
|
|
416
|
+
notes.push("some changed lines have no word diff: they are too long, or the word budget ended");
|
|
417
|
+
}
|
|
418
|
+
const hunks = [];
|
|
419
|
+
let shown = 0;
|
|
420
|
+
let current;
|
|
421
|
+
const lineOf = (entry) => {
|
|
422
|
+
switch (entry.op) {
|
|
423
|
+
case "same": return {
|
|
424
|
+
op: "same",
|
|
425
|
+
text: a[entry.a]
|
|
426
|
+
};
|
|
427
|
+
case "removed": return {
|
|
428
|
+
op: "removed",
|
|
429
|
+
text: a[entry.a]
|
|
430
|
+
};
|
|
431
|
+
case "added": return {
|
|
432
|
+
op: "added",
|
|
433
|
+
text: b[entry.b]
|
|
434
|
+
};
|
|
435
|
+
default: return {
|
|
436
|
+
op: "changed",
|
|
437
|
+
before: a[entry.a],
|
|
438
|
+
after: b[entry.b],
|
|
439
|
+
words: entry.words
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const close = () => {
|
|
444
|
+
if (current === void 0) return;
|
|
445
|
+
hunks.push({
|
|
446
|
+
from_line: current.from + 1,
|
|
447
|
+
from_count: current.a,
|
|
448
|
+
to_line: current.to + 1,
|
|
449
|
+
to_count: current.b,
|
|
450
|
+
lines: current.lines
|
|
451
|
+
});
|
|
452
|
+
current = void 0;
|
|
453
|
+
};
|
|
454
|
+
let posA = 0;
|
|
455
|
+
let posB = 0;
|
|
456
|
+
const positions = [];
|
|
457
|
+
for (const entry of entries) {
|
|
458
|
+
positions.push([posA, posB]);
|
|
459
|
+
if (entry.op !== "added") posA += 1;
|
|
460
|
+
if (entry.op !== "removed") posB += 1;
|
|
461
|
+
}
|
|
462
|
+
const changeAt = [];
|
|
463
|
+
for (const [at, entry] of entries.entries()) if (entry.op !== "same") changeAt.push(at);
|
|
464
|
+
let hunkCut = false;
|
|
465
|
+
for (const at of changeAt) {
|
|
466
|
+
if (shown >= limits.maxHunkLines) {
|
|
467
|
+
hunkCut = true;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
const start = Math.max(0, at - limits.context);
|
|
471
|
+
const stop = Math.min(entries.length - 1, at + limits.context);
|
|
472
|
+
if (current !== void 0 && start > current.end + 1) close();
|
|
473
|
+
if (current === void 0) {
|
|
474
|
+
const [fromA, fromB] = positions[start];
|
|
475
|
+
current = {
|
|
476
|
+
lines: [],
|
|
477
|
+
from: fromA,
|
|
478
|
+
to: fromB,
|
|
479
|
+
a: 0,
|
|
480
|
+
b: 0,
|
|
481
|
+
end: start - 1
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
for (let line = current.end + 1; line <= stop; line += 1) {
|
|
485
|
+
if (shown >= limits.maxHunkLines) {
|
|
486
|
+
hunkCut = true;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
const entry = entries[line];
|
|
490
|
+
current.lines.push(lineOf(entry));
|
|
491
|
+
if (entry.op !== "added") current.a += 1;
|
|
492
|
+
if (entry.op !== "removed") current.b += 1;
|
|
493
|
+
current.end = line;
|
|
494
|
+
shown += 1;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
close();
|
|
498
|
+
if (hunkCut) {
|
|
499
|
+
truncated = true;
|
|
500
|
+
notes.push(`the hunks show ${String(limits.maxHunkLines)} lines, and the summary counts each line`);
|
|
501
|
+
}
|
|
502
|
+
const fields = metaChanges(before.fields, after.fields, limits);
|
|
503
|
+
if (fields.count > fields.changes.length) {
|
|
504
|
+
truncated = true;
|
|
505
|
+
notes.push(`the result shows ${String(fields.changes.length)} of ${String(fields.count)} changes of the metadata`);
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
summary: {
|
|
509
|
+
lines_added: linesAdded,
|
|
510
|
+
lines_removed: linesRemoved,
|
|
511
|
+
lines_changed: linesChanged,
|
|
512
|
+
words_added: wordsAdded,
|
|
513
|
+
words_removed: wordsRemoved,
|
|
514
|
+
meta_changes: fields.count
|
|
515
|
+
},
|
|
516
|
+
hunks,
|
|
517
|
+
meta: fields.changes,
|
|
518
|
+
same: linesAdded === 0 && linesRemoved === 0 && fields.count === 0 && before.text === after.text,
|
|
519
|
+
exact,
|
|
520
|
+
truncated,
|
|
521
|
+
steps: lineBudget.steps + wordBudget.steps,
|
|
522
|
+
notes
|
|
523
|
+
};
|
|
524
|
+
};
|
|
525
|
+
/** This function gives the version of a choice, or throws an error that names the problem. */
|
|
526
|
+
const chosen = (runtime, id, choice) => {
|
|
527
|
+
if (typeof choice === "string") {
|
|
528
|
+
const version = runtime.index.versionText(id, choice);
|
|
529
|
+
if (version === void 0) throw new Error(`the document ${id} has no version from the capture ${choice}`);
|
|
530
|
+
return {
|
|
531
|
+
version,
|
|
532
|
+
at: null
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
if (!isRecord(choice) || choice.at === void 0) throw new RangeError("a version is a capture id, or an object with the time at");
|
|
536
|
+
const at = isoTime("at", choice.at);
|
|
537
|
+
const found = versionAtTime(runtime, id, at);
|
|
538
|
+
if (found.version === void 0) throw new Error(`the document ${id} has no version at ${at} or before${found.first === void 0 ? "" : `: its first capture is from ${found.first}`}`);
|
|
539
|
+
return {
|
|
540
|
+
version: found.version,
|
|
541
|
+
at
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
const sideOf = (version, at, held) => ({
|
|
545
|
+
capture: version.capture,
|
|
546
|
+
fetched_at: version.fetched_at,
|
|
547
|
+
chars: version.chars,
|
|
548
|
+
rendered: version.rendered,
|
|
549
|
+
held_back: held,
|
|
550
|
+
at
|
|
551
|
+
});
|
|
552
|
+
/**
|
|
553
|
+
* This function compares two versions of a stored document, and writes nothing. It applies the
|
|
554
|
+
* rule for a held-back document. When the document or a version is held back, the diff compares
|
|
555
|
+
* only the structural metadata, and it shows no text and no title.
|
|
556
|
+
*/
|
|
557
|
+
const diffDocument = async (runtime, id, options = {}) => {
|
|
558
|
+
await Promise.resolve();
|
|
559
|
+
const doc = runtime.index.get(id);
|
|
560
|
+
if (doc === void 0) throw new Error(`no stored document ${id}: find one with find(), or fetch its page`);
|
|
561
|
+
let to;
|
|
562
|
+
if (options.to === void 0) {
|
|
563
|
+
const newest = runtime.index.versions(id, { limit: 1 })[0];
|
|
564
|
+
const version = newest === void 0 ? void 0 : runtime.index.versionText(id, newest.capture);
|
|
565
|
+
if (version === void 0) throw new Error(`the document ${id} has no version`);
|
|
566
|
+
to = {
|
|
567
|
+
version,
|
|
568
|
+
at: null
|
|
569
|
+
};
|
|
570
|
+
} else to = chosen(runtime, id, options.to);
|
|
571
|
+
let from;
|
|
572
|
+
if (options.from === void 0) {
|
|
573
|
+
const earlier = runtime.index.versions(id, { limit: 1e3 }).find((version) => version.capture !== to.version.capture && version.recipe === to.version.recipe && version.rendered === to.version.rendered && version.fetched_at <= to.version.fetched_at);
|
|
574
|
+
const version = earlier === void 0 ? void 0 : runtime.index.versionText(id, earlier.capture);
|
|
575
|
+
if (version !== void 0) from = {
|
|
576
|
+
version,
|
|
577
|
+
at: null
|
|
578
|
+
};
|
|
579
|
+
} else from = chosen(runtime, id, options.from);
|
|
580
|
+
const notes = [];
|
|
581
|
+
if (from === void 0) {
|
|
582
|
+
from = to;
|
|
583
|
+
notes.push(`the document ${id} has no earlier version of the same kind to compare`);
|
|
584
|
+
}
|
|
585
|
+
const docHeld = isHeldBack(doc);
|
|
586
|
+
const fromHeld = docHeld || isHeldBack(from.version);
|
|
587
|
+
const toHeld = docHeld || isHeldBack(to.version);
|
|
588
|
+
const held = fromHeld || toHeld;
|
|
589
|
+
const fields = (version) => held ? {
|
|
590
|
+
kind: version.kind,
|
|
591
|
+
url: version.url,
|
|
592
|
+
meta: heldBackMeta(version.meta)
|
|
593
|
+
} : {
|
|
594
|
+
title: version.title,
|
|
595
|
+
kind: version.kind,
|
|
596
|
+
url: version.url,
|
|
597
|
+
meta: version.meta
|
|
598
|
+
};
|
|
599
|
+
const diff = diffTexts({
|
|
600
|
+
text: held ? "" : from.version.text,
|
|
601
|
+
fields: fields(from.version)
|
|
602
|
+
}, {
|
|
603
|
+
text: held ? "" : to.version.text,
|
|
604
|
+
fields: fields(to.version)
|
|
605
|
+
}, options.limits ?? {});
|
|
606
|
+
if (held) notes.unshift(HELD_BACK);
|
|
607
|
+
notes.push(...diff.notes);
|
|
608
|
+
return {
|
|
609
|
+
doc: doc.doc,
|
|
610
|
+
url: to.version.url ?? doc.url,
|
|
611
|
+
from: sideOf(from.version, from.at, fromHeld),
|
|
612
|
+
to: sideOf(to.version, to.at, toHeld),
|
|
613
|
+
same: held ? null : diff.same,
|
|
614
|
+
summary: diff.summary,
|
|
615
|
+
hunks: diff.hunks,
|
|
616
|
+
meta: diff.meta,
|
|
617
|
+
held_back: held,
|
|
618
|
+
exact: diff.exact,
|
|
619
|
+
truncated: diff.truncated,
|
|
620
|
+
steps: diff.steps,
|
|
621
|
+
note: notes.length > 0 ? notes.join("; ") : null
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
/** This function gives the text of the word diff of a changed line: `[-removed-]` and `{+added+}` mark the words. */
|
|
625
|
+
const wordsText = (line) => line.words === null ? `[-${line.before}-]{+${line.after}+}` : line.words.map((part) => part.op === "same" ? part.text : part.op === "added" ? `{+${part.text}+}` : `[-${part.text}-]`).join("");
|
|
626
|
+
/**
|
|
627
|
+
* This function gives the text of a diff for a person or an assistant. The text has the counts, the
|
|
628
|
+
* hunks as a unified diff, and the changes of the metadata. A changed line marks its words with `[-removed-]`
|
|
629
|
+
* and `{+added+}`. The text holds the text of the pages, so the MCP server puts it in an envelope.
|
|
630
|
+
*/
|
|
631
|
+
const formatDiff = (diff) => {
|
|
632
|
+
const { summary } = diff;
|
|
633
|
+
const lines = [`the text: ${String(summary.lines_added)} lines added, ${String(summary.lines_removed)} removed (${String(summary.lines_changed)} changed), ${String(summary.words_added)} words added, ${String(summary.words_removed)} removed`];
|
|
634
|
+
for (const hunk of diff.hunks) {
|
|
635
|
+
lines.push(`@@ -${String(hunk.from_line)},${String(hunk.from_count)} +${String(hunk.to_line)},${String(hunk.to_count)} @@`);
|
|
636
|
+
for (const line of hunk.lines) if (line.op === "changed") lines.push(`~ ${wordsText(line)}`);
|
|
637
|
+
else lines.push(`${line.op === "same" ? " " : line.op === "added" ? "+" : "-"} ${line.text}`);
|
|
638
|
+
}
|
|
639
|
+
lines.push(`the metadata: ${String(summary.meta_changes)} changes`);
|
|
640
|
+
for (const change of diff.meta) if (change.change === "added") lines.push(`+ ${change.path}: ${change.after ?? ""}`);
|
|
641
|
+
else if (change.change === "removed") lines.push(`- ${change.path}: ${change.before ?? ""}`);
|
|
642
|
+
else lines.push(`~ ${change.path}: ${change.before ?? ""} -> ${change.after ?? ""}`);
|
|
643
|
+
return lines.join("\n");
|
|
644
|
+
};
|
|
645
|
+
//#endregion
|
|
646
|
+
export { formatDiff as a, diff_exports as i, diffDocument as n, diffTexts as r, DIFF_LIMITS as t };
|
|
647
|
+
|
|
648
|
+
//# sourceMappingURL=diff-NQcQqKe9.mjs.map
|