@sanity/ailf 2.9.0 → 3.0.0

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 (42) hide show
  1. package/dist/_vendor/ailf-core/artifact-registry.d.ts +1 -1
  2. package/dist/_vendor/ailf-core/artifact-registry.js +1 -18
  3. package/dist/_vendor/ailf-core/batch-signing.d.ts +64 -0
  4. package/dist/_vendor/ailf-core/batch-signing.js +23 -0
  5. package/dist/_vendor/ailf-core/index.d.ts +1 -1
  6. package/dist/_vendor/ailf-core/index.js +1 -1
  7. package/dist/_vendor/ailf-core/ports/context.d.ts +4 -20
  8. package/dist/_vendor/ailf-core/ports/index.d.ts +0 -2
  9. package/dist/adapters/config-sources/file-config-adapter.js +0 -4
  10. package/dist/artifact-capture/redact-artifact.d.ts +3 -5
  11. package/dist/artifact-capture/redact-artifact.js +3 -5
  12. package/dist/cli.js +56 -2
  13. package/dist/commands/explain-handler.js +1 -5
  14. package/dist/commands/pipeline-action.d.ts +0 -4
  15. package/dist/commands/pipeline-action.js +11 -45
  16. package/dist/commands/pipeline.d.ts +1 -5
  17. package/dist/commands/pipeline.js +1 -5
  18. package/dist/commands/runs.d.ts +18 -0
  19. package/dist/commands/runs.js +71 -0
  20. package/dist/composition-root.js +2 -28
  21. package/dist/orchestration/build-app-context.js +4 -7
  22. package/dist/orchestration/pipeline-orchestrator.js +3 -23
  23. package/dist/pipeline/map-request-to-config.js +0 -4
  24. package/package.json +1 -1
  25. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.d.ts +0 -14
  26. package/dist/_vendor/ailf-core/artifact-capture/noop-collector.js +0 -25
  27. package/dist/_vendor/ailf-core/ports/artifact-collector.d.ts +0 -94
  28. package/dist/_vendor/ailf-core/ports/artifact-collector.js +0 -13
  29. package/dist/_vendor/ailf-core/ports/capture-comparator.d.ts +0 -138
  30. package/dist/_vendor/ailf-core/ports/capture-comparator.js +0 -10
  31. package/dist/artifact-capture/comparator.d.ts +0 -22
  32. package/dist/artifact-capture/comparator.js +0 -493
  33. package/dist/artifact-capture/filesystem-collector.d.ts +0 -60
  34. package/dist/artifact-capture/filesystem-collector.js +0 -262
  35. package/dist/artifact-capture/gcs-collector.d.ts +0 -55
  36. package/dist/artifact-capture/gcs-collector.js +0 -117
  37. package/dist/commands/capture-compare.d.ts +0 -15
  38. package/dist/commands/capture-compare.js +0 -253
  39. package/dist/commands/capture-list.d.ts +0 -12
  40. package/dist/commands/capture-list.js +0 -150
  41. package/dist/commands/capture.d.ts +0 -9
  42. package/dist/commands/capture.js +0 -16
