@davmash/bygone 0.10.1 → 0.11.1

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +64 -0
  3. package/bin/bygone.js +18 -1
  4. package/cli/commandSpec.js +22 -2
  5. package/cli/completions.js +30 -8
  6. package/cli/correspondence.js +71 -0
  7. package/cli/dependencies.js +62 -0
  8. package/cli/evidence.js +67 -0
  9. package/cli/present.js +61 -16
  10. package/cli/tour.js +18 -1
  11. package/cli/tourExport.js +1 -1
  12. package/completions/_bygone +15 -3
  13. package/completions/bygone +13 -5
  14. package/completions/bygone.fish +9 -0
  15. package/docs/change-tour-format.md +60 -6
  16. package/docs/correspondence.md +259 -0
  17. package/docs/dependencies.md +94 -0
  18. package/docs/evidence-review.md +163 -0
  19. package/docs/generating-change-tours.md +3 -0
  20. package/docs/present.md +3 -0
  21. package/docs/product-surface.md +2 -1
  22. package/docs/releasing.md +11 -0
  23. package/docs/vscode-integration.md +183 -0
  24. package/media/diff.worker.js +10 -9
  25. package/media/webview.css +1 -1
  26. package/media/webview.js +167 -167
  27. package/out/changeCorrespondence.js +739 -0
  28. package/out/changeTour.js +1366 -967
  29. package/out/dependencyGraph.js +213939 -0
  30. package/out/gitComparison.js +4 -0
  31. package/out/requirements.js +427 -0
  32. package/out/reviewDependencies.js +669 -0
  33. package/out/reviewWorkspace.js +1129 -0
  34. package/out/standalone-main.js +3382 -1341
  35. package/out/tourCoverage.js +1852 -1448
  36. package/package.json +2 -2
  37. package/schemas/change-tour-source.schema.json +48 -3
  38. package/web/export-runtime.json +6 -6
  39. package/web/index.html +13 -0
  40. package/web/presenter.css +81 -0
  41. package/web/web-host.js +23 -20
@@ -45,11 +45,13 @@ var import_child_process = require("child_process");
45
45
  var fs = __toESM(require("fs"));
46
46
  var path = __toESM(require("path"));
47
47
  var DEFAULT_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
48
+ var DEFAULT_GIT_TIMEOUT_MS = 3e4;
48
49
  function createGitRunner(maxBuffer = readGitMaxBuffer()) {
49
50
  return (args, cwd) => (0, import_child_process.execFileSync)("git", [...args], {
50
51
  cwd,
51
52
  encoding: "utf8",
52
53
  maxBuffer,
54
+ timeout: DEFAULT_GIT_TIMEOUT_MS,
53
55
  stdio: ["ignore", "pipe", "pipe"]
54
56
  }).trimEnd();
55
57
  }
@@ -306,6 +308,7 @@ function readGitBlob(repoRoot, commit, relativePath) {
306
308
  cwd: repoRoot,
307
309
  encoding: "buffer",
308
310
  maxBuffer: readGitMaxBuffer(),
311
+ timeout: DEFAULT_GIT_TIMEOUT_MS,
309
312
  stdio: ["ignore", "pipe", "pipe"]
310
313
  });
311
314
  }
@@ -314,6 +317,7 @@ function runGitText(repoRoot, args) {
314
317
  cwd: repoRoot,
315
318
  encoding: "utf8",
316
319
  maxBuffer: readGitMaxBuffer(),
320
+ timeout: DEFAULT_GIT_TIMEOUT_MS,
317
321
  stdio: ["ignore", "pipe", "pipe"]
318
322
  });
319
323
  }
@@ -0,0 +1,427 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // packages/strand/src/input.js
38
+ var require_input = __commonJS({
39
+ "packages/strand/src/input.js"(exports2, module2) {
40
+ "use strict";
41
+ var LIMITS = Object.freeze({ maxFiles: 50, maxFileBytes: 1048576, maxBytes: 4194304, maxRecords: 2e4, timeoutMs: 3e4, maxOutputBytes: 16777216 });
42
+ function inputProblem(before, after, limits) {
43
+ if (!before || !after || typeof before !== "object" || typeof after !== "object" || Array.isArray(before) || Array.isArray(after)) return "invalid-snapshot-map";
44
+ if ((/* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])).size > Math.min(limits.maxFiles, LIMITS.maxFiles)) return "file-budget";
45
+ let bytes = 0;
46
+ for (const files of [before, after]) for (const value of Object.values(files)) {
47
+ if (typeof value !== "string") return "invalid-snapshot-text";
48
+ const size = Buffer.byteLength(value);
49
+ bytes += size;
50
+ if (size > Math.min(limits.maxFileBytes, LIMITS.maxFileBytes)) return "file-byte-budget";
51
+ }
52
+ return bytes > Math.min(limits.maxBytes, LIMITS.maxBytes) ? "comparison-byte-budget" : null;
53
+ }
54
+ function boundedProcessOptions(options, timeoutKey, outputKey, defaultTimeout) {
55
+ const timeout = options[timeoutKey] === void 0 ? defaultTimeout : options[timeoutKey];
56
+ const maxBuffer = options[outputKey] === void 0 ? LIMITS.maxOutputBytes : options[outputKey];
57
+ if (![timeout, maxBuffer].every((value) => Number.isSafeInteger(value) && value > 0)) return null;
58
+ return { timeout: Math.min(timeout, defaultTimeout), maxBuffer: Math.min(maxBuffer, LIMITS.maxOutputBytes) };
59
+ }
60
+ module2.exports = { LIMITS, inputProblem, boundedProcessOptions };
61
+ }
62
+ });
63
+
64
+ // packages/strand/src/source-references.js
65
+ var require_source_references = __commonJS({
66
+ "packages/strand/src/source-references.js"(exports2, module2) {
67
+ "use strict";
68
+ var { createHash: createHash2 } = require("node:crypto");
69
+ var { inputProblem, LIMITS } = require_input();
70
+ var digest2 = (text) => createHash2("sha256").update(text, "utf8").digest("hex");
71
+ var key = (ref) => JSON.stringify([ref.revision, ref.path, ref.start, ref.end, ref.digest]);
72
+ function identifier(value) {
73
+ if (typeof value !== "string" || !value.trim() || value.length > 1e5) {
74
+ throw new TypeError("Expected bounded nonempty identifier");
75
+ }
76
+ return value;
77
+ }
78
+ function parseSourceRef2(value) {
79
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected source reference");
80
+ const revision2 = identifier(value.revision), path = identifier(value.path);
81
+ if (path.startsWith("/") || /^[A-Za-z]:/.test(path) || path.includes("\\") || path.includes("\0") || path.split("/").some((part) => !part || part === "." || part === "..")) {
82
+ throw new TypeError("Invalid relative source path");
83
+ }
84
+ if (!Number.isSafeInteger(value.start) || !Number.isSafeInteger(value.end) || value.start < 0 || value.end <= value.start) {
85
+ throw new TypeError("Invalid UTF-16 range");
86
+ }
87
+ if (typeof value.digest !== "string" || !/^[a-f0-9]{64}$/.test(value.digest)) throw new TypeError("Expected SHA-256");
88
+ return { revision: revision2, path, start: value.start, end: value.end, digest: value.digest };
89
+ }
90
+ function checkSnapshot(snapshot) {
91
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) throw new TypeError("Expected snapshot");
92
+ identifier(snapshot.revision);
93
+ const problem = inputProblem(snapshot.files, {}, LIMITS);
94
+ if (problem) throw new TypeError(problem);
95
+ }
96
+ function sourceAt(snapshot, path) {
97
+ return Object.prototype.hasOwnProperty.call(snapshot.files, path) ? snapshot.files[path] : void 0;
98
+ }
99
+ function pinSourceRef(snapshot, path, start, end) {
100
+ checkSnapshot(snapshot);
101
+ const ref = parseSourceRef2({ revision: snapshot.revision, path, start, end, digest: "0".repeat(64) });
102
+ const text = sourceAt(snapshot, ref.path);
103
+ if (typeof text !== "string" || end > text.length) throw new TypeError("Source is unavailable or range exceeds file");
104
+ return { ...ref, digest: digest2(text.slice(start, end)) };
105
+ }
106
+ function resolveChecked(ref, snapshot) {
107
+ if (snapshot.revision !== ref.revision) return { status: "broken", reason: "Revision unavailable in this comparison" };
108
+ const text = sourceAt(snapshot, ref.path);
109
+ if (typeof text !== "string" || ref.end > text.length) return { status: "broken", reason: "Source path or range unavailable" };
110
+ const slice = text.slice(ref.start, ref.end);
111
+ if (digest2(slice) !== ref.digest) return { status: "broken", reason: "Source digest mismatch" };
112
+ return { status: "valid", text: slice };
113
+ }
114
+ function resolveSourceRef2(value, snapshot) {
115
+ const ref = parseSourceRef2(value);
116
+ checkSnapshot(snapshot);
117
+ return resolveChecked(ref, snapshot);
118
+ }
119
+ function boundedList(value) {
120
+ if (!Array.isArray(value) || value.length > LIMITS.maxRecords) throw new TypeError("Expected bounded reference array");
121
+ return value;
122
+ }
123
+ function relocateSourceRefs2(values, before, after, pairs) {
124
+ checkSnapshot(before);
125
+ checkSnapshot(after);
126
+ const problem = inputProblem(before.files, after.files, LIMITS);
127
+ if (problem) throw new TypeError(problem);
128
+ if (before.revision === after.revision) throw new TypeError("Relocation requires distinct snapshot revisions");
129
+ const refs = boundedList(values).map(parseSourceRef2);
130
+ const candidates = boundedList(pairs).map((pair) => {
131
+ if (!pair || typeof pair !== "object") throw new TypeError("Expected correspondence pair");
132
+ return { before: parseSourceRef2(pair.before), after: parseSourceRef2(pair.after) };
133
+ });
134
+ const destinations = /* @__PURE__ */ new Map(), usedAfter = /* @__PURE__ */ new Set();
135
+ for (const pair of candidates) {
136
+ const from = key(pair.before), to = key(pair.after);
137
+ if (destinations.has(from) || usedAfter.has(to)) throw new TypeError("Duplicate correspondence endpoint");
138
+ destinations.set(from, pair.after);
139
+ usedAfter.add(to);
140
+ }
141
+ for (const pair of candidates) {
142
+ if (resolveChecked(pair.before, before).status !== "valid" || resolveChecked(pair.after, after).status !== "valid") {
143
+ throw new TypeError("Invalid correspondence endpoint");
144
+ }
145
+ }
146
+ return refs.map((ref) => {
147
+ let destination;
148
+ if (resolveChecked(ref, before).status === "valid") {
149
+ destination = sourceAt(before, ref.path) === sourceAt(after, ref.path) ? { ...ref, revision: after.revision } : destinations.get(key(ref));
150
+ }
151
+ if (!destination) return { status: "unresolved", before: ref };
152
+ const status = ref.digest !== destination.digest ? "changed" : ref.path !== destination.path || ref.start !== destination.start ? "relocated" : "unchanged";
153
+ return { status, before: ref, after: { ...destination } };
154
+ });
155
+ }
156
+ module2.exports = { parseSourceRef: parseSourceRef2, pinSourceRef, resolveSourceRef: resolveSourceRef2, relocateSourceRefs: relocateSourceRefs2 };
157
+ }
158
+ });
159
+
160
+ // packages/strand/source-references.js
161
+ var require_source_references2 = __commonJS({
162
+ "packages/strand/source-references.js"(exports2, module2) {
163
+ "use strict";
164
+ module2.exports = require_source_references();
165
+ }
166
+ });
167
+
168
+ // src/requirements.ts
169
+ var requirements_exports = {};
170
+ __export(requirements_exports, {
171
+ correspondenceEvidence: () => correspondenceEvidence,
172
+ digest: () => digest,
173
+ fingerprint: () => fingerprint,
174
+ inspect: () => inspect,
175
+ parseReview: () => parseReview,
176
+ refresh: () => refresh,
177
+ requirementFingerprint: () => requirementFingerprint
178
+ });
179
+ module.exports = __toCommonJS(requirements_exports);
180
+ var import_crypto = require("crypto");
181
+ var import_source_references = __toESM(require_source_references2());
182
+ var digest = (text) => (0, import_crypto.createHash)("sha256").update(text, "utf8").digest("hex");
183
+ function record(value) {
184
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected object");
185
+ return value;
186
+ }
187
+ function string(value) {
188
+ if (typeof value !== "string" || !value.trim() || value.length > 1e5) throw new Error("Expected bounded nonempty string");
189
+ return value;
190
+ }
191
+ function list(value) {
192
+ if (!Array.isArray(value) || value.length > 1e3) throw new Error("Expected bounded array");
193
+ return value;
194
+ }
195
+ function choice(value, choices) {
196
+ if (!choices.includes(value)) throw new Error(`Expected ${choices.join("/")}`);
197
+ return value;
198
+ }
199
+ function hash(value) {
200
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) throw new Error("Expected SHA-256");
201
+ return value;
202
+ }
203
+ function revision(value) {
204
+ if (typeof value !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value)) throw new Error("Expected immutable Git revision");
205
+ return value;
206
+ }
207
+ function sourceRef(value) {
208
+ const ref = (0, import_source_references.parseSourceRef)(value);
209
+ revision(ref.revision);
210
+ return ref;
211
+ }
212
+ function unique(values) {
213
+ if (new Set(values).size !== values.length) throw new Error("Duplicate ID");
214
+ }
215
+ function sources(value) {
216
+ const result = list(value).map((value2) => {
217
+ const s = record(value2);
218
+ const source = { title: string(s.title), text: string(s.text), quote: string(s.quote) };
219
+ if (!source.text.includes(source.quote)) throw new Error("Source quotation does not exist");
220
+ return source;
221
+ });
222
+ if (!result.length) throw new Error("Expected at least one source");
223
+ return result;
224
+ }
225
+ function parseAnalysis(value) {
226
+ const a = record(value), scope = list(a.scope).map(string);
227
+ if (!scope.length) throw new Error("Expected analysis scope");
228
+ return {
229
+ base: revision(a.base),
230
+ head: revision(a.head),
231
+ producer: string(a.producer),
232
+ status: choice(a.status, ["complete", "partial", "unavailable"]),
233
+ scope,
234
+ limitations: list(a.limitations).map(string),
235
+ artifactDigest: hash(a.artifactDigest)
236
+ };
237
+ }
238
+ function parseRequirement(value) {
239
+ const r = record(value);
240
+ return { id: string(r.id), text: string(r.text), sources: sources(r.sources) };
241
+ }
242
+ function parseQuestion(value, requirement = string) {
243
+ const q = record(value);
244
+ const alternatives = list(q.alternatives).map((value2) => {
245
+ const a = record(value2);
246
+ return { text: string(a.text), sources: sources(a.sources) };
247
+ });
248
+ const common = { id: string(q.id), requirement: requirement(q.requirement), text: string(q.text), alternatives };
249
+ const status = choice(q.status, ["open", "resolved"]);
250
+ if (status === "open" && q.resolution !== void 0) throw new Error("Open question cannot have a resolution");
251
+ return status === "open" ? { ...common, status } : { ...common, status, resolution: string(q.resolution) };
252
+ }
253
+ function parseLink(value, requirement = string) {
254
+ const x = record(value);
255
+ return { id: string(x.id), requirement: requirement(x.requirement), role: choice(x.role, ["implementation", "test", "context", "counterexample"]), rationale: string(x.rationale), ref: sourceRef(x.ref) };
256
+ }
257
+ function parseRun(value, requirement = string) {
258
+ const x = record(value), associations = list(x.requirements).map((value2) => {
259
+ const a = record(value2);
260
+ return { requirement: requirement(a.requirement), fingerprint: hash(a.fingerprint), scope: string(a.scope) };
261
+ });
262
+ unique(associations.map((a) => a.requirement));
263
+ return { revision: revision(x.revision), requirements: associations, command: string(x.command), outcome: choice(x.outcome, ["passed", "failed"]), outputDigest: hash(x.outputDigest) };
264
+ }
265
+ function correspondenceEvidence(artifact, scope) {
266
+ const ref = (side, id) => {
267
+ const symbol = artifact.symbols[side].find((s) => s.id === id);
268
+ if (!symbol) throw new Error("Unknown correspondence symbol");
269
+ return sourceRef({
270
+ revision: side === "before" ? artifact.range.baseOid : artifact.range.headOid,
271
+ path: symbol.path,
272
+ start: symbol.start,
273
+ end: symbol.end,
274
+ digest: symbol.digest
275
+ });
276
+ };
277
+ return {
278
+ pairs: artifact.relations.map((r) => ({ before: ref("before", r.sourceId), after: ref("after", r.destinationId) })),
279
+ analysis: parseAnalysis({
280
+ base: artifact.range.baseOid,
281
+ head: artifact.range.headOid,
282
+ producer: artifact.producer,
283
+ status: artifact.status,
284
+ scope: [...scope, ...artifact.diagnostics?.scope || []],
285
+ limitations: artifact.limitations,
286
+ artifactDigest: digest(JSON.stringify(artifact))
287
+ })
288
+ };
289
+ }
290
+ function parseReview(value) {
291
+ const r = record(value);
292
+ if (r.version !== 2) throw new Error("Unsupported review version");
293
+ const requirements = list(r.requirements).map(parseRequirement);
294
+ unique(requirements.map((r2) => r2.id));
295
+ const requirement = (value2) => {
296
+ const id = string(value2);
297
+ if (!requirements.some((r2) => r2.id === id)) throw new Error(`Unknown requirement: ${id}`);
298
+ return id;
299
+ };
300
+ const questions = list(r.questions).map((value2) => parseQuestion(value2, requirement));
301
+ unique(questions.map((q) => q.id));
302
+ const links = list(r.links).map((value2) => parseLink(value2, requirement));
303
+ unique(links.map((l) => l.id));
304
+ const runs = list(r.runs).map((value2) => parseRun(value2, requirement));
305
+ const decisions = list(r.decisions).map((value2) => {
306
+ const x = record(value2);
307
+ return { requirement: requirement(x.requirement), fingerprint: hash(x.fingerprint), note: string(x.note) };
308
+ });
309
+ unique(decisions.map((d) => d.requirement));
310
+ return { version: 2, repository: string(r.repository), base: revision(r.base), head: revision(r.head), requirements, questions, links, runs, analyses: list(r.analyses).map(parseAnalysis), decisions };
311
+ }
312
+ function snapshotsMatch(review, base, head) {
313
+ if (review.repository !== base.repository || review.repository !== head.repository || review.base !== base.revision || review.head !== head.revision) throw new Error("Repository or comparison mismatch");
314
+ }
315
+ function sourceSubset(snapshot, paths) {
316
+ const present = [...new Set(paths)].filter((path) => Object.prototype.hasOwnProperty.call(snapshot.files, path));
317
+ return { revision: snapshot.revision, files: Object.fromEntries(present.map((path) => [path, snapshot.files[path]])) };
318
+ }
319
+ function resolveReference(ref, snapshot) {
320
+ return (0, import_source_references.resolveSourceRef)(ref, sourceSubset(snapshot, [ref.path]));
321
+ }
322
+ function requirementFingerprint(review, id) {
323
+ const requirement = review.requirements.find((r) => r.id === id);
324
+ if (!requirement) throw new Error(`Unknown requirement: ${id}`);
325
+ return digest(JSON.stringify({
326
+ repository: review.repository,
327
+ requirement: parseRequirement(requirement),
328
+ questions: review.questions.filter((q) => q.requirement === id).map((q) => parseQuestion(q))
329
+ }));
330
+ }
331
+ function fingerprint(review, id) {
332
+ return digest(JSON.stringify({
333
+ repository: review.repository,
334
+ base: review.base,
335
+ head: review.head,
336
+ requirement: requirementFingerprint(review, id),
337
+ links: review.links.filter((l) => l.requirement === id).map((l) => parseLink(l)),
338
+ runs: review.runs.filter((r) => r.requirements.some((a) => a.requirement === id)).map((r) => parseRun(r)),
339
+ analyses: review.analyses.map(parseAnalysis)
340
+ }));
341
+ }
342
+ function inspect(review, base, head) {
343
+ snapshotsMatch(review, base, head);
344
+ const links = review.links.map((link) => ({ ...link, resolution: resolveReference(link.ref, link.ref.revision === base.revision ? base : head) }));
345
+ const requirements = review.requirements.map((requirement) => {
346
+ const attached = links.filter((l) => l.requirement === requirement.id);
347
+ const valid = attached.filter((l) => l.resolution.status === "valid");
348
+ const key = requirementFingerprint(review, requirement.id);
349
+ const runs = review.runs.flatMap((run) => run.requirements.filter((a) => a.requirement === requirement.id).map((association) => ({
350
+ ...run,
351
+ association,
352
+ applicability: run.revision !== head.revision ? "historical-revision" : association.fingerprint === key ? "current" : "stale-requirement"
353
+ })));
354
+ const currentRuns = runs.filter((r) => r.applicability === "current");
355
+ const decision = review.decisions.find((d) => d.requirement === requirement.id);
356
+ return {
357
+ ...requirement,
358
+ questions: review.questions.filter((q) => q.requirement === requirement.id),
359
+ runs,
360
+ links: attached,
361
+ implementationLinked: valid.some((l) => l.role === "implementation"),
362
+ testLinked: valid.some((l) => l.role === "test"),
363
+ counterexampleLinked: valid.some((l) => l.role === "counterexample"),
364
+ verification: currentRuns.length ? currentRuns.some((r) => r.outcome === "failed") ? "recorded-failure" : "recorded-pass" : runs.some((r) => r.applicability === "stale-requirement") ? "needs-reassessment" : "not-recorded",
365
+ reviewer: !decision ? "unreviewed" : decision.fingerprint === fingerprint(review, requirement.id) && attached.every((l) => l.resolution.status === "valid") ? "inspected" : "needs-review",
366
+ note: decision?.note
367
+ };
368
+ });
369
+ const changedPaths = [.../* @__PURE__ */ new Set([...Object.keys(base.files), ...Object.keys(head.files)])].filter((p) => base.files[p] !== head.files[p]).sort();
370
+ return {
371
+ requirements,
372
+ analyses: review.analyses.map((analysis) => ({
373
+ ...analysis,
374
+ relevance: analysis.head !== review.head ? "historical" : analysis.base === review.base ? "current-comparison" : "current-head-delta"
375
+ })),
376
+ changedPathsWithoutLinks: changedPaths.filter((p) => !links.some((l) => l.ref.path === p && l.resolution.status === "valid"))
377
+ };
378
+ }
379
+ function refresh(review, oldHead, newHead, evidence, requirements = review.requirements) {
380
+ if (review.repository !== oldHead.repository || review.repository !== newHead.repository || review.head !== oldHead.revision) throw new Error("Repository or old head mismatch");
381
+ revision(oldHead.revision);
382
+ revision(newHead.revision);
383
+ if (evidence !== void 0) {
384
+ record(evidence);
385
+ if (!Array.isArray(evidence.pairs)) throw new Error("Expected correspondence pairs");
386
+ }
387
+ const analysis = evidence === void 0 ? void 0 : parseAnalysis(evidence.analysis);
388
+ const pairs = evidence === void 0 ? [] : evidence.pairs;
389
+ if (analysis && (analysis.base !== oldHead.revision || analysis.head !== newHead.revision)) throw new Error("Analysis comparison mismatch");
390
+ if (analysis?.status === "unavailable" && pairs.length) throw new Error("Unavailable analysis cannot supply pairs");
391
+ const current = review.links.filter((link) => link.ref.revision !== review.base);
392
+ let relocated;
393
+ if (oldHead.revision === newHead.revision) {
394
+ if (pairs.length) throw new Error("Same-revision updates cannot supply correspondence pairs");
395
+ relocated = current.map(({ ref }) => resolveReference(ref, oldHead).status === "valid" && resolveReference(ref, newHead).status === "valid" ? { status: "unchanged", before: ref, after: ref } : { status: "unresolved", before: ref });
396
+ } else {
397
+ const paths = current.map((link) => link.ref.path);
398
+ const before = sourceSubset(oldHead, [...paths, ...pairs.map((pair) => pair.before.path)]);
399
+ const after = sourceSubset(newHead, [...paths, ...pairs.map((pair) => pair.after.path)]);
400
+ relocated = (0, import_source_references.relocateSourceRefs)(current.map((link) => link.ref), before, after, pairs);
401
+ }
402
+ const transitions = [];
403
+ let index = 0;
404
+ const links = review.links.map((link) => {
405
+ const before = link.ref;
406
+ if (before.revision === review.base) {
407
+ transitions.push({ link: link.id, status: "base-evidence", before });
408
+ return link;
409
+ }
410
+ const transition = relocated[index++];
411
+ transitions.push({ ...transition, link: link.id });
412
+ return transition.status === "unresolved" ? link : { ...link, ref: transition.after };
413
+ });
414
+ const next = parseReview({ ...review, head: newHead.revision, requirements, links, analyses: analysis ? [...review.analyses, analysis] : review.analyses });
415
+ return { review: next, transitions };
416
+ }
417
+ // Annotate the CommonJS export names for ESM import in node:
418
+ 0 && (module.exports = {
419
+ correspondenceEvidence,
420
+ digest,
421
+ fingerprint,
422
+ inspect,
423
+ parseReview,
424
+ refresh,
425
+ requirementFingerprint
426
+ });
427
+ //# sourceMappingURL=requirements.js.map