@sanity/ailf 0.1.28 → 0.1.29

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.
@@ -35,6 +35,8 @@ export interface ResolvedConfig {
35
35
  areas?: string[];
36
36
  /** Task ID filter */
37
37
  tasks?: string[];
38
+ /** Tag filter — tasks must have at least one matching tag */
39
+ tags?: string[];
38
40
  /** Changed doc slugs for impact scoping */
39
41
  changedDocs?: string[];
40
42
  /** Documentation source name */
@@ -112,6 +112,8 @@ export interface TaskDefinition {
112
112
  baseline?: BaselineConfig;
113
113
  /** Additional template variables beyond task (e.g., custom vars) */
114
114
  extraVars?: Record<string, unknown>;
115
+ /** Freeform labels for filtering and organization */
116
+ tags?: string[];
115
117
  }
116
118
  /** Check if a canonical doc ref resolves by slug.
117
119
  *
@@ -179,6 +179,8 @@ export interface FeatureScore {
179
179
  export interface FilterOptions {
180
180
  /** Feature areas to include (filename stems, e.g., ["groq", "frameworks"]) */
181
181
  areas?: string[];
182
+ /** Tags to include — tasks must have at least one matching tag */
183
+ tags?: string[];
182
184
  /** Specific task IDs to include (e.g., ["groq-blog-queries"]) */
183
185
  taskIds?: string[];
184
186
  }
@@ -35,6 +35,7 @@ export interface RemoteConfigSlice {
35
35
  };
36
36
  areas?: string[];
37
37
  tasks?: string[];
38
+ tags?: string[];
38
39
  changedDocs?: string[];
39
40
  source?: string;
40
41
  compareEnabled?: boolean;
@@ -167,12 +167,16 @@ function taskToInlineFormat(task) {
167
167
  if (task.baseline) {
168
168
  inline.baseline = task.baseline;
169
169
  }
170
+ if (task.tags?.length) {
171
+ inline.tags = task.tags;
172
+ }
170
173
  return inline;
171
174
  }
172
175
  function buildFilterOptions(config) {
173
176
  const areas = config.areas?.length ? config.areas : undefined;
174
177
  const taskIds = config.tasks?.length ? config.tasks : undefined;
175
- if (!areas && !taskIds)
178
+ const tags = config.tags?.length ? config.tags : undefined;
179
+ if (!areas && !taskIds && !tags)
176
180
  return undefined;
177
- return { areas, taskIds };
181
+ return { areas, taskIds, tags };
178
182
  }
@@ -32,6 +32,7 @@ const TASKS_QUERY = /* groq */ `
32
32
  && (!defined($areas) || featureArea->areaId.current in $areas)
33
33
  && (!defined($taskIds) || id.current in $taskIds)
34
34
  && (execution.enabled != false)
35
+ && (!defined($tags) || count((tags)[@ in $tags]) > 0)
35
36
  ] | order(featureArea->areaId.current asc, id.current asc) {
36
37
  "taskId": id.current,
37
38
  description,
@@ -51,6 +52,7 @@ const TASKS_QUERY = /* groq */ `
51
52
  assert,
52
53
  rawAssert,
53
54
  baseline,
55
+ tags,
54
56
  "referenceSolutionTitle": referenceSolution->title
55
57
  }