@@ -1,253 +0,0 @@
1
- /**
2
- * capture compare — compare two pipeline capture directories.
3
- *
4
- * Reads manifest.json from both captures, runs compareCaptures(),
5
- * and prints a human-readable table or JSON diff report.
6
- *
7
- * Supports both raw directories and .tar.gz archives.
8
- *
9
- * Exit codes:
10
- * 0 — captures are equivalent
11
- * 1 — differences found
12
- * 2 — error (missing files, invalid manifest, etc.)
13
- */
14
- import { execFileSync } from "node:child_process";
15
- import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
16
- import { tmpdir } from "node:os";
17
- import { join, resolve } from "node:path";
18
- import { Command, Option } from "commander";
19
- import { compareCaptures } from "../artifact-capture/comparator.js";
20
- // ---------------------------------------------------------------------------
21
- // Command factory
22
- // ---------------------------------------------------------------------------
23
- export function createCaptureCompareCommand() {
24
- return new Command("compare")
25
- .description("Compare two pipeline capture directories")
26
- .argument("<baseline>", "Path to baseline capture (directory or .tar.gz)")
27
- .argument("<experiment>", "Path to experiment capture (directory or .tar.gz)")
28
- .addOption(new Option("-m, --mode <mode>", "Comparison mode")
29
- .choices(["inventory", "structural", "strict"])
30
- .default("inventory"))
31
- .option("-f, --format <fmt>", "Output format: table or json", "table")
32
- .option("-o, --output <path>", "Write JSON report to file")
33
- .option("--score-threshold <n>", "Aggregate score regression threshold (points)", parseFloat, 5)
34
- .option("--task-threshold <n>", "Per-task score regression threshold (points)", parseFloat, 10)
35
- .option("--timing-threshold <n>", "Step timing multiplier threshold", parseFloat, 2)
36
- .option("--json-depth <n>", "JSON structural diff depth", parseInt, 3)
37
- .action(async (baselinePath, experimentPath, opts) => {
38
- const cleanups = [];
39
- try {
40
- const baseline = resolveCapturePath(resolve(baselinePath), cleanups);
41
- const experiment = resolveCapturePath(resolve(experimentPath), cleanups);
42
- console.log("");
43
- console.log(" ailf capture compare");
44
- console.log(" " + "─".repeat(40));
45
- console.log("");
46
- console.log(` Baseline: ${baselinePath}`);
47
- console.log(` Experiment: ${experimentPath}`);
48
- console.log(` Mode: ${opts.mode}`);
49
- console.log("");
50
- const report = compareCaptures(baseline, experiment, {
51
- mode: opts.mode,
52
- scoreThresholds: {
53
- aggregate: opts.scoreThreshold,
54
- perTask: opts.taskThreshold,
55
- },
56
- timingThresholds: { multiplier: opts.timingThreshold },
57
- jsonDiffDepth: opts.jsonDepth,
58
- });
59
- if (opts.format === "json") {
60
- const json = JSON.stringify(report, null, 2);
61
- if (opts.output) {
62
- writeFileSync(opts.output, json, "utf-8");
63
- console.log(` Report written to ${opts.output}`);
64
- }
65
- else {
66
- console.log(json);
67
- }
68
- }
69
- else {
70
- printTableReport(report);
71
- if (opts.output) {
72
- const json = JSON.stringify(report, null, 2);
73
- writeFileSync(opts.output, json, "utf-8");
74
- console.log(` Report also written to ${opts.output}`);
75
- }
76
- }
77
- process.exitCode = report.equivalent ? 0 : 1;
78
- }
79
- catch (err) {
80
- console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);
81
- process.exitCode = 2;
82
- }
83
- finally {
84
- for (const cleanup of cleanups) {
85
- try {
86
- cleanup();
87
- }
88
- catch {
89
- // Best-effort cleanup
90
- }
91
- }
92
- }
93
- });
94
- }
95
- // ---------------------------------------------------------------------------
96
- // Path resolution (handles tar.gz, subdirectories, raw dirs)
97
- // ---------------------------------------------------------------------------
98
- function resolveCapturePath(inputPath, cleanups) {
99
- if (!existsSync(inputPath)) {
100
- throw new Error(`Path does not exist: ${inputPath}`);
101
- }
102
- if (inputPath.endsWith(".tar.gz")) {
103
- const tempDir = mkdtempSync(join(tmpdir(), "ailf-capture-cmp-"));
104
- cleanups.push(() => rmSync(tempDir, { recursive: true, force: true }));
105
- execFileSync("tar", ["-xzf", inputPath, "-C", tempDir]);
106
- return findManifestDir(tempDir);
107
- }
108
- return findManifestDir(inputPath);
109
- }
110
- /**
111
- * Find the directory containing manifest.json.
112
- *
113
- * Handles two cases:
114
- * 1. Path IS the capture dir (contains manifest.json directly)
115
- * 2. Path is the parent captures/ dir (contains a single timestamped subdir)
116
- */
117
- function findManifestDir(dir) {
118
- if (existsSync(join(dir, "manifest.json")))
119
- return dir;
120
- // Look one level down for a capture subdirectory
121
- const entries = readdirSync(dir).filter((e) => !e.startsWith(".") && !e.endsWith(".tar.gz"));
122
- for (const entry of entries) {
123
- const sub = join(dir, entry);
124
- if (existsSync(join(sub, "manifest.json")))
125
- return sub;
126
- }
127
- throw new Error(`No manifest.json found in ${dir} or its subdirectories. ` +
128
- `Is this a valid capture directory?`);
129
- }
130
- // ---------------------------------------------------------------------------
131
- // Table formatting
132
- // ---------------------------------------------------------------------------
133
- function printTableReport(report) {
134
- // Inventory
135
- console.log(" Inventory");
136
- console.log(" " + "─".repeat(40));
137
- console.log(` Common: ${report.inventory.common.length} artifact(s)`);
138
- if (report.inventory.added.length > 0) {
139
- console.log(` Added: ${report.inventory.added.length} (${report.inventory.added.join(", ")})`);
140
- }
141
- else {
142
- console.log(" Added: 0");
143
- }
144
- if (report.inventory.removed.length > 0) {
145
- console.log(` Removed: ${report.inventory.removed.length} (${report.inventory.removed.join(", ")})`);
146
- }
147
- else {
148
- console.log(" Removed: 0");
149
- }
150
- console.log("");
151
- // Content diff preview (structural/strict modes)
152
- if (report.content && report.content.length > 0) {
153
- console.log(" Content Changes");
154
- console.log(" " + "─".repeat(40));
155
- for (const diff of report.content.slice(0, 10)) {
156
- printContentDiff(diff);
157
- }
158
- if (report.content.length > 10) {
159
- console.log(` ... and ${report.content.length - 10} more changed artifact(s)`);
160
- }
161
- console.log("");
162
- }
163
- // Scores
164
- if (report.scores) {
165
- console.log(" Scores");
166
- console.log(" " + "─".repeat(40));
167
- const { baselineMean, currentMean, delta } = report.scores;
168
- const icon = delta > 0 ? "+" : delta < 0 ? "" : " ";
169
- console.log(` Aggregate: ${baselineMean.toFixed(1)} -> ${currentMean.toFixed(1)} (${icon}${delta.toFixed(1)})`);
170
- if (report.scores.breaches.length > 0) {
171
- console.log(` Breaches: ${report.scores.breaches.length}`);
172
- for (const b of report.scores.breaches) {
173
- console.log(` - ${b}`);
174
- }
175
- }
176
- else {
177
- console.log(" Breaches: none");
178
- }
179
- console.log("");
180
- }
181
- // Timing
182
- if (report.timing) {
183
- console.log(" Timing");
184
- console.log(" " + "─".repeat(40));
185
- const { totalDeltaMs } = report.timing;
186
- const sign = totalDeltaMs >= 0 ? "+" : "";
187
- console.log(` Total delta: ${sign}${totalDeltaMs}ms`);
188
- if (report.timing.breaches.length > 0) {
189
- console.log(` Breaches: ${report.timing.breaches.length}`);
190
- for (const b of report.timing.breaches) {
191
- console.log(` - ${b}`);
192
- }
193
- }
194
- else {
195
- console.log(" Breaches: none");
196
- }
197
- console.log("");
198
- }
199
- // Security
200
- console.log(" Security");
201
- console.log(" " + "─".repeat(40));
202
- if (report.security.leaksFound) {
203
- console.log(` Leaks: ${report.security.violations.length} finding(s)`);
204
- for (const v of report.security.violations.slice(0, 5)) {
205
- console.log(` - ${v.file}: ${v.detail}`);
206
- }
207
- if (report.security.violations.length > 5) {
208
- console.log(` ... and ${report.security.violations.length - 5} more`);
209
- }
210
- }
211
- else {
212
- console.log(" Leaks: none");
213
- }
214
- console.log("");
215
- // Result
216
- if (report.equivalent) {
217
- console.log(" Result: EQUIVALENT");
218
- }
219
- else {
220
- console.log(` Result: DIFFERENCES FOUND`);
221
- console.log(` ${report.summary}`);
222
- }
223
- console.log("");
224
- }
225
- /**
226
- * Print up to 3 changed key paths for a content diff.
227
- */
228
- function printContentDiff(diff) {
229
- console.log(` ${diff.artifactKey} (${diff.format})`);
230
- if (Array.isArray(diff.changes)) {
231
- // JSON diff — show up to 3 changed paths
232
- const jsonChanges = diff.changes;
233
- for (const change of jsonChanges.slice(0, 3)) {
234
- if (change.baseline === undefined) {
235
- console.log(` + ${change.path} (added)`);
236
- }
237
- else if (change.experiment === undefined) {
238
- console.log(` - ${change.path} (removed)`);
239
- }
240
- else {
241
- console.log(` ~ ${change.path} (changed)`);
242
- }
243
- }
244
- if (jsonChanges.length > 3) {
245
- console.log(` ... ${jsonChanges.length - 3} more change(s)`);
246
- }
247
- }
248
- else {
249
- // Text/markdown diff
250
- const { addedLines, removedLines } = diff.changes;
251
- console.log(` +${addedLines} / -${removedLines} lines`);
252
- }
253
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * capture list — list pipeline captures in a directory.
3
- *
4
- * Scans a capture directory for subdirectories containing manifest.json,
5
- * reads each manifest, and prints a summary table sorted by date.
6
- *
7
- * Usage:
8
- * ailf capture list # default: .ailf/results/captures/
9
- * ailf capture list ./my-captures # custom directory
10
- */
11
- import { Command } from "commander";
12
- export declare function createCaptureListCommand(): Command;
@@ -1,150 +0,0 @@
1
- /**
2
- * capture list — list pipeline captures in a directory.
3
- *
4
- * Scans a capture directory for subdirectories containing manifest.json,
5
- * reads each manifest, and prints a summary table sorted by date.
6
- *
7
- * Usage:
8
- * ailf capture list # default: .ailf/results/captures/
9
- * ailf capture list ./my-captures # custom directory
10
- */
11
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
12
- import { join, resolve } from "node:path";
13
- import { Command } from "commander";
14
- import { resolveOutputDir } from "./shared/resolve-output-dir.js";
15
- // ---------------------------------------------------------------------------
16
- // Command factory
17
- // ---------------------------------------------------------------------------
18
- export function createCaptureListCommand() {
19
- return new Command("list")
20
- .description("List pipeline captures in a directory")
21
- .argument("[dir]", "Captures directory (default: .ailf/results/captures/)")
22
- .option("-f, --format <fmt>", "Output format: table or json", "table")
23
- .action(async (dir, opts) => {
24
- const captureDir = dir
25
- ? resolve(dir)
26
- : resolve(resolveOutputDir(), "..", "captures");
27
- if (!existsSync(captureDir)) {
28
- console.error(` No captures directory found at ${captureDir}`);
29
- console.error(" Run 'ailf pipeline --capture' to create captures.");
30
- process.exitCode = 1;
31
- return;
32
- }
33
- const captures = discoverCaptures(captureDir);
34
- if (captures.length === 0) {
35
- console.log(` No captures found in ${captureDir}`);
36
- return;
37
- }
38
- // Sort by startedAt descending (newest first)
39
- captures.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
40
- if (opts.format === "json") {
41
- console.log(JSON.stringify(captures, null, 2));
42
- return;
43
- }
44
- console.log("");
45
- console.log(" ailf capture list");
46
- console.log(" " + "─".repeat(60));
47
- console.log("");
48
- console.log(" " +
49
- "Date".padEnd(22) +
50
- "Mode".padEnd(18) +
51
- "Artifacts".padEnd(12) +
52
- "Size".padEnd(10) +
53
- "Path");
54
- console.log(" " + "─".repeat(60));
55
- for (const c of captures) {
56
- const date = new Date(c.startedAt).toLocaleString("en-US", {
57
- month: "short",
58
- day: "2-digit",
59
- hour: "2-digit",
60
- minute: "2-digit",
61
- });
62
- const mode = c.mode.padEnd(18);
63
- const artifacts = String(c.artifactCount).padEnd(12);
64
- const size = formatBytes(c.totalBytes).padEnd(10);
65
- console.log(` ${date.padEnd(22)}${mode}${artifacts}${size}${c.name}`);
66
- }
67
- console.log("");
68
- console.log(` ${captures.length} capture(s) found in ${captureDir}`);
69
- console.log("");
70
- });
71
- }
72
- function discoverCaptures(captureDir) {
73
- const entries = [];
74
- for (const name of readdirSync(captureDir)) {
75
- if (name.startsWith("."))
76
- continue;
77
- const fullPath = join(captureDir, name);
78
- // Raw directory with manifest.json
79
- const manifestPath = join(fullPath, "manifest.json");
80
- if (existsSync(manifestPath)) {
81
- try {
82
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
83
- const totalBytes = manifest.artifacts.reduce((sum, a) => sum + a.bytes, 0);
84
- entries.push({
85
- name,
86
- path: fullPath,
87
- mode: manifest.pipeline.mode,
88
- startedAt: manifest.startedAt,
89
- artifactCount: manifest.artifacts.length,
90
- totalBytes,
91
- compressed: false,
92
- });
93
- }
94
- catch {
95
- // Skip unparseable manifests
96
- }
97
- continue;
98
- }
99
- // .tar.gz archive — read size but don't extract
100
- if (name.endsWith(".tar.gz")) {
101
- try {
102
- const stat = statSync(fullPath);
103
- entries.push({
104
- name,
105
- path: fullPath,
106
- mode: extractModeFromName(name),
107
- startedAt: extractTimestampFromName(name),
108
- artifactCount: -1, // Unknown without extracting
109
- totalBytes: stat.size,
110
- compressed: true,
111
- });
112
- }
113
- catch {
114
- // Skip
115
- }
116
- }
117
- }
118
- return entries;
119
- }
120
- /** Known modes — used to correctly extract mode from hyphenated filenames. */
121
- const KNOWN_MODES = [
122
- "literacy",
123
- "mcp-server",
124
- "agent-harness",
125
- "knowledge-probe",
126
- ];
127
- function extractModeFromName(name) {
128
- for (const mode of KNOWN_MODES) {
129
- if (name.startsWith(mode + "-"))
130
- return mode;
131
- }
132
- return name.split("-")[0] ?? "unknown";
133
- }
134
- function extractTimestampFromName(name) {
135
- // Pattern: {mode}-YYYYMMDD-HHmmss-{id}.tar.gz
136
- const match = name.match(/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})/);
137
- if (!match)
138
- return "unknown";
139
- const [, y, m, d, h, min, s] = match;
140
- return `${y}-${m}-${d}T${h}:${min}:${s}Z`;
141
- }
142
- function formatBytes(bytes) {
143
- if (bytes < 0)
144
- return "?";
145
- if (bytes < 1024)
146
- return `${bytes}B`;
147
- if (bytes < 1024 * 1024)
148
- return `${(bytes / 1024).toFixed(1)}KB`;
149
- return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
150
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * capture command — manage and compare pipeline artifact captures.
3
- *
4
- * Parent command for capture-related subcommands:
5
- * ailf capture compare <baseline> <experiment>
6
- * ailf capture list [dir]
7
- */
8
- import { Command } from "commander";
9
- export declare function createCaptureCommand(): Command;
@@ -1,16 +0,0 @@
1
- /**
2
- * capture command — manage and compare pipeline artifact captures.
3
- *
4
- * Parent command for capture-related subcommands:
5
- * ailf capture compare <baseline> <experiment>
6
- * ailf capture list [dir]
7
- */
8
- import { Command } from "commander";
9
- import { createCaptureCompareCommand } from "./capture-compare.js";
10
- import { createCaptureListCommand } from "./capture-list.js";
11
- export function createCaptureCommand() {
12
- const cmd = new Command("capture").description("Manage and compare pipeline artifact captures");
13
- cmd.addCommand(createCaptureCompareCommand());
14
- cmd.addCommand(createCaptureListCommand());
15
- return cmd;
16
- }