@h-rig/task-sources-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158

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 (51) hide show
  1. package/dist/src/control-plane/native/github-token-env.d.ts +3 -0
  2. package/dist/src/control-plane/native/github-token-env.js +26 -0
  3. package/dist/src/control-plane/native/native-git.d.ts +18 -0
  4. package/dist/src/control-plane/native/native-git.js +291 -0
  5. package/dist/src/control-plane/native/runtime-binary-build.d.ts +9 -0
  6. package/dist/src/control-plane/native/runtime-binary-build.js +107 -0
  7. package/dist/src/control-plane/native/task-ops.d.ts +52 -0
  8. package/dist/src/control-plane/native/task-ops.js +3192 -0
  9. package/dist/src/control-plane/native/task-state.d.ts +30 -0
  10. package/dist/src/control-plane/native/task-state.js +944 -0
  11. package/dist/src/control-plane/native/utils.d.ts +7 -0
  12. package/dist/src/control-plane/native/utils.js +110 -0
  13. package/dist/src/control-plane/native/validator-binaries.d.ts +3 -0
  14. package/dist/src/control-plane/native/validator-binaries.js +175 -0
  15. package/dist/src/control-plane/native/validator.d.ts +44 -0
  16. package/dist/src/control-plane/native/validator.js +979 -0
  17. package/dist/src/control-plane/state-sync/index.d.ts +4 -0
  18. package/dist/src/control-plane/state-sync/index.js +1205 -0
  19. package/dist/src/control-plane/state-sync/native-git.d.ts +1 -0
  20. package/dist/src/control-plane/state-sync/native-git.js +281 -0
  21. package/dist/src/control-plane/state-sync/read.d.ts +46 -0
  22. package/dist/src/control-plane/state-sync/read.js +564 -0
  23. package/dist/src/control-plane/state-sync/reconcile.d.ts +28 -0
  24. package/dist/src/control-plane/state-sync/reconcile.js +260 -0
  25. package/dist/src/control-plane/state-sync/repo.d.ts +1 -0
  26. package/dist/src/control-plane/state-sync/repo.js +42 -0
  27. package/dist/src/control-plane/state-sync/types.d.ts +28 -0
  28. package/dist/src/control-plane/state-sync/types.js +111 -0
  29. package/dist/src/control-plane/state-sync/write.d.ts +83 -0
  30. package/dist/src/control-plane/state-sync/write.js +1165 -0
  31. package/dist/src/control-plane/task-data-service.d.ts +17 -0
  32. package/dist/src/control-plane/task-data-service.js +3653 -0
  33. package/dist/src/control-plane/task-fields.d.ts +1 -0
  34. package/dist/src/control-plane/task-fields.js +6 -0
  35. package/dist/src/control-plane/task-io-service.d.ts +6 -0
  36. package/dist/src/control-plane/task-io-service.js +108 -0
  37. package/dist/src/control-plane/task-source-bootstrap.d.ts +1 -0
  38. package/dist/src/control-plane/task-source-bootstrap.js +6 -0
  39. package/dist/src/control-plane/task-source.d.ts +2 -0
  40. package/dist/src/control-plane/task-source.js +6 -0
  41. package/dist/src/control-plane/tasks/legacy-task-config-source.d.ts +19 -0
  42. package/dist/src/control-plane/tasks/legacy-task-config-source.js +124 -0
  43. package/dist/src/control-plane/tasks/plugin-task-source.d.ts +30 -0
  44. package/dist/src/control-plane/tasks/plugin-task-source.js +99 -0
  45. package/dist/src/control-plane/tasks/source-aware-task-config-source.d.ts +28 -0
  46. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +642 -0
  47. package/dist/src/control-plane/tasks/source-lifecycle.d.ts +56 -0
  48. package/dist/src/control-plane/tasks/source-lifecycle.js +834 -0
  49. package/dist/src/plugin.d.ts +1 -1
  50. package/dist/src/plugin.js +3927 -64
  51. package/package.json +57 -4
