@sanity/ailf 0.2.0 → 0.3.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 (34) hide show
  1. package/config/models.yaml +3 -2
  2. package/dist/_vendor/ailf-core/types/index.d.ts +53 -0
  3. package/dist/composition-root.js +7 -2
  4. package/dist/orchestration/pipeline-orchestrator.js +27 -2
  5. package/dist/orchestration/step-runner.js +8 -0
  6. package/dist/orchestration/steps/calculate-scores-step.js +4 -0
  7. package/dist/orchestration/steps/generate-configs-step.js +1 -0
  8. package/dist/orchestration/steps/grader-consistency-step.js +1 -0
  9. package/dist/orchestration/steps/mirror-repo-tasks-step.js +2 -1
  10. package/dist/pipeline/calculate-scores.d.ts +5 -0
  11. package/dist/pipeline/calculate-scores.js +219 -146
  12. package/dist/pipeline/coverage-audit.d.ts +2 -1
  13. package/dist/pipeline/coverage-audit.js +5 -3
  14. package/dist/pipeline/expand-tasks.d.ts +2 -1
  15. package/dist/pipeline/expand-tasks.js +33 -2
  16. package/dist/pipeline/generate-configs.d.ts +3 -1
  17. package/dist/pipeline/generate-configs.js +47 -28
  18. package/dist/pipeline/grader-api.d.ts +2 -1
  19. package/dist/pipeline/grader-api.js +11 -9
  20. package/dist/pipeline/grader-compare-runner.d.ts +3 -0
  21. package/dist/pipeline/grader-compare-runner.js +21 -19
  22. package/dist/pipeline/grader-consistency-runner.d.ts +3 -0
  23. package/dist/pipeline/grader-consistency-runner.js +16 -14
  24. package/dist/pipeline/grader-sensitivity-runner.d.ts +3 -0
  25. package/dist/pipeline/grader-sensitivity-runner.js +18 -16
  26. package/dist/pipeline/grader-validate-runner.d.ts +3 -0
  27. package/dist/pipeline/grader-validate-runner.js +16 -14
  28. package/dist/pipeline/mirror-repo-tasks.d.ts +3 -1
  29. package/dist/pipeline/mirror-repo-tasks.js +8 -6
  30. package/dist/pipeline/provenance.d.ts +3 -0
  31. package/dist/pipeline/provenance.js +25 -3
  32. package/dist/sources.d.ts +2 -1
  33. package/dist/sources.js +28 -1
  34. package/package.json +3 -3
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
18
18
  import { join } from "path";
19
+ import { ConsoleLogger } from "../adapters/loggers/index.js";
19
20
  import { gradeOnce } from "./grader-api.js";
20
21
  import { analyzeConsistency, } from "./grader-consistency.js";
21
22
  // ---------------------------------------------------------------------------
