@sanity/ailf 4.0.5 → 4.0.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/dist/_vendor/ailf-core/constants.d.ts +22 -0
- package/dist/_vendor/ailf-core/constants.js +22 -0
- package/dist/_vendor/ailf-core/index.d.ts +1 -0
- package/dist/_vendor/ailf-core/index.js +1 -0
- package/dist/adapters/api-client/build-request.js +7 -2
- package/dist/adapters/task-sources/repo-schemas.js +6 -2
- package/dist/adapters/task-sources/repo-task-source.d.ts +11 -1
- package/dist/adapters/task-sources/repo-task-source.js +7 -4
- package/dist/commands/pipeline-action.js +16 -0
- package/dist/composition-root.d.ts +7 -0
- package/dist/composition-root.js +24 -11
- package/dist/orchestration/steps/fetch-docs-step.js +10 -30
- package/dist/orchestration/steps/generate-configs-step.d.ts +8 -15
- package/dist/orchestration/steps/generate-configs-step.js +26 -118
- package/dist/orchestration/steps/mirror-repo-tasks-step.js +12 -0
- package/dist/pipeline/run-context.d.ts +6 -1
- package/dist/pipeline/run-context.js +17 -2
- package/package.json +1 -1
- package/dist/orchestration/load-pipeline-tasks.d.ts +0 -40
- package/dist/orchestration/load-pipeline-tasks.js +0 -57
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sanity/ailf-core — Cross-package sentinel constants.
|
|
3
|
+
*
|
|
4
|
+
* Sentinel strings shared between the init template (which writes them
|
|
5
|
+
* into `.github/workflows/ailf-eval.yml`) and the consumers that must
|
|
6
|
+
* recognize them (run-context builder, caller envelope assembly, CLI
|
|
7
|
+
* pre-flight checks). Centralizing here keeps the placeholder definition
|
|
8
|
+
* in exactly one place — duplicating the literal across producers and
|
|
9
|
+
* consumers historically caused the literal to leak into Sanity reports
|
|
10
|
+
* (W0143).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The literal string `ailf init` writes into the scaffolded GitHub Actions
|
|
14
|
+
* workflow as the value of `AILF_OWNER_TEAM`. Consumers treat this string
|
|
15
|
+
* as semantically unset — it must never end up persisted as a real team
|
|
16
|
+
* slug on a run report.
|
|
17
|
+
*
|
|
18
|
+
* @see packages/core/examples/ailf-eval-workflow.yml — the canonical source
|
|
19
|
+
* @see packages/eval/src/pipeline/run-context.ts — sanitizes on read
|
|
20
|
+
* @see packages/eval/src/adapters/api-client/build-request.ts — drops on the wire
|
|
21
|
+
*/
|
|
22
|
+
export declare const PLACEHOLDER_OWNER_TEAM = "<REPLACE-WITH-YOUR-TEAM-SLUG>";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sanity/ailf-core — Cross-package sentinel constants.
|
|
3
|
+
*
|
|
4
|
+
* Sentinel strings shared between the init template (which writes them
|
|
5
|
+
* into `.github/workflows/ailf-eval.yml`) and the consumers that must
|
|
6
|
+
* recognize them (run-context builder, caller envelope assembly, CLI
|
|
7
|
+
* pre-flight checks). Centralizing here keeps the placeholder definition
|
|
8
|
+
* in exactly one place — duplicating the literal across producers and
|
|
9
|
+
* consumers historically caused the literal to leak into Sanity reports
|
|
10
|
+
* (W0143).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The literal string `ailf init` writes into the scaffolded GitHub Actions
|
|
14
|
+
* workflow as the value of `AILF_OWNER_TEAM`. Consumers treat this string
|
|
15
|
+
* as semantically unset — it must never end up persisted as a real team
|
|
16
|
+
* slug on a run report.
|
|
17
|
+
*
|
|
18
|
+
* @see packages/core/examples/ailf-eval-workflow.yml — the canonical source
|
|
19
|
+
* @see packages/eval/src/pipeline/run-context.ts — sanitizes on read
|
|
20
|
+
* @see packages/eval/src/adapters/api-client/build-request.ts — drops on the wire
|
|
21
|
+
*/
|
|
22
|
+
export const PLACEHOLDER_OWNER_TEAM = "<REPLACE-WITH-YOUR-TEAM-SLUG>";
|
|
@@ -17,6 +17,7 @@ export * from "./services/index.js";
|
|
|
17
17
|
export * from "./examples/index.js";
|
|
18
18
|
export * from "./artifact-registry.js";
|
|
19
19
|
export * from "./batch-signing.js";
|
|
20
|
+
export * from "./constants.js";
|
|
20
21
|
export { defineCanaryTasks, defineConfig, defineFeatures, defineModeBase, defineModels, definePricingTable, definePreset, definePrompts, defineRubrics, defineSchedules, defineSinks, defineSources, defineTask, defineTestBudgets, defineThresholds, } from "./config-helpers.js";
|
|
21
22
|
export type { PricingEntry, PromptEntry, SourceEntry, } from "./config-helpers.js";
|
|
22
23
|
export { env } from "./env-helper.js";
|
|
@@ -17,6 +17,7 @@ export * from "./services/index.js";
|
|
|
17
17
|
export * from "./examples/index.js";
|
|
18
18
|
export * from "./artifact-registry.js";
|
|
19
19
|
export * from "./batch-signing.js";
|
|
20
|
+
export * from "./constants.js";
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Architecture overhaul — Phase 0 helpers
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync } from "fs";
|
|
16
16
|
import { resolve } from "path";
|
|
17
|
-
import { PipelineRequestSchema, } from "../../_vendor/ailf-core/index.js";
|
|
17
|
+
import { PLACEHOLDER_OWNER_TEAM, PipelineRequestSchema, } from "../../_vendor/ailf-core/index.js";
|
|
18
18
|
import { LEGACY_EVAL_MODE_ALIASES, isRunClassification, } from "../../_vendor/ailf-shared/index.js";
|
|
19
19
|
import { RepoTaskSource } from "../task-sources/repo-task-source.js";
|
|
20
20
|
const LEGACY_LITERACY_VARIANT_SET = new Set(LEGACY_EVAL_MODE_ALIASES);
|
|
@@ -316,7 +316,12 @@ export function buildCallerEnvelope(config) {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
// Owner: flag > env. Team required, individual optional.
|
|
319
|
-
|
|
319
|
+
// W0143: drop the init-template placeholder — when a consumer hasn't
|
|
320
|
+
// filled in their team slug yet we treat it as unset rather than
|
|
321
|
+
// shipping the literal `<REPLACE-WITH-YOUR-TEAM-SLUG>` across the wire
|
|
322
|
+
// and into the report's Provenance card.
|
|
323
|
+
const rawTeam = config.ownerTeamOption ?? process.env.AILF_OWNER_TEAM?.trim() ?? undefined;
|
|
324
|
+
const team = rawTeam === PLACEHOLDER_OWNER_TEAM ? undefined : rawTeam;
|
|
320
325
|
const individual = config.ownerIndividualOption ??
|
|
321
326
|
process.env.AILF_OWNER_INDIVIDUAL?.trim() ??
|
|
322
327
|
process.env.GITHUB_ACTOR?.trim() ??
|
|
@@ -376,12 +376,16 @@ export function parseCanonicalTaskFile(raw, filename) {
|
|
|
376
376
|
// (featureArea, canonicalDocs, assert, vars), surface a helpful error
|
|
377
377
|
// message telling them what the canonical names are.
|
|
378
378
|
// ---------------------------------------------------------------------------
|
|
379
|
+
// Phrasing avoids literal `{` and `}` characters on purpose. GitHub Actions
|
|
380
|
+
// registers each line of a multi-line secret as a mask, so a pretty-printed
|
|
381
|
+
// JSON secret introduces standalone `{` / `}` masks that then redact every
|
|
382
|
+
// `{` / `}` in subsequent log output. See W0144.
|
|
379
383
|
/** Old field names from @sanity/ailf-tasks → canonical equivalents */
|
|
380
384
|
const LEGACY_FIELD_MAP = {
|
|
381
385
|
featureArea: "area",
|
|
382
|
-
canonicalDocs: "context.docs (
|
|
386
|
+
canonicalDocs: "context.docs (move the docs array under a context parent)",
|
|
383
387
|
assert: "assertions",
|
|
384
|
-
vars: "prompt (
|
|
388
|
+
vars: "prompt (move the text string under a prompt parent)",
|
|
385
389
|
};
|
|
386
390
|
/**
|
|
387
391
|
* Detect legacy field names in raw task data and return helpful messages.
|
|
@@ -19,8 +19,18 @@
|
|
|
19
19
|
* @see packages/core/src/ports/task-source.ts — TaskSource port
|
|
20
20
|
*/
|
|
21
21
|
import type { FilterOptions, GeneralizedTaskDefinition, TaskSource } from "../../_vendor/ailf-core/index.d.ts";
|
|
22
|
+
export interface RepoTaskSourceOptions {
|
|
23
|
+
/**
|
|
24
|
+
* When true, treat a missing directory or empty task set as a valid
|
|
25
|
+
* empty result instead of throwing. Used by the composition root for
|
|
26
|
+
* the AILF-bundled `tasks/${mode}/` source, which is missing in some
|
|
27
|
+
* test rootDirs and modes that ship no defaults.
|
|
28
|
+
*/
|
|
29
|
+
allowMissing?: boolean;
|
|
30
|
+
}
|
|
22
31
|
export declare class RepoTaskSource implements TaskSource {
|
|
23
32
|
private readonly tasksDir;
|
|
24
|
-
|
|
33
|
+
private readonly options;
|
|
34
|
+
constructor(tasksDir: string, options?: RepoTaskSourceOptions);
|
|
25
35
|
loadTasks(filter?: FilterOptions): Promise<GeneralizedTaskDefinition[]>;
|
|
26
36
|
}
|
|
@@ -26,16 +26,17 @@ import { detectLegacyFieldNames, parseCanonicalTaskFile, } from "./repo-schemas.
|
|
|
26
26
|
import { discoverTsTaskFiles, loadTsTaskFile } from "./task-file-loader.js";
|
|
27
27
|
/** Set of canonical mode names for O(1) lookup */
|
|
28
28
|
const KNOWN_MODES = new Set(CANONICAL_EVAL_MODES);
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// RepoTaskSource adapter
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
29
|
export class RepoTaskSource {
|
|
33
30
|
tasksDir;
|
|
34
|
-
|
|
31
|
+
options;
|
|
32
|
+
constructor(tasksDir, options = {}) {
|
|
35
33
|
this.tasksDir = tasksDir;
|
|
34
|
+
this.options = options;
|
|
36
35
|
}
|
|
37
36
|
async loadTasks(filter) {
|
|
38
37
|
if (!existsSync(this.tasksDir)) {
|
|
38
|
+
if (this.options.allowMissing)
|
|
39
|
+
return [];
|
|
39
40
|
throw new Error(`Repo tasks directory not found: ${this.tasksDir}\n` +
|
|
40
41
|
" Provide a valid path via --repo-tasks-path");
|
|
41
42
|
}
|
|
@@ -44,6 +45,8 @@ export class RepoTaskSource {
|
|
|
44
45
|
.sort();
|
|
45
46
|
const tsFiles = discoverTsTaskFiles(this.tasksDir);
|
|
46
47
|
if (yamlFiles.length === 0 && tsFiles.length === 0) {
|
|
48
|
+
if (this.options.allowMissing)
|
|
49
|
+
return [];
|
|
47
50
|
throw new Error(`No task files found in ${this.tasksDir}\n` +
|
|
48
51
|
" Expected .ailf/tasks/*.yaml or .ailf/tasks/*.task.ts files");
|
|
49
52
|
}
|
|
@@ -20,6 +20,7 @@ import { buildAppContext, parseArtifactUploadEnv, } from "../orchestration/build
|
|
|
20
20
|
import { buildStepSequence } from "../orchestration/build-step-sequence.js";
|
|
21
21
|
import { orchestratePipeline } from "../orchestration/pipeline-orchestrator.js";
|
|
22
22
|
import { load } from "js-yaml";
|
|
23
|
+
import { PLACEHOLDER_OWNER_TEAM } from "../_vendor/ailf-core/index.js";
|
|
23
24
|
import { parseRepoConfig, } from "../adapters/task-sources/repo-schemas.js";
|
|
24
25
|
import { getCallerCwd, resolveOutputDir } from "./shared/resolve-output-dir.js";
|
|
25
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -406,6 +407,7 @@ function resolveRepoTasksPath(callerCwd, explicitPath, taskSourceType) {
|
|
|
406
407
|
* 4. Delegate to the PipelineOrchestrator
|
|
407
408
|
*/
|
|
408
409
|
export async function executePipeline(cliOpts) {
|
|
410
|
+
warnIfPlaceholderOwnerTeam();
|
|
409
411
|
// When --config is provided, resolve config from file instead of CLI flags
|
|
410
412
|
if (cliOpts.config) {
|
|
411
413
|
const { existsSync } = await import("fs");
|
|
@@ -493,6 +495,20 @@ export async function executePipeline(cliOpts) {
|
|
|
493
495
|
// ---------------------------------------------------------------------------
|
|
494
496
|
// Internal helpers
|
|
495
497
|
// ---------------------------------------------------------------------------
|
|
498
|
+
/**
|
|
499
|
+
* W0143: warn once on stderr when the consumer is still running with the
|
|
500
|
+
* unedited init-template placeholder for AILF_OWNER_TEAM. The run
|
|
501
|
+
* continues — provenance just gets attributed as unset rather than the
|
|
502
|
+
* literal placeholder.
|
|
503
|
+
*/
|
|
504
|
+
function warnIfPlaceholderOwnerTeam() {
|
|
505
|
+
if (process.env.AILF_OWNER_TEAM?.trim() !== PLACEHOLDER_OWNER_TEAM)
|
|
506
|
+
return;
|
|
507
|
+
console.warn(` ⚠️ AILF_OWNER_TEAM is still set to the init-template placeholder ` +
|
|
508
|
+
`"${PLACEHOLDER_OWNER_TEAM}". Provenance will be logged as unset. ` +
|
|
509
|
+
`Set a real team slug in .github/workflows/ailf-eval.yml (or unset ` +
|
|
510
|
+
`AILF_OWNER_TEAM) to attribute this run.`);
|
|
511
|
+
}
|
|
496
512
|
/**
|
|
497
513
|
* Resolve CLI options into typed ResolvedOptions.
|
|
498
514
|
*/
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @see docs/archive/exec-plans/ports-and-adapters/phase-7-composition-root.md
|
|
17
17
|
*/
|
|
18
18
|
import { type AppContext, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssertionRegistration, type Logger, type ResolvedConfig } from "./_vendor/ailf-core/index.d.ts";
|
|
19
|
+
import { CompositeTaskSource, ContentLakeTaskSource, RepoTaskSource } from "./adapters/task-sources/index.js";
|
|
19
20
|
/**
|
|
20
21
|
* Create a fully wired AppContext from resolved configuration.
|
|
21
22
|
*
|
|
@@ -42,6 +43,12 @@ export declare function createAppContext(config: ResolvedConfig): AppContext;
|
|
|
42
43
|
* Exported for unit-test access; not part of the public package API.
|
|
43
44
|
*/
|
|
44
45
|
export declare function createArtifactWriter(config: ResolvedConfig, logger: Logger, progress?: ArtifactWriterProgressOptions): ArtifactWriter;
|
|
46
|
+
/**
|
|
47
|
+
* Build the `TaskSource` adapter wired by the composition root for a
|
|
48
|
+
* given `ResolvedConfig`. Exported for test access — composition-root
|
|
49
|
+
* wiring is a contract worth asserting directly.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createTaskSource(config: ResolvedConfig): CompositeTaskSource | ContentLakeTaskSource | RepoTaskSource;
|
|
45
52
|
/**
|
|
46
53
|
* Generic Promptfoo assertion types available to all evaluation modes.
|
|
47
54
|
*
|
package/dist/composition-root.js
CHANGED
|
@@ -32,6 +32,7 @@ import { PromptfooEvalAdapter } from "./adapters/eval-runners/promptfoo-eval-ada
|
|
|
32
32
|
import { ConsoleLogger, JsonLogger, QuietLogger, } from "./adapters/loggers/index.js";
|
|
33
33
|
import { ConsoleProgressReporter } from "./adapters/progress/console-progress-reporter.js";
|
|
34
34
|
import { CompositeTaskSource, ContentLakeTaskSource, RepoTaskSource, } from "./adapters/task-sources/index.js";
|
|
35
|
+
import { resolveVendoredSubdir } from "./pipeline/compiler/config-loader.js";
|
|
35
36
|
import { createAgentHarnessBase, createKnowledgeProbeBase, createLiteracyModeBase, createMcpServerModeBase, } from "./pipeline/compiler/mode-bases/index.js";
|
|
36
37
|
import { createSanityLiteracyPreset } from "./pipeline/compiler/presets/index.js";
|
|
37
38
|
import { getSanityClient } from "./sanity/client.js";
|
|
@@ -297,7 +298,12 @@ function createCache(config) {
|
|
|
297
298
|
return local;
|
|
298
299
|
return new ContentLakeCacheAdapter(local, createReportStore(config));
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Build the `TaskSource` adapter wired by the composition root for a
|
|
303
|
+
* given `ResolvedConfig`. Exported for test access — composition-root
|
|
304
|
+
* wiring is a contract worth asserting directly.
|
|
305
|
+
*/
|
|
306
|
+
export function createTaskSource(config) {
|
|
301
307
|
// "repo" mode — use ONLY repo tasks, no Content Lake or YAML merge.
|
|
302
308
|
// This is the correct mode for API-triggered inline-task evaluations
|
|
303
309
|
// where the caller sent their own task definitions. Without this,
|
|
@@ -309,21 +315,28 @@ function createTaskSource(config) {
|
|
|
309
315
|
}
|
|
310
316
|
return new RepoTaskSource(config.repoTasksPath);
|
|
311
317
|
}
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
process.env.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
// "content-lake" — Studio-authored ailf.task documents only.
|
|
319
|
+
if (config.taskSourceType === "content-lake") {
|
|
320
|
+
return new ContentLakeTaskSource(getSanityClient({
|
|
321
|
+
token: process.env.AILF_REPORT_SANITY_API_TOKEN ??
|
|
322
|
+
process.env.SANITY_API_TOKEN ??
|
|
323
|
+
undefined,
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
// Unset — AILF-bundled defaults from `tasks/${mode}/`, optionally
|
|
327
|
+
// augmented with the caller's `--repo-tasks-path` (W0146). The
|
|
328
|
+
// bundled directory is allowed to be missing so test rootDirs and
|
|
329
|
+
// modes that ship no defaults degrade gracefully to the augment
|
|
330
|
+
// source (or empty).
|
|
331
|
+
const bundledDir = resolveVendoredSubdir(config.rootDir, `tasks/${config.mode}`);
|
|
332
|
+
const bundled = new RepoTaskSource(bundledDir, { allowMissing: true });
|
|
320
333
|
if (config.repoTasksPath) {
|
|
321
334
|
return new CompositeTaskSource([
|
|
322
|
-
|
|
335
|
+
bundled,
|
|
323
336
|
new RepoTaskSource(config.repoTasksPath),
|
|
324
337
|
]);
|
|
325
338
|
}
|
|
326
|
-
return
|
|
339
|
+
return bundled;
|
|
327
340
|
}
|
|
328
341
|
// ---------------------------------------------------------------------------
|
|
329
342
|
// Layer 0: Framework built-in assertions
|
|
@@ -17,7 +17,6 @@ import { emitFileContents } from "../../artifact-capture/emit-file.js";
|
|
|
17
17
|
import { getStepInputPaths } from "../../pipeline/cache.js";
|
|
18
18
|
import { buildCacheContext } from "../cache-context.js";
|
|
19
19
|
import { checkCanonicalContextsExist } from "../../pipeline/checks.js";
|
|
20
|
-
import { loadPipelineTasks } from "../load-pipeline-tasks.js";
|
|
21
20
|
import { loadSource } from "../../sources.js";
|
|
22
21
|
import { configToSourceOverrides } from "../config-to-source-overrides.js";
|
|
23
22
|
export class FetchDocsStep {
|
|
@@ -30,35 +29,16 @@ export class FetchDocsStep {
|
|
|
30
29
|
return { status: "skipped", reason: "--no-fetch" };
|
|
31
30
|
}
|
|
32
31
|
const start = Date.now();
|
|
33
|
-
// Load tasks —
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// AILF defaults from tasks/${mode}/ + optional repoTasksPath augment).
|
|
44
|
-
let allTasks;
|
|
45
|
-
if (ctx.config.taskSourceType === "content-lake" ||
|
|
46
|
-
ctx.config.taskSourceType === "repo") {
|
|
47
|
-
const filter = {
|
|
48
|
-
...(ctx.config.areas?.length ? { areas: ctx.config.areas } : {}),
|
|
49
|
-
...(ctx.config.tasks?.length ? { taskIds: ctx.config.tasks } : {}),
|
|
50
|
-
...(ctx.config.tags?.length ? { tags: ctx.config.tags } : {}),
|
|
51
|
-
};
|
|
52
|
-
allTasks = await ctx.taskSource.loadTasks(Object.keys(filter).length > 0 ? filter : undefined);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
allTasks = await loadPipelineTasks({
|
|
56
|
-
rootDir: ctx.config.rootDir,
|
|
57
|
-
mode: ctx.config.mode,
|
|
58
|
-
repoTasksPath: ctx.config.repoTasksPath,
|
|
59
|
-
taskSourceType: ctx.config.taskSourceType,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
32
|
+
// Load tasks via ctx.taskSource — the composition root wires the
|
|
33
|
+
// right adapter for every taskSourceType (W0146). FetchDocsStep and
|
|
34
|
+
// GenerateConfigsStep MUST go through the same adapter so configs
|
|
35
|
+
// reference context files that were actually fetched.
|
|
36
|
+
const filter = {
|
|
37
|
+
...(ctx.config.areas?.length ? { areas: ctx.config.areas } : {}),
|
|
38
|
+
...(ctx.config.tasks?.length ? { taskIds: ctx.config.tasks } : {}),
|
|
39
|
+
...(ctx.config.tags?.length ? { tags: ctx.config.tags } : {}),
|
|
40
|
+
};
|
|
41
|
+
const allTasks = await ctx.taskSource.loadTasks(Object.keys(filter).length > 0 ? filter : undefined);
|
|
62
42
|
// Bridge: narrow to literacy tasks for canonical doc access
|
|
63
43
|
const literacyTasks = allTasks.filter((t) => t.mode === "literacy");
|
|
64
44
|
const tasksWithDocs = literacyTasks.filter((t) => (t.context?.docs?.length ?? 0) > 0);
|
|
@@ -17,24 +17,17 @@ export declare class GenerateConfigsStep implements PipelineStep {
|
|
|
17
17
|
execute(ctx: AppContext, state: PipelineState): Promise<StepResult>;
|
|
18
18
|
private compileLiteracyVariants;
|
|
19
19
|
private compileSingleMode;
|
|
20
|
-
private loadTasks;
|
|
21
20
|
/**
|
|
22
|
-
* Load tasks via ctx.taskSource
|
|
21
|
+
* Load tasks via ctx.taskSource — the single adapter wired by the
|
|
22
|
+
* composition root for every taskSourceType (W0146). FetchDocsStep
|
|
23
|
+
* and GenerateConfigsStep MUST go through the same adapter so configs
|
|
24
|
+
* reference context files that were actually fetched.
|
|
23
25
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* pushes it into the GROQ query, RepoTaskSource applies it in-memory.
|
|
26
|
+
* Filtering by area/task/tag is delegated to the adapter:
|
|
27
|
+
* ContentLakeTaskSource pushes it into the GROQ query;
|
|
28
|
+
* RepoTaskSource applies it in-memory.
|
|
28
29
|
*/
|
|
29
|
-
private
|
|
30
|
-
/**
|
|
31
|
-
* Load tasks from filesystem .task.ts files.
|
|
32
|
-
*
|
|
33
|
-
* This is the original path used for repo-based and inline tasks.
|
|
34
|
-
* It scans tasks/{mode}/ and optionally --repo-tasks-path.
|
|
35
|
-
*/
|
|
36
|
-
private loadTasksFromFilesystem;
|
|
37
|
-
private applyFilters;
|
|
30
|
+
private loadTasks;
|
|
38
31
|
/**
|
|
39
32
|
* Build a descriptive error message when no tasks match the current filters.
|
|
40
33
|
* Distinguishes between "no tasks exist" and "tasks exist but filters exclude them".
|
|
@@ -208,99 +208,36 @@ export class GenerateConfigsStep {
|
|
|
208
208
|
// ---------------------------------------------------------------------------
|
|
209
209
|
// Task loading — unified for all modes
|
|
210
210
|
// ---------------------------------------------------------------------------
|
|
211
|
-
async loadTasks(ctx, mode, state) {
|
|
212
|
-
// Adapter path — use ctx.taskSource. The composition root wires the
|
|
213
|
-
// right adapter for each taskSourceType:
|
|
214
|
-
// - "content-lake" → ContentLakeTaskSource (Studio-owned ailf.task docs)
|
|
215
|
-
// - "repo" → RepoTaskSource (loads .yaml AND .task.ts from repoTasksPath)
|
|
216
|
-
// Routing both through ctx.taskSource keeps the orchestration step
|
|
217
|
-
// file-format-agnostic (W0148: external-consumer evals materialize
|
|
218
|
-
// inline tasks as .yaml, which loadPipelineTasks can't read).
|
|
219
|
-
if (ctx.config.taskSourceType === "content-lake" ||
|
|
220
|
-
ctx.config.taskSourceType === "repo") {
|
|
221
|
-
return this.loadTasksFromAdapter(ctx, state);
|
|
222
|
-
}
|
|
223
|
-
// Filesystem path — load from .task.ts files (legacy unset path:
|
|
224
|
-
// AILF defaults from tasks/${mode}/ + optional repoTasksPath augment).
|
|
225
|
-
return this.loadTasksFromFilesystem(ctx, mode, state);
|
|
226
|
-
}
|
|
227
211
|
/**
|
|
228
|
-
* Load tasks via ctx.taskSource
|
|
212
|
+
* Load tasks via ctx.taskSource — the single adapter wired by the
|
|
213
|
+
* composition root for every taskSourceType (W0146). FetchDocsStep
|
|
214
|
+
* and GenerateConfigsStep MUST go through the same adapter so configs
|
|
215
|
+
* reference context files that were actually fetched.
|
|
229
216
|
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* pushes it into the GROQ query, RepoTaskSource applies it in-memory.
|
|
217
|
+
* Filtering by area/task/tag is delegated to the adapter:
|
|
218
|
+
* ContentLakeTaskSource pushes it into the GROQ query;
|
|
219
|
+
* RepoTaskSource applies it in-memory.
|
|
234
220
|
*/
|
|
235
|
-
async
|
|
221
|
+
async loadTasks(ctx, mode, state) {
|
|
236
222
|
const filter = {
|
|
237
223
|
...(ctx.config.areas?.length ? { areas: ctx.config.areas } : {}),
|
|
238
224
|
...(ctx.config.tasks?.length ? { taskIds: ctx.config.tasks } : {}),
|
|
239
225
|
...(ctx.config.tags?.length ? { tags: ctx.config.tags } : {}),
|
|
240
226
|
};
|
|
241
|
-
const
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// Release auto-scope
|
|
247
|
-
if (state.releaseAutoScope && !ctx.config.noAutoScope) {
|
|
248
|
-
const scopedIds = new Set(state.releaseAutoScope.affectedTaskIds);
|
|
249
|
-
const beforeCount = tasks.length;
|
|
250
|
-
const scoped = tasks.filter((t) => "id" in t && scopedIds.has(t.id));
|
|
251
|
-
ctx.logger.info(` 🎯 Auto-scoped to ${scoped.length} of ${beforeCount} task(s) affected by release`);
|
|
252
|
-
return scoped;
|
|
253
|
-
}
|
|
254
|
-
return tasks;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Load tasks from filesystem .task.ts files.
|
|
258
|
-
*
|
|
259
|
-
* This is the original path used for repo-based and inline tasks.
|
|
260
|
-
* It scans tasks/{mode}/ and optionally --repo-tasks-path.
|
|
261
|
-
*/
|
|
262
|
-
async loadTasksFromFilesystem(ctx, mode, state) {
|
|
263
|
-
const { resolve } = await import("path");
|
|
264
|
-
const { discoverTsTaskFiles, loadTsTaskFile } = await import("../../adapters/task-sources/task-file-loader.js");
|
|
265
|
-
const { resolveVendoredSubdir } = await import("../../pipeline/compiler/config-loader.js");
|
|
266
|
-
// Discover task files from the mode-specific directory and --repo-tasks-path.
|
|
267
|
-
// Use vendored copies in dist/ when @sanity/ailf-core isn't resolvable
|
|
268
|
-
// (i.e., running outside the monorepo via npx).
|
|
269
|
-
//
|
|
270
|
-
// When taskSourceType === "repo", skip the AILF-bundled tasks/${mode}/
|
|
271
|
-
// directory and load ONLY from repoTasksPath. Mirrors the composition-root
|
|
272
|
-
// contract for repo-only mode (see composition-root.ts:392-405).
|
|
273
|
-
const dirs = [];
|
|
274
|
-
if (ctx.config.taskSourceType !== "repo") {
|
|
275
|
-
dirs.push(resolveVendoredSubdir(ctx.config.rootDir, `tasks/${mode}`));
|
|
276
|
-
}
|
|
277
|
-
else if (!ctx.config.repoTasksPath) {
|
|
278
|
-
throw new Error('taskSourceType "repo" requires repoTasksPath to be set (no AILF defaults loaded in repo-only mode)');
|
|
279
|
-
}
|
|
280
|
-
// Also search --repo-tasks-path (e.g., .ailf/tasks/) for repo-based tasks
|
|
281
|
-
if (ctx.config.repoTasksPath) {
|
|
282
|
-
const repoDir = resolve(ctx.config.repoTasksPath);
|
|
283
|
-
if (!dirs.includes(repoDir)) {
|
|
284
|
-
dirs.push(repoDir);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
227
|
+
const allTasks = await ctx.taskSource.loadTasks(Object.keys(filter).length > 0 ? filter : undefined);
|
|
228
|
+
// Mode filter — the adapter may return a mixed-mode set (e.g. a user's
|
|
229
|
+
// `--repo-tasks-path` containing tasks of multiple modes). Skip
|
|
230
|
+
// non-matching modes with a warning so unintentional misclassification
|
|
231
|
+
// is visible without breaking the run.
|
|
287
232
|
const tasks = [];
|
|
288
233
|
const skippedByMode = new Map();
|
|
289
|
-
for (const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (!("mode" in task) || task.mode === mode) {
|
|
297
|
-
tasks.push(task);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
const taskMode = task.mode ?? "unknown";
|
|
301
|
-
skippedByMode.set(taskMode, (skippedByMode.get(taskMode) ?? 0) + 1);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
234
|
+
for (const task of allTasks) {
|
|
235
|
+
if (!("mode" in task) || task.mode === mode) {
|
|
236
|
+
tasks.push(task);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const taskMode = task.mode ?? "unknown";
|
|
240
|
+
skippedByMode.set(taskMode, (skippedByMode.get(taskMode) ?? 0) + 1);
|
|
304
241
|
}
|
|
305
242
|
}
|
|
306
243
|
if (skippedByMode.size > 0) {
|
|
@@ -310,46 +247,17 @@ export class GenerateConfigsStep {
|
|
|
310
247
|
.join(", ");
|
|
311
248
|
ctx.logger.warn(` ⚠ Skipped ${total} task(s) with non-matching mode (${summary}). Current pipeline mode: ${mode}. Run with --mode <mode> to include them.`);
|
|
312
249
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
250
|
+
this.lastLoadedTaskIds = tasks
|
|
251
|
+
.map((t) => t.id)
|
|
252
|
+
.filter((id) => !!id);
|
|
316
253
|
if (state.releaseAutoScope && !ctx.config.noAutoScope) {
|
|
317
254
|
const scopedIds = new Set(state.releaseAutoScope.affectedTaskIds);
|
|
318
|
-
const beforeCount =
|
|
319
|
-
const scoped =
|
|
255
|
+
const beforeCount = tasks.length;
|
|
256
|
+
const scoped = tasks.filter((t) => "id" in t && scopedIds.has(t.id));
|
|
320
257
|
ctx.logger.info(` 🎯 Auto-scoped to ${scoped.length} of ${beforeCount} task(s) affected by release`);
|
|
321
258
|
return scoped;
|
|
322
259
|
}
|
|
323
|
-
return
|
|
324
|
-
}
|
|
325
|
-
applyFilters(ctx, tasks) {
|
|
326
|
-
// Capture pre-filter IDs for diagnostic messages
|
|
327
|
-
this.lastLoadedTaskIds = tasks
|
|
328
|
-
.map((t) => t.id)
|
|
329
|
-
.filter((id) => !!id);
|
|
330
|
-
let result = tasks;
|
|
331
|
-
if (ctx.config.areas?.length) {
|
|
332
|
-
const allowed = new Set(ctx.config.areas.map((a) => a.toLowerCase()));
|
|
333
|
-
result = result.filter((t) => {
|
|
334
|
-
const area = t.area?.toLowerCase();
|
|
335
|
-
return area && allowed.has(area);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (ctx.config.tasks?.length) {
|
|
339
|
-
const allowed = new Set(ctx.config.tasks);
|
|
340
|
-
result = result.filter((t) => {
|
|
341
|
-
const id = t.id;
|
|
342
|
-
return id && allowed.has(id);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
if (ctx.config.tags?.length) {
|
|
346
|
-
const allowed = new Set(ctx.config.tags);
|
|
347
|
-
result = result.filter((t) => {
|
|
348
|
-
const tags = t.tags;
|
|
349
|
-
return tags?.some((tag) => allowed.has(tag));
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
return result;
|
|
260
|
+
return tasks;
|
|
353
261
|
}
|
|
354
262
|
/**
|
|
355
263
|
* Build a descriptive error message when no tasks match the current filters.
|
|
@@ -28,6 +28,18 @@ export class MirrorRepoTasksStep {
|
|
|
28
28
|
if (!ctx.config.repoTasksPath) {
|
|
29
29
|
return { status: "skipped", reason: "No --repo-tasks-path provided" };
|
|
30
30
|
}
|
|
31
|
+
// W0145 — never mirror under repo-only mode. The API gateway maps
|
|
32
|
+
// PipelineRequest.taskMode="inline" → taskSourceType="repo", so an
|
|
33
|
+
// external consumer's ephemeral inline tasks would otherwise be
|
|
34
|
+
// upserted into AILF's canonical Content Lake. Mirroring is only
|
|
35
|
+
// correct for the in-tree dogfood path (taskSourceType unset +
|
|
36
|
+
// repoTasksPath set, e.g. external-eval.yml).
|
|
37
|
+
if (ctx.config.taskSourceType === "repo") {
|
|
38
|
+
return {
|
|
39
|
+
status: "skipped",
|
|
40
|
+
reason: 'taskSourceType="repo" — inline tasks are not mirrored',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
31
43
|
// Need a write token for mirroring
|
|
32
44
|
const token = process.env.AILF_REPORT_SANITY_API_TOKEN ?? process.env.SANITY_API_TOKEN;
|
|
33
45
|
if (!token) {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @see docs/decisions/D0032-run-anchored-artifact-store.md (§ Move 5 — Drift Prevention)
|
|
14
14
|
*/
|
|
15
|
-
import type
|
|
15
|
+
import { type Logger, type RunContext } from "../_vendor/ailf-core/index.d.ts";
|
|
16
16
|
import { type RunClassification, type RunExecutor, type RunExecutorSurface, type RunHost, type RunLineage, type RunOwner, type RunTool } from "../_vendor/ailf-shared/index.d.ts";
|
|
17
17
|
import type { ResolvedSourceConfig } from "../sources.js";
|
|
18
18
|
import type { EvalMode } from "./types.js";
|
|
@@ -92,6 +92,11 @@ export declare function detectClassification(log: Logger): RunClassification;
|
|
|
92
92
|
/**
|
|
93
93
|
* Resolve `owner` from `AILF_OWNER_TEAM` (+ optional
|
|
94
94
|
* `AILF_OWNER_INDIVIDUAL`). `team` is free-form; default is `"unknown"`.
|
|
95
|
+
*
|
|
96
|
+
* The init-template placeholder (`PLACEHOLDER_OWNER_TEAM`) is treated as
|
|
97
|
+
* if the env var were unset — consumers that haven't filled it in yet
|
|
98
|
+
* end up with `team: "unknown"` instead of the literal placeholder being
|
|
99
|
+
* persisted onto the report (W0143).
|
|
95
100
|
*/
|
|
96
101
|
export declare function detectOwner(): RunOwner;
|
|
97
102
|
/**
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { execSync } from "node:child_process";
|
|
16
16
|
import { createRequire } from "node:module";
|
|
17
17
|
import * as os from "node:os";
|
|
18
|
+
import { PLACEHOLDER_OWNER_TEAM, } from "../_vendor/ailf-core/index.js";
|
|
18
19
|
import { isRunClassification, } from "../_vendor/ailf-shared/index.js";
|
|
19
20
|
import { ConsoleLogger } from "../adapters/loggers/index.js";
|
|
20
21
|
import { tryLoadConfigFile } from "./compiler/config-loader.js";
|
|
@@ -45,7 +46,15 @@ export function buildRunContext(input) {
|
|
|
45
46
|
// preservation across the --remote boundary.
|
|
46
47
|
const envelope = input.callerEnvelope;
|
|
47
48
|
const classification = envelope?.classification ?? detectClassification(log);
|
|
48
|
-
|
|
49
|
+
// W0143: a caller-supplied owner whose team is the init-template
|
|
50
|
+
// placeholder is treated as "user hasn't filled this in yet" — drop the
|
|
51
|
+
// envelope owner and fall through to env detection (which performs the
|
|
52
|
+
// same sanitization on AILF_OWNER_TEAM). Avoids persisting the literal
|
|
53
|
+
// placeholder verbatim on the report.
|
|
54
|
+
const sanitizedEnvelopeOwner = envelope?.owner && envelope.owner.team !== PLACEHOLDER_OWNER_TEAM
|
|
55
|
+
? envelope.owner
|
|
56
|
+
: undefined;
|
|
57
|
+
const owner = sanitizedEnvelopeOwner ?? detectOwner();
|
|
49
58
|
const executor = envelope?.executor ?? detectExecutor();
|
|
50
59
|
// `tool` and `host` are server-environment facts — they always reflect
|
|
51
60
|
// where this pipeline is actually running, never what a caller claimed.
|
|
@@ -184,9 +193,15 @@ export function detectClassification(log) {
|
|
|
184
193
|
/**
|
|
185
194
|
* Resolve `owner` from `AILF_OWNER_TEAM` (+ optional
|
|
186
195
|
* `AILF_OWNER_INDIVIDUAL`). `team` is free-form; default is `"unknown"`.
|
|
196
|
+
*
|
|
197
|
+
* The init-template placeholder (`PLACEHOLDER_OWNER_TEAM`) is treated as
|
|
198
|
+
* if the env var were unset — consumers that haven't filled it in yet
|
|
199
|
+
* end up with `team: "unknown"` instead of the literal placeholder being
|
|
200
|
+
* persisted onto the report (W0143).
|
|
187
201
|
*/
|
|
188
202
|
export function detectOwner() {
|
|
189
|
-
const
|
|
203
|
+
const rawTeam = process.env.AILF_OWNER_TEAM?.trim();
|
|
204
|
+
const team = rawTeam && rawTeam !== PLACEHOLDER_OWNER_TEAM ? rawTeam : "unknown";
|
|
190
205
|
const individual = process.env.AILF_OWNER_INDIVIDUAL?.trim() || undefined;
|
|
191
206
|
return individual ? { individual, team } : { team };
|
|
192
207
|
}
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared task loading for pipeline orchestration steps.
|
|
3
|
-
*
|
|
4
|
-
* Both FetchDocsStep and GenerateConfigsStep need to see the same set of
|
|
5
|
-
* tasks. This function loads from filesystem .task.ts files — the
|
|
6
|
-
* authoritative source for the current pipeline architecture.
|
|
7
|
-
*
|
|
8
|
-
* Background: The composition root wires ctx.taskSource to
|
|
9
|
-
* ContentLakeTaskSource by default, but GenerateConfigsStep bypasses it
|
|
10
|
-
* and loads directly from the filesystem. FetchDocsStep must use the
|
|
11
|
-
* same source to avoid a mismatch where configs reference context files
|
|
12
|
-
* that were never fetched.
|
|
13
|
-
*
|
|
14
|
-
* @see packages/eval/src/orchestration/steps/generate-configs-step.ts
|
|
15
|
-
* @see packages/eval/src/orchestration/steps/fetch-docs-step.ts
|
|
16
|
-
*/
|
|
17
|
-
import type { GeneralizedTaskDefinition } from "../_vendor/ailf-core/index.d.ts";
|
|
18
|
-
export interface LoadPipelineTasksOptions {
|
|
19
|
-
/** Absolute path to the eval package root (packages/eval) */
|
|
20
|
-
rootDir: string;
|
|
21
|
-
/** Evaluation mode — determines the tasks/{mode}/ subdirectory */
|
|
22
|
-
mode: string;
|
|
23
|
-
/** Optional extra directory for repo-based tasks (--repo-tasks-path) */
|
|
24
|
-
repoTasksPath?: string;
|
|
25
|
-
/**
|
|
26
|
-
* When `"repo"`, load ONLY from `repoTasksPath` and skip the AILF
|
|
27
|
-
* bundled `tasks/${mode}/` directory. Mirrors the composition-root
|
|
28
|
-
* contract for `taskSourceType: "repo"` (see composition-root.ts).
|
|
29
|
-
*/
|
|
30
|
-
taskSourceType?: "content-lake" | "repo";
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Load task definitions from the filesystem, matching the pipeline's
|
|
34
|
-
* authoritative task source.
|
|
35
|
-
*
|
|
36
|
-
* Discovers and loads `*.task.ts` files from `tasks/{mode}/` and
|
|
37
|
-
* optionally `--repo-tasks-path`. Tasks whose `mode` field doesn't
|
|
38
|
-
* match the requested mode are excluded.
|
|
39
|
-
*/
|
|
40
|
-
export declare function loadPipelineTasks(opts: LoadPipelineTasksOptions): Promise<GeneralizedTaskDefinition[]>;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared task loading for pipeline orchestration steps.
|
|
3
|
-
*
|
|
4
|
-
* Both FetchDocsStep and GenerateConfigsStep need to see the same set of
|
|
5
|
-
* tasks. This function loads from filesystem .task.ts files — the
|
|
6
|
-
* authoritative source for the current pipeline architecture.
|
|
7
|
-
*
|
|
8
|
-
* Background: The composition root wires ctx.taskSource to
|
|
9
|
-
* ContentLakeTaskSource by default, but GenerateConfigsStep bypasses it
|
|
10
|
-
* and loads directly from the filesystem. FetchDocsStep must use the
|
|
11
|
-
* same source to avoid a mismatch where configs reference context files
|
|
12
|
-
* that were never fetched.
|
|
13
|
-
*
|
|
14
|
-
* @see packages/eval/src/orchestration/steps/generate-configs-step.ts
|
|
15
|
-
* @see packages/eval/src/orchestration/steps/fetch-docs-step.ts
|
|
16
|
-
*/
|
|
17
|
-
import { resolve } from "path";
|
|
18
|
-
import { discoverTsTaskFiles, loadTsTaskFile, } from "../adapters/task-sources/task-file-loader.js";
|
|
19
|
-
import { resolveVendoredSubdir } from "../pipeline/compiler/config-loader.js";
|
|
20
|
-
/**
|
|
21
|
-
* Load task definitions from the filesystem, matching the pipeline's
|
|
22
|
-
* authoritative task source.
|
|
23
|
-
*
|
|
24
|
-
* Discovers and loads `*.task.ts` files from `tasks/{mode}/` and
|
|
25
|
-
* optionally `--repo-tasks-path`. Tasks whose `mode` field doesn't
|
|
26
|
-
* match the requested mode are excluded.
|
|
27
|
-
*/
|
|
28
|
-
export async function loadPipelineTasks(opts) {
|
|
29
|
-
const dirs = [];
|
|
30
|
-
if (opts.taskSourceType !== "repo") {
|
|
31
|
-
dirs.push(resolveVendoredSubdir(opts.rootDir, `tasks/${opts.mode}`));
|
|
32
|
-
}
|
|
33
|
-
else if (!opts.repoTasksPath) {
|
|
34
|
-
throw new Error('taskSourceType "repo" requires repoTasksPath to be set (no AILF defaults loaded in repo-only mode)');
|
|
35
|
-
}
|
|
36
|
-
if (opts.repoTasksPath) {
|
|
37
|
-
const repoDir = resolve(opts.repoTasksPath);
|
|
38
|
-
if (!dirs.includes(repoDir)) {
|
|
39
|
-
dirs.push(repoDir);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const tasks = [];
|
|
43
|
-
for (const dir of dirs) {
|
|
44
|
-
const files = discoverTsTaskFiles(dir);
|
|
45
|
-
for (const file of files) {
|
|
46
|
-
const raw = await loadTsTaskFile(file);
|
|
47
|
-
for (const t of raw.tasks) {
|
|
48
|
-
const task = t;
|
|
49
|
-
// Filter to matching mode (skip tasks from other modes in same dir)
|
|
50
|
-
if (!("mode" in task) || task.mode === opts.mode) {
|
|
51
|
-
tasks.push(task);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return tasks;
|
|
57
|
-
}
|