@@ -0,0 +1,979 @@
1
+ // @bun
2
+ // packages/task-sources-plugin/src/control-plane/native/validator.ts
3
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
4
+ import { resolve as resolve7 } from "path";
5
+ import { assertPathInsideRoot as assertPathInsideRoot2, safePathSegment as safePathSegment2 } from "@rig/core/safe-identifiers";
6
+ import { resolveMonorepoRoot } from "@rig/core/layout";
7
+
8
+ // packages/task-sources-plugin/src/control-plane/native/task-state.ts
9
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
10
+ import { basename, resolve as resolve2 } from "path";
11
+ import { assertPathInsideRoot, safePathSegment } from "@rig/core/safe-identifiers";
12
+ import { loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
13
+
14
+ // packages/task-sources-plugin/src/control-plane/state-sync/types.ts
15
+ var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
16
+ "draft",
17
+ "open",
18
+ "ready",
19
+ "queued",
20
+ "in_progress",
21
+ "under_review",
22
+ "blocked",
23
+ "completed",
24
+ "cancelled"
25
+ ]);
26
+ // packages/task-sources-plugin/src/control-plane/native/native-git.ts
27
+ import { tmpdir } from "os";
28
+ import { dirname, isAbsolute, resolve } from "path";
29
+ var taskSourcesGitNativeOutputDir = resolve(tmpdir(), "rig-task-sources-native");
30
+ var taskSourcesGitNativeOutputPath = resolve(taskSourcesGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
31
+ // packages/task-sources-plugin/src/control-plane/native/utils.ts
32
+ import { getScopeRules } from "@rig/core/scope-rules";
33
+ import { unique } from "@rig/core/exec";
34
+ import {
35
+ runCapture,
36
+ runCaptureAsync,
37
+ runInherit,
38
+ getValidationTimeoutMs,
39
+ readJsonFile,
40
+ nowIso,
41
+ unique as unique2,
42
+ fileLines
43
+ } from "@rig/core/exec";
44
+ import { resolveCheckoutRoot } from "@rig/core/checkout-root";
45
+ import { resolveHarnessPaths } from "@rig/core/harness-paths";
46
+ var scopeRegexCache = new Map;
47
+
48
+ // packages/task-sources-plugin/src/control-plane/state-sync/read.ts
49
+ import { RUNTIME_CONTEXT_ENV } from "@rig/core/runtime-context";
50
+
51
+ // packages/task-sources-plugin/src/control-plane/state-sync/repo.ts
52
+ import { MANAGED_REPO_SERVICE_CAPABILITY } from "@rig/contracts";
53
+ import { defineCapability } from "@rig/core/capability";
54
+ import { getInstalledCapability } from "@rig/core/capability-loaders";
55
+ // packages/task-sources-plugin/src/control-plane/state-sync/reconcile.ts
56
+ var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
57
+ // packages/task-sources-plugin/src/control-plane/native/task-state.ts
58
+ function readTaskConfig(projectRoot) {
59
+ const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
60
+ return stripTaskConfigMetadata(raw);
61
+ }
62
+ function readValidationDescriptions(projectRoot) {
63
+ const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
64
+ return readValidationDescriptionMap(raw);
65
+ }
66
+ function readValidationDescriptionMap(raw) {
67
+ return {
68
+ ...coerceValidationDescriptions(raw.validation_descriptions),
69
+ ...coerceValidationDescriptions(readValidationDescriptionsFromMeta(raw._meta))
70
+ };
71
+ }
72
+ function stripTaskConfigMetadata(raw) {
73
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
74
+ return tasks;
75
+ }
76
+ function coerceValidationDescriptions(candidate) {
77
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
78
+ return {};
79
+ }
80
+ const descriptions = {};
81
+ for (const [key, value] of Object.entries(candidate)) {
82
+ if (typeof value === "string" && value.length > 0) {
83
+ descriptions[key] = value;
84
+ }
85
+ }
86
+ return descriptions;
87
+ }
88
+ function readValidationDescriptionsFromMeta(meta) {
89
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
90
+ return;
91
+ }
92
+ return meta.validation_descriptions;
93
+ }
94
+ function resolveTaskConfigPath(projectRoot) {
95
+ const paths = resolveHarnessPaths(projectRoot);
96
+ if (existsSync(paths.taskConfigPath)) {
97
+ return paths.taskConfigPath;
98
+ }
99
+ for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
100
+ if (existsSync(candidate)) {
101
+ return candidate;
102
+ }
103
+ }
104
+ throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
105
+ }
106
+ function sourceTaskConfigCandidates(projectRoot) {
107
+ const runtimeContext = loadRuntimeContextFromEnv();
108
+ return [
109
+ runtimeContext?.monorepoMainRoot ? resolve2(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
110
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve2(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
111
+ resolve2(resolveCheckoutRoot(projectRoot), ".rig", "task-config.json")
112
+ ].filter(Boolean);
113
+ }
114
+
115
+ // packages/task-sources-plugin/src/control-plane/tasks/source-lifecycle.ts
116
+ import { resolvePluginHost } from "@rig/core/project-plugins";
117
+ import { setScopeRules } from "@rig/core/scope-rules";
118
+
119
+ // packages/task-sources-plugin/src/control-plane/task-source-bootstrap.ts
120
+ import { buildTaskSourceRegistry } from "@rig/core/plugin-host-registries";
121
+
122
+ // packages/task-sources-plugin/src/control-plane/tasks/source-aware-task-config-source.ts
123
+ import { spawnSync } from "child_process";
124
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
125
+ import { basename as basename2, join, resolve as resolve4 } from "path";
126
+
127
+ // packages/task-sources-plugin/src/control-plane/tasks/legacy-task-config-source.ts
128
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
129
+ import { resolve as resolve3 } from "path";
130
+ import { findTaskById } from "@rig/core/task-record-reader";
131
+
132
+ class LegacyTaskConfigReadError extends Error {
133
+ code = "LEGACY_TASK_CONFIG_READ_FAILED";
134
+ projectRoot;
135
+ configPath;
136
+ cause;
137
+ constructor(input) {
138
+ super(input.message, { cause: input.cause });
139
+ this.name = "LegacyTaskConfigReadError";
140
+ this.projectRoot = input.projectRoot;
141
+ this.configPath = input.configPath;
142
+ this.cause = input.cause;
143
+ }
144
+ }
145
+ function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
146
+ const configPath = options.configPath ?? resolve3(projectRoot, ".rig", "task-config.json");
147
+ const reader = {
148
+ async listTasks() {
149
+ return readLegacyTaskRecords(projectRoot, configPath);
150
+ },
151
+ async getTask(id) {
152
+ return findTaskById(reader, id);
153
+ }
154
+ };
155
+ return reader;
156
+ }
157
+ function readLegacyTaskRecords(projectRoot, configPath = resolve3(projectRoot, ".rig", "task-config.json")) {
158
+ if (!existsSync2(configPath)) {
159
+ return [];
160
+ }
161
+ const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
162
+ return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
163
+ }
164
+ function readLegacyTaskConfigJson(projectRoot, configPath) {
165
+ try {
166
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
167
+ if (isPlainRecord(parsed)) {
168
+ return parsed;
169
+ }
170
+ throw new Error("task config root must be a JSON object");
171
+ } catch (cause) {
172
+ throw new LegacyTaskConfigReadError({
173
+ projectRoot,
174
+ configPath,
175
+ message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
176
+ cause
177
+ });
178
+ }
179
+ }
180
+ function stripLegacyTaskConfigMetadata(raw) {
181
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
182
+ return tasks;
183
+ }
184
+ function legacyTaskConfigEntryToRecord(id, entry) {
185
+ if (!isPlainRecord(entry)) {
186
+ return null;
187
+ }
188
+ const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
189
+ const validation = readStringList(entry.validation);
190
+ const validators = readStringList(entry.validators);
191
+ const scope = readStringList(entry.scope);
192
+ const status = typeof entry.status === "string" ? entry.status : "open";
193
+ const title = typeof entry.title === "string" ? entry.title : undefined;
194
+ const description = typeof entry.description === "string" ? entry.description : undefined;
195
+ const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
196
+ return {
197
+ id,
198
+ deps,
199
+ status,
200
+ source: "legacy-task-config",
201
+ ...title ? { title } : {},
202
+ ...description ? { description } : {},
203
+ ...acceptanceCriteria ? { acceptanceCriteria } : {},
204
+ ...scope.length > 0 ? { scope } : {},
205
+ ...validation.length > 0 ? { validation } : {},
206
+ ...validators.length > 0 ? { validators } : {},
207
+ ...preservedLegacyFields(entry)
208
+ };
209
+ }
210
+ function preservedLegacyFields(entry) {
211
+ const preserved = {};
212
+ for (const key of [
213
+ "role",
214
+ "browser",
215
+ "repo_pins",
216
+ "criticality",
217
+ "queue_weight",
218
+ "creates_repo",
219
+ "auto_synced"
220
+ ]) {
221
+ if (entry[key] !== undefined) {
222
+ preserved[key] = entry[key];
223
+ }
224
+ }
225
+ return preserved;
226
+ }
227
+ function firstStringList(...candidates) {
228
+ for (const candidate of candidates) {
229
+ const list = readStringList(candidate);
230
+ if (list.length > 0) {
231
+ return list;
232
+ }
233
+ }
234
+ return [];
235
+ }
236
+ function readStringList(candidate) {
237
+ if (!Array.isArray(candidate)) {
238
+ return [];
239
+ }
240
+ return candidate.filter((value) => typeof value === "string");
241
+ }
242
+ function isPlainRecord(candidate) {
243
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
244
+ }
245
+
246
+ // packages/task-sources-plugin/src/control-plane/native/github-token-env.ts
247
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
248
+ function cleanToken(value) {
249
+ const trimmed = value?.trim() ?? "";
250
+ return trimmed.length > 0 ? trimmed : null;
251
+ }
252
+ function authStateToken(env = process.env) {
253
+ const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
254
+ if (!file || !existsSync3(file))
255
+ return null;
256
+ try {
257
+ const parsed = JSON.parse(readFileSync3(file, "utf8"));
258
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
263
+
264
+ // packages/task-sources-plugin/src/control-plane/tasks/source-aware-task-config-source.ts
265
+ var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
266
+ var FILE_TASK_PATTERN = /\.(task\.)?json$/;
267
+ function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
268
+ const configPath = options.configPath ?? resolve4(projectRoot, ".rig", "task-config.json");
269
+ const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
270
+ const spawnFn = options.spawn ?? spawnSync;
271
+ const ghBinary = options.ghBinary ?? "gh";
272
+ const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
273
+ return {
274
+ async listTasks() {
275
+ const rawConfig = readRawTaskConfig(configPath);
276
+ if (!rawConfig) {
277
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
278
+ return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
279
+ }
280
+ const tasks = [];
281
+ const legacyTasks = await legacy.listTasks();
282
+ const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
283
+ for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
284
+ if (!isPlainRecord2(rawEntry)) {
285
+ continue;
286
+ }
287
+ const metadata = readMaterializedTaskMetadata(rawEntry);
288
+ if (metadata.taskSource?.kind === "github-issues") {
289
+ tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
290
+ continue;
291
+ }
292
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
293
+ const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
294
+ if (fileTask)
295
+ tasks.push(fileTask);
296
+ continue;
297
+ }
298
+ if (!allowLocalFallback) {
299
+ continue;
300
+ }
301
+ const legacyTask = legacyById.get(id);
302
+ if (legacyTask) {
303
+ tasks.push(legacyTask);
304
+ }
305
+ }
306
+ return tasks;
307
+ },
308
+ async getTask(id) {
309
+ const rawEntry = readRawTaskEntry(configPath, id);
310
+ if (!rawEntry) {
311
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
312
+ return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
313
+ }
314
+ const metadata = readMaterializedTaskMetadata(rawEntry);
315
+ if (metadata.taskSource?.kind === "github-issues") {
316
+ return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
317
+ }
318
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
319
+ return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
320
+ }
321
+ return allowLocalFallback ? legacy.getTask(id) : null;
322
+ }
323
+ };
324
+ }
325
+ function readMaterializedTaskMetadata(entry) {
326
+ const rawRig = entry._rig;
327
+ if (!isPlainRecord2(rawRig)) {
328
+ return {};
329
+ }
330
+ const rawSource = rawRig.taskSource;
331
+ const metadata = {};
332
+ if (isPlainRecord2(rawSource)) {
333
+ const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
334
+ if (kind.length > 0) {
335
+ metadata.taskSource = {
336
+ kind,
337
+ ...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
338
+ ...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
339
+ ...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
340
+ ...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
341
+ ...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
342
+ };
343
+ }
344
+ }
345
+ if (typeof rawRig.sourceIssueId === "string") {
346
+ metadata.sourceIssueId = rawRig.sourceIssueId;
347
+ }
348
+ return metadata;
349
+ }
350
+ function readConfiguredFilesTaskSourcePath(projectRoot) {
351
+ const jsonPath = resolve4(projectRoot, "rig.config.json");
352
+ if (existsSync4(jsonPath)) {
353
+ try {
354
+ const parsed = JSON.parse(readFileSync4(jsonPath, "utf8"));
355
+ if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
356
+ const source = parsed.taskSource;
357
+ return source.kind === "files" && typeof source.path === "string" ? source.path : null;
358
+ }
359
+ } catch {
360
+ return null;
361
+ }
362
+ }
363
+ const tsPath = resolve4(projectRoot, "rig.config.ts");
364
+ if (!existsSync4(tsPath)) {
365
+ return null;
366
+ }
367
+ try {
368
+ const source = readFileSync4(tsPath, "utf8");
369
+ const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
370
+ const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
371
+ if (kind !== "files") {
372
+ return null;
373
+ }
374
+ return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
375
+ } catch {
376
+ return null;
377
+ }
378
+ }
379
+ function readRawTaskEntry(configPath, taskId) {
380
+ const rawConfig = readRawTaskConfig(configPath);
381
+ if (!rawConfig) {
382
+ return null;
383
+ }
384
+ const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
385
+ return isPlainRecord2(entry) ? entry : null;
386
+ }
387
+ function readRawTaskConfig(configPath) {
388
+ if (!existsSync4(configPath)) {
389
+ return null;
390
+ }
391
+ const parsed = JSON.parse(readFileSync4(configPath, "utf8"));
392
+ return isPlainRecord2(parsed) ? parsed : null;
393
+ }
394
+ function stripLegacyTaskConfigMetadata2(raw) {
395
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
396
+ return tasks;
397
+ }
398
+ function listFileBackedTasks(projectRoot, sourcePath) {
399
+ const directory = resolve4(projectRoot, sourcePath);
400
+ if (!existsSync4(directory)) {
401
+ return [];
402
+ }
403
+ const tasks = [];
404
+ for (const name of readdirSync2(directory)) {
405
+ if (!FILE_TASK_PATTERN.test(name))
406
+ continue;
407
+ const inferredId = basename2(name).replace(FILE_TASK_PATTERN, "");
408
+ const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
409
+ if (task)
410
+ tasks.push(task);
411
+ }
412
+ return tasks;
413
+ }
414
+ function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
415
+ const file = findFileBackedTaskFile(resolve4(projectRoot, sourcePath), taskId);
416
+ if (!file) {
417
+ return null;
418
+ }
419
+ const raw = JSON.parse(readFileSync4(file, "utf8"));
420
+ if (!isPlainRecord2(raw)) {
421
+ return null;
422
+ }
423
+ return {
424
+ id: typeof raw.id === "string" ? raw.id : taskId,
425
+ deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
426
+ status: typeof raw.status === "string" ? raw.status : "ready",
427
+ title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
428
+ ...raw
429
+ };
430
+ }
431
+ function findFileBackedTaskFile(directory, taskId) {
432
+ if (!existsSync4(directory)) {
433
+ return null;
434
+ }
435
+ for (const name of readdirSync2(directory)) {
436
+ if (!FILE_TASK_PATTERN.test(name))
437
+ continue;
438
+ const file = join(directory, name);
439
+ try {
440
+ if (!statSync2(file).isFile())
441
+ continue;
442
+ const raw = JSON.parse(readFileSync4(file, "utf8"));
443
+ const inferredId = basename2(file).replace(FILE_TASK_PATTERN, "");
444
+ const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
445
+ if (id === taskId) {
446
+ return file;
447
+ }
448
+ } catch {}
449
+ }
450
+ return null;
451
+ }
452
+ function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
453
+ const source = requireGithubIssueSource(metadata, id);
454
+ const issue = runGh(bin, [
455
+ "issue",
456
+ "view",
457
+ String(id),
458
+ "--repo",
459
+ `${source.owner}/${source.repo}`,
460
+ "--json",
461
+ "number,title,body,labels,state,url,assignees"
462
+ ], spawnFn);
463
+ return githubIssueToTask(issue, source, rawEntry);
464
+ }
465
+ function requireGithubIssueSource(metadata, id) {
466
+ const source = metadata.taskSource;
467
+ if (source?.kind === "github-issues" && source.owner && source.repo) {
468
+ return { owner: source.owner, repo: source.repo };
469
+ }
470
+ const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
471
+ if (parsed && parsed[3] === id) {
472
+ return { owner: parsed[1], repo: parsed[2] };
473
+ }
474
+ throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
475
+ }
476
+ function githubIssueToTask(issue, source, rawEntry) {
477
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
478
+ const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
479
+ const roleLabel = labelNames.find((label) => label.startsWith("role:"));
480
+ const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
481
+ const body = issue.body ?? "";
482
+ const repo = `${source.owner}/${source.repo}`;
483
+ return {
484
+ id: String(issue.number),
485
+ deps: parseDeps(body),
486
+ status: githubStatusFor(issue),
487
+ title: issue.title,
488
+ body,
489
+ ...scope.length > 0 ? { scope } : {},
490
+ ...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
491
+ ...validators.length > 0 ? { validators } : {},
492
+ ...issue.url ? { url: issue.url } : {},
493
+ issueType: issueTypeFor(labelNames),
494
+ sourceIssueId: `${repo}#${issue.number}`,
495
+ parentChildDeps: parseParents(body),
496
+ labels: labelNames,
497
+ raw: issue,
498
+ source: "github-issues",
499
+ _rig: {
500
+ taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
501
+ sourceIssueId: `${repo}#${issue.number}`
502
+ }
503
+ };
504
+ }
505
+ function githubStatusFor(issue) {
506
+ const state = (issue.state ?? "").toUpperCase();
507
+ if (state === "CLOSED")
508
+ return "closed";
509
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
510
+ if (labelNames.includes("in-progress"))
511
+ return "in_progress";
512
+ if (labelNames.includes("blocked"))
513
+ return "blocked";
514
+ if (labelNames.includes("ready"))
515
+ return "ready";
516
+ if (labelNames.includes("under-review"))
517
+ return "under_review";
518
+ if (labelNames.includes("failed"))
519
+ return "failed";
520
+ if (labelNames.includes("cancelled"))
521
+ return "cancelled";
522
+ return "open";
523
+ }
524
+ function selectedGitHubEnv() {
525
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || authStateToken(process.env) || "";
526
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
527
+ }
528
+ function ghSpawnOptions() {
529
+ return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
530
+ }
531
+ function tokenDiagnostic(value) {
532
+ const clean = value?.trim() ?? "";
533
+ return clean ? `present(len=${clean.length})` : "missing";
534
+ }
535
+ function runGh(bin, args, spawnFn) {
536
+ const options = ghSpawnOptions();
537
+ const res = spawnFn(bin, [...args], options);
538
+ assertGhSuccess(args, res, options.env);
539
+ if (!res.stdout || res.stdout.trim() === "") {
540
+ throw new Error(`gh ${args.join(" ")} returned empty stdout`);
541
+ }
542
+ return JSON.parse(res.stdout);
543
+ }
544
+ function assertGhSuccess(args, res, env) {
545
+ if (res.error) {
546
+ const msg = res.error.message ?? String(res.error);
547
+ throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
548
+ }
549
+ if (res.status !== 0) {
550
+ throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
551
+ [rig gh env:source-aware] GH_TOKEN=${tokenDiagnostic(env.GH_TOKEN)} GITHUB_TOKEN=${tokenDiagnostic(env.GITHUB_TOKEN)} RIG_GITHUB_TOKEN=${tokenDiagnostic(env.RIG_GITHUB_TOKEN)}`);
552
+ }
553
+ }
554
+ function parseDeps(body) {
555
+ return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
556
+ }
557
+ function parseParents(body) {
558
+ return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
559
+ }
560
+ function parseIssueRefs(body, pattern) {
561
+ const match = body.match(pattern);
562
+ if (!match)
563
+ return [];
564
+ return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
565
+ }
566
+ function issueTypeFor(labels) {
567
+ const typed = labels.find((label) => label.startsWith("type:"));
568
+ if (typed)
569
+ return typed.slice("type:".length);
570
+ if (labels.includes("epic"))
571
+ return "epic";
572
+ return "task";
573
+ }
574
+ function isPlainRecord2(candidate) {
575
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
576
+ }
577
+
578
+ // packages/task-sources-plugin/src/control-plane/tasks/source-lifecycle.ts
579
+ async function resolveTaskSourceContext(projectRoot) {
580
+ let resolved;
581
+ try {
582
+ resolved = await resolvePluginHost(projectRoot);
583
+ } catch (err) {
584
+ const msg = err instanceof Error ? err.message : String(err);
585
+ if (msg.includes("no rig.config"))
586
+ return null;
587
+ throw err;
588
+ }
589
+ const { config, host } = resolved;
590
+ setScopeRules(config.workspace.scopeNormalization);
591
+ return {
592
+ taskSourceRegistry: buildTaskSourceRegistry(config, host, { projectRoot })
593
+ };
594
+ }
595
+ function hasRunnableTaskSource(source) {
596
+ return Boolean(source && typeof source === "object" && !Array.isArray(source));
597
+ }
598
+ async function getPluginTask(projectRoot, taskId) {
599
+ const ctx = await resolveTaskSourceContext(projectRoot);
600
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
601
+ if (!hasRunnableTaskSource(source)) {
602
+ return ctx ? { configured: false, sourceKind: null, task: null } : null;
603
+ }
604
+ const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
605
+ return {
606
+ configured: true,
607
+ sourceKind: source.kind,
608
+ task
609
+ };
610
+ }
611
+ async function readConfiguredTaskSourceTask(projectRoot, taskId) {
612
+ const pluginResult = await getPluginTask(projectRoot, taskId);
613
+ if (pluginResult)
614
+ return pluginResult;
615
+ const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
616
+ return {
617
+ configured: false,
618
+ sourceKind: null,
619
+ task
620
+ };
621
+ }
622
+
623
+ // packages/task-sources-plugin/src/control-plane/native/validator-binaries.ts
624
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, rmSync as rmSync2, statSync as statSync3 } from "fs";
625
+ import { dirname as dirname3, resolve as resolve6 } from "path";
626
+ import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
627
+ import { resolveBunBinaryPath } from "@rig/core/runtime-paths";
628
+
629
+ // packages/task-sources-plugin/src/control-plane/native/runtime-binary-build.ts
630
+ import { chmodSync, existsSync as existsSync5, mkdirSync, renameSync, rmSync } from "fs";
631
+ import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute2, resolve as resolve5 } from "path";
632
+ var runtimeBinaryBuildQueue = Promise.resolve();
633
+ async function buildRuntimeBinary(options) {
634
+ await runSerializedRuntimeBinaryBuild(async () => {
635
+ const entrypoint = isAbsolute2(options.sourcePath) ? options.sourcePath : resolve5(options.cwd, options.sourcePath);
636
+ const outputPath = resolve5(options.outputPath);
637
+ const tempBuildDir = resolve5(dirname2(outputPath), `.bun-build-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
638
+ const tempOutputPath = resolve5(tempBuildDir, basename3(outputPath));
639
+ mkdirSync(tempBuildDir, { recursive: true });
640
+ await withTemporaryEnv({
641
+ ...options.env,
642
+ ...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
643
+ }, async () => withTemporaryCwd(tempBuildDir, async () => {
644
+ const buildResult = await Bun.build({
645
+ entrypoints: [entrypoint],
646
+ compile: {
647
+ target: currentCompileTarget(),
648
+ outfile: tempOutputPath
649
+ },
650
+ target: "bun",
651
+ format: "esm",
652
+ minify: true,
653
+ bytecode: true,
654
+ ...options.external ? { external: options.external } : {},
655
+ ...options.define ? { define: { __RIG_BUILD_CONFIG__: JSON.stringify(options.define) } } : {}
656
+ });
657
+ if (!buildResult.success) {
658
+ const details = buildResult.logs.map((log) => [
659
+ log.message,
660
+ log.position?.file ? `${log.position.file}:${log.position.line}:${log.position.column}` : ""
661
+ ].filter(Boolean).join(" ")).filter(Boolean).join(`
662
+ `);
663
+ throw new Error(`Failed to build ${entrypoint}: ${details || "Bun.build() returned errors"}`);
664
+ }
665
+ if (!existsSync5(tempOutputPath)) {
666
+ const emitted = buildResult.outputs.map((output) => output.path).join(", ") || "(none)";
667
+ throw new Error(`Failed to build ${entrypoint}: Bun.build() did not emit ${tempOutputPath}. Emitted: ${emitted}`);
668
+ }
669
+ renameSync(tempOutputPath, outputPath);
670
+ chmodSync(outputPath, 493);
671
+ })).finally(() => {
672
+ rmSync(tempBuildDir, { recursive: true, force: true });
673
+ });
674
+ });
675
+ }
676
+ function currentCompileTarget() {
677
+ if (process.platform === "darwin") {
678
+ return process.arch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
679
+ }
680
+ if (process.platform === "linux") {
681
+ return process.arch === "arm64" ? "bun-linux-arm64" : "bun-linux-x64";
682
+ }
683
+ return "bun-windows-x64";
684
+ }
685
+ async function runSerializedRuntimeBinaryBuild(action) {
686
+ const previous = runtimeBinaryBuildQueue;
687
+ let release;
688
+ runtimeBinaryBuildQueue = new Promise((resolveRelease) => {
689
+ release = resolveRelease;
690
+ });
691
+ await previous;
692
+ try {
693
+ return await action();
694
+ } finally {
695
+ release();
696
+ }
697
+ }
698
+ async function withTemporaryEnv(env, action) {
699
+ if (!env) {
700
+ return action();
701
+ }
702
+ const previousValues = new Map;
703
+ for (const [key, value] of Object.entries(env)) {
704
+ previousValues.set(key, process.env[key]);
705
+ if (value === undefined) {
706
+ delete process.env[key];
707
+ } else {
708
+ process.env[key] = value;
709
+ }
710
+ }
711
+ try {
712
+ return await action();
713
+ } finally {
714
+ for (const [key, value] of previousValues.entries()) {
715
+ if (value === undefined) {
716
+ delete process.env[key];
717
+ } else {
718
+ process.env[key] = value;
719
+ }
720
+ }
721
+ }
722
+ }
723
+ async function withTemporaryCwd(cwd, action) {
724
+ const previousCwd = process.cwd();
725
+ process.chdir(cwd);
726
+ try {
727
+ return await action();
728
+ } finally {
729
+ process.chdir(previousCwd);
730
+ }
731
+ }
732
+
733
+ // packages/task-sources-plugin/src/control-plane/native/validator-binaries.ts
734
+ function resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext) {
735
+ if (runtimeContext) {
736
+ return resolve6(runtimeContext.binDir, "validators", binaryName);
737
+ }
738
+ return resolve6(resolveHarnessPaths(projectRoot).binDir, "validators", binaryName);
739
+ }
740
+ async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
741
+ const match = checkId.match(/^([a-z][\w-]*):([a-z][\w-]*)$/);
742
+ if (!match) {
743
+ return null;
744
+ }
745
+ const category = match[1];
746
+ const check = match[2];
747
+ if (!category || !check) {
748
+ return null;
749
+ }
750
+ const binaryName = `${category}-${check}`;
751
+ const binaryPath = resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
752
+ const hostProjectRoot = runtimeContext?.hostProjectRoot?.trim() || projectRoot;
753
+ const sourcePath = resolve6(hostProjectRoot, "packages/validator-kit/src/validators", category, `${check}.ts`);
754
+ if (!existsSync6(sourcePath)) {
755
+ return null;
756
+ }
757
+ const sourceMtime = statSync3(sourcePath).mtimeMs;
758
+ const binaryExists = existsSync6(binaryPath);
759
+ const binaryMtime = binaryExists ? statSync3(binaryPath).mtimeMs : 0;
760
+ if (!binaryExists || sourceMtime > binaryMtime) {
761
+ if (binaryExists) {
762
+ rmSync2(binaryPath, { force: true });
763
+ rmSync2(`${binaryPath}.build-manifest.json`, { force: true });
764
+ }
765
+ mkdirSync2(dirname3(binaryPath), { recursive: true });
766
+ await buildRuntimeBinary({
767
+ sourcePath: `packages/validator-kit/src/validators/${category}/${check}.ts`,
768
+ outputPath: binaryPath,
769
+ cwd: hostProjectRoot,
770
+ define: { AGENT_BUN_PATH: resolveBunBinaryPath() },
771
+ env: runtimeProvisioningEnv()
772
+ });
773
+ }
774
+ return existsSync6(binaryPath) ? binaryPath : null;
775
+ }
776
+
777
+ // packages/task-sources-plugin/src/control-plane/native/validator.ts
778
+ import { createValidatorRegistry } from "@rig/validator-kit";
779
+ function isCheckId(entry) {
780
+ return /^[a-z][\w-]*:[a-z][\w-]*$/.test(entry);
781
+ }
782
+ function stringArray(candidate) {
783
+ return Array.isArray(candidate) ? candidate.filter((entry) => typeof entry === "string") : [];
784
+ }
785
+ function safeReadTaskConfig(projectRoot) {
786
+ try {
787
+ return readTaskConfig(projectRoot);
788
+ } catch {
789
+ return {};
790
+ }
791
+ }
792
+ async function readTaskSourceValidation(projectRoot, taskId) {
793
+ const sourceTask = await readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task).catch(() => null);
794
+ if (!sourceTask) {
795
+ return { validation: [], scope: [], taskConfig: undefined };
796
+ }
797
+ const record = sourceTask;
798
+ const validation = stringArray(record.validation).length > 0 ? stringArray(record.validation) : stringArray(record.validators);
799
+ return {
800
+ validation,
801
+ scope: stringArray(record.scope),
802
+ taskConfig: {
803
+ ...typeof record.role === "string" ? { role: record.role } : {},
804
+ ...stringArray(record.scope).length > 0 ? { scope: stringArray(record.scope) } : {},
805
+ ...validation.length > 0 ? { validation } : {}
806
+ }
807
+ };
808
+ }
809
+ function resolveValidationPaths(projectRoot, taskId, runtimeContext) {
810
+ const taskSegment = safePathSegment2(taskId, { fallback: "task", maxLength: 96 });
811
+ if (runtimeContext) {
812
+ const runtimeArtifactsRoot = resolve7(runtimeContext.workspaceDir, "artifacts");
813
+ return {
814
+ taskLogDir: assertPathInsideRoot2(runtimeContext.logsDir, resolve7(runtimeContext.logsDir, taskSegment), "validation log directory"),
815
+ artifactDir: assertPathInsideRoot2(runtimeArtifactsRoot, resolve7(runtimeArtifactsRoot, taskSegment), "validation artifact directory")
816
+ };
817
+ }
818
+ const paths = resolveHarnessPaths(projectRoot);
819
+ return {
820
+ taskLogDir: assertPathInsideRoot2(paths.logsDir, resolve7(paths.logsDir, taskSegment), "validation log directory"),
821
+ artifactDir: assertPathInsideRoot2(paths.artifactsDir, resolve7(paths.artifactsDir, taskSegment), "validation artifact directory")
822
+ };
823
+ }
824
+ async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext) {
825
+ const binaryName = checkId.replace(":", "-");
826
+ const binaryPath = await ensureValidatorBinary(projectRoot, checkId, runtimeContext) ?? resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
827
+ if (!existsSync7(binaryPath)) {
828
+ return {
829
+ result: {
830
+ id: checkId,
831
+ passed: false,
832
+ summary: `Validator binary not found: ${binaryPath}`
833
+ },
834
+ exitCode: 2
835
+ };
836
+ }
837
+ const validatorCwd = runtimeContext?.workspaceDir || resolveMonorepoRoot(projectRoot);
838
+ const runtimeShellPath = runtimeContext ? resolve7(runtimeContext.binDir, "rig-shell") : "";
839
+ const monorepoMainRoot = runtimeContext?.monorepoMainRoot || process.env.MONOREPO_MAIN_ROOT?.trim() || resolveMonorepoRoot(projectRoot);
840
+ const validatorEnv = {
841
+ PROJECT_RIG_ROOT: runtimeContext?.hostProjectRoot || projectRoot,
842
+ RIG_HOST_PROJECT_ROOT: projectRoot,
843
+ RIG_TASK_WORKSPACE: validatorCwd,
844
+ MONOREPO_ROOT: validatorCwd,
845
+ MONOREPO_MAIN_ROOT: monorepoMainRoot,
846
+ RIG_TASK_ID: taskId
847
+ };
848
+ if (runtimeContext) {
849
+ validatorEnv.RIG_TASK_RUNTIME_ID = runtimeContext.runtimeId;
850
+ validatorEnv.RIG_LOGS_DIR = runtimeContext.logsDir;
851
+ validatorEnv.RIG_RUNTIME_BIN_DIR = runtimeContext.binDir;
852
+ }
853
+ const { exitCode, stdout, stderr } = await runCaptureAsync(runtimeShellPath && existsSync7(runtimeShellPath) ? [runtimeShellPath, "run-binary", binaryPath] : [binaryPath], validatorCwd, validatorEnv);
854
+ try {
855
+ const result = JSON.parse(stdout.trim());
856
+ return { result, exitCode };
857
+ } catch {
858
+ return {
859
+ result: {
860
+ id: checkId,
861
+ passed: false,
862
+ summary: `Failed to parse validator output: ${stderr || stdout}`.slice(0, 200)
863
+ },
864
+ exitCode: exitCode || 2
865
+ };
866
+ }
867
+ }
868
+ async function dispatchValidator(checkId, registry, ctx, subprocessFallback) {
869
+ let registered;
870
+ try {
871
+ registered = registry.resolve(checkId);
872
+ } catch {
873
+ return subprocessFallback(checkId);
874
+ }
875
+ const validatorResult = await registered.run(ctx);
876
+ return {
877
+ result: {
878
+ id: validatorResult.id,
879
+ passed: validatorResult.passed,
880
+ summary: validatorResult.summary,
881
+ ...validatorResult.details !== undefined ? { details: validatorResult.details } : {}
882
+ },
883
+ exitCode: validatorResult.passed ? 0 : 1
884
+ };
885
+ }
886
+ async function validateTask(projectRoot, taskId, runtimeContext, registry, options = {}) {
887
+ const resolvedContext = runtimeContext ?? null;
888
+ const taskConfig = resolvedContext ? {} : options.taskConfig ?? safeReadTaskConfig(projectRoot);
889
+ const sourceValidation = !resolvedContext ? await readTaskSourceValidation(projectRoot, taskId) : { validation: [], scope: [], taskConfig: undefined };
890
+ const configuredValidation = stringArray(taskConfig[taskId]?.validation);
891
+ const commands = resolvedContext?.validation?.length ? resolvedContext.validation : configuredValidation.length > 0 ? configuredValidation : sourceValidation.validation;
892
+ const { taskLogDir, artifactDir } = resolveValidationPaths(projectRoot, taskId, resolvedContext);
893
+ mkdirSync3(taskLogDir, { recursive: true });
894
+ mkdirSync3(artifactDir, { recursive: true });
895
+ if (commands.length === 0) {
896
+ const skipped = {
897
+ status: "skipped",
898
+ total: 0,
899
+ passed: 0,
900
+ failed: 0,
901
+ categories: []
902
+ };
903
+ writeFileSync3(assertPathInsideRoot2(artifactDir, resolve7(artifactDir, "validation-summary.json"), "validation summary file"), `${JSON.stringify(skipped, null, 2)}
904
+ `, "utf-8");
905
+ return skipped;
906
+ }
907
+ const effectiveRegistry = registry ?? createValidatorRegistry();
908
+ const workspaceRoot = resolvedContext?.workspaceDir ?? resolveMonorepoRoot(projectRoot);
909
+ const monorepoRoot = resolvedContext?.monorepoMainRoot ?? process.env.MONOREPO_MAIN_ROOT?.trim() ?? resolveMonorepoRoot(projectRoot);
910
+ const validatorCtx = {
911
+ taskId,
912
+ workspaceRoot,
913
+ scope: resolvedContext?.scopes ?? (stringArray(taskConfig[taskId]?.scope).length > 0 ? stringArray(taskConfig[taskId]?.scope) : sourceValidation.scope),
914
+ monorepoRoot,
915
+ artifactsDir: artifactDir,
916
+ taskConfig: sourceValidation.taskConfig ?? taskConfig[taskId] ?? undefined
917
+ };
918
+ const valDescriptions = resolvedContext ? {} : options.validationDescriptions ?? (() => {
919
+ try {
920
+ return readValidationDescriptions(projectRoot);
921
+ } catch {
922
+ return {};
923
+ }
924
+ })();
925
+ const categories = [];
926
+ let passed = 0;
927
+ let failed = 0;
928
+ for (const cmd of commands) {
929
+ const startedAt = Date.now();
930
+ if (!isCheckId(cmd)) {
931
+ failed += 1;
932
+ categories.push({
933
+ category: cmd,
934
+ status: "fail",
935
+ exit_code: 2,
936
+ duration_seconds: 0
937
+ });
938
+ const logFile2 = assertPathInsideRoot2(taskLogDir, resolve7(taskLogDir, `invalid-entry-validation.log`), "validation log file");
939
+ mkdirSync3(taskLogDir, { recursive: true });
940
+ writeFileSync3(logFile2, `=== ${nowIso()} :: ${cmd} ===
941
+ Invalid validation entry: not a check-ID. All entries must use format "category:check-name".
942
+ `, "utf-8");
943
+ continue;
944
+ }
945
+ const { result, exitCode } = await dispatchValidator(cmd, effectiveRegistry, validatorCtx, (id) => runValidatorBinary(projectRoot, taskId, id, resolvedContext));
946
+ const durationSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
947
+ const logFile = assertPathInsideRoot2(taskLogDir, resolve7(taskLogDir, `${cmd.replace(":", "-")}-validation.log`), "validation log file");
948
+ mkdirSync3(taskLogDir, { recursive: true });
949
+ writeFileSync3(logFile, `=== ${nowIso()} :: ${cmd} ===
950
+ ${JSON.stringify(result, null, 2)}
951
+ `, "utf-8");
952
+ if (result.passed) {
953
+ passed += 1;
954
+ categories.push({ category: cmd, status: "pass", duration_seconds: durationSeconds });
955
+ } else {
956
+ failed += 1;
957
+ categories.push({ category: cmd, status: "fail", exit_code: exitCode, duration_seconds: durationSeconds });
958
+ const desc = valDescriptions[cmd];
959
+ if (desc) {
960
+ console.log(` What this checks (${cmd}): ${desc}`);
961
+ }
962
+ }
963
+ }
964
+ const summary = {
965
+ status: failed === 0 ? "pass" : "fail",
966
+ total: commands.length,
967
+ passed,
968
+ failed,
969
+ categories
970
+ };
971
+ mkdirSync3(artifactDir, { recursive: true });
972
+ writeFileSync3(assertPathInsideRoot2(artifactDir, resolve7(artifactDir, "validation-summary.json"), "validation summary file"), `${JSON.stringify(summary, null, 2)}
973
+ `, "utf-8");
974
+ return summary;
975
+ }
976
+ export {
977
+ validateTask,
978
+ dispatchValidator
979
+ };