@@ -192,7 +193,8 @@ export function formatConsistencyReport(result, graderModel) {
192
193
  */
193
194
  export async function runGraderConsistency(options) {
194
195
  const { replications, resultsPath, rootDir } = options;
195
- console.log("=== Grader Consistency Analysis ===\n");
196
+ const log = options.logger ?? new ConsoleLogger();
197
+ log.section("Grader Consistency Analysis");
196
198
  // Validate inputs
197
199
  if (!existsSync(resultsPath)) {
198
200
  throw new Error(`Results file not found: ${resultsPath}. Run 'pnpm eval' first to generate results.`);
@@ -201,8 +203,8 @@ export async function runGraderConsistency(options) {
201
203
  throw new Error("Need at least 2 replications for meaningful analysis.");
202
204
  }
203
205
  // Load eval results
204
- console.log(` Results: ${resultsPath}`);
205
- console.log(` Replications: ${replications}`);
206
+ log.info(`Results: ${resultsPath}`);
207
+ log.info(`Replications: ${replications}`);
206
208
  const file = JSON.parse(readFileSync(resultsPath, "utf-8"));
207
209
  // Extract grader model
208
210
  const graderModel = file.config?.defaultTest?.options?.rubricProvider ??
@@ -210,20 +212,20 @@ export async function runGraderConsistency(options) {
210
212
  if (!graderModel) {
211
213
  throw new Error("Could not determine grader model from eval results config.");
212
214
  }
213
- console.log(` Grader: ${graderModel}`);
215
+ log.info(`Grader: ${graderModel}`);
214
216
  // Extract judgments
215
217
  const judgments = extractGradingJudgments(file);
216
- console.log(` Judgments: ${judgments.length} (gold tests × rubric dimensions)`);
218
+ log.info(`Judgments: ${judgments.length} (gold tests × rubric dimensions)`);
217
219
  if (judgments.length === 0) {
218
220
  throw new Error("No gradable judgments found in results.");
219
221
  }
220
222
  const totalCalls = judgments.length * replications;
221
223
  const estimatedCost = totalCalls * 0.005;
222
- console.log(` API calls: ${totalCalls} (${judgments.length} × ${replications})`);
223
- console.log(` Est. cost: ~$${estimatedCost.toFixed(2)}`);
224
- console.log();
224
+ log.info(`API calls: ${totalCalls} (${judgments.length} × ${replications})`);
225
+ log.info(`Est. cost: ~$${estimatedCost.toFixed(2)}`);
226
+ log.info("");
225
227
  // Re-grade each judgment N times
226
- console.log(` Running ${replications} replications per judgment...`);
228
+ log.info(`Running ${replications} replications per judgment...`);
227
229
  const gradings = [];
228
230
  let completed = 0;
229
231
  let failed = 0;
@@ -251,20 +253,20 @@ export async function runGraderConsistency(options) {
251
253
  taskId: judgment.description,
252
254
  });
253
255
  }
254
- console.log(); // newline after progress
256
+ log.info(""); // newline after progress
255
257
  if (failed > 0) {
256
- console.log(` ⚠ ${failed} grading calls failed (excluded from analysis)`);
258
+ log.warn(`${failed} grading calls failed (excluded from analysis)`);
257
259
  }
258
- console.log();
260
+ log.info("");
259
261
  // Analyze consistency
260
262
  const result = analyzeConsistency(gradings);
261
263
  // Print report
262
- console.log(formatConsistencyReport(result, graderModel));
264
+ log.info(formatConsistencyReport(result, graderModel));
263
265
  // Write output
264
266
  const outDir = join(rootDir, "results", "latest");
265
267
  mkdirSync(outDir, { recursive: true });
266
268
  const outPath = join(outDir, "grader-consistency.json");
267
269
  writeFileSync(outPath, JSON.stringify(result, null, 2));
268
- console.log(`\n 📄 Results written to ${outPath}`);
270
+ log.info(`\n📄 Results written to ${outPath}`);
269
271
  return result;
270
272
  }
@@ -13,12 +13,15 @@
13
13
  *
14
14
  * @see docs/exec-plans/grader-reliability.md — Phase 4
15
15
  */
16
+ import type { Logger } from "../_vendor/ailf-core/index.d.ts";
16
17
  import { type GraderSensitivityResult } from "./grader-sensitivity.js";
17
18
  export interface GraderSensitivityRunnerOptions {
18
19
  /** Filter to a specific feature area (e.g., "groq") */
19
20
  areaFilter?: string;
20
21
  /** Output format */
21
22
  format?: "json" | "table";
23
+ /** Logger instance (defaults to ConsoleLogger) */
24
+ logger?: Logger;
22
25
  /** Custom output path */
23
26
  outputPath?: string;
24
27
  /** Root directory of the eval package */
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "fs";
17
17
  import { basename, join } from "path";
18
+ import { ConsoleLogger } from "../adapters/loggers/index.js";
18
19
  import { DEGRADATION_STRATEGIES } from "./degradations.js";
19
20
  import { gradeOnce, loadGraderModel } from "./grader-api.js";
20
21
  import { analyzeSensitivity, } from "./grader-sensitivity.js";
@@ -182,19 +183,20 @@ export function formatSensitivityReport(result) {
182
183
  */
183
184
  export async function runGraderSensitivity(options) {
184
185
  const { rootDir, areaFilter, format = "table" } = options;
185
- console.log("=== Grader Sensitivity Analysis ===\n");
186
+ const log = options.logger ?? new ConsoleLogger();
187
+ log.info("=== Grader Sensitivity Analysis ===\n");
186
188
  // Resolve grader model
187
189
  const grader = loadGraderModel(rootDir);
188
- console.log(` Grader: ${grader.label} (${grader.id})`);
190
+ log.info(` Grader: ${grader.label} (${grader.id})`);
189
191
  // Discover reference solutions
190
192
  const solutions = discoverReferenceSolutions(rootDir, areaFilter);
191
- console.log(` Solutions: ${solutions.length} reference files`);
193
+ log.info(` Solutions: ${solutions.length} reference files`);
192
194
  if (areaFilter) {
193
- console.log(` Area filter: ${areaFilter}`);
195
+ log.info(` Area filter: ${areaFilter}`);
194
196
  }
195
197
  // Generate degraded pairs
196
198
  const degradedPairs = generateDegradedPairs(solutions);
197
- console.log(` Pairs: ${degradedPairs.length} (solutions × degradations)`);
199
+ log.info(` Pairs: ${degradedPairs.length} (solutions × degradations)`);
198
200
  if (degradedPairs.length === 0) {
199
201
  throw new Error("No degraded pairs generated. Check reference solutions.");
200
202
  }
@@ -206,11 +208,11 @@ export async function runGraderSensitivity(options) {
206
208
  ];
207
209
  const totalCalls = degradedPairs.length * dimensions.length * 2;
208
210
  const estimatedCost = totalCalls * 0.005;
209
- console.log(` API calls: ${totalCalls}`);
210
- console.log(` Est. cost: ~$${estimatedCost.toFixed(2)}`);
211
- console.log();
211
+ log.info(` API calls: ${totalCalls}`);
212
+ log.info(` Est. cost: ~$${estimatedCost.toFixed(2)}`);
213
+ log.info("");
212
214
  // Grade each pair
213
- console.log(" Grading original and degraded pairs...");
215
+ log.info(" Grading original and degraded pairs...");
214
216
  const rubricTemplate = (dim) => {
215
217
  const labels = {
216
218
  codeCorrectness: "Evaluate code correctness: Does the code follow best practices, use correct APIs, and avoid anti-patterns? Score 0–100.",
@@ -237,7 +239,7 @@ export async function runGraderSensitivity(options) {
237
239
  completed === degradedPairs.length * dimensions.length) {
238
240
  const total = degradedPairs.length * dimensions.length;
239
241
  const pct = Math.round((completed / total) * 100);
240
- process.stdout.write(`\r Progress: ${completed}/${total} (${pct}%)`);
242
+ log.info(` Progress: ${completed}/${total} (${pct}%)`);
241
243
  }
242
244
  if (originalScore === null || degradedScore === null) {
243
245
  failed++;
@@ -254,11 +256,11 @@ export async function runGraderSensitivity(options) {
254
256
  });
255
257
  }
256
258
  }
257
- console.log(); // newline after progress
259
+ log.info(""); // newline after progress
258
260
  if (failed > 0) {
259
- console.log(` ⚠ ${failed} grading pairs failed (excluded)`);
261
+ log.warn(` ⚠ ${failed} grading pairs failed (excluded)`);
260
262
  }
261
- console.log();
263
+ log.info("");
262
264
  if (sensitivityPairs.length === 0) {
263
265
  throw new Error("No sensitivity pairs to analyze. All grading calls failed.");
264
266
  }
@@ -266,10 +268,10 @@ export async function runGraderSensitivity(options) {
266
268
  const result = analyzeSensitivity(sensitivityPairs, grader.id);
267
269
  // Output
268
270
  if (format === "table") {
269
- console.log(formatSensitivityReport(result));
271
+ log.info(formatSensitivityReport(result));
270
272
  }
271
273
  else {
272
- console.log(JSON.stringify(result, null, 2));
274
+ log.info(JSON.stringify(result, null, 2));
273
275
  }
274
276
  // Write output
275
277
  const outPath = options.outputPath ??
@@ -277,6 +279,6 @@ export async function runGraderSensitivity(options) {
277
279
  const outDir = join(outPath, "..");
278
280
  mkdirSync(outDir, { recursive: true });
279
281
  writeFileSync(outPath, JSON.stringify(result, null, 2));
280
- console.log(`\n 📄 Results written to ${outPath}`);
282
+ log.info(`\n 📄 Results written to ${outPath}`);
281
283
  return result;
282
284
  }
@@ -13,10 +13,13 @@
13
13
  *
14
14
  * @see docs/exec-plans/grader-reliability.md — Phase 2
15
15
  */
16
+ import type { Logger } from "../_vendor/ailf-core/index.d.ts";
16
17
  import { type GraderValidation } from "./grader-validation.js";
17
18
  export interface GraderValidateRunnerOptions {
18
19
  /** Grader model to validate (defaults to loadGraderModel(rootDir).id) */
19
20
  graderModel?: string;
21
+ /** Logger instance (defaults to ConsoleLogger) */
22
+ logger?: Logger;
20
23
  /** MAE threshold for pass/fail (default: 10) */
21
24
  maeThreshold?: number;
22
25
  /** Root directory of the eval package */
@@ -16,6 +16,7 @@
16
16
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from "fs";
17
17
  import { join } from "path";
18
18
  import { load } from "js-yaml";
19
+ import { ConsoleLogger } from "../adapters/loggers/index.js";
19
20
  import { gradeOnce, loadGraderModel } from "./grader-api.js";
20
21
  import { classifyCorrelation, validateGrader, } from "./grader-validation.js";
21
22
  // ---------------------------------------------------------------------------
@@ -154,26 +155,27 @@ export function formatValidationReport(result) {
154
155
  */
155
156
  export async function runGraderValidate(options) {
156
157
  const { rootDir } = options;
158
+ const log = options.logger ?? new ConsoleLogger();
157
159
  const maeThreshold = options.maeThreshold ?? 10;
158
- console.log("=== Grader Validation ===\n");
160
+ log.section("Grader Validation");
159
161
  // Resolve grader model
160
162
  const graderModel = options.graderModel ?? loadGraderModel(rootDir).id;
161
- console.log(` Grader: ${graderModel}`);
162
- console.log(` Threshold: MAE < ${maeThreshold}`);
163
+ log.info(`Grader: ${graderModel}`);
164
+ log.info(`Threshold: MAE < ${maeThreshold}`);
163
165
  // Load reference grades
164
166
  const rawGrades = loadReferenceGrades(rootDir);
165
- console.log(` Samples: ${rawGrades.length} reference-graded responses`);
167
+ log.info(`Samples: ${rawGrades.length} reference-graded responses`);
166
168
  // Count total rubric judgments
167
169
  let totalJudgments = 0;
168
170
  for (const rg of rawGrades) {
169
171
  totalJudgments += rg.rubrics.length;
170
172
  }
171
- console.log(` Judgments: ${totalJudgments} (response × rubric pairs)`);
173
+ log.info(`Judgments: ${totalJudgments} (response × rubric pairs)`);
172
174
  const estimatedCost = totalJudgments * 0.005;
173
- console.log(` Est. cost: ~$${estimatedCost.toFixed(2)}`);
174
- console.log();
175
+ log.info(`Est. cost: ~$${estimatedCost.toFixed(2)}`);
176
+ log.info("");
175
177
  // Grade each reference sample
176
- console.log(" Running grader on reference samples...");
178
+ log.info("Running grader on reference samples...");
177
179
  const grades = [];
178
180
  let completed = 0;
179
181
  let failed = 0;
@@ -181,7 +183,7 @@ export async function runGraderValidate(options) {
181
183
  for (const rubric of ref.rubrics) {
182
184
  const dimension = mapDimension(rubric.dimension);
183
185
  if (!dimension) {
184
- console.error(` ⚠ Unknown dimension '${rubric.dimension}' — skipping`);
186
+ log.error(`⚠ Unknown dimension '${rubric.dimension}' — skipping`);
185
187
  continue;
186
188
  }
187
189
  const graderScore = await gradeOnce(graderModel, ref.response, rubric.rubricText);
@@ -203,24 +205,24 @@ export async function runGraderValidate(options) {
203
205
  });
204
206
  }
205
207
  }
206
- console.log(); // newline after progress
208
+ log.info(""); // newline after progress
207
209
  if (failed > 0) {
208
- console.log(` ⚠ ${failed} grading calls failed (excluded from analysis)`);
210
+ log.warn(`${failed} grading calls failed (excluded from analysis)`);
209
211
  }
210
- console.log();
212
+ log.info("");
211
213
  if (grades.length === 0) {
212
214
  throw new Error("No grades to analyze. All grading calls failed.");
213
215
  }
214
216
  // Validate
215
217
  const result = validateGrader(grades, graderModel, { maeThreshold });
216
218
  // Print report
217
- console.log(formatValidationReport(result));
219
+ log.info(formatValidationReport(result));
218
220
  // Write output
219
221
  const outDir = join(rootDir, "results", "latest");
220
222
  mkdirSync(outDir, { recursive: true });
221
223
  const outPath = join(outDir, "grader-validation.json");
222
224
  writeFileSync(outPath, JSON.stringify(result, null, 2));
223
- console.log(`\n 📄 Results written to ${outPath}`);
225
+ log.info(`\n📄 Results written to ${outPath}`);
224
226
  // Throw if threshold not met (instead of process.exit)
225
227
  if (!result.passesThreshold) {
226
228
  throw new Error(`VALIDATION FAILED: MAE ${result.overallMae} exceeds threshold ${maeThreshold}`);
@@ -13,7 +13,7 @@
13
13
  * @see docs/exec-plans/tasks-as-content/phase-5-content-lake-mirroring.md
14
14
  */
15
15
  import type { SanityClient } from "@sanity/client";
16
- import { type TaskDefinition } from "../_vendor/ailf-core/index.d.ts";
16
+ import { type Logger, type TaskDefinition } from "../_vendor/ailf-core/index.d.ts";
17
17
  export interface MirrorOptions {
18
18
  /** Sanity client with write access */
19
19
  client: SanityClient;
@@ -23,6 +23,8 @@ export interface MirrorOptions {
23
23
  git: GitContext;
24
24
  /** If true, log what would be done without writing */
25
25
  dryRun?: boolean;
26
+ /** Logger instance (defaults to ConsoleLogger) */
27
+ logger?: Logger;
26
28
  }
27
29
  /** Authorship info extracted from git context or GitHub Actions environment. */
28
30
  export interface GitAuthor {
@@ -15,6 +15,7 @@
15
15
  import { createHash } from "crypto";
16
16
  import { readFileSync } from "fs";
17
17
  import { isIdRef, isPathRef, isPerspectiveRef, isSlugRef, } from "../_vendor/ailf-core/index.js";
18
+ import { ConsoleLogger } from "../adapters/loggers/index.js";
18
19
  // ---------------------------------------------------------------------------
19
20
  // Public API
20
21
  // ---------------------------------------------------------------------------
@@ -30,7 +31,8 @@ import { isIdRef, isPathRef, isPerspectiveRef, isSlugRef, } from "../_vendor/ail
30
31
  * 6. Upsert the ailf.task document with origin block
31
32
  */
32
33
  export async function mirrorRepoTasks(options) {
33
- const { client, tasks, git, dryRun = false } = options;
34
+ const { client, tasks, git, dryRun = false, logger } = options;
35
+ const log = logger ?? new ConsoleLogger();
34
36
  const result = {
35
37
  total: tasks.length,
36
38
  upserted: 0,
@@ -55,7 +57,7 @@ export async function mirrorRepoTasks(options) {
55
57
  }
56
58
  // Ensure all feature areas exist
57
59
  const areas = [...new Set(tasks.map((t) => t.featureArea))];
58
- const createdAreas = await ensureFeatureAreas(client, areas, dryRun);
60
+ const createdAreas = await ensureFeatureAreas(client, areas, dryRun, log);
59
61
  result.areasCreated = createdAreas;
60
62
  // Fetch existing mirror document state for change detection + ownership check
61
63
  const mirrorIds = tasks.map((t) => mirrorDocId(git.owner, git.name, t.id));
@@ -67,7 +69,7 @@ export async function mirrorRepoTasks(options) {
67
69
  const existing = existingDocState.get(docId);
68
70
  // Skip graduated tasks — ownership was changed to "studio"
69
71
  if (existing?.ownership === "studio") {
70
- console.log(` ℹ️ Skipping "${task.id}" — graduated to Studio ownership`);
72
+ log.info(` ℹ️ Skipping "${task.id}" — graduated to Studio ownership`);
71
73
  result.skipped++;
72
74
  continue;
73
75
  }
@@ -85,7 +87,7 @@ export async function mirrorRepoTasks(options) {
85
87
  slugToDocId,
86
88
  });
87
89
  if (dryRun) {
88
- console.log(` [dry-run] Would upsert: ${docId}`);
90
+ log.info(` [dry-run] Would upsert: ${docId}`);
89
91
  result.upserted++;
90
92
  continue;
91
93
  }
@@ -287,7 +289,7 @@ async function batchResolveDocSlugs(client, slugs) {
287
289
  * Ensure ailf.featureArea documents exist for all referenced areas.
288
290
  * Returns the list of newly created area IDs.
289
291
  */
290
- async function ensureFeatureAreas(client, areas, dryRun) {
292
+ async function ensureFeatureAreas(client, areas, dryRun, log) {
291
293
  if (areas.length === 0)
292
294
  return [];
293
295
  // Check which areas already exist
@@ -298,7 +300,7 @@ async function ensureFeatureAreas(client, areas, dryRun) {
298
300
  return [];
299
301
  if (dryRun) {
300
302
  for (const area of missing) {
301
- console.log(` [dry-run] Would create feature area: ${area}`);
303
+ log.info(` [dry-run] Would create feature area: ${area}`);
302
304
  }
303
305
  return missing;
304
306
  }
@@ -11,11 +11,14 @@
11
11
  * @see docs/design-docs/report-store/domain-model.md
12
12
  * @see docs/design-docs/report-store/architecture.md — Provenance collection
13
13
  */
14
+ import type { Logger } from "../_vendor/ailf-core/index.d.ts";
14
15
  import type { ResolvedSourceConfig } from "../sources.js";
15
16
  import type { EvalMode, PromptfooUrlEntry, ReportAutoScope, ReportProvenance } from "./types.js";
16
17
  export interface ProvenanceInput {
17
18
  /** Feature areas that were evaluated */
18
19
  areas: string[];
20
+ /** Logger instance (defaults to ConsoleLogger) */
21
+ logger?: Logger;
19
22
  /** Release auto-scope metadata (when perspective evaluation was scoped) */
20
23
  autoScope?: ReportAutoScope;
21
24
  /**
@@ -14,6 +14,7 @@
14
14
  import { readFileSync } from "fs";
15
15
  import { resolve } from "path";
16
16
  import { load } from "js-yaml";
17
+ import { ConsoleLogger } from "../adapters/loggers/index.js";
17
18
  /**
18
19
  * Build a ReportProvenance object from pipeline context.
19
20
  *
@@ -24,7 +25,20 @@ import { load } from "js-yaml";
24
25
  * - Optional metadata (context hash, Promptfoo URL)
25
26
  */
26
27
  export function buildProvenance(input) {
27
- const models = loadModelsConfig(input.rootDir);
28
+ const log = input.logger ?? new ConsoleLogger();
29
+ const models = loadModelsConfig(input.rootDir, log);
30
+ log.debug("Assembling provenance input", {
31
+ mode: input.mode,
32
+ sourceName: input.source.name,
33
+ sourceBaseUrl: input.source.baseUrl,
34
+ areas: input.areas,
35
+ taskIds: input.taskIds,
36
+ hasContextHash: Boolean(input.contextHash),
37
+ hasEvalFingerprint: Boolean(input.evalFingerprint),
38
+ hasCallerGit: Boolean(input.callerGit),
39
+ hasSourceReportId: Boolean(input.sourceReportId),
40
+ modelCount: models.models.length,
41
+ });
28
42
  // Cross-repo evaluations: prefer explicit caller git metadata over
29
43
  // CI env vars (which always reflect the AILF core repo).
30
44
  const git = input.callerGit
@@ -39,6 +53,14 @@ export function buildProvenance(input) {
39
53
  const lineage = input.sourceReportId
40
54
  ? { rerunOf: input.sourceReportId }
41
55
  : undefined;
56
+ const trigger = detectTrigger();
57
+ log.debug("Provenance computed", {
58
+ triggerType: trigger.type,
59
+ gitRepo: git?.repo,
60
+ gitBranch: git?.branch,
61
+ evalFingerprint: input.evalFingerprint,
62
+ hasLineage: Boolean(lineage),
63
+ });
42
64
  return {
43
65
  areas: input.areas,
44
66
  autoScope: input.autoScope,
@@ -149,13 +171,13 @@ function detectTrigger() {
149
171
  * Load config/models.yaml to extract model list and grader info.
150
172
  * Falls back to a minimal config if the file can't be read.
151
173
  */
152
- function loadModelsConfig(rootDir) {
174
+ function loadModelsConfig(rootDir, log) {
153
175
  try {
154
176
  const content = readFileSync(resolve(rootDir, "config", "models.yaml"), "utf-8");
155
177
  return load(content);
156
178
  }
157
179
  catch {
158
- console.warn(" ⚠️ Could not read config/models.yaml for provenance");
180
+ log.warn("Could not read config/models.yaml for provenance");
159
181
  return {
160
182
  defaults: {},
161
183
  grader: { id: "unknown" },
package/dist/sources.d.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  * Environment variables in config/sources.yaml are resolved via ${{ VAR | default }}
13
13
  * interpolation at load time.
14
14
  */
15
+ import type { Logger } from "./_vendor/ailf-core/index.d.ts";
15
16
  /**
16
17
  * @deprecated Use {@link ResolvedSourceConfig} instead.
17
18
  * Kept as a type alias for backward compatibility during the transition.
@@ -89,7 +90,7 @@ export interface SourceOverrides {
89
90
  * @param name - Source name from config/sources.yaml. If omitted, uses DOC_SOURCE
90
91
  * env var or the first source defined in config/sources.yaml.
91
92
  */
92
- export declare function loadSource(name?: string, overrides?: SourceOverrides): ResolvedSourceConfig;
93
+ export declare function loadSource(name?: string, overrides?: SourceOverrides, logger?: Logger): ResolvedSourceConfig;
93
94
  /**
94
95
  * Match a hostname against an origin pattern that may contain `*` wildcards.
95
96
  *
package/dist/sources.js CHANGED
@@ -16,6 +16,7 @@ import { readFileSync } from "fs";
16
16
  import { dirname, resolve } from "path";
17
17
  import { fileURLToPath } from "url";
18
18
  import { load } from "js-yaml";
19
+ import { ConsoleLogger } from "./adapters/loggers/index.js";
19
20
  import { interpolate } from "./interpolate.js";
20
21
  // ---------------------------------------------------------------------------
21
22
  // Paths
@@ -72,11 +73,17 @@ export function isAllowedOrigin(url, origins) {
72
73
  * @param name - Source name from config/sources.yaml. If omitted, uses DOC_SOURCE
73
74
  * env var or the first source defined in config/sources.yaml.
74
75
  */
75
- export function loadSource(name, overrides) {
76
+ export function loadSource(name, overrides, logger) {
77
+ const log = logger ?? new ConsoleLogger();
76
78
  // Priority 1: DOC_BASE_URL env var creates an ad-hoc source
77
79
  const envBaseUrl = overrides?.baseUrl ?? process.env.DOC_BASE_URL;
78
80
  if (envBaseUrl) {
79
81
  const domain = extractHostname(envBaseUrl);
82
+ log.debug("Source resolved from DOC_BASE_URL env override", {
83
+ baseUrl: envBaseUrl,
84
+ domain,
85
+ hasOverrides: Boolean(overrides),
86
+ });
80
87
  return {
81
88
  allowedOrigins: overrides?.allowedOrigins ?? parseAllowedOriginsEnv(),
82
89
  baseUrl: envBaseUrl,
@@ -106,10 +113,16 @@ export function loadSource(name, overrides) {
106
113
  }
107
114
  catch {
108
115
  // No config/sources.yaml — use built-in default
116
+ log.debug("No config/sources.yaml found, falling back to built-in default", {
117
+ path: SOURCES_PATH,
118
+ defaultName: DEFAULT_SOURCE.name,
119
+ defaultBaseUrl: DEFAULT_SOURCE.baseUrl,
120
+ });
109
121
  console.log(" No config/sources.yaml found, using built-in default (sanity.io production)");
110
122
  return DEFAULT_SOURCE;
111
123
  }
112
124
  if (!rawFile?.sources || Object.keys(rawFile.sources).length === 0) {
125
+ log.debug("config/sources.yaml is empty, falling back to built-in default");
113
126
  console.log(" config/sources.yaml is empty, using built-in default");
114
127
  return DEFAULT_SOURCE;
115
128
  }
@@ -117,6 +130,11 @@ export function loadSource(name, overrides) {
117
130
  const sourceName =
118
131
  // oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty strings should fall through to next candidate
119
132
  name || process.env.DOC_SOURCE || Object.keys(rawFile.sources)[0];
133
+ log.debug("Resolving source from config/sources.yaml", {
134
+ requestedName: name,
135
+ resolvedName: sourceName,
136
+ availableSources: Object.keys(rawFile.sources),
137
+ });
120
138
  const rawEntry = rawFile.sources[sourceName];
121
139
  if (!rawEntry) {
122
140
  const available = Object.keys(rawFile.sources).join(", ");
@@ -133,6 +151,15 @@ export function loadSource(name, overrides) {
133
151
  // An interpolation like ${{ DOC_ALLOWED_ORIGIN | }} resolves to "",
134
152
  // Which would produce [""] — matching nothing and silently blocking all origins.
135
153
  const filteredOrigins = resolved.allowedOrigins?.filter(Boolean);
154
+ log.debug("Source resolved from config/sources.yaml", {
155
+ name: sourceName,
156
+ baseUrl: resolved.baseUrl,
157
+ dataset: resolved.dataset,
158
+ projectId: resolved.projectId,
159
+ hasOverrides: Boolean(overrides),
160
+ hasAllowedOrigins: Boolean(filteredOrigins?.length),
161
+ hasPerspective: Boolean(overrides?.perspective ?? process.env.SANITY_PERSPECTIVE),
162
+ });
136
163
  return {
137
164
  allowedOrigins: filteredOrigins && filteredOrigins.length > 0
138
165
  ? filteredOrigins
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "restricted"
@@ -41,8 +41,8 @@
41
41
  "tsx": "^4.19.2",
42
42
  "typescript": "^5.7.3",
43
43
  "@sanity/ailf-core": "0.1.0",
44
- "@sanity/ailf-shared": "0.1.0",
45
- "@sanity/ailf-tasks": "0.1.4"
44
+ "@sanity/ailf-tasks": "0.1.4",
45
+ "@sanity/ailf-shared": "0.1.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc && tsx scripts/bundle-workspace-deps.ts",