56
58
  `;
@@ -90,6 +92,7 @@ function buildGroqParams(filter) {
90
92
  areas: filter?.areas && filter.areas.length > 0
91
93
  ? filter.areas.map((a) => a.toLowerCase())
92
94
  : null,
95
+ tags: filter?.tags && filter.tags.length > 0 ? filter.tags : null,
93
96
  taskIds: filter?.taskIds && filter.taskIds.length > 0 ? filter.taskIds : null,
94
97
  };
95
98
  }
@@ -116,6 +119,21 @@ function mapToTaskDefinition(raw) {
116
119
  .map(mapCanonicalDocRef)
117
120
  .filter((d) => d !== null);
118
121
  const assertions = mapAssertions(raw.assert ?? []);
122
+ // Append raw pass-through assertions (escape hatch for arbitrary Promptfoo
123
+ // assertion types that aren't in the curated list). These bypass template
124
+ // resolution and flow directly into the expanded Promptfoo test case as
125
+ // value-based assertions. In baseline mode, buildBaselineAsserts() with
126
+ // "abbreviated" (the default) drops non-rubric assertions, so rawAssert
127
+ // entries only run in the gold variant — consistent with how regular
128
+ // value-based assertions like `contains` or `regex` behave.
129
+ const rawAssertions = (raw.rawAssert ?? [])
130
+ .filter((a) => !!a.type)
131
+ .map((a) => ({
132
+ type: a.type,
133
+ ...(a.value !== undefined ? { value: a.value } : {}),
134
+ ...(a.threshold !== undefined ? { threshold: a.threshold } : {}),
135
+ }));
136
+ const allAssertions = [...assertions, ...rawAssertions];
119
137
  const baseline = raw.baseline
120
138
  ? {
121
139
  ...(raw.baseline.enabled !== undefined
@@ -129,7 +147,7 @@ function mapToTaskDefinition(raw) {
129
147
  }
130
148
  : undefined;
131
149
  return {
132
- assertions,
150
+ assertions: allAssertions,
133
151
  canonicalDocs,
134
152
  description: raw.description,
135
153
  docCoverage: raw.docCoverage ?? false,
@@ -143,6 +161,7 @@ function mapToTaskDefinition(raw) {
143
161
  referenceSolution: "",
144
162
  taskPrompt: raw.taskPrompt,
145
163
  ...(baseline ? { baseline } : {}),
164
+ ...(raw.tags?.length ? { tags: raw.tags } : {}),
146
165
  };
147
166
  }
148
167
  /**
@@ -79,6 +79,12 @@ export class RepoTaskSource {
79
79
  if (entry.execution?.enabled === false) {
80
80
  continue;
81
81
  }
82
+ // Tag filter — skip tasks that don't match any requested tag
83
+ if (filter?.tags &&
84
+ filter.tags.length > 0 &&
85
+ (!entry.tags || !entry.tags.some((t) => filter.tags.includes(t)))) {
86
+ continue;
87
+ }
82
88
  definitions.push(mapToTaskDefinition(entry));
83
89
  }
84
90
  }
@@ -108,5 +114,6 @@ function mapToTaskDefinition(raw) {
108
114
  taskPrompt: typeof task === "string" ? task : "",
109
115
  ...(raw.baseline ? { baseline: raw.baseline } : {}),
110
116
  ...(extraVars ? { extraVars } : {}),
117
+ ...(raw.tags?.length ? { tags: raw.tags } : {}),
111
118
  };
112
119
  }
@@ -688,6 +688,7 @@ async function buildPipelineExplainPlan(actionCommand, rootDir) {
688
688
  skipEval: raw.skipEval ?? false,
689
689
  skipFetch: raw.skipFetch ?? false,
690
690
  source: raw.source,
691
+ tag: raw.tag ?? [],
691
692
  task: raw.task,
692
693
  threshold: raw.threshold,
693
694
  url: raw.url ?? [],
@@ -54,6 +54,7 @@ export interface ResolvedOptions {
54
54
  remote: boolean;
55
55
  repoTasksPath?: string;
56
56
  taskOption?: string;
57
+ tagOption?: string[];
57
58
  taskSourceType?: "content-lake" | "repo" | "yaml";
58
59
  urlArgs: string[];
59
60
  apiUrl: string;
@@ -126,6 +126,13 @@ export function computeResolvedOptions(opts) {
126
126
  // Scoping
127
127
  const areaOption = opts.area ?? process.env.EVAL_FILTER_AREAS ?? undefined;
128
128
  const taskOption = opts.task ?? process.env.EVAL_FILTER_TASKS ?? undefined;
129
+ const tagOption = opts.tag?.length
130
+ ? opts.tag
131
+ : process.env.EVAL_FILTER_TAGS
132
+ ? process.env.EVAL_FILTER_TAGS.split(",")
133
+ .map((s) => s.trim())
134
+ .filter(Boolean)
135
+ : undefined;
129
136
  const changedDocsOption = opts.changedDocs ?? process.env.EVAL_CHANGED_DOCS ?? undefined;
130
137
  // Document-driven scoping (pure — computes impactSummary without env writes)
131
138
  let impactSummary;
@@ -237,6 +244,7 @@ export function computeResolvedOptions(opts) {
237
244
  ? resolve(callerCwd, opts.repoTasksPath)
238
245
  : undefined,
239
246
  taskOption,
247
+ tagOption,
240
248
  taskSourceType: resolveTaskSourceType(opts.taskSource),
241
249
  urlArgs,
242
250
  };
@@ -56,6 +56,7 @@ export interface PipelineCliOptions {
56
56
  remote: boolean;
57
57
  repoTasksPath?: string;
58
58
  task?: string;
59
+ tag: string[];
59
60
  taskSource?: string;
60
61
  threshold?: number;
61
62
  url: string[];
@@ -22,6 +22,13 @@ export function createPipelineCommand() {
22
22
  .option("--no-auto-scope", "Disable release-aware auto-scoping (evaluate all tasks even when a perspective is set)")
23
23
  .option("-a, --area <areas>", "Scope to feature areas (comma-separated)")
24
24
  .option("-t, --task <id>", "Scope to specific task ID")
25
+ .option("--tag <tags>", "Scope to tasks with matching tags (comma-separated, repeatable)", (val, prev) => [
26
+ ...prev,
27
+ ...val
28
+ .split(",")
29
+ .map((s) => s.trim())
30
+ .filter(Boolean),
31
+ ], [])
25
32
  .option("--changed-docs <slugs>", "Auto-scope to tasks affected by these document slugs")
26
33
  .option("-j, --concurrency <n>", "Max parallel API calls during evaluation", parseInt)
27
34
  .option("--grader-replications <n>", "Grader consistency replications", parseInt)
@@ -30,6 +30,7 @@ export function mapToResolvedConfig(opts, rootDir) {
30
30
  ?.split(",")
31
31
  .map((s) => s.trim())
32
32
  .filter(Boolean),
33
+ tags: opts.tagOption,
33
34
  changedDocs: opts.changedDocsOption
34
35
  ?.split(",")
35
36
  .map((s) => s.trim())
@@ -121,12 +121,13 @@ export class FetchDocsStep {
121
121
  // Helpers
122
122
  // ---------------------------------------------------------------------------
123
123
  function buildFilter(ctx) {
124
- const { areas, tasks } = ctx.config;
125
- if (!areas && !tasks)
124
+ const { areas, tasks, tags } = ctx.config;
125
+ if (!areas && !tasks && !tags)
126
126
  return undefined;
127
127
  return {
128
128
  ...(areas ? { areas } : {}),
129
129
  ...(tasks ? { taskIds: tasks } : {}),
130
+ ...(tags ? { tags } : {}),
130
131
  };
131
132
  }
132
133
  /**
@@ -28,8 +28,12 @@ export class GenerateConfigsStep {
28
28
  // repo-based, and YAML tasks depending on which adapter is wired.
29
29
  let tasks;
30
30
  try {
31
- const filter = ctx.config.areas || ctx.config.tasks
32
- ? { areas: ctx.config.areas, taskIds: ctx.config.tasks }
31
+ const filter = ctx.config.areas || ctx.config.tasks || ctx.config.tags
32
+ ? {
33
+ areas: ctx.config.areas,
34
+ taskIds: ctx.config.tasks,
35
+ tags: ctx.config.tags,
36
+ }
33
37
  : undefined;
34
38
  tasks = await ctx.taskSource.loadTasks(filter);
35
39
  }
@@ -54,10 +58,11 @@ export class GenerateConfigsStep {
54
58
  try {
55
59
  generateConfigs({
56
60
  allowedOrigins: ctx.config.allowedOrigins,
57
- filter: ctx.config.areas || ctx.config.tasks
61
+ filter: ctx.config.areas || ctx.config.tasks || ctx.config.tags
58
62
  ? {
59
63
  areas: ctx.config.areas,
60
64
  taskIds: ctx.config.tasks,
65
+ tags: ctx.config.tags,
61
66
  }
62
67
  : undefined,
63
68
  resolvedSource,
@@ -40,10 +40,11 @@ export class RunEvalStep {
40
40
  // Precondition: canonical context files exist for filtered tasks.
41
41
  // Must apply the same area/task filter as fetch-docs so we only
42
42
  // check contexts that were actually fetched.
43
- const filter = ctx.config.areas || ctx.config.tasks
43
+ const filter = ctx.config.areas || ctx.config.tasks || ctx.config.tags
44
44
  ? {
45
45
  ...(ctx.config.areas ? { areas: ctx.config.areas } : {}),
46
46
  ...(ctx.config.tasks ? { taskIds: ctx.config.tasks } : {}),
47
+ ...(ctx.config.tags ? { tags: ctx.config.tags } : {}),
47
48
  }
48
49
  : undefined;
49
50
  let tasks = await ctx.taskSource.loadTasks(filter);
@@ -76,10 +77,11 @@ export class RunEvalStep {
76
77
  if (!debug?.enabled) {
77
78
  try {
78
79
  evalFingerprint = computeEvalFingerprint({
79
- filter: ctx.config.areas || ctx.config.tasks
80
+ filter: ctx.config.areas || ctx.config.tasks || ctx.config.tags
80
81
  ? {
81
82
  areas: ctx.config.areas,
82
83
  taskIds: ctx.config.tasks,
84
+ tags: ctx.config.tags,
83
85
  }
84
86
  : undefined,
85
87
  graderModel: "default",
@@ -145,6 +145,7 @@ export interface PlanOptions {
145
145
  skipEval: boolean;
146
146
  skipFetch: boolean;
147
147
  source?: string;
148
+ tagOption?: string[];
148
149
  taskOption?: string;
149
150
  }
150
151
  /**
@@ -117,7 +117,7 @@ export async function buildPipelinePlan(opts, rootDir) {
117
117
  .filter((i) => i.severity === "error")
118
118
  .map((i) => `[${i.source}] ${i.message}`);
119
119
  // 2. Expand tasks with filters
120
- const filter = opts.areaOption || opts.taskOption
120
+ const filter = opts.areaOption || opts.taskOption || opts.tagOption?.length
121
121
  ? {
122
122
  areas: opts.areaOption
123
123
  ? opts.areaOption.split(",").map((a) => a.trim())
@@ -125,6 +125,7 @@ export async function buildPipelinePlan(opts, rootDir) {
125
125
  taskIds: opts.taskOption
126
126
  ? opts.taskOption.split(",").map((t) => t.trim())
127
127
  : undefined,
128
+ tags: opts.tagOption,
128
129
  }
129
130
  : undefined;
130
131
  let totalTests = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "restricted"