@prismatic-io/lux 0.0.2-preview.4 → 0.0.2-preview.7
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/lib/assertions/authoring.d.ts +2 -2
- package/lib/assertions/authoring.d.ts.map +1 -1
- package/lib/assertions/authoring.js.map +1 -1
- package/lib/assertions/core/index.d.ts.map +1 -1
- package/lib/assertions/core/index.js +2 -1
- package/lib/assertions/core/index.js.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts +19 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +32 -0
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +6 -65
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/view.d.ts +13 -0
- package/lib/cli/view.d.ts.map +1 -0
- package/lib/cli/view.js +126 -0
- package/lib/cli/view.js.map +1 -0
- package/lib/core/action-events.d.ts +29 -0
- package/lib/core/action-events.d.ts.map +1 -0
- package/lib/core/action-events.js +51 -0
- package/lib/core/action-events.js.map +1 -0
- package/lib/core/agent-activity.d.ts +49 -0
- package/lib/core/agent-activity.d.ts.map +1 -0
- package/lib/core/agent-activity.js +206 -0
- package/lib/core/agent-activity.js.map +1 -0
- package/lib/core/agent-events.d.ts +26 -0
- package/lib/core/agent-events.d.ts.map +1 -0
- package/lib/core/agent-events.js +13 -0
- package/lib/core/agent-events.js.map +1 -0
- package/lib/core/driver-metrics.d.ts +98 -0
- package/lib/core/driver-metrics.d.ts.map +1 -0
- package/lib/core/driver-metrics.js +36 -0
- package/lib/core/driver-metrics.js.map +1 -0
- package/lib/core/events.d.ts +34 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +11 -2
- package/lib/core/events.js.map +1 -1
- package/lib/core/experiment.d.ts +19 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +2 -0
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +4 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/run.d.ts +149 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +9 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/wire.d.ts +11 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.js +73 -8
- package/lib/drivers/claude-code/parse-events.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts +9 -2
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +93 -12
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +10 -14
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/index.d.ts +6 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -2
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +13 -1
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +12 -0
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +19 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +17 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +19 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +38 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/index.d.ts +1 -1
- package/lib/orchestrator/index.d.ts.map +1 -1
- package/lib/orchestrator/index.js +1 -1
- package/lib/orchestrator/index.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +7 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +50 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +17 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +34 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +68 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +45 -7
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/artifacts.d.ts +25 -0
- package/lib/previewer/artifacts.d.ts.map +1 -0
- package/lib/previewer/artifacts.js +180 -0
- package/lib/previewer/artifacts.js.map +1 -0
- package/lib/previewer/server.d.ts +105 -0
- package/lib/previewer/server.d.ts.map +1 -0
- package/lib/previewer/server.js +542 -0
- package/lib/previewer/server.js.map +1 -0
- package/package.json +2 -1
- package/skills/lux/references/eval-authoring.md +4 -2
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/assertions/authoring.ts +2 -0
- package/src/assertions/core/index.ts +2 -0
- package/src/assertions/core/resource-checks.ts +35 -0
- package/src/cli/program.ts +5 -91
- package/src/cli/view.ts +168 -0
- package/src/core/action-events.ts +59 -0
- package/src/core/agent-activity.ts +236 -0
- package/src/core/agent-events.ts +17 -0
- package/src/core/driver-metrics.ts +51 -0
- package/src/core/events.ts +13 -2
- package/src/core/experiment.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/run.ts +9 -0
- package/src/drivers/claude-code/parse-events.ts +98 -12
- package/src/drivers/codex/app-events.ts +116 -16
- package/src/drivers/codex/index.ts +16 -14
- package/src/index.ts +40 -0
- package/src/orchestrator/compare.ts +18 -0
- package/src/orchestrator/experiment-evaluation.ts +23 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/list-runs.ts +66 -5
- package/src/orchestrator/run-execution.ts +19 -2
- package/src/orchestrator/suite-summary.ts +101 -8
- package/src/previewer/artifacts.ts +221 -0
- package/src/previewer/server.ts +765 -0
- package/viewer/app.css +2075 -0
- package/viewer/app.js +606 -0
- package/viewer/artifact-view.js +48 -0
- package/viewer/charts.js +86 -0
- package/viewer/detail.js +1214 -0
- package/viewer/format.js +129 -0
- package/viewer/index.html +151 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type DriverMetric,
|
|
6
|
+
DriverMetricUnitSchema,
|
|
7
|
+
harnessUsage,
|
|
8
|
+
isUsageEmpty,
|
|
9
|
+
type RunMetadata,
|
|
10
|
+
} from "../core/index.js";
|
|
5
11
|
import { runComparisonIdentity } from "./comparison-identity.js";
|
|
6
12
|
import { safeReadJson } from "./fs-read.js";
|
|
7
13
|
import type { SuiteCaseResult } from "./orchestrator.js";
|
|
@@ -16,6 +22,9 @@ export const MetricSummarySchema = z.object({
|
|
|
16
22
|
mean: z.number(),
|
|
17
23
|
median: z.number(),
|
|
18
24
|
p90: z.number(),
|
|
25
|
+
unit: z.exactOptional(DriverMetricUnitSchema),
|
|
26
|
+
label: z.exactOptional(z.string()),
|
|
27
|
+
better: z.exactOptional(z.enum(["lower", "higher", "neutral"])),
|
|
19
28
|
});
|
|
20
29
|
export type MetricSummary = z.infer<typeof MetricSummarySchema>;
|
|
21
30
|
|
|
@@ -59,13 +68,22 @@ export const SuiteSummarySchema = z.object({
|
|
|
59
68
|
});
|
|
60
69
|
export type SuiteSummary = z.infer<typeof SuiteSummarySchema>;
|
|
61
70
|
|
|
62
|
-
type MetricSamples = Map<
|
|
71
|
+
type MetricSamples = Map<
|
|
72
|
+
string,
|
|
73
|
+
{ values: number[]; descriptor?: Omit<DriverMetric, "value"> | undefined }
|
|
74
|
+
>;
|
|
63
75
|
|
|
64
|
-
const add = (
|
|
76
|
+
const add = (
|
|
77
|
+
samples: MetricSamples,
|
|
78
|
+
name: string,
|
|
79
|
+
value: number | undefined,
|
|
80
|
+
descriptor?: Omit<DriverMetric, "value">,
|
|
81
|
+
): void => {
|
|
65
82
|
if (value === undefined || !Number.isFinite(value)) return;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
const sample = samples.get(name) ?? { values: [], descriptor };
|
|
84
|
+
if (!sample.descriptor && descriptor) sample.descriptor = descriptor;
|
|
85
|
+
sample.values.push(value);
|
|
86
|
+
samples.set(name, sample);
|
|
69
87
|
};
|
|
70
88
|
|
|
71
89
|
const cacheHitRate = (usage: {
|
|
@@ -137,6 +155,78 @@ const collectMetadata = (samples: MetricSamples, metadata: RunMetadata): void =>
|
|
|
137
155
|
add(samples, "phase.agentMs", metadata.phaseDurationMs?.agent);
|
|
138
156
|
add(samples, "phase.collectMs", metadata.phaseDurationMs?.collect);
|
|
139
157
|
add(samples, "phase.gradingMs", metadata.phaseDurationMs?.grading);
|
|
158
|
+
add(samples, "timeToFirstActionMs", metadata.actionTiming?.timeToFirstActionMs);
|
|
159
|
+
add(samples, "timeToFirstMutationMs", metadata.actionTiming?.timeToFirstMutationMs);
|
|
160
|
+
add(samples, "timeToFirstDurableMutationMs", metadata.actionTiming?.timeToFirstDurableMutationMs);
|
|
161
|
+
const subagents = (metadata.agentActivity ?? []).filter(
|
|
162
|
+
(activity) => activity.agent.role === "subagent",
|
|
163
|
+
);
|
|
164
|
+
add(samples, "subagents.count", subagents.length, {
|
|
165
|
+
unit: "count",
|
|
166
|
+
label: "Subagents spawned",
|
|
167
|
+
better: "neutral",
|
|
168
|
+
});
|
|
169
|
+
add(
|
|
170
|
+
samples,
|
|
171
|
+
"subagents.events",
|
|
172
|
+
subagents.reduce((sum, activity) => sum + activity.eventCount, 0),
|
|
173
|
+
{ unit: "count", label: "Subagent events", better: "neutral" },
|
|
174
|
+
);
|
|
175
|
+
add(
|
|
176
|
+
samples,
|
|
177
|
+
"subagents.toolCalls",
|
|
178
|
+
subagents.reduce((sum, activity) => sum + activity.toolCalls.total, 0),
|
|
179
|
+
{ unit: "count", label: "Subagent tool calls", better: "neutral" },
|
|
180
|
+
);
|
|
181
|
+
add(
|
|
182
|
+
samples,
|
|
183
|
+
"subagents.toolCalls.failed",
|
|
184
|
+
subagents.reduce((sum, activity) => sum + activity.toolCalls.failed, 0),
|
|
185
|
+
{ unit: "count", label: "Failed subagent tool calls", better: "lower" },
|
|
186
|
+
);
|
|
187
|
+
add(
|
|
188
|
+
samples,
|
|
189
|
+
"subagents.tokens",
|
|
190
|
+
subagents.reduce((sum, activity) => sum + activity.usage.total, 0),
|
|
191
|
+
{ unit: "tokens", label: "Subagent tokens", better: "lower" },
|
|
192
|
+
);
|
|
193
|
+
add(
|
|
194
|
+
samples,
|
|
195
|
+
"subagents.actions",
|
|
196
|
+
subagents.reduce((sum, activity) => sum + activity.actions.total, 0),
|
|
197
|
+
{ unit: "count", label: "Subagent actions", better: "neutral" },
|
|
198
|
+
);
|
|
199
|
+
add(
|
|
200
|
+
samples,
|
|
201
|
+
"subagents.mutations",
|
|
202
|
+
subagents.reduce((sum, activity) => sum + activity.actions.mutations, 0),
|
|
203
|
+
{ unit: "count", label: "Subagent mutations", better: "neutral" },
|
|
204
|
+
);
|
|
205
|
+
add(
|
|
206
|
+
samples,
|
|
207
|
+
"subagents.totalDurationMs",
|
|
208
|
+
subagents.reduce((sum, activity) => sum + activity.durationMs, 0),
|
|
209
|
+
{ unit: "milliseconds", label: "Total subagent time", better: "neutral" },
|
|
210
|
+
);
|
|
211
|
+
add(
|
|
212
|
+
samples,
|
|
213
|
+
"subagents.maxDurationMs",
|
|
214
|
+
subagents.length > 0 ? Math.max(...subagents.map((activity) => activity.durationMs)) : 0,
|
|
215
|
+
{ unit: "milliseconds", label: "Longest subagent", better: "lower" },
|
|
216
|
+
);
|
|
217
|
+
const firstSubagentAction = subagents
|
|
218
|
+
.map((activity) => activity.actions.timeToFirstActionMs)
|
|
219
|
+
.filter((value): value is number => value !== undefined)
|
|
220
|
+
.sort((a, b) => a - b)[0];
|
|
221
|
+
add(samples, "subagents.timeToFirstActionMs", firstSubagentAction, {
|
|
222
|
+
unit: "milliseconds",
|
|
223
|
+
label: "First subagent action",
|
|
224
|
+
better: "lower",
|
|
225
|
+
});
|
|
226
|
+
for (const [name, metric] of Object.entries(metadata.driverMetrics ?? {})) {
|
|
227
|
+
const { value, ...descriptor } = metric;
|
|
228
|
+
add(samples, `driver.${name}`, value, descriptor);
|
|
229
|
+
}
|
|
140
230
|
};
|
|
141
231
|
|
|
142
232
|
const percentile = (sorted: readonly number[], fraction: number): number => {
|
|
@@ -165,7 +255,10 @@ const summarizeMetrics = (samples: MetricSamples): Record<string, MetricSummary>
|
|
|
165
255
|
Object.fromEntries(
|
|
166
256
|
[...samples.entries()]
|
|
167
257
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
168
|
-
.map(([name, values]) => [
|
|
258
|
+
.map(([name, { values, descriptor }]) => [
|
|
259
|
+
name,
|
|
260
|
+
MetricSummarySchema.parse({ ...summarizeNumbers(values), ...descriptor }),
|
|
261
|
+
]),
|
|
169
262
|
);
|
|
170
263
|
|
|
171
264
|
const summarizeOutcomes = (
|
|
@@ -258,7 +351,7 @@ export const buildSuiteSummary = (
|
|
|
258
351
|
? { score: result.grading.caseScore }
|
|
259
352
|
: {}),
|
|
260
353
|
metrics: Object.fromEntries(
|
|
261
|
-
[...samples.entries()].map(([name,
|
|
354
|
+
[...samples.entries()].map(([name, sample]) => [name, sample.values[0] ?? 0]),
|
|
262
355
|
),
|
|
263
356
|
};
|
|
264
357
|
}),
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { Artifact } from "../core/index.js";
|
|
2
|
+
import { inspectRunArtifact, readRunArtifactBytes } from "../core/index.js";
|
|
3
|
+
|
|
4
|
+
const MAX_PREVIEW_BYTES = 512_000;
|
|
5
|
+
const MAX_DIFF_LINES = 2_000;
|
|
6
|
+
const MAX_DIFF_CELLS = 1_000_000;
|
|
7
|
+
|
|
8
|
+
export type ArtifactPreview = {
|
|
9
|
+
path: string;
|
|
10
|
+
exists: boolean;
|
|
11
|
+
viewable: boolean;
|
|
12
|
+
size: number | null;
|
|
13
|
+
contentType: string | null;
|
|
14
|
+
text: string | null;
|
|
15
|
+
reason: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ArtifactDiffOperation = {
|
|
19
|
+
kind: "same" | "added" | "removed";
|
|
20
|
+
lines: string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ArtifactComparison = {
|
|
24
|
+
path: string;
|
|
25
|
+
before: ArtifactPreview;
|
|
26
|
+
after: ArtifactPreview;
|
|
27
|
+
operations: ArtifactDiffOperation[] | null;
|
|
28
|
+
reason: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const evidenceArtifacts = (
|
|
32
|
+
indexed: readonly Artifact[],
|
|
33
|
+
knownPaths: readonly string[],
|
|
34
|
+
): Artifact[] => {
|
|
35
|
+
const out = [...indexed];
|
|
36
|
+
const indexedPaths = new Set(indexed.map((artifact) => artifact.path));
|
|
37
|
+
for (const path of knownPaths) {
|
|
38
|
+
if (!indexedPaths.has(path)) out.push({ path });
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const loadArtifactPreview = async (
|
|
44
|
+
runDir: string,
|
|
45
|
+
indexed: readonly Artifact[],
|
|
46
|
+
knownPaths: readonly string[],
|
|
47
|
+
path: string,
|
|
48
|
+
): Promise<ArtifactPreview> => {
|
|
49
|
+
if (!knownPaths.includes(path) && !indexed.some((artifact) => artifact.path === path)) {
|
|
50
|
+
return {
|
|
51
|
+
path,
|
|
52
|
+
exists: false,
|
|
53
|
+
viewable: false,
|
|
54
|
+
size: null,
|
|
55
|
+
contentType: null,
|
|
56
|
+
text: null,
|
|
57
|
+
reason: "artifact was not captured by this run",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const artifacts = evidenceArtifacts(indexed, knownPaths);
|
|
61
|
+
const run = { runDir, artifacts };
|
|
62
|
+
const inspected = await inspectRunArtifact(run, path);
|
|
63
|
+
const contentType = inspected.artifact?.contentType ?? null;
|
|
64
|
+
if (!inspected.stats?.isFile()) {
|
|
65
|
+
return {
|
|
66
|
+
path,
|
|
67
|
+
exists: false,
|
|
68
|
+
viewable: false,
|
|
69
|
+
size: inspected.stats?.size ?? null,
|
|
70
|
+
contentType,
|
|
71
|
+
text: null,
|
|
72
|
+
reason: inspected.issue ?? "artifact is not a regular file",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (inspected.stats.size > MAX_PREVIEW_BYTES) {
|
|
76
|
+
return {
|
|
77
|
+
path,
|
|
78
|
+
exists: true,
|
|
79
|
+
viewable: false,
|
|
80
|
+
size: inspected.stats.size,
|
|
81
|
+
contentType,
|
|
82
|
+
text: null,
|
|
83
|
+
reason: `artifact exceeds the ${MAX_PREVIEW_BYTES}-byte preview limit`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const bytes = await readRunArtifactBytes(run, path, { maxBytes: MAX_PREVIEW_BYTES });
|
|
87
|
+
let text: string;
|
|
88
|
+
try {
|
|
89
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
90
|
+
} catch {
|
|
91
|
+
return {
|
|
92
|
+
path,
|
|
93
|
+
exists: true,
|
|
94
|
+
viewable: false,
|
|
95
|
+
size: bytes.length,
|
|
96
|
+
contentType,
|
|
97
|
+
text: null,
|
|
98
|
+
reason: "artifact is not valid UTF-8 text",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (text.includes("\0")) {
|
|
102
|
+
return {
|
|
103
|
+
path,
|
|
104
|
+
exists: true,
|
|
105
|
+
viewable: false,
|
|
106
|
+
size: bytes.length,
|
|
107
|
+
contentType,
|
|
108
|
+
text: null,
|
|
109
|
+
reason: "artifact appears to be binary",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
path,
|
|
114
|
+
exists: true,
|
|
115
|
+
viewable: true,
|
|
116
|
+
size: bytes.length,
|
|
117
|
+
contentType,
|
|
118
|
+
text,
|
|
119
|
+
reason: null,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const appendOperation = (
|
|
124
|
+
operations: ArtifactDiffOperation[],
|
|
125
|
+
kind: ArtifactDiffOperation["kind"],
|
|
126
|
+
line: string,
|
|
127
|
+
): void => {
|
|
128
|
+
const current = operations.at(-1);
|
|
129
|
+
if (current?.kind === kind) current.lines.push(line);
|
|
130
|
+
else operations.push({ kind, lines: [line] });
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const diffLines = (beforeText: string, afterText: string): ArtifactDiffOperation[] | null => {
|
|
134
|
+
const before = beforeText.split("\n");
|
|
135
|
+
const after = afterText.split("\n");
|
|
136
|
+
if (
|
|
137
|
+
before.length > MAX_DIFF_LINES ||
|
|
138
|
+
after.length > MAX_DIFF_LINES ||
|
|
139
|
+
before.length * after.length > MAX_DIFF_CELLS
|
|
140
|
+
) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const width = after.length + 1;
|
|
144
|
+
const table = new Uint32Array((before.length + 1) * width);
|
|
145
|
+
for (let left = before.length - 1; left >= 0; left--) {
|
|
146
|
+
for (let right = after.length - 1; right >= 0; right--) {
|
|
147
|
+
const cell = left * width + right;
|
|
148
|
+
table[cell] =
|
|
149
|
+
before[left] === after[right]
|
|
150
|
+
? (table[(left + 1) * width + right + 1] ?? 0) + 1
|
|
151
|
+
: Math.max(table[(left + 1) * width + right] ?? 0, table[left * width + right + 1] ?? 0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const operations: ArtifactDiffOperation[] = [];
|
|
155
|
+
let left = 0;
|
|
156
|
+
let right = 0;
|
|
157
|
+
while (left < before.length && right < after.length) {
|
|
158
|
+
if (before[left] === after[right]) {
|
|
159
|
+
appendOperation(operations, "same", before[left] ?? "");
|
|
160
|
+
left++;
|
|
161
|
+
right++;
|
|
162
|
+
} else if ((table[(left + 1) * width + right] ?? 0) >= (table[left * width + right + 1] ?? 0)) {
|
|
163
|
+
appendOperation(operations, "removed", before[left] ?? "");
|
|
164
|
+
left++;
|
|
165
|
+
} else {
|
|
166
|
+
appendOperation(operations, "added", after[right] ?? "");
|
|
167
|
+
right++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
while (left < before.length) {
|
|
171
|
+
appendOperation(operations, "removed", before[left] ?? "");
|
|
172
|
+
left++;
|
|
173
|
+
}
|
|
174
|
+
while (right < after.length) {
|
|
175
|
+
appendOperation(operations, "added", after[right] ?? "");
|
|
176
|
+
right++;
|
|
177
|
+
}
|
|
178
|
+
return operations;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const compareArtifactPreviews = (
|
|
182
|
+
path: string,
|
|
183
|
+
before: ArtifactPreview,
|
|
184
|
+
after: ArtifactPreview,
|
|
185
|
+
): ArtifactComparison => {
|
|
186
|
+
if (before.viewable && before.text !== null && !after.exists) {
|
|
187
|
+
return {
|
|
188
|
+
path,
|
|
189
|
+
before,
|
|
190
|
+
after,
|
|
191
|
+
operations: [{ kind: "removed", lines: before.text.split("\n") }],
|
|
192
|
+
reason: null,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (!before.exists && after.viewable && after.text !== null) {
|
|
196
|
+
return {
|
|
197
|
+
path,
|
|
198
|
+
before,
|
|
199
|
+
after,
|
|
200
|
+
operations: [{ kind: "added", lines: after.text.split("\n") }],
|
|
201
|
+
reason: null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (!before.viewable || !after.viewable || before.text === null || after.text === null) {
|
|
205
|
+
return {
|
|
206
|
+
path,
|
|
207
|
+
before,
|
|
208
|
+
after,
|
|
209
|
+
operations: null,
|
|
210
|
+
reason: before.reason ?? after.reason ?? "artifact text is unavailable",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const operations = diffLines(before.text, after.text);
|
|
214
|
+
return {
|
|
215
|
+
path,
|
|
216
|
+
before,
|
|
217
|
+
after,
|
|
218
|
+
operations,
|
|
219
|
+
reason: operations ? null : "artifact is too line-dense for an inline diff",
|
|
220
|
+
};
|
|
221
|
+
};
|