storybook-addon-designbook 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1049 -973
- package/dist/config.cjs +9 -0
- package/dist/config.d.cts +7 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -0
- package/dist/preset.js +36 -26
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as actualFS from 'fs';
|
|
3
|
-
import { readFileSync, existsSync, readdirSync, mkdirSync, appendFileSync, renameSync, writeFileSync, unlinkSync, openSync, closeSync,
|
|
3
|
+
import { readFileSync, existsSync, readdirSync, mkdirSync, appendFileSync, renameSync, writeFileSync, statSync, unlinkSync, openSync, closeSync, realpathSync as realpathSync$1, readlinkSync, readdir as readdir$1, lstatSync } from 'fs';
|
|
4
4
|
import { load, dump } from 'js-yaml';
|
|
5
|
-
import { resolve, dirname, basename, join, parse,
|
|
5
|
+
import { resolve, dirname, basename, join, parse, extname as extname$1, delimiter, relative, win32, posix } from 'path';
|
|
6
6
|
import jsonata9 from 'jsonata';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
import Stream from 'stream';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'url';
|
|
|
11
11
|
import { realpath, readlink, readdir, lstat, readFile, mkdir, writeFile } from 'fs/promises';
|
|
12
12
|
import { unescape, escape, Minimatch, GLOBSTAR } from 'minimatch';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
|
-
import {
|
|
14
|
+
import { spawn, execSync } from 'child_process';
|
|
15
15
|
import * as http from 'http';
|
|
16
16
|
import { createHash, randomBytes } from 'crypto';
|
|
17
17
|
import { Command } from 'commander';
|
|
@@ -2366,10 +2366,10 @@ var init_esm2 = __esm({
|
|
|
2366
2366
|
* Return a void Promise that resolves once the stream ends.
|
|
2367
2367
|
*/
|
|
2368
2368
|
async promise() {
|
|
2369
|
-
return new Promise((
|
|
2369
|
+
return new Promise((resolve20, reject) => {
|
|
2370
2370
|
this.on(DESTROYED, () => reject(new Error("stream destroyed")));
|
|
2371
2371
|
this.on("error", (er) => reject(er));
|
|
2372
|
-
this.on("end", () =>
|
|
2372
|
+
this.on("end", () => resolve20());
|
|
2373
2373
|
});
|
|
2374
2374
|
}
|
|
2375
2375
|
/**
|
|
@@ -2393,7 +2393,7 @@ var init_esm2 = __esm({
|
|
|
2393
2393
|
return Promise.resolve({ done: false, value: res });
|
|
2394
2394
|
if (this[EOF])
|
|
2395
2395
|
return stop();
|
|
2396
|
-
let
|
|
2396
|
+
let resolve20;
|
|
2397
2397
|
let reject;
|
|
2398
2398
|
const onerr = (er) => {
|
|
2399
2399
|
this.off("data", ondata);
|
|
@@ -2407,19 +2407,19 @@ var init_esm2 = __esm({
|
|
|
2407
2407
|
this.off("end", onend);
|
|
2408
2408
|
this.off(DESTROYED, ondestroy);
|
|
2409
2409
|
this.pause();
|
|
2410
|
-
|
|
2410
|
+
resolve20({ value, done: !!this[EOF] });
|
|
2411
2411
|
};
|
|
2412
2412
|
const onend = () => {
|
|
2413
2413
|
this.off("error", onerr);
|
|
2414
2414
|
this.off("data", ondata);
|
|
2415
2415
|
this.off(DESTROYED, ondestroy);
|
|
2416
2416
|
stop();
|
|
2417
|
-
|
|
2417
|
+
resolve20({ done: true, value: void 0 });
|
|
2418
2418
|
};
|
|
2419
2419
|
const ondestroy = () => onerr(new Error("stream destroyed"));
|
|
2420
2420
|
return new Promise((res2, rej) => {
|
|
2421
2421
|
reject = rej;
|
|
2422
|
-
|
|
2422
|
+
resolve20 = res2;
|
|
2423
2423
|
this.once(DESTROYED, ondestroy);
|
|
2424
2424
|
this.once("error", onerr);
|
|
2425
2425
|
this.once("end", onend);
|
|
@@ -3288,7 +3288,7 @@ var init_esm3 = __esm({
|
|
|
3288
3288
|
}
|
|
3289
3289
|
}
|
|
3290
3290
|
#applyStat(st) {
|
|
3291
|
-
const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs, nlink, rdev, size, uid } = st;
|
|
3291
|
+
const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs: mtimeMs2, nlink, rdev, size, uid } = st;
|
|
3292
3292
|
this.#atime = atime;
|
|
3293
3293
|
this.#atimeMs = atimeMs;
|
|
3294
3294
|
this.#birthtime = birthtime;
|
|
@@ -3302,7 +3302,7 @@ var init_esm3 = __esm({
|
|
|
3302
3302
|
this.#ino = ino;
|
|
3303
3303
|
this.#mode = mode;
|
|
3304
3304
|
this.#mtime = mtime;
|
|
3305
|
-
this.#mtimeMs =
|
|
3305
|
+
this.#mtimeMs = mtimeMs2;
|
|
3306
3306
|
this.#nlink = nlink;
|
|
3307
3307
|
this.#rdev = rdev;
|
|
3308
3308
|
this.#size = size;
|
|
@@ -3396,9 +3396,9 @@ var init_esm3 = __esm({
|
|
|
3396
3396
|
if (this.#asyncReaddirInFlight) {
|
|
3397
3397
|
await this.#asyncReaddirInFlight;
|
|
3398
3398
|
} else {
|
|
3399
|
-
let
|
|
3399
|
+
let resolve20 = () => {
|
|
3400
3400
|
};
|
|
3401
|
-
this.#asyncReaddirInFlight = new Promise((res) =>
|
|
3401
|
+
this.#asyncReaddirInFlight = new Promise((res) => resolve20 = res);
|
|
3402
3402
|
try {
|
|
3403
3403
|
for (const e of await this.#fs.promises.readdir(fullpath, {
|
|
3404
3404
|
withFileTypes: true
|
|
@@ -3411,7 +3411,7 @@ var init_esm3 = __esm({
|
|
|
3411
3411
|
children.provisional = 0;
|
|
3412
3412
|
}
|
|
3413
3413
|
this.#asyncReaddirInFlight = void 0;
|
|
3414
|
-
|
|
3414
|
+
resolve20();
|
|
3415
3415
|
}
|
|
3416
3416
|
return children.slice(0, children.provisional);
|
|
3417
3417
|
}
|
|
@@ -4480,10 +4480,10 @@ var init_ignore = __esm({
|
|
|
4480
4480
|
ignored(p) {
|
|
4481
4481
|
const fullpath = p.fullpath();
|
|
4482
4482
|
const fullpaths = `${fullpath}/`;
|
|
4483
|
-
const
|
|
4484
|
-
const relatives = `${
|
|
4483
|
+
const relative3 = p.relative() || ".";
|
|
4484
|
+
const relatives = `${relative3}/`;
|
|
4485
4485
|
for (const m of this.relative) {
|
|
4486
|
-
if (m.match(
|
|
4486
|
+
if (m.match(relative3) || m.match(relatives))
|
|
4487
4487
|
return true;
|
|
4488
4488
|
}
|
|
4489
4489
|
for (const m of this.absolute) {
|
|
@@ -4494,9 +4494,9 @@ var init_ignore = __esm({
|
|
|
4494
4494
|
}
|
|
4495
4495
|
childrenIgnored(p) {
|
|
4496
4496
|
const fullpath = p.fullpath() + "/";
|
|
4497
|
-
const
|
|
4497
|
+
const relative3 = (p.relative() || ".") + "/";
|
|
4498
4498
|
for (const m of this.relativeChildren) {
|
|
4499
|
-
if (m.match(
|
|
4499
|
+
if (m.match(relative3))
|
|
4500
4500
|
return true;
|
|
4501
4501
|
}
|
|
4502
4502
|
for (const m of this.absoluteChildren) {
|
|
@@ -5345,21 +5345,21 @@ var init_esm4 = __esm({
|
|
|
5345
5345
|
}
|
|
5346
5346
|
});
|
|
5347
5347
|
function findFreePort() {
|
|
5348
|
-
return new Promise((
|
|
5348
|
+
return new Promise((resolve20, reject) => {
|
|
5349
5349
|
const server = http.createServer();
|
|
5350
5350
|
server.listen(0, "127.0.0.1", () => {
|
|
5351
5351
|
const addr = server.address();
|
|
5352
5352
|
const port = typeof addr === "object" && addr ? addr.port : null;
|
|
5353
5353
|
server.close((err) => {
|
|
5354
5354
|
if (err || port === null) reject(err ?? new Error("Could not determine port"));
|
|
5355
|
-
else
|
|
5355
|
+
else resolve20(port);
|
|
5356
5356
|
});
|
|
5357
5357
|
});
|
|
5358
5358
|
server.on("error", reject);
|
|
5359
5359
|
});
|
|
5360
5360
|
}
|
|
5361
5361
|
function fetchJson(url2) {
|
|
5362
|
-
return new Promise((
|
|
5362
|
+
return new Promise((resolve20, reject) => {
|
|
5363
5363
|
http.get(url2, (res) => {
|
|
5364
5364
|
if (res.statusCode !== 200) {
|
|
5365
5365
|
res.resume();
|
|
@@ -5372,7 +5372,7 @@ function fetchJson(url2) {
|
|
|
5372
5372
|
});
|
|
5373
5373
|
res.on("end", () => {
|
|
5374
5374
|
try {
|
|
5375
|
-
|
|
5375
|
+
resolve20(JSON.parse(body));
|
|
5376
5376
|
} catch {
|
|
5377
5377
|
reject(new Error("Invalid JSON"));
|
|
5378
5378
|
}
|
|
@@ -6308,6 +6308,15 @@ function toKebab(input) {
|
|
|
6308
6308
|
}
|
|
6309
6309
|
function normaliseToSectionId(input) {
|
|
6310
6310
|
const [withoutVariant = input] = input.split("--");
|
|
6311
|
+
if (!withoutVariant.includes("/")) {
|
|
6312
|
+
if (DESIGN_SYSTEM_STORY_ID.test(withoutVariant)) {
|
|
6313
|
+
return { id: "shell" };
|
|
6314
|
+
}
|
|
6315
|
+
const storyMatch = SECTION_STORY_ID.exec(withoutVariant);
|
|
6316
|
+
if (storyMatch?.[1]) {
|
|
6317
|
+
return { id: storyMatch[1] };
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6311
6320
|
const segments = withoutVariant.split("/").map((s) => s.trim()).filter(Boolean);
|
|
6312
6321
|
if (segments.length === 0) {
|
|
6313
6322
|
return null;
|
|
@@ -6327,9 +6336,11 @@ function normaliseToSectionId(input) {
|
|
|
6327
6336
|
const id = toKebab(first);
|
|
6328
6337
|
return id ? { id } : null;
|
|
6329
6338
|
}
|
|
6330
|
-
var scenePathResolver;
|
|
6339
|
+
var SECTION_STORY_ID, DESIGN_SYSTEM_STORY_ID, scenePathResolver;
|
|
6331
6340
|
var init_scene_path = __esm({
|
|
6332
6341
|
"src/resolvers/scene-path.ts"() {
|
|
6342
|
+
SECTION_STORY_ID = /^designbook-sections-(.+?)-scenes$/;
|
|
6343
|
+
DESIGN_SYSTEM_STORY_ID = /^designbook-design-system-scenes$/;
|
|
6333
6344
|
scenePathResolver = {
|
|
6334
6345
|
name: "scene_path",
|
|
6335
6346
|
resolve(input, _config, _context) {
|
|
@@ -6391,6 +6402,9 @@ var init_components_index = __esm({
|
|
|
6391
6402
|
init_story_patterns();
|
|
6392
6403
|
componentsIndexResolver = {
|
|
6393
6404
|
name: "components_index",
|
|
6405
|
+
// Producer: derives the inventory from the live Storybook index, with no
|
|
6406
|
+
// input param. Must run even though nothing supplies `components`.
|
|
6407
|
+
requiresInput: false,
|
|
6394
6408
|
async resolve(_input, _config, context2) {
|
|
6395
6409
|
const config = context2.config;
|
|
6396
6410
|
const framework = String(config["frameworks.component"] ?? "");
|
|
@@ -6946,8 +6960,8 @@ function pickRegionLabel(params) {
|
|
|
6946
6960
|
if (segments[0] === "design-system") {
|
|
6947
6961
|
return "shell";
|
|
6948
6962
|
}
|
|
6949
|
-
const
|
|
6950
|
-
return
|
|
6963
|
+
const basename5 = segments[segments.length - 1] ?? "";
|
|
6964
|
+
return basename5.replace(/(\.[^./]+)+$/, "");
|
|
6951
6965
|
}
|
|
6952
6966
|
return "";
|
|
6953
6967
|
}
|
|
@@ -7166,7 +7180,7 @@ async function resolveParams(schema, context2) {
|
|
|
7166
7180
|
return;
|
|
7167
7181
|
}
|
|
7168
7182
|
const input = outputParams[key];
|
|
7169
|
-
if (input === void 0 && !decl.from) return;
|
|
7183
|
+
if (input === void 0 && !decl.from && resolver.requiresInput !== false) return;
|
|
7170
7184
|
let effectiveInput;
|
|
7171
7185
|
if (decl.from) {
|
|
7172
7186
|
let fromValue;
|
|
@@ -8269,6 +8283,9 @@ __export(validation_registry_exports, {
|
|
|
8269
8283
|
getValidatorKeys: () => getValidatorKeys,
|
|
8270
8284
|
validateByKeys: () => validateByKeys
|
|
8271
8285
|
});
|
|
8286
|
+
function shellQuote(value) {
|
|
8287
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
8288
|
+
}
|
|
8272
8289
|
function toFileResult(result, file, type) {
|
|
8273
8290
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8274
8291
|
return {
|
|
@@ -8296,7 +8313,7 @@ async function validateByKeys(keys, file, config) {
|
|
|
8296
8313
|
for (const key of keys) {
|
|
8297
8314
|
if (key.startsWith("cmd:")) {
|
|
8298
8315
|
const cmdTemplate = key.slice(4);
|
|
8299
|
-
const cmd = cmdTemplate.replace(/\{\{\s*file\s*\}\}/g, file);
|
|
8316
|
+
const cmd = cmdTemplate.replace(/\{\{\s*file\s*\}\}/g, shellQuote(file));
|
|
8300
8317
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8301
8318
|
try {
|
|
8302
8319
|
execSync(cmd, { timeout: 3e4, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -8346,11 +8363,11 @@ var init_validation_registry = __esm({
|
|
|
8346
8363
|
const buildResult = await validateSceneBuild2(file, config);
|
|
8347
8364
|
if (!buildResult.valid) return buildResult;
|
|
8348
8365
|
const { load: parseYaml16 } = await import('js-yaml');
|
|
8349
|
-
const { readFileSync:
|
|
8350
|
-
if (!
|
|
8366
|
+
const { readFileSync: readFileSync23, existsSync: existsSync25 } = await import('fs');
|
|
8367
|
+
if (!existsSync25(file)) return buildResult;
|
|
8351
8368
|
let raw;
|
|
8352
8369
|
try {
|
|
8353
|
-
raw = parseYaml16(
|
|
8370
|
+
raw = parseYaml16(readFileSync23(file, "utf-8"));
|
|
8354
8371
|
} catch {
|
|
8355
8372
|
return buildResult;
|
|
8356
8373
|
}
|
|
@@ -8688,6 +8705,11 @@ function resolveSkillsRoot(configDir) {
|
|
|
8688
8705
|
}
|
|
8689
8706
|
return resolve(configDir, ".claude");
|
|
8690
8707
|
}
|
|
8708
|
+
function expandTilde(p) {
|
|
8709
|
+
if (p === "~") return homedir();
|
|
8710
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return resolve(homedir(), p.slice(2));
|
|
8711
|
+
return p;
|
|
8712
|
+
}
|
|
8691
8713
|
function assertNotRepoRoot(dataDir) {
|
|
8692
8714
|
const parent = dirname(dataDir);
|
|
8693
8715
|
const hasPnpmWorkspace = existsSync(resolve(parent, "pnpm-workspace.yaml"));
|
|
@@ -8752,6 +8774,9 @@ function loadConfig(startDir) {
|
|
|
8752
8774
|
config[key] = resolve(configDir, config[key]);
|
|
8753
8775
|
}
|
|
8754
8776
|
}
|
|
8777
|
+
if (typeof config["skills"] === "string") {
|
|
8778
|
+
config["skills"] = resolve(configDir, expandTilde(config["skills"]));
|
|
8779
|
+
}
|
|
8755
8780
|
return config;
|
|
8756
8781
|
} catch (err) {
|
|
8757
8782
|
throw new Error(`Failed to parse designbook.config.yml: ${err.message}`);
|
|
@@ -8761,6 +8786,165 @@ function loadConfig(startDir) {
|
|
|
8761
8786
|
// src/cli.ts
|
|
8762
8787
|
init_data();
|
|
8763
8788
|
init_entity_mapping();
|
|
8789
|
+
var ARTIFACT_SUBDIRS = ["workflows", "tasks", "rules", "blueprints"];
|
|
8790
|
+
function isSkillContentRoot(dir) {
|
|
8791
|
+
if (!existsSync(dir)) return false;
|
|
8792
|
+
for (const sub of ARTIFACT_SUBDIRS) {
|
|
8793
|
+
if (existsSync(resolve(dir, sub))) return true;
|
|
8794
|
+
}
|
|
8795
|
+
let children;
|
|
8796
|
+
try {
|
|
8797
|
+
children = readdirSync(dir);
|
|
8798
|
+
} catch {
|
|
8799
|
+
return false;
|
|
8800
|
+
}
|
|
8801
|
+
for (const child of children) {
|
|
8802
|
+
const childDir = resolve(dir, child);
|
|
8803
|
+
if (!isDir(childDir)) continue;
|
|
8804
|
+
for (const sub of ARTIFACT_SUBDIRS) {
|
|
8805
|
+
if (existsSync(resolve(childDir, sub))) return true;
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
return false;
|
|
8809
|
+
}
|
|
8810
|
+
function isDir(p) {
|
|
8811
|
+
try {
|
|
8812
|
+
return statSync(p).isDirectory();
|
|
8813
|
+
} catch {
|
|
8814
|
+
return false;
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
function mtimeMs(p) {
|
|
8818
|
+
try {
|
|
8819
|
+
return statSync(p).mtimeMs;
|
|
8820
|
+
} catch {
|
|
8821
|
+
return 0;
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
function listDirs(dir) {
|
|
8825
|
+
try {
|
|
8826
|
+
return readdirSync(dir).filter((c) => isDir(resolve(dir, c)));
|
|
8827
|
+
} catch {
|
|
8828
|
+
return [];
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
function deriveSkillSourcesFromBase(base) {
|
|
8832
|
+
const B = resolve(base);
|
|
8833
|
+
if (!existsSync(B)) return [];
|
|
8834
|
+
const sources = [];
|
|
8835
|
+
for (const skillName of listDirs(B)) {
|
|
8836
|
+
const skillDir = resolve(B, skillName);
|
|
8837
|
+
const hashRoots = listDirs(skillDir).map((h) => resolve(skillDir, h)).filter(isSkillContentRoot);
|
|
8838
|
+
if (hashRoots.length > 0) {
|
|
8839
|
+
const best = hashRoots.reduce((a, b) => mtimeMs(b) > mtimeMs(a) ? b : a);
|
|
8840
|
+
sources.push({ name: skillName, root: best, origin: "plugin" });
|
|
8841
|
+
continue;
|
|
8842
|
+
}
|
|
8843
|
+
if (isSkillContentRoot(skillDir)) {
|
|
8844
|
+
sources.push({ name: skillName, root: skillDir, origin: "plugin" });
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
if (sources.length > 0) return sources;
|
|
8848
|
+
if (isSkillContentRoot(B)) return [{ name: basename(B), root: B, origin: "plugin" }];
|
|
8849
|
+
return [];
|
|
8850
|
+
}
|
|
8851
|
+
function resolveProjectSkillSources(configDir) {
|
|
8852
|
+
const skillsRoot = resolveSkillsRoot(configDir);
|
|
8853
|
+
const skillsDir = resolve(skillsRoot, "skills");
|
|
8854
|
+
if (!existsSync(skillsDir)) return [];
|
|
8855
|
+
const sources = [];
|
|
8856
|
+
for (const child of listDirs(skillsDir)) {
|
|
8857
|
+
sources.push({ name: child, root: resolve(skillsDir, child), origin: "project" });
|
|
8858
|
+
}
|
|
8859
|
+
return sources;
|
|
8860
|
+
}
|
|
8861
|
+
|
|
8862
|
+
// src/skill-resolver.ts
|
|
8863
|
+
function pathScanBase(env) {
|
|
8864
|
+
const raw = env["PATH"] ?? "";
|
|
8865
|
+
for (const entry of raw.split(delimiter)) {
|
|
8866
|
+
if (!entry) continue;
|
|
8867
|
+
const cleaned = entry.replace(/[\\/]+$/, "");
|
|
8868
|
+
if (!/[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/]designbook(?:-[^\\/]+)?[\\/][^\\/]+[\\/]bin$/.test(cleaned)) {
|
|
8869
|
+
continue;
|
|
8870
|
+
}
|
|
8871
|
+
const contentRoot = dirname(cleaned);
|
|
8872
|
+
const skillDir = dirname(contentRoot);
|
|
8873
|
+
return dirname(skillDir);
|
|
8874
|
+
}
|
|
8875
|
+
return null;
|
|
8876
|
+
}
|
|
8877
|
+
function firstNonEmpty(bases) {
|
|
8878
|
+
for (const base of bases) {
|
|
8879
|
+
if (!base) continue;
|
|
8880
|
+
const sources = deriveSkillSourcesFromBase(base);
|
|
8881
|
+
if (sources.length > 0) return sources;
|
|
8882
|
+
}
|
|
8883
|
+
return [];
|
|
8884
|
+
}
|
|
8885
|
+
var configResolver = {
|
|
8886
|
+
name: "config",
|
|
8887
|
+
apply: (ctx) => typeof ctx.config.skills === "string" && ctx.config.skills ? firstNonEmpty([ctx.config.skills]) : []
|
|
8888
|
+
};
|
|
8889
|
+
var claudeCodeResolver = {
|
|
8890
|
+
name: "claude-code",
|
|
8891
|
+
apply: (ctx) => {
|
|
8892
|
+
const isClaudeCode = ctx.env["CLAUDECODE"] === "1" || /claude-code/.test(ctx.env["AI_AGENT"] ?? "");
|
|
8893
|
+
if (!isClaudeCode) return [];
|
|
8894
|
+
return firstNonEmpty([pathScanBase(ctx.env), join(ctx.home, ".claude", "plugins", "cache", "designbook")]);
|
|
8895
|
+
}
|
|
8896
|
+
};
|
|
8897
|
+
var codexResolver = {
|
|
8898
|
+
name: "codex",
|
|
8899
|
+
apply: (ctx) => {
|
|
8900
|
+
const codexHome = ctx.env["CODEX_HOME"] ? ctx.env["CODEX_HOME"] : join(ctx.home, ".codex");
|
|
8901
|
+
return firstNonEmpty([join(codexHome, "skills")]);
|
|
8902
|
+
}
|
|
8903
|
+
};
|
|
8904
|
+
var geminiResolver = {
|
|
8905
|
+
name: "gemini",
|
|
8906
|
+
apply: (ctx) => firstNonEmpty([join(ctx.home, ".gemini", "skills")])
|
|
8907
|
+
};
|
|
8908
|
+
var agentsResolver = {
|
|
8909
|
+
name: "agents",
|
|
8910
|
+
apply: (ctx) => firstNonEmpty([join(ctx.home, ".agents", "skills")])
|
|
8911
|
+
};
|
|
8912
|
+
var BUILT_IN_RESOLVERS = [
|
|
8913
|
+
configResolver,
|
|
8914
|
+
claudeCodeResolver,
|
|
8915
|
+
codexResolver,
|
|
8916
|
+
geminiResolver,
|
|
8917
|
+
agentsResolver
|
|
8918
|
+
];
|
|
8919
|
+
function resolvePluginSkillSources(ctx) {
|
|
8920
|
+
const runtimeDisabled = ctx.env["DESIGNBOOK_DISABLE_AUTODETECT"] === "1";
|
|
8921
|
+
for (const resolver of BUILT_IN_RESOLVERS) {
|
|
8922
|
+
if (runtimeDisabled && resolver.name !== "config") continue;
|
|
8923
|
+
const sources = resolver.apply(ctx);
|
|
8924
|
+
if (sources.length > 0) return { runtime: resolver.name, sources };
|
|
8925
|
+
}
|
|
8926
|
+
return null;
|
|
8927
|
+
}
|
|
8928
|
+
function resolveSkillSources(configDir, overrides) {
|
|
8929
|
+
const ctx = {
|
|
8930
|
+
env: process.env,
|
|
8931
|
+
home: homedir(),
|
|
8932
|
+
config: loadConfig(configDir)
|
|
8933
|
+
};
|
|
8934
|
+
const projectSources = resolveProjectSkillSources(configDir);
|
|
8935
|
+
const pluginResult = resolvePluginSkillSources(ctx);
|
|
8936
|
+
if (pluginResult && process.env["DESIGNBOOK_DEBUG"]) {
|
|
8937
|
+
const base = pluginResult.sources[0]?.root ?? "?";
|
|
8938
|
+
process.stderr.write(`[designbook] skills: ${pluginResult.runtime} \u2192 ${base}
|
|
8939
|
+
`);
|
|
8940
|
+
}
|
|
8941
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8942
|
+
for (const src of projectSources) byName.set(src.name, src);
|
|
8943
|
+
for (const src of pluginResult?.sources ?? []) {
|
|
8944
|
+
if (!byName.has(src.name)) byName.set(src.name, src);
|
|
8945
|
+
}
|
|
8946
|
+
return Array.from(byName.values());
|
|
8947
|
+
}
|
|
8764
8948
|
var LONG_RUNNING_MS = 1e4;
|
|
8765
8949
|
function digestLog(logPath2) {
|
|
8766
8950
|
if (!existsSync(logPath2)) {
|
|
@@ -8831,50 +9015,125 @@ function evalAssertions(assertions, output) {
|
|
|
8831
9015
|
}
|
|
8832
9016
|
return { passed, total, failures };
|
|
8833
9017
|
}
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
9018
|
+
function deboSuffix(data) {
|
|
9019
|
+
if (!data.workflow_id) throw new Error("workflow_id is required for direct engine stash-at-target");
|
|
9020
|
+
return `.${data.workflow_id}.debo`;
|
|
9021
|
+
}
|
|
9022
|
+
function stashPath(data, fileEntry) {
|
|
9023
|
+
return fileEntry.path + deboSuffix(data);
|
|
9024
|
+
}
|
|
9025
|
+
var directEngine = {
|
|
9026
|
+
writeFile(data, task, key, content) {
|
|
9027
|
+
const fileEntry = (task.files ?? []).find((f) => f.key === key);
|
|
9028
|
+
if (!fileEntry) throw new Error(`No file entry with key '${key}' in task '${task.id}'`);
|
|
9029
|
+
const path = stashPath(data, fileEntry);
|
|
9030
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
9031
|
+
writeFileSync(path, content);
|
|
9032
|
+
return { path };
|
|
9033
|
+
},
|
|
9034
|
+
getStagedPath(data, task, key) {
|
|
9035
|
+
const fileEntry = (task.files ?? []).find((f) => f.key === key);
|
|
9036
|
+
if (fileEntry) return stashPath(data, fileEntry);
|
|
9037
|
+
const resultEntry = task.result?.[key];
|
|
9038
|
+
if (resultEntry?.path) {
|
|
9039
|
+
return resultEntry.path;
|
|
9040
|
+
}
|
|
9041
|
+
throw new Error(`No file entry with key '${key}' in task '${task.id}'`);
|
|
9042
|
+
},
|
|
9043
|
+
flush(data, tasks) {
|
|
9044
|
+
const suffix = deboSuffix(data);
|
|
9045
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9046
|
+
for (const task of tasks) {
|
|
9047
|
+
for (const file of task.files ?? []) {
|
|
9048
|
+
if (!file.validation_result) continue;
|
|
9049
|
+
const src = file.path + suffix;
|
|
9050
|
+
if (!existsSync(src)) continue;
|
|
9051
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
9052
|
+
renameSync(src, file.path);
|
|
9053
|
+
file.flushed_at = now;
|
|
9054
|
+
}
|
|
9055
|
+
}
|
|
9056
|
+
return Promise.resolve();
|
|
9057
|
+
},
|
|
9058
|
+
done() {
|
|
9059
|
+
return { archive: true };
|
|
9060
|
+
},
|
|
9061
|
+
cleanup(data) {
|
|
9062
|
+
const suffix = deboSuffix(data);
|
|
9063
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
9064
|
+
for (const task of data.tasks) {
|
|
9065
|
+
for (const file of task.files ?? []) {
|
|
9066
|
+
dirs.add(dirname(file.path));
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
for (const dir of dirs) {
|
|
9070
|
+
if (!existsSync(dir)) continue;
|
|
8840
9071
|
try {
|
|
8841
|
-
const
|
|
8842
|
-
const
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
unlinkSync(lockPath);
|
|
8846
|
-
} catch {
|
|
9072
|
+
const entries = readdirSync(dir);
|
|
9073
|
+
for (const entry of entries) {
|
|
9074
|
+
if (entry.endsWith(suffix)) {
|
|
9075
|
+
unlinkSync(resolve(dir, entry));
|
|
8847
9076
|
}
|
|
8848
9077
|
}
|
|
8849
9078
|
} catch {
|
|
8850
9079
|
}
|
|
8851
9080
|
}
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
9081
|
+
},
|
|
9082
|
+
async onTransition(from, to, ctx) {
|
|
9083
|
+
const declared = ctx.data.stages ? Object.keys(ctx.data.stages) : [];
|
|
9084
|
+
if (declared.includes(from)) {
|
|
9085
|
+
const stageTasks = ctx.data.tasks.filter((t) => t.stage === from);
|
|
9086
|
+
if (stageTasks.length > 0) {
|
|
9087
|
+
await directEngine.flush(ctx.data, stageTasks);
|
|
8858
9088
|
}
|
|
8859
9089
|
}
|
|
9090
|
+
if (from === "finalizing" && to === "done") {
|
|
9091
|
+
return { archive: true };
|
|
9092
|
+
}
|
|
9093
|
+
return {};
|
|
8860
9094
|
}
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
9095
|
+
};
|
|
9096
|
+
|
|
9097
|
+
// src/engines/index.ts
|
|
9098
|
+
var engines = {
|
|
9099
|
+
direct: directEngine
|
|
9100
|
+
};
|
|
9101
|
+
|
|
9102
|
+
// src/workflow-lifecycle.ts
|
|
9103
|
+
var IMPLICIT_STAGES = {
|
|
9104
|
+
_after_declared: ["committed"],
|
|
9105
|
+
_before_done: ["finalizing", "done"]
|
|
9106
|
+
};
|
|
9107
|
+
function buildLifecycleOrder(stages) {
|
|
9108
|
+
const declared = Object.keys(stages).filter((s) => (stages[s].steps?.length ?? 0) > 0);
|
|
9109
|
+
return ["created", "planned", ...declared, ...IMPLICIT_STAGES._after_declared, ...IMPLICIT_STAGES._before_done];
|
|
8864
9110
|
}
|
|
8865
|
-
function
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
9111
|
+
function getNextStage(current, stages) {
|
|
9112
|
+
const order = buildLifecycleOrder(stages);
|
|
9113
|
+
const currentIdx = order.indexOf(current);
|
|
9114
|
+
if (currentIdx === -1 || currentIdx >= order.length - 1) return null;
|
|
9115
|
+
return order[currentIdx + 1] ?? null;
|
|
8870
9116
|
}
|
|
8871
|
-
|
|
8872
|
-
const
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
9117
|
+
function getNextStep(currentStage, _completedStep, tasks) {
|
|
9118
|
+
const pending = tasks.find((t) => t.stage === currentStage && t.status !== "done");
|
|
9119
|
+
return pending?.step ?? null;
|
|
9120
|
+
}
|
|
9121
|
+
function checkStageParams(stage, stages, provided) {
|
|
9122
|
+
const def = stages[stage];
|
|
9123
|
+
if (!def?.params) return null;
|
|
9124
|
+
const unfulfilled = {};
|
|
9125
|
+
for (const [key, param] of Object.entries(def.params)) {
|
|
9126
|
+
if (provided[key] === void 0) {
|
|
9127
|
+
unfulfilled[key] = param;
|
|
9128
|
+
}
|
|
8877
9129
|
}
|
|
9130
|
+
return Object.keys(unfulfilled).length > 0 ? unfulfilled : null;
|
|
9131
|
+
}
|
|
9132
|
+
function interpolatePrompt(prompt, state) {
|
|
9133
|
+
return prompt.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
9134
|
+
const value = state[key];
|
|
9135
|
+
return value !== void 0 ? String(value) : `{${key}}`;
|
|
9136
|
+
});
|
|
8878
9137
|
}
|
|
8879
9138
|
|
|
8880
9139
|
// src/workflow-resolve.ts
|
|
@@ -8970,7 +9229,7 @@ function pullType(ref2, baseFilePath, baseFileSchemas, definitions, input, visit
|
|
|
8970
9229
|
srcFile = baseFilePath;
|
|
8971
9230
|
srcSchemas = baseFileSchemas;
|
|
8972
9231
|
} else {
|
|
8973
|
-
const r = resolveSchemaRef(ref2, baseFilePath, input.skillsRoot);
|
|
9232
|
+
const r = resolveSchemaRef(ref2, baseFilePath, input.skillsRoot, input.sources);
|
|
8974
9233
|
typeName = r.typeName;
|
|
8975
9234
|
schema = r.schema;
|
|
8976
9235
|
srcFile = r.schemaFilePath;
|
|
@@ -9084,10 +9343,10 @@ function parseSchemaExtension(filePath) {
|
|
|
9084
9343
|
}
|
|
9085
9344
|
return hasAny ? ext : null;
|
|
9086
9345
|
}
|
|
9087
|
-
function resolveRefsInExtension(obj, sourceFilePath, skillsRoot, schemas) {
|
|
9346
|
+
function resolveRefsInExtension(obj, sourceFilePath, skillsRoot, schemas, sources) {
|
|
9088
9347
|
for (const [key, value] of Object.entries(obj)) {
|
|
9089
9348
|
if (key === "$ref" && typeof value === "string") {
|
|
9090
|
-
const { typeName, schema } = resolveSchemaRef(value, sourceFilePath, skillsRoot);
|
|
9349
|
+
const { typeName, schema } = resolveSchemaRef(value, sourceFilePath, skillsRoot, sources);
|
|
9091
9350
|
schemas[typeName] = schema;
|
|
9092
9351
|
delete obj.$ref;
|
|
9093
9352
|
Object.assign(obj, schema);
|
|
@@ -9096,11 +9355,11 @@ function resolveRefsInExtension(obj, sourceFilePath, skillsRoot, schemas) {
|
|
|
9096
9355
|
if (Array.isArray(value)) {
|
|
9097
9356
|
for (const item of value) {
|
|
9098
9357
|
if (item && typeof item === "object") {
|
|
9099
|
-
resolveRefsInExtension(item, sourceFilePath, skillsRoot, schemas);
|
|
9358
|
+
resolveRefsInExtension(item, sourceFilePath, skillsRoot, schemas, sources);
|
|
9100
9359
|
}
|
|
9101
9360
|
}
|
|
9102
9361
|
} else if (value && typeof value === "object") {
|
|
9103
|
-
resolveRefsInExtension(value, sourceFilePath, skillsRoot, schemas);
|
|
9362
|
+
resolveRefsInExtension(value, sourceFilePath, skillsRoot, schemas, sources);
|
|
9104
9363
|
}
|
|
9105
9364
|
}
|
|
9106
9365
|
}
|
|
@@ -9184,9 +9443,9 @@ function computeMergedSchema(baseResult, input) {
|
|
|
9184
9443
|
if (ext.constrains) {
|
|
9185
9444
|
throw new Error(`Blueprint '${bp2}' uses constrains: \u2014 only rules may constrain schemas`);
|
|
9186
9445
|
}
|
|
9187
|
-
if (ext.extends) resolveRefsInExtension(ext.extends, bp2, input.skillsRoot, input.schemas);
|
|
9446
|
+
if (ext.extends) resolveRefsInExtension(ext.extends, bp2, input.skillsRoot, input.schemas, input.sources);
|
|
9188
9447
|
if (ext.provides && typeof ext.provides === "object") {
|
|
9189
|
-
resolveRefsInExtension(ext.provides, bp2, input.skillsRoot, input.schemas);
|
|
9448
|
+
resolveRefsInExtension(ext.provides, bp2, input.skillsRoot, input.schemas, input.sources);
|
|
9190
9449
|
}
|
|
9191
9450
|
blueprintExts.push({ path: bp2, ext });
|
|
9192
9451
|
}
|
|
@@ -9197,11 +9456,17 @@ function computeMergedSchema(baseResult, input) {
|
|
|
9197
9456
|
if (typeof ext.provides === "string") {
|
|
9198
9457
|
ext.provides = void 0;
|
|
9199
9458
|
}
|
|
9200
|
-
if (ext.extends) resolveRefsInExtension(ext.extends, rule, input.skillsRoot, input.schemas);
|
|
9459
|
+
if (ext.extends) resolveRefsInExtension(ext.extends, rule, input.skillsRoot, input.schemas, input.sources);
|
|
9201
9460
|
if (ext.provides && typeof ext.provides === "object") {
|
|
9202
|
-
resolveRefsInExtension(
|
|
9461
|
+
resolveRefsInExtension(
|
|
9462
|
+
ext.provides,
|
|
9463
|
+
rule,
|
|
9464
|
+
input.skillsRoot,
|
|
9465
|
+
input.schemas,
|
|
9466
|
+
input.sources
|
|
9467
|
+
);
|
|
9203
9468
|
}
|
|
9204
|
-
if (ext.constrains) resolveRefsInExtension(ext.constrains, rule, input.skillsRoot, input.schemas);
|
|
9469
|
+
if (ext.constrains) resolveRefsInExtension(ext.constrains, rule, input.skillsRoot, input.schemas, input.sources);
|
|
9205
9470
|
ruleExts.push({ path: rule, ext });
|
|
9206
9471
|
}
|
|
9207
9472
|
}
|
|
@@ -9270,6 +9535,22 @@ function computeMergedSchema(baseResult, input) {
|
|
|
9270
9535
|
}
|
|
9271
9536
|
|
|
9272
9537
|
// src/workflow-resolve.ts
|
|
9538
|
+
function pluginSources(sources) {
|
|
9539
|
+
return (sources ?? []).filter((s) => s.origin === "plugin");
|
|
9540
|
+
}
|
|
9541
|
+
function toSourcePattern(globPattern) {
|
|
9542
|
+
return globPattern.replace(/^skills\/\*\*\//, "**/");
|
|
9543
|
+
}
|
|
9544
|
+
function derivePluginArtifactName(source2, filePath) {
|
|
9545
|
+
const rel = relative(source2.root, filePath).replace(/\\/g, "/");
|
|
9546
|
+
const parts = rel.split("/");
|
|
9547
|
+
const artifact = (parts[parts.length - 1] ?? "").replace(/\.md$/, "");
|
|
9548
|
+
if (parts.length >= 3) {
|
|
9549
|
+
const concern = parts[0];
|
|
9550
|
+
return `${source2.name}:${concern}:${artifact}`;
|
|
9551
|
+
}
|
|
9552
|
+
return `${source2.name}:${artifact}`;
|
|
9553
|
+
}
|
|
9273
9554
|
function loadSchemaFile(schemaFilePath) {
|
|
9274
9555
|
if (!existsSync(schemaFilePath)) {
|
|
9275
9556
|
throw new Error(`Schema file not found: ${schemaFilePath}`);
|
|
@@ -9291,7 +9572,21 @@ function loadSchemaFile(schemaFilePath) {
|
|
|
9291
9572
|
}
|
|
9292
9573
|
return schemas;
|
|
9293
9574
|
}
|
|
9294
|
-
function
|
|
9575
|
+
function reanchorRelativeRefToPluginSource(resolvedPath, sources) {
|
|
9576
|
+
const plugins = pluginSources(sources);
|
|
9577
|
+
if (plugins.length === 0) return void 0;
|
|
9578
|
+
const byName = new Map(plugins.map((s) => [s.name, s]));
|
|
9579
|
+
const segments = resolvedPath.replace(/\\/g, "/").split("/");
|
|
9580
|
+
for (let i = segments.length - 2; i >= 0; i--) {
|
|
9581
|
+
const source2 = byName.get(segments[i]);
|
|
9582
|
+
if (!source2) continue;
|
|
9583
|
+
const rest = segments.slice(i + 1).join("/");
|
|
9584
|
+
const candidate = resolve(source2.root, rest);
|
|
9585
|
+
if (existsSync(candidate)) return candidate;
|
|
9586
|
+
}
|
|
9587
|
+
return void 0;
|
|
9588
|
+
}
|
|
9589
|
+
function resolveSchemaRef(ref2, taskFilePath, skillsRoot, sources) {
|
|
9295
9590
|
const hashIdx = ref2.indexOf("#/");
|
|
9296
9591
|
if (hashIdx === -1) {
|
|
9297
9592
|
throw new Error(`Invalid $ref '${ref2}' \u2014 must contain '#/' fragment (e.g. ../schemas.yml#/TypeName)`);
|
|
@@ -9301,8 +9596,19 @@ function resolveSchemaRef(ref2, taskFilePath, skillsRoot) {
|
|
|
9301
9596
|
let schemaFilePath;
|
|
9302
9597
|
if (filePart.startsWith(".") || filePart.startsWith("/")) {
|
|
9303
9598
|
schemaFilePath = resolve(dirname(taskFilePath), filePart);
|
|
9599
|
+
if (!existsSync(schemaFilePath)) {
|
|
9600
|
+
const reanchored = reanchorRelativeRefToPluginSource(schemaFilePath, sources);
|
|
9601
|
+
if (reanchored) schemaFilePath = reanchored;
|
|
9602
|
+
}
|
|
9304
9603
|
} else {
|
|
9305
|
-
|
|
9604
|
+
const skillName = filePart.split("/")[0] ?? "";
|
|
9605
|
+
const pluginSource = pluginSources(sources).find((s) => s.name === skillName);
|
|
9606
|
+
if (pluginSource) {
|
|
9607
|
+
const rest = filePart.slice(skillName.length + 1);
|
|
9608
|
+
schemaFilePath = resolve(pluginSource.root, rest);
|
|
9609
|
+
} else {
|
|
9610
|
+
schemaFilePath = resolve(skillsRoot, filePart);
|
|
9611
|
+
}
|
|
9306
9612
|
}
|
|
9307
9613
|
const fileSchemas = loadSchemaFile(schemaFilePath);
|
|
9308
9614
|
if (!(typeName in fileSchemas)) {
|
|
@@ -9311,10 +9617,10 @@ function resolveSchemaRef(ref2, taskFilePath, skillsRoot) {
|
|
|
9311
9617
|
}
|
|
9312
9618
|
return { typeName, schema: fileSchemas[typeName], schemaFilePath, fileSchemas };
|
|
9313
9619
|
}
|
|
9314
|
-
function collectLocalRefsFromSchema(node, fileSchemas, schemas, visited, schemaFilePath, skillsRoot) {
|
|
9620
|
+
function collectLocalRefsFromSchema(node, fileSchemas, schemas, visited, schemaFilePath, skillsRoot, sources) {
|
|
9315
9621
|
if (Array.isArray(node)) {
|
|
9316
9622
|
for (const item of node)
|
|
9317
|
-
collectLocalRefsFromSchema(item, fileSchemas, schemas, visited, schemaFilePath, skillsRoot);
|
|
9623
|
+
collectLocalRefsFromSchema(item, fileSchemas, schemas, visited, schemaFilePath, skillsRoot, sources);
|
|
9318
9624
|
return;
|
|
9319
9625
|
}
|
|
9320
9626
|
if (!node || typeof node !== "object") return;
|
|
@@ -9326,10 +9632,18 @@ function collectLocalRefsFromSchema(node, fileSchemas, schemas, visited, schemaF
|
|
|
9326
9632
|
if (typeName && !(typeName in schemas) && typeName in fileSchemas && !visited.has(typeName)) {
|
|
9327
9633
|
visited.add(typeName);
|
|
9328
9634
|
schemas[typeName] = fileSchemas[typeName];
|
|
9329
|
-
collectLocalRefsFromSchema(
|
|
9635
|
+
collectLocalRefsFromSchema(
|
|
9636
|
+
fileSchemas[typeName],
|
|
9637
|
+
fileSchemas,
|
|
9638
|
+
schemas,
|
|
9639
|
+
visited,
|
|
9640
|
+
schemaFilePath,
|
|
9641
|
+
skillsRoot,
|
|
9642
|
+
sources
|
|
9643
|
+
);
|
|
9330
9644
|
}
|
|
9331
9645
|
} else if (ref2.includes("#/")) {
|
|
9332
|
-
const resolved = resolveSchemaRef(ref2, schemaFilePath, skillsRoot);
|
|
9646
|
+
const resolved = resolveSchemaRef(ref2, schemaFilePath, skillsRoot, sources);
|
|
9333
9647
|
obj.$ref = `#/${resolved.typeName}`;
|
|
9334
9648
|
if (!(resolved.typeName in schemas) && !visited.has(resolved.typeName)) {
|
|
9335
9649
|
visited.add(resolved.typeName);
|
|
@@ -9340,16 +9654,17 @@ function collectLocalRefsFromSchema(node, fileSchemas, schemas, visited, schemaF
|
|
|
9340
9654
|
schemas,
|
|
9341
9655
|
visited,
|
|
9342
9656
|
resolved.schemaFilePath,
|
|
9343
|
-
skillsRoot
|
|
9657
|
+
skillsRoot,
|
|
9658
|
+
sources
|
|
9344
9659
|
);
|
|
9345
9660
|
}
|
|
9346
9661
|
}
|
|
9347
9662
|
}
|
|
9348
9663
|
for (const value of Object.values(obj)) {
|
|
9349
|
-
collectLocalRefsFromSchema(value, fileSchemas, schemas, visited, schemaFilePath, skillsRoot);
|
|
9664
|
+
collectLocalRefsFromSchema(value, fileSchemas, schemas, visited, schemaFilePath, skillsRoot, sources);
|
|
9350
9665
|
}
|
|
9351
9666
|
}
|
|
9352
|
-
function resolveSchemasForTasks(tasks, skillsRoot, schemas) {
|
|
9667
|
+
function resolveSchemasForTasks(tasks, skillsRoot, schemas, sources) {
|
|
9353
9668
|
for (const task of tasks) {
|
|
9354
9669
|
if (!task.result || !task.task_file) continue;
|
|
9355
9670
|
const taskFilePath = task.task_file;
|
|
@@ -9359,25 +9674,40 @@ function resolveSchemasForTasks(tasks, skillsRoot, schemas) {
|
|
|
9359
9674
|
if (!resultProperties) continue;
|
|
9360
9675
|
for (const [key, resultDecl] of Object.entries(resultProperties)) {
|
|
9361
9676
|
const originalRef = resultDecl.$ref;
|
|
9362
|
-
resolveRefsInDeclaration(resultDecl, taskFilePath, skillsRoot, schemas);
|
|
9677
|
+
resolveRefsInDeclaration(resultDecl, taskFilePath, skillsRoot, schemas, sources);
|
|
9363
9678
|
if (task.result[key] && originalRef) {
|
|
9364
9679
|
const { typeName, schema, fileSchemas, schemaFilePath } = resolveSchemaRef(
|
|
9365
9680
|
originalRef,
|
|
9366
9681
|
taskFilePath,
|
|
9367
|
-
skillsRoot
|
|
9682
|
+
skillsRoot,
|
|
9683
|
+
sources
|
|
9368
9684
|
);
|
|
9369
9685
|
schemas[typeName] = schema;
|
|
9370
|
-
collectLocalRefsFromSchema(
|
|
9686
|
+
collectLocalRefsFromSchema(
|
|
9687
|
+
schema,
|
|
9688
|
+
fileSchemas,
|
|
9689
|
+
schemas,
|
|
9690
|
+
/* @__PURE__ */ new Set([typeName]),
|
|
9691
|
+
schemaFilePath,
|
|
9692
|
+
skillsRoot,
|
|
9693
|
+
sources
|
|
9694
|
+
);
|
|
9371
9695
|
task.result[key].schema = schema;
|
|
9372
9696
|
}
|
|
9373
9697
|
if (task.result[key]?.schema && typeof task.result[key].schema === "object") {
|
|
9374
|
-
rewriteRefsInSchema(
|
|
9698
|
+
rewriteRefsInSchema(
|
|
9699
|
+
task.result[key].schema,
|
|
9700
|
+
taskFilePath,
|
|
9701
|
+
skillsRoot,
|
|
9702
|
+
schemas,
|
|
9703
|
+
sources
|
|
9704
|
+
);
|
|
9375
9705
|
}
|
|
9376
9706
|
}
|
|
9377
9707
|
if (taskFm.each) {
|
|
9378
9708
|
for (const eachValue of Object.values(taskFm.each)) {
|
|
9379
9709
|
if (eachValue && typeof eachValue === "object") {
|
|
9380
|
-
resolveRefsInDeclaration(eachValue, taskFilePath, skillsRoot, schemas);
|
|
9710
|
+
resolveRefsInDeclaration(eachValue, taskFilePath, skillsRoot, schemas, sources);
|
|
9381
9711
|
}
|
|
9382
9712
|
}
|
|
9383
9713
|
}
|
|
@@ -9388,9 +9718,17 @@ function resolveSchemasForTasks(tasks, skillsRoot, schemas) {
|
|
|
9388
9718
|
schema: refSchema,
|
|
9389
9719
|
fileSchemas,
|
|
9390
9720
|
schemaFilePath
|
|
9391
|
-
} = resolveSchemaRef(ref2, taskFilePath, skillsRoot);
|
|
9721
|
+
} = resolveSchemaRef(ref2, taskFilePath, skillsRoot, sources);
|
|
9392
9722
|
schemas[typeName] = refSchema;
|
|
9393
|
-
collectLocalRefsFromSchema(
|
|
9723
|
+
collectLocalRefsFromSchema(
|
|
9724
|
+
refSchema,
|
|
9725
|
+
fileSchemas,
|
|
9726
|
+
schemas,
|
|
9727
|
+
/* @__PURE__ */ new Set([typeName]),
|
|
9728
|
+
schemaFilePath,
|
|
9729
|
+
skillsRoot,
|
|
9730
|
+
sources
|
|
9731
|
+
);
|
|
9394
9732
|
}
|
|
9395
9733
|
}
|
|
9396
9734
|
let inventory;
|
|
@@ -9415,26 +9753,31 @@ function deriveSkillsRootFromTaskFile(taskFilePath) {
|
|
|
9415
9753
|
if (idx === -1) return void 0;
|
|
9416
9754
|
return taskFilePath.slice(0, idx + marker.length - 1);
|
|
9417
9755
|
}
|
|
9418
|
-
function resolveRefsInDeclaration(obj, taskFilePath, skillsRoot, schemas) {
|
|
9756
|
+
function resolveRefsInDeclaration(obj, taskFilePath, skillsRoot, schemas, sources) {
|
|
9419
9757
|
if (obj.$ref && typeof obj.$ref === "string") {
|
|
9420
|
-
const { typeName, schema, fileSchemas, schemaFilePath } = resolveSchemaRef(
|
|
9758
|
+
const { typeName, schema, fileSchemas, schemaFilePath } = resolveSchemaRef(
|
|
9759
|
+
obj.$ref,
|
|
9760
|
+
taskFilePath,
|
|
9761
|
+
skillsRoot,
|
|
9762
|
+
sources
|
|
9763
|
+
);
|
|
9421
9764
|
schemas[typeName] = schema;
|
|
9422
|
-
collectLocalRefsFromSchema(schema, fileSchemas, schemas, /* @__PURE__ */ new Set([typeName]), schemaFilePath, skillsRoot);
|
|
9765
|
+
collectLocalRefsFromSchema(schema, fileSchemas, schemas, /* @__PURE__ */ new Set([typeName]), schemaFilePath, skillsRoot, sources);
|
|
9423
9766
|
obj.$ref = `#/${typeName}`;
|
|
9424
9767
|
}
|
|
9425
9768
|
for (const value of Object.values(obj)) {
|
|
9426
9769
|
if (Array.isArray(value)) {
|
|
9427
9770
|
for (const item of value) {
|
|
9428
9771
|
if (item && typeof item === "object") {
|
|
9429
|
-
resolveRefsInDeclaration(item, taskFilePath, skillsRoot, schemas);
|
|
9772
|
+
resolveRefsInDeclaration(item, taskFilePath, skillsRoot, schemas, sources);
|
|
9430
9773
|
}
|
|
9431
9774
|
}
|
|
9432
9775
|
} else if (value && typeof value === "object") {
|
|
9433
|
-
resolveRefsInDeclaration(value, taskFilePath, skillsRoot, schemas);
|
|
9776
|
+
resolveRefsInDeclaration(value, taskFilePath, skillsRoot, schemas, sources);
|
|
9434
9777
|
}
|
|
9435
9778
|
}
|
|
9436
9779
|
}
|
|
9437
|
-
function rewriteRefsInSchema(obj, taskFilePath, skillsRoot, schemas) {
|
|
9780
|
+
function rewriteRefsInSchema(obj, taskFilePath, skillsRoot, schemas, sources) {
|
|
9438
9781
|
if (obj.$ref && typeof obj.$ref === "string") {
|
|
9439
9782
|
const ref2 = obj.$ref;
|
|
9440
9783
|
if (ref2.includes("#/") && !ref2.startsWith("#/")) {
|
|
@@ -9443,9 +9786,9 @@ function rewriteRefsInSchema(obj, taskFilePath, skillsRoot, schemas) {
|
|
|
9443
9786
|
schema,
|
|
9444
9787
|
fileSchemas,
|
|
9445
9788
|
schemaFilePath: refFilePath
|
|
9446
|
-
} = resolveSchemaRef(ref2, taskFilePath, skillsRoot);
|
|
9789
|
+
} = resolveSchemaRef(ref2, taskFilePath, skillsRoot, sources);
|
|
9447
9790
|
schemas[typeName] = schema;
|
|
9448
|
-
collectLocalRefsFromSchema(schema, fileSchemas, schemas, /* @__PURE__ */ new Set([typeName]), refFilePath, skillsRoot);
|
|
9791
|
+
collectLocalRefsFromSchema(schema, fileSchemas, schemas, /* @__PURE__ */ new Set([typeName]), refFilePath, skillsRoot, sources);
|
|
9449
9792
|
obj.$ref = `#/${typeName}`;
|
|
9450
9793
|
}
|
|
9451
9794
|
}
|
|
@@ -9453,11 +9796,11 @@ function rewriteRefsInSchema(obj, taskFilePath, skillsRoot, schemas) {
|
|
|
9453
9796
|
if (Array.isArray(value)) {
|
|
9454
9797
|
for (const item of value) {
|
|
9455
9798
|
if (item && typeof item === "object") {
|
|
9456
|
-
rewriteRefsInSchema(item, taskFilePath, skillsRoot, schemas);
|
|
9799
|
+
rewriteRefsInSchema(item, taskFilePath, skillsRoot, schemas, sources);
|
|
9457
9800
|
}
|
|
9458
9801
|
}
|
|
9459
9802
|
} else if (value && typeof value === "object") {
|
|
9460
|
-
rewriteRefsInSchema(value, taskFilePath, skillsRoot, schemas);
|
|
9803
|
+
rewriteRefsInSchema(value, taskFilePath, skillsRoot, schemas, sources);
|
|
9461
9804
|
}
|
|
9462
9805
|
}
|
|
9463
9806
|
}
|
|
@@ -9478,19 +9821,21 @@ function getWorkflowStageDefinitions(fm3) {
|
|
|
9478
9821
|
function getWorkflowTitle(fm3) {
|
|
9479
9822
|
return fm3.title ?? fm3.workflow?.title ?? "";
|
|
9480
9823
|
}
|
|
9481
|
-
function deriveArtifactName(filePath, agentsDir, frontmatter) {
|
|
9824
|
+
function deriveArtifactName(filePath, agentsDir, frontmatter, source2) {
|
|
9482
9825
|
if (frontmatter?.type && typeof frontmatter.type === "string") {
|
|
9483
9826
|
if (frontmatter.name && typeof frontmatter.name === "string" && frontmatter.name.includes(":")) {
|
|
9484
9827
|
return frontmatter.name;
|
|
9485
9828
|
}
|
|
9486
9829
|
const bpName = frontmatter.name ?? filePath.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/, "") ?? "";
|
|
9487
|
-
const
|
|
9488
|
-
const skill2 = rel2.split("/")[0] ?? "";
|
|
9830
|
+
const skill2 = source2 ? source2.name : relative(resolve(agentsDir, "skills"), filePath).replace(/\\/g, "/").split("/")[0] ?? "";
|
|
9489
9831
|
return `${skill2}:blueprints:${frontmatter.type}/${bpName}`;
|
|
9490
9832
|
}
|
|
9491
9833
|
if (frontmatter?.name && typeof frontmatter.name === "string") {
|
|
9492
9834
|
return frontmatter.name;
|
|
9493
9835
|
}
|
|
9836
|
+
if (source2) {
|
|
9837
|
+
return derivePluginArtifactName(source2, filePath);
|
|
9838
|
+
}
|
|
9494
9839
|
const rel = relative(resolve(agentsDir, "skills"), filePath).replace(/\\/g, "/");
|
|
9495
9840
|
const parts = rel.split("/");
|
|
9496
9841
|
const skill = parts[0] ?? "";
|
|
@@ -9603,30 +9948,23 @@ function buildEnvMap(config) {
|
|
|
9603
9948
|
env["DESIGNBOOK_EXTENSION_SKILLS"] = getExtensionSkillIds(extensions);
|
|
9604
9949
|
return env;
|
|
9605
9950
|
}
|
|
9606
|
-
function
|
|
9607
|
-
const
|
|
9608
|
-
|
|
9609
|
-
for (const
|
|
9610
|
-
|
|
9611
|
-
const relPath = relative(rootDir, value);
|
|
9612
|
-
remapped[key] = resolve(worktreePath, relPath);
|
|
9613
|
-
}
|
|
9614
|
-
if (envMap["DESIGNBOOK_HOME"]) {
|
|
9615
|
-
remapped["DESIGNBOOK_HOME"] = resolve(worktreePath, relative(rootDir, envMap["DESIGNBOOK_HOME"]));
|
|
9951
|
+
function resolveFiles(globPattern, context2, config, agentsDir, requireWhen = true, sources) {
|
|
9952
|
+
const results = [];
|
|
9953
|
+
const candidates = [];
|
|
9954
|
+
for (const filePath of globSync(globPattern, { cwd: agentsDir, absolute: true })) {
|
|
9955
|
+
candidates.push({ filePath });
|
|
9616
9956
|
}
|
|
9617
|
-
|
|
9618
|
-
|
|
9957
|
+
const sourcePattern = toSourcePattern(globPattern);
|
|
9958
|
+
for (const source2 of pluginSources(sources)) {
|
|
9959
|
+
for (const filePath of globSync(sourcePattern, { cwd: source2.root, absolute: true })) {
|
|
9960
|
+
candidates.push({ filePath, source: source2 });
|
|
9961
|
+
}
|
|
9619
9962
|
}
|
|
9620
|
-
|
|
9621
|
-
}
|
|
9622
|
-
function resolveFiles(globPattern, context2, config, agentsDir, requireWhen = true) {
|
|
9623
|
-
const results = [];
|
|
9624
|
-
const paths = globSync(globPattern, { cwd: agentsDir, absolute: true });
|
|
9625
|
-
for (const filePath of paths) {
|
|
9963
|
+
for (const { filePath, source: source2 } of candidates) {
|
|
9626
9964
|
const frontmatter = parseFrontmatter(filePath);
|
|
9627
9965
|
const trigger = frontmatter?.trigger;
|
|
9628
9966
|
const filter = frontmatter?.filter;
|
|
9629
|
-
const name = deriveArtifactName(filePath, agentsDir, frontmatter);
|
|
9967
|
+
const name = deriveArtifactName(filePath, agentsDir, frontmatter, source2);
|
|
9630
9968
|
const triggerCount = trigger ? Object.keys(trigger).length : 0;
|
|
9631
9969
|
const filterCount = filter ? Object.keys(filter).length : 0;
|
|
9632
9970
|
if (triggerCount + filterCount === 0) {
|
|
@@ -9702,10 +10040,19 @@ function deduplicateByNameAs(files, agentsDir, warnings = []) {
|
|
|
9702
10040
|
});
|
|
9703
10041
|
return standalone;
|
|
9704
10042
|
}
|
|
9705
|
-
function
|
|
10043
|
+
function resolveExplicitTaskInPluginSources(skillName, taskName, sources) {
|
|
10044
|
+
const source2 = pluginSources(sources).find((s) => s.name === skillName);
|
|
10045
|
+
if (!source2) return void 0;
|
|
10046
|
+
const flat = resolve(source2.root, "tasks", `${taskName}.md`);
|
|
10047
|
+
if (existsSync(flat)) return flat;
|
|
10048
|
+
const nested = globSync(`**/tasks/${taskName}.md`, { cwd: source2.root, absolute: true });
|
|
10049
|
+
if (nested.length > 0) return nested[0];
|
|
10050
|
+
return void 0;
|
|
10051
|
+
}
|
|
10052
|
+
function resolveTaskFilesRich(stage, config, agentsDir, sources) {
|
|
9706
10053
|
const context2 = buildRuntimeContext(stage);
|
|
9707
10054
|
const enrichedConfig = buildEnrichedConfig(config);
|
|
9708
|
-
const broadMatches = resolveFiles("skills/**/tasks/*.md", context2, enrichedConfig, agentsDir, true);
|
|
10055
|
+
const broadMatches = resolveFiles("skills/**/tasks/*.md", context2, enrichedConfig, agentsDir, true, sources);
|
|
9709
10056
|
if (stage.includes(":")) {
|
|
9710
10057
|
if (broadMatches.length > 0) {
|
|
9711
10058
|
broadMatches.sort((a, b) => b.specificity - a.specificity);
|
|
@@ -9723,12 +10070,29 @@ function resolveTaskFilesRich(stage, config, agentsDir) {
|
|
|
9723
10070
|
const name = deriveArtifactName(taskPath, agentsDir, frontmatter);
|
|
9724
10071
|
return [{ path: taskPath, name, specificity: 0, frontmatter }];
|
|
9725
10072
|
}
|
|
10073
|
+
const pluginTaskPath = resolveExplicitTaskInPluginSources(skillName, taskName, sources);
|
|
10074
|
+
if (pluginTaskPath) {
|
|
10075
|
+
console.warn(
|
|
10076
|
+
`[designbook] task "${pluginTaskPath}" resolved by filename \u2014 add trigger.steps: [${stage}] to frontmatter`
|
|
10077
|
+
);
|
|
10078
|
+
const frontmatter = parseFrontmatter(pluginTaskPath);
|
|
10079
|
+
const pluginSource = pluginSources(sources).find((s) => s.name === skillName);
|
|
10080
|
+
const name = deriveArtifactName(pluginTaskPath, agentsDir, frontmatter, pluginSource);
|
|
10081
|
+
return [{ path: pluginTaskPath, name, specificity: 0, frontmatter }];
|
|
10082
|
+
}
|
|
9726
10083
|
return [];
|
|
9727
10084
|
}
|
|
9728
10085
|
if (broadMatches.length > 0) {
|
|
9729
10086
|
return deduplicateByNameAs(broadMatches, agentsDir);
|
|
9730
10087
|
}
|
|
9731
|
-
const filenameMatches = resolveFiles(
|
|
10088
|
+
const filenameMatches = resolveFiles(
|
|
10089
|
+
`skills/**/tasks/${stage}.md`,
|
|
10090
|
+
context2,
|
|
10091
|
+
enrichedConfig,
|
|
10092
|
+
agentsDir,
|
|
10093
|
+
false,
|
|
10094
|
+
sources
|
|
10095
|
+
);
|
|
9732
10096
|
if (filenameMatches.length > 0) {
|
|
9733
10097
|
for (const m of filenameMatches) {
|
|
9734
10098
|
console.warn(`[designbook] task "${m.path}" resolved by filename \u2014 add trigger.steps: [${stage}] to frontmatter`);
|
|
@@ -9737,22 +10101,22 @@ function resolveTaskFilesRich(stage, config, agentsDir) {
|
|
|
9737
10101
|
}
|
|
9738
10102
|
return [];
|
|
9739
10103
|
}
|
|
9740
|
-
function matchRuleFiles(stage, config, agentsDir, extraConditions, effectiveDomains) {
|
|
10104
|
+
function matchRuleFiles(stage, config, agentsDir, extraConditions, effectiveDomains, sources) {
|
|
9741
10105
|
const context2 = buildRuntimeContext(stage);
|
|
9742
10106
|
if (effectiveDomains && effectiveDomains.length > 0) {
|
|
9743
10107
|
context2["domain"] = effectiveDomains;
|
|
9744
10108
|
}
|
|
9745
10109
|
const enrichedConfig = buildEnrichedConfig(config);
|
|
9746
|
-
const matches = resolveFiles("skills/**/rules/*.md", context2, enrichedConfig, agentsDir, true);
|
|
10110
|
+
const matches = resolveFiles("skills/**/rules/*.md", context2, enrichedConfig, agentsDir, true, sources);
|
|
9747
10111
|
return matches.map((m) => m.path);
|
|
9748
10112
|
}
|
|
9749
|
-
function matchBlueprintFiles(stage, config, agentsDir, extraConditions, effectiveDomains) {
|
|
10113
|
+
function matchBlueprintFiles(stage, config, agentsDir, extraConditions, effectiveDomains, sources) {
|
|
9750
10114
|
const context2 = buildRuntimeContext(stage);
|
|
9751
10115
|
if (effectiveDomains && effectiveDomains.length > 0) {
|
|
9752
10116
|
context2["domain"] = effectiveDomains;
|
|
9753
10117
|
}
|
|
9754
10118
|
const enrichedConfig = buildEnrichedConfig(config);
|
|
9755
|
-
const matches = resolveFiles("skills/**/blueprints/*.md", context2, enrichedConfig, agentsDir, true);
|
|
10119
|
+
const matches = resolveFiles("skills/**/blueprints/*.md", context2, enrichedConfig, agentsDir, true, sources);
|
|
9756
10120
|
const byKey = /* @__PURE__ */ new Map();
|
|
9757
10121
|
for (const m of matches) {
|
|
9758
10122
|
const type = m.frontmatter?.["type"];
|
|
@@ -9908,9 +10272,9 @@ function resolveConfigForStep(stage, rawConfig) {
|
|
|
9908
10272
|
config_instructions: [...explicitInstructions, ...extensionSkills]
|
|
9909
10273
|
};
|
|
9910
10274
|
}
|
|
9911
|
-
function resolveParamsRef(params, taskFilePath, skillsRoot) {
|
|
10275
|
+
function resolveParamsRef(params, taskFilePath, skillsRoot, sources) {
|
|
9912
10276
|
const ref2 = params["$ref"];
|
|
9913
|
-
const { schema } = resolveSchemaRef(ref2, taskFilePath, skillsRoot);
|
|
10277
|
+
const { schema } = resolveSchemaRef(ref2, taskFilePath, skillsRoot, sources);
|
|
9914
10278
|
const schemaObj = schema;
|
|
9915
10279
|
const schemaProps = schemaObj.properties;
|
|
9916
10280
|
if (!schemaProps) {
|
|
@@ -9997,7 +10361,7 @@ function generateTaskId(stage, params, _schemaParams, index = 0) {
|
|
|
9997
10361
|
const hash = createHash("sha256").update(stage + JSON.stringify(params) + index).digest("hex").slice(0, 6);
|
|
9998
10362
|
return `${baseName}-${hash}`;
|
|
9999
10363
|
}
|
|
10000
|
-
async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir) {
|
|
10364
|
+
async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir, sources) {
|
|
10001
10365
|
const wfFm = parseFrontmatter(workflowFilePath);
|
|
10002
10366
|
const stageDefs = wfFm ? getWorkflowStageDefinitions(wfFm) : void 0;
|
|
10003
10367
|
const allSteps = wfFm ? getWorkflowSteps(wfFm) : void 0;
|
|
@@ -10010,9 +10374,9 @@ async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir)
|
|
|
10010
10374
|
const expectedParams = {};
|
|
10011
10375
|
const collectedSchemas = {};
|
|
10012
10376
|
for (const step of allSteps ?? []) {
|
|
10013
|
-
let resolvedTaskFiles = resolveTaskFilesRich(step, config, agentsDir);
|
|
10377
|
+
let resolvedTaskFiles = resolveTaskFilesRich(step, config, agentsDir, sources);
|
|
10014
10378
|
if (resolvedTaskFiles.length === 0 && !step.includes(":") && workflowId) {
|
|
10015
|
-
resolvedTaskFiles = resolveTaskFilesRich(`${workflowId}:${step}`, config, agentsDir);
|
|
10379
|
+
resolvedTaskFiles = resolveTaskFilesRich(`${workflowId}:${step}`, config, agentsDir, sources);
|
|
10016
10380
|
}
|
|
10017
10381
|
if (resolvedTaskFiles.length === 0) {
|
|
10018
10382
|
console.debug(`[Designbook] workflow: step "${step}" skipped \u2014 no matching task file`);
|
|
@@ -10051,13 +10415,13 @@ async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir)
|
|
|
10051
10415
|
const effectiveDomainsArg = effectiveDomains.length > 0 ? effectiveDomains : void 0;
|
|
10052
10416
|
const ruleFiles = [];
|
|
10053
10417
|
for (const s of stepsToMatch) {
|
|
10054
|
-
for (const r of matchRuleFiles(s, config, agentsDir, void 0, effectiveDomainsArg)) {
|
|
10418
|
+
for (const r of matchRuleFiles(s, config, agentsDir, void 0, effectiveDomainsArg, sources)) {
|
|
10055
10419
|
if (!ruleFiles.includes(r)) ruleFiles.push(r);
|
|
10056
10420
|
}
|
|
10057
10421
|
}
|
|
10058
10422
|
const blueprintFiles = [];
|
|
10059
10423
|
for (const s of stepsToMatch) {
|
|
10060
|
-
for (const b of matchBlueprintFiles(s, config, agentsDir, void 0, effectiveDomainsArg)) {
|
|
10424
|
+
for (const b of matchBlueprintFiles(s, config, agentsDir, void 0, effectiveDomainsArg, sources)) {
|
|
10061
10425
|
if (!blueprintFiles.includes(b)) blueprintFiles.push(b);
|
|
10062
10426
|
}
|
|
10063
10427
|
}
|
|
@@ -10086,7 +10450,8 @@ async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir)
|
|
|
10086
10450
|
ruleFiles,
|
|
10087
10451
|
skillsRoot: resolve(agentsDir, "skills"),
|
|
10088
10452
|
schemas: collectedSchemas,
|
|
10089
|
-
refMap
|
|
10453
|
+
refMap,
|
|
10454
|
+
sources
|
|
10090
10455
|
});
|
|
10091
10456
|
}
|
|
10092
10457
|
}
|
|
@@ -10097,7 +10462,8 @@ async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir)
|
|
|
10097
10462
|
result: taskFmForSchema?.result,
|
|
10098
10463
|
taskFilePath: primaryTaskFile,
|
|
10099
10464
|
skillsRoot: resolve(agentsDir, "skills"),
|
|
10100
|
-
envMap
|
|
10465
|
+
envMap,
|
|
10466
|
+
sources
|
|
10101
10467
|
});
|
|
10102
10468
|
if (mergedSchema) {
|
|
10103
10469
|
for (const [resultKey, composedSchema] of Object.entries(mergedSchema)) {
|
|
@@ -10141,7 +10507,7 @@ async function resolveAllStages(workflowFilePath, config, rawConfig, agentsDir)
|
|
|
10141
10507
|
let params = taskFm?.params;
|
|
10142
10508
|
if (!params) continue;
|
|
10143
10509
|
if ("$ref" in params) {
|
|
10144
|
-
params = resolveParamsRef(params, taskFile, skillsRoot);
|
|
10510
|
+
params = resolveParamsRef(params, taskFile, skillsRoot, sources);
|
|
10145
10511
|
}
|
|
10146
10512
|
validateParamFormats(params, taskFile);
|
|
10147
10513
|
const properties = params.properties ?? {};
|
|
@@ -10240,292 +10606,30 @@ async function generateTaskTitle(stage, params, schemaParams, explicitTitle) {
|
|
|
10240
10606
|
}
|
|
10241
10607
|
return words;
|
|
10242
10608
|
}
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
function checkPreflightClean(rootDir, outputsRoot) {
|
|
10246
|
-
try {
|
|
10247
|
-
const relOutputsRoot = relative(rootDir, outputsRoot);
|
|
10248
|
-
const output = execFileSync("git", ["-C", rootDir, "status", "--porcelain", "--", relOutputsRoot], {
|
|
10249
|
-
encoding: "utf-8"
|
|
10250
|
-
});
|
|
10251
|
-
if (!output.trim()) return { clean: true, files: [] };
|
|
10252
|
-
const files = output.split("\n").filter((line) => line.length >= 4).map((line) => line.slice(3).trim()).filter(Boolean);
|
|
10253
|
-
return { clean: false, files };
|
|
10254
|
-
} catch {
|
|
10255
|
-
return { clean: true, files: [] };
|
|
10256
|
-
}
|
|
10609
|
+
function getExpr(value) {
|
|
10610
|
+
return typeof value === "string" ? value : value.expr;
|
|
10257
10611
|
}
|
|
10258
|
-
function
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
const
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
}
|
|
10280
|
-
const branchName = `workflow/${ctx.workflowName}`;
|
|
10281
|
-
const preflight = checkPreflightClean(ctx.rootDir, ctx.workspace);
|
|
10282
|
-
if (!preflight.clean) {
|
|
10283
|
-
const files = preflight.files.map((f) => ` \xB7 ${f}`).join("\n");
|
|
10284
|
-
throw new Error(`Uncommitted changes in workspace \u2014 commit these files before running workflow plan:
|
|
10285
|
-
${files}`);
|
|
10286
|
-
}
|
|
10287
|
-
createGitWorktree(ctx.worktreePath, branchName, ctx.rootDir);
|
|
10288
|
-
return {
|
|
10289
|
-
envMap: buildWorktreeEnvMap(ctx.envMap, ctx.worktreePath, ctx.rootDir),
|
|
10290
|
-
write_root: ctx.worktreePath,
|
|
10291
|
-
worktree_branch: branchName
|
|
10292
|
-
};
|
|
10293
|
-
},
|
|
10294
|
-
commit(data) {
|
|
10295
|
-
if (!data.write_root || !data.worktree_branch || !data.workspace_root) return;
|
|
10296
|
-
if (!existsSync(data.write_root)) return;
|
|
10297
|
-
const outputTasks = data.tasks.filter((t) => t.type !== "test");
|
|
10298
|
-
const outputPaths = outputTasks.flatMap((t) => (t.files ?? []).map((f) => f.path));
|
|
10299
|
-
if (outputPaths.length > 0) {
|
|
10300
|
-
const relPaths = outputPaths.map((p) => relative(data.write_root, p)).filter((r) => !r.startsWith(".."));
|
|
10301
|
-
if (relPaths.length > 0) {
|
|
10302
|
-
execFileSync("git", ["-C", data.write_root, "add", ...relPaths]);
|
|
10303
|
-
}
|
|
10304
|
-
}
|
|
10305
|
-
execFileSync("git", ["-C", data.write_root, "commit", "--allow-empty", "-m", `workflow: ${data.worktree_branch}`]);
|
|
10306
|
-
execFileSync("git", ["worktree", "remove", data.write_root, "--force"], { cwd: data.workspace_root });
|
|
10307
|
-
},
|
|
10308
|
-
merge(data) {
|
|
10309
|
-
const branch = data.worktree_branch;
|
|
10310
|
-
const rootDir = data.workspace_root;
|
|
10311
|
-
if (!branch) throw new Error("Workflow has no worktree_branch \u2014 nothing to merge");
|
|
10312
|
-
if (!rootDir) throw new Error("Workflow has no workspace_root");
|
|
10313
|
-
execFileSync("git", ["-C", rootDir, "merge", "--squash", branch]);
|
|
10314
|
-
execFileSync("git", ["-C", rootDir, "commit", "-m", `workflow: ${data.workflow}`]);
|
|
10315
|
-
execFileSync("git", ["-C", rootDir, "branch", "-D", branch]);
|
|
10316
|
-
return { branch, workspace_root: rootDir };
|
|
10317
|
-
},
|
|
10318
|
-
done() {
|
|
10319
|
-
return { archive: false };
|
|
10320
|
-
},
|
|
10321
|
-
cleanup(data) {
|
|
10322
|
-
if (data.write_root && existsSync(data.write_root)) {
|
|
10323
|
-
try {
|
|
10324
|
-
execFileSync("git", ["worktree", "remove", data.write_root, "--force"], { cwd: data.workspace_root });
|
|
10325
|
-
} catch {
|
|
10326
|
-
}
|
|
10327
|
-
}
|
|
10328
|
-
if (data.worktree_branch && data.workspace_root) {
|
|
10329
|
-
try {
|
|
10330
|
-
execFileSync("git", ["-C", data.workspace_root, "branch", "-D", data.worktree_branch]);
|
|
10331
|
-
} catch {
|
|
10332
|
-
}
|
|
10333
|
-
}
|
|
10334
|
-
},
|
|
10335
|
-
onTransition(from, to, ctx) {
|
|
10336
|
-
const { data } = ctx;
|
|
10337
|
-
switch (`${from}\u2192${to}`) {
|
|
10338
|
-
case "planned\u2192execute": {
|
|
10339
|
-
if (!ctx.setupCtx) return {};
|
|
10340
|
-
const result = gitWorktreeEngine.setup(ctx.setupCtx);
|
|
10341
|
-
return {
|
|
10342
|
-
envMap: result.envMap,
|
|
10343
|
-
write_root: result.write_root,
|
|
10344
|
-
worktree_branch: result.worktree_branch
|
|
10345
|
-
};
|
|
10346
|
-
}
|
|
10347
|
-
case "execute\u2192committed": {
|
|
10348
|
-
gitWorktreeEngine.commit(data);
|
|
10349
|
-
return {};
|
|
10350
|
-
}
|
|
10351
|
-
case "committed\u2192test": {
|
|
10352
|
-
return {};
|
|
10353
|
-
}
|
|
10354
|
-
case "finalizing\u2192done": {
|
|
10355
|
-
if (!ctx.params?.merge_approved) {
|
|
10356
|
-
return {
|
|
10357
|
-
requires: {
|
|
10358
|
-
merge_approved: {
|
|
10359
|
-
type: "boolean",
|
|
10360
|
-
prompt: "Branch {branch} mergen?"
|
|
10361
|
-
}
|
|
10362
|
-
}
|
|
10363
|
-
};
|
|
10364
|
-
}
|
|
10365
|
-
const mergeResult = gitWorktreeEngine.merge(data);
|
|
10366
|
-
return { archive: true, branch: mergeResult.branch };
|
|
10367
|
-
}
|
|
10368
|
-
default:
|
|
10369
|
-
if (to === "abandoned") {
|
|
10370
|
-
gitWorktreeEngine.cleanup(data);
|
|
10371
|
-
}
|
|
10372
|
-
return {};
|
|
10373
|
-
}
|
|
10374
|
-
}
|
|
10375
|
-
};
|
|
10376
|
-
function deboSuffix(data) {
|
|
10377
|
-
if (!data.workflow_id) throw new Error("workflow_id is required for direct engine stash-at-target");
|
|
10378
|
-
return `.${data.workflow_id}.debo`;
|
|
10379
|
-
}
|
|
10380
|
-
function stashPath(data, fileEntry) {
|
|
10381
|
-
return fileEntry.path + deboSuffix(data);
|
|
10382
|
-
}
|
|
10383
|
-
var directEngine = {
|
|
10384
|
-
setup(ctx) {
|
|
10385
|
-
return { envMap: ctx.envMap };
|
|
10386
|
-
},
|
|
10387
|
-
writeFile(data, task, key, content) {
|
|
10388
|
-
const fileEntry = (task.files ?? []).find((f) => f.key === key);
|
|
10389
|
-
if (!fileEntry) throw new Error(`No file entry with key '${key}' in task '${task.id}'`);
|
|
10390
|
-
const path = stashPath(data, fileEntry);
|
|
10391
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
10392
|
-
writeFileSync(path, content);
|
|
10393
|
-
return { path };
|
|
10394
|
-
},
|
|
10395
|
-
getStagedPath(data, task, key) {
|
|
10396
|
-
const fileEntry = (task.files ?? []).find((f) => f.key === key);
|
|
10397
|
-
if (fileEntry) return stashPath(data, fileEntry);
|
|
10398
|
-
const resultEntry = task.result?.[key];
|
|
10399
|
-
if (resultEntry?.path) {
|
|
10400
|
-
return resultEntry.path;
|
|
10401
|
-
}
|
|
10402
|
-
throw new Error(`No file entry with key '${key}' in task '${task.id}'`);
|
|
10403
|
-
},
|
|
10404
|
-
flush(data, tasks) {
|
|
10405
|
-
const suffix = deboSuffix(data);
|
|
10406
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10407
|
-
for (const task of tasks) {
|
|
10408
|
-
for (const file of task.files ?? []) {
|
|
10409
|
-
if (!file.validation_result) continue;
|
|
10410
|
-
const src = file.path + suffix;
|
|
10411
|
-
if (!existsSync(src)) continue;
|
|
10412
|
-
mkdirSync(dirname(file.path), { recursive: true });
|
|
10413
|
-
renameSync(src, file.path);
|
|
10414
|
-
file.flushed_at = now;
|
|
10415
|
-
}
|
|
10416
|
-
}
|
|
10417
|
-
return Promise.resolve();
|
|
10418
|
-
},
|
|
10419
|
-
commit() {
|
|
10420
|
-
},
|
|
10421
|
-
merge() {
|
|
10422
|
-
throw new Error('Engine "direct" does not support merge \u2014 files are already written to real paths');
|
|
10423
|
-
},
|
|
10424
|
-
done() {
|
|
10425
|
-
return { archive: true };
|
|
10426
|
-
},
|
|
10427
|
-
cleanup(data) {
|
|
10428
|
-
const suffix = deboSuffix(data);
|
|
10429
|
-
const dirs = /* @__PURE__ */ new Set();
|
|
10430
|
-
for (const task of data.tasks) {
|
|
10431
|
-
for (const file of task.files ?? []) {
|
|
10432
|
-
dirs.add(dirname(file.path));
|
|
10433
|
-
}
|
|
10434
|
-
}
|
|
10435
|
-
for (const dir of dirs) {
|
|
10436
|
-
if (!existsSync(dir)) continue;
|
|
10437
|
-
try {
|
|
10438
|
-
const entries = readdirSync(dir);
|
|
10439
|
-
for (const entry of entries) {
|
|
10440
|
-
if (entry.endsWith(suffix)) {
|
|
10441
|
-
unlinkSync(resolve(dir, entry));
|
|
10442
|
-
}
|
|
10443
|
-
}
|
|
10444
|
-
} catch {
|
|
10445
|
-
}
|
|
10446
|
-
}
|
|
10447
|
-
},
|
|
10448
|
-
async onTransition(from, to, ctx) {
|
|
10449
|
-
const declared = ctx.data.stages ? Object.keys(ctx.data.stages) : [];
|
|
10450
|
-
if (declared.includes(from)) {
|
|
10451
|
-
const stageTasks = ctx.data.tasks.filter((t) => t.stage === from);
|
|
10452
|
-
if (stageTasks.length > 0) {
|
|
10453
|
-
await directEngine.flush(ctx.data, stageTasks);
|
|
10454
|
-
}
|
|
10455
|
-
}
|
|
10456
|
-
if (from === "finalizing" && to === "done") {
|
|
10457
|
-
return { archive: true };
|
|
10458
|
-
}
|
|
10459
|
-
return {};
|
|
10460
|
-
}
|
|
10461
|
-
};
|
|
10462
|
-
|
|
10463
|
-
// src/engines/index.ts
|
|
10464
|
-
var engines = {
|
|
10465
|
-
"git-worktree": gitWorktreeEngine,
|
|
10466
|
-
direct: directEngine
|
|
10467
|
-
};
|
|
10468
|
-
|
|
10469
|
-
// src/workflow-lifecycle.ts
|
|
10470
|
-
var IMPLICIT_STAGES = {
|
|
10471
|
-
_after_declared: ["committed"],
|
|
10472
|
-
_before_done: ["finalizing", "done"]
|
|
10473
|
-
};
|
|
10474
|
-
function buildLifecycleOrder(stages) {
|
|
10475
|
-
const declared = Object.keys(stages).filter((s) => (stages[s].steps?.length ?? 0) > 0);
|
|
10476
|
-
return ["created", "planned", ...declared, ...IMPLICIT_STAGES._after_declared, ...IMPLICIT_STAGES._before_done];
|
|
10477
|
-
}
|
|
10478
|
-
function getNextStage(current, stages) {
|
|
10479
|
-
const order = buildLifecycleOrder(stages);
|
|
10480
|
-
const currentIdx = order.indexOf(current);
|
|
10481
|
-
if (currentIdx === -1 || currentIdx >= order.length - 1) return null;
|
|
10482
|
-
return order[currentIdx + 1] ?? null;
|
|
10483
|
-
}
|
|
10484
|
-
function getNextStep(currentStage, _completedStep, tasks) {
|
|
10485
|
-
const pending = tasks.find((t) => t.stage === currentStage && t.status !== "done");
|
|
10486
|
-
return pending?.step ?? null;
|
|
10487
|
-
}
|
|
10488
|
-
function checkStageParams(stage, stages, provided) {
|
|
10489
|
-
const def = stages[stage];
|
|
10490
|
-
if (!def?.params) return null;
|
|
10491
|
-
const unfulfilled = {};
|
|
10492
|
-
for (const [key, param] of Object.entries(def.params)) {
|
|
10493
|
-
if (provided[key] === void 0) {
|
|
10494
|
-
unfulfilled[key] = param;
|
|
10495
|
-
}
|
|
10496
|
-
}
|
|
10497
|
-
return Object.keys(unfulfilled).length > 0 ? unfulfilled : null;
|
|
10498
|
-
}
|
|
10499
|
-
function interpolatePrompt(prompt, state) {
|
|
10500
|
-
return prompt.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
10501
|
-
const value = state[key];
|
|
10502
|
-
return value !== void 0 ? String(value) : `{${key}}`;
|
|
10503
|
-
});
|
|
10504
|
-
}
|
|
10505
|
-
function getExpr(value) {
|
|
10506
|
-
return typeof value === "string" ? value : value.expr;
|
|
10507
|
-
}
|
|
10508
|
-
async function resolveEach(each, scope) {
|
|
10509
|
-
const entries = Object.entries(each);
|
|
10510
|
-
if (entries.length === 0) return [];
|
|
10511
|
-
let combos = [{}];
|
|
10512
|
-
for (const [binding, raw] of entries) {
|
|
10513
|
-
const expr = getExpr(raw);
|
|
10514
|
-
const next = [];
|
|
10515
|
-
for (const combo of combos) {
|
|
10516
|
-
const innerScope = { ...scope, ...combo };
|
|
10517
|
-
const value = await jsonata9(expr).evaluate(innerScope);
|
|
10518
|
-
if (value === void 0 || value === null) continue;
|
|
10519
|
-
const items = Array.isArray(value) ? value : [value];
|
|
10520
|
-
for (const item of items) {
|
|
10521
|
-
next.push({ ...combo, [binding]: item });
|
|
10522
|
-
}
|
|
10523
|
-
}
|
|
10524
|
-
combos = next;
|
|
10525
|
-
if (combos.length === 0) break;
|
|
10526
|
-
}
|
|
10527
|
-
const total = combos.length;
|
|
10528
|
-
return combos.map((c, i) => ({ ...c, $i: i, $total: total }));
|
|
10612
|
+
async function resolveEach(each, scope) {
|
|
10613
|
+
const entries = Object.entries(each);
|
|
10614
|
+
if (entries.length === 0) return [];
|
|
10615
|
+
let combos = [{}];
|
|
10616
|
+
for (const [binding, raw] of entries) {
|
|
10617
|
+
const expr = getExpr(raw);
|
|
10618
|
+
const next = [];
|
|
10619
|
+
for (const combo of combos) {
|
|
10620
|
+
const innerScope = { ...scope, ...combo };
|
|
10621
|
+
const value = await jsonata9(expr).evaluate(innerScope);
|
|
10622
|
+
if (value === void 0 || value === null) continue;
|
|
10623
|
+
const items = Array.isArray(value) ? value : [value];
|
|
10624
|
+
for (const item of items) {
|
|
10625
|
+
next.push({ ...combo, [binding]: item });
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
combos = next;
|
|
10629
|
+
if (combos.length === 0) break;
|
|
10630
|
+
}
|
|
10631
|
+
const total = combos.length;
|
|
10632
|
+
return combos.map((c, i) => ({ ...c, $i: i, $total: total }));
|
|
10529
10633
|
}
|
|
10530
10634
|
|
|
10531
10635
|
// src/workflow.ts
|
|
@@ -10581,9 +10685,8 @@ function renderSubmitResultsHint(taskId, results) {
|
|
|
10581
10685
|
}
|
|
10582
10686
|
|
|
10583
10687
|
// src/workflow.ts
|
|
10584
|
-
function resolveWorkflowEngine(
|
|
10585
|
-
|
|
10586
|
-
return engines[name];
|
|
10688
|
+
function resolveWorkflowEngine(_data) {
|
|
10689
|
+
return engines["direct"];
|
|
10587
10690
|
}
|
|
10588
10691
|
function timestamp() {
|
|
10589
10692
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -10613,11 +10716,6 @@ function readWorkflow(filePath) {
|
|
|
10613
10716
|
throw new Error(`Workflow not found: ${name}`);
|
|
10614
10717
|
}
|
|
10615
10718
|
const data = load(readFileSync(filePath, "utf-8"));
|
|
10616
|
-
const legacy = data;
|
|
10617
|
-
if (!data.workspace_root && legacy.root_dir) {
|
|
10618
|
-
data.workspace_root = legacy.root_dir;
|
|
10619
|
-
}
|
|
10620
|
-
delete legacy.root_dir;
|
|
10621
10719
|
resolveWorkflowPaths(data);
|
|
10622
10720
|
return data;
|
|
10623
10721
|
}
|
|
@@ -10709,7 +10807,7 @@ function workflowAbandon(dataDir, name) {
|
|
|
10709
10807
|
const changesDir = resolve(dataDir, "workflows", "changes", name);
|
|
10710
10808
|
const filePath = resolve(changesDir, "tasks.yml");
|
|
10711
10809
|
const data = readWorkflow(filePath);
|
|
10712
|
-
const engine = resolveWorkflowEngine(
|
|
10810
|
+
const engine = resolveWorkflowEngine();
|
|
10713
10811
|
if (engine) engine.cleanup(data);
|
|
10714
10812
|
data.status = "incomplete";
|
|
10715
10813
|
data.completed_at = timestamp();
|
|
@@ -10804,16 +10902,6 @@ function resolveWorkflowPaths(data) {
|
|
|
10804
10902
|
function relativizeWorkflowPaths(data) {
|
|
10805
10903
|
return transformWorkflowPaths(data, relativizePath);
|
|
10806
10904
|
}
|
|
10807
|
-
function workflowMerge(dataDir, name) {
|
|
10808
|
-
const changesDir = resolve(dataDir, "workflows", "changes", name);
|
|
10809
|
-
const filePath = resolve(changesDir, "tasks.yml");
|
|
10810
|
-
const data = readWorkflow(filePath);
|
|
10811
|
-
const engine = resolveWorkflowEngine(data);
|
|
10812
|
-
if (!engine) throw new Error(`Unknown engine: "${data.engine}"`);
|
|
10813
|
-
const result = engine.merge(data);
|
|
10814
|
-
archiveAndCascade(dataDir, name, data);
|
|
10815
|
-
return result;
|
|
10816
|
-
}
|
|
10817
10905
|
function workflowList(dataDir, workflowId, includeArchived) {
|
|
10818
10906
|
const changesDir = resolve(dataDir, "workflows", "changes");
|
|
10819
10907
|
const active = existsSync(changesDir) ? readdirSync(changesDir).filter((name) => name.startsWith(`${workflowId}-`)) : [];
|
|
@@ -11055,455 +11143,439 @@ async function expandTasksFromParams(stageLoaded, stages, params, existingTasks,
|
|
|
11055
11143
|
async function workflowDone(dataDir, name, taskId, loaded, options) {
|
|
11056
11144
|
const changesDir = resolve(dataDir, "workflows", "changes", name);
|
|
11057
11145
|
const filePath = resolve(changesDir, "tasks.yml");
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11146
|
+
const data = readWorkflow(filePath);
|
|
11147
|
+
data._changesDir = changesDir;
|
|
11148
|
+
const task = data.tasks.find((t) => t.id === taskId);
|
|
11149
|
+
if (!task) {
|
|
11150
|
+
throw new Error(`Task not found: ${taskId} (available: ${data.tasks.map((t) => t.id).join(", ")})`);
|
|
11151
|
+
}
|
|
11152
|
+
if (task.status === "done") {
|
|
11153
|
+
throw new Error(`Task '${taskId}' is already done`);
|
|
11154
|
+
}
|
|
11155
|
+
if (options?.data && task.result) {
|
|
11156
|
+
const dataPayload = options.data;
|
|
11157
|
+
const declaredKeys = new Set(Object.keys(task.result));
|
|
11158
|
+
const unknownKeys = Object.keys(dataPayload).filter((k) => !declaredKeys.has(k));
|
|
11159
|
+
if (unknownKeys.length > 0) {
|
|
11160
|
+
throw new Error(
|
|
11161
|
+
`Unknown result key(s) in --data: ${unknownKeys.join(", ")}. Valid keys: ${[...declaredKeys].join(", ")}`
|
|
11162
|
+
);
|
|
11163
|
+
}
|
|
11164
|
+
const validationErrors = [];
|
|
11165
|
+
for (const [key, rawValue] of Object.entries(dataPayload)) {
|
|
11166
|
+
const value = rawValue;
|
|
11167
|
+
const resultEntry = task.result[key];
|
|
11168
|
+
if (!resultEntry) continue;
|
|
11169
|
+
if (resultEntry.path) {
|
|
11170
|
+
if (/\{\{[^}]*\}\}/.test(resultEntry.path)) {
|
|
11171
|
+
throw new Error(`result path template unresolved: ${resultEntry.path}`);
|
|
11172
|
+
}
|
|
11173
|
+
const { serializeForPath: serializeForPath2 } = await Promise.resolve().then(() => (init_workflow_serialize(), workflow_serialize_exports));
|
|
11174
|
+
const serialized = serializeForPath2(
|
|
11175
|
+
resultEntry.path,
|
|
11176
|
+
value,
|
|
11177
|
+
resultEntry.schema
|
|
11075
11178
|
);
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
const serialized = serializeForPath2(
|
|
11088
|
-
resultEntry.path,
|
|
11089
|
-
value,
|
|
11090
|
-
resultEntry.schema
|
|
11091
|
-
);
|
|
11092
|
-
const engine2 = resolveWorkflowEngine(data);
|
|
11093
|
-
const dataWithDir = data;
|
|
11094
|
-
dataWithDir._changesDir = changesDir;
|
|
11095
|
-
if (!task.files?.some((f) => f.key === key)) {
|
|
11096
|
-
task.files = task.files ?? [];
|
|
11097
|
-
task.files.push({ path: resultEntry.path, key, validators: resultEntry.validators ?? [] });
|
|
11098
|
-
}
|
|
11099
|
-
let writtenPath;
|
|
11100
|
-
if (engine2?.writeFile) {
|
|
11101
|
-
const result = engine2.writeFile(dataWithDir, task, key, serialized);
|
|
11102
|
-
writtenPath = result.path;
|
|
11103
|
-
} else {
|
|
11104
|
-
const dir = dirname(resultEntry.path);
|
|
11105
|
-
mkdirSync(dir, { recursive: true });
|
|
11106
|
-
writeFileSync(resultEntry.path, serialized);
|
|
11107
|
-
writtenPath = resultEntry.path;
|
|
11108
|
-
}
|
|
11109
|
-
const config = options.config ?? { data: dataDir, technology: "html", extensions: [] };
|
|
11110
|
-
const schemaErrors = await validateResultEntry(resultEntry, value, data.schemas, config, "data");
|
|
11111
|
-
const semanticErrors = resultEntry.validators && resultEntry.validators.length > 0 ? await validateResultEntry(
|
|
11112
|
-
{ ...resultEntry, schema: void 0 },
|
|
11113
|
-
writtenPath,
|
|
11114
|
-
data.schemas,
|
|
11115
|
-
config,
|
|
11116
|
-
"file"
|
|
11117
|
-
) : [];
|
|
11118
|
-
const errors = [...schemaErrors, ...semanticErrors];
|
|
11119
|
-
if (errors.length > 0) {
|
|
11120
|
-
validationErrors.push(...errors.map((e) => `${key}: ${e}`));
|
|
11121
|
-
resultEntry.valid = false;
|
|
11122
|
-
resultEntry.error = errors.join("; ");
|
|
11123
|
-
} else {
|
|
11124
|
-
resultEntry.valid = true;
|
|
11125
|
-
delete resultEntry.error;
|
|
11126
|
-
}
|
|
11127
|
-
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11128
|
-
const fileEntry = task.files?.find((f) => f.key === key);
|
|
11129
|
-
if (fileEntry) {
|
|
11130
|
-
fileEntry.validation_result = {
|
|
11131
|
-
file: resultEntry.path,
|
|
11132
|
-
type: key,
|
|
11133
|
-
valid: resultEntry.valid,
|
|
11134
|
-
error: resultEntry.error,
|
|
11135
|
-
last_validated: resultEntry.last_validated,
|
|
11136
|
-
last_passed: resultEntry.valid ? resultEntry.last_validated : void 0,
|
|
11137
|
-
last_failed: !resultEntry.valid ? resultEntry.last_validated : void 0
|
|
11138
|
-
};
|
|
11139
|
-
}
|
|
11140
|
-
if (resultEntry.flush === "immediate" && writtenPath !== resultEntry.path) {
|
|
11141
|
-
mkdirSync(dirname(resultEntry.path), { recursive: true });
|
|
11142
|
-
renameSync(writtenPath, resultEntry.path);
|
|
11143
|
-
resultEntry.flushed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
11144
|
-
if (fileEntry) fileEntry.flushed_at = resultEntry.flushed_at;
|
|
11145
|
-
}
|
|
11179
|
+
const engine2 = resolveWorkflowEngine();
|
|
11180
|
+
const dataWithDir = data;
|
|
11181
|
+
dataWithDir._changesDir = changesDir;
|
|
11182
|
+
if (!task.files?.some((f) => f.key === key)) {
|
|
11183
|
+
task.files = task.files ?? [];
|
|
11184
|
+
task.files.push({ path: resultEntry.path, key, validators: resultEntry.validators ?? [] });
|
|
11185
|
+
}
|
|
11186
|
+
let writtenPath;
|
|
11187
|
+
if (engine2?.writeFile) {
|
|
11188
|
+
const result = engine2.writeFile(dataWithDir, task, key, serialized);
|
|
11189
|
+
writtenPath = result.path;
|
|
11146
11190
|
} else {
|
|
11147
|
-
const
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11191
|
+
const dir = dirname(resultEntry.path);
|
|
11192
|
+
mkdirSync(dir, { recursive: true });
|
|
11193
|
+
writeFileSync(resultEntry.path, serialized);
|
|
11194
|
+
writtenPath = resultEntry.path;
|
|
11195
|
+
}
|
|
11196
|
+
const config = options.config ?? { data: dataDir, technology: "html", extensions: [] };
|
|
11197
|
+
const schemaErrors = await validateResultEntry(resultEntry, value, data.schemas, config, "data");
|
|
11198
|
+
const semanticErrors = resultEntry.validators && resultEntry.validators.length > 0 ? await validateResultEntry(
|
|
11199
|
+
{ ...resultEntry, schema: void 0 },
|
|
11200
|
+
writtenPath,
|
|
11201
|
+
data.schemas,
|
|
11202
|
+
config,
|
|
11203
|
+
"file"
|
|
11204
|
+
) : [];
|
|
11205
|
+
const errors = [...schemaErrors, ...semanticErrors];
|
|
11206
|
+
if (errors.length > 0) {
|
|
11207
|
+
validationErrors.push(...errors.map((e) => `${key}: ${e}`));
|
|
11208
|
+
resultEntry.valid = false;
|
|
11209
|
+
resultEntry.error = errors.join("; ");
|
|
11210
|
+
} else {
|
|
11211
|
+
resultEntry.valid = true;
|
|
11212
|
+
delete resultEntry.error;
|
|
11213
|
+
}
|
|
11214
|
+
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11215
|
+
const fileEntry = task.files?.find((f) => f.key === key);
|
|
11216
|
+
if (fileEntry) {
|
|
11217
|
+
fileEntry.validation_result = {
|
|
11218
|
+
file: resultEntry.path,
|
|
11219
|
+
type: key,
|
|
11220
|
+
valid: resultEntry.valid,
|
|
11221
|
+
error: resultEntry.error,
|
|
11222
|
+
last_validated: resultEntry.last_validated,
|
|
11223
|
+
last_passed: resultEntry.valid ? resultEntry.last_validated : void 0,
|
|
11224
|
+
last_failed: !resultEntry.valid ? resultEntry.last_validated : void 0
|
|
11225
|
+
};
|
|
11159
11226
|
}
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
data,
|
|
11166
|
-
response: {
|
|
11167
|
-
stage: data.current_stage ?? "unknown",
|
|
11168
|
-
validation_errors: validationErrors
|
|
11169
|
-
}
|
|
11170
|
-
};
|
|
11171
|
-
}
|
|
11172
|
-
}
|
|
11173
|
-
if (task.result) {
|
|
11174
|
-
const directWrites = [];
|
|
11175
|
-
for (const [key, resultEntry] of Object.entries(task.result)) {
|
|
11176
|
-
if (!resultEntry.path) continue;
|
|
11177
|
-
if (resultEntry.valid !== void 0) continue;
|
|
11178
|
-
if (resultEntry.submission === "direct") continue;
|
|
11179
|
-
if (/\{[a-zA-Z]\w*\}/.test(resultEntry.path)) continue;
|
|
11180
|
-
if (existsSync(resultEntry.path)) {
|
|
11181
|
-
directWrites.push(` \xB7 result \`${key}\` at \`${resultEntry.path}\``);
|
|
11227
|
+
if (resultEntry.flush === "immediate" && writtenPath !== resultEntry.path) {
|
|
11228
|
+
mkdirSync(dirname(resultEntry.path), { recursive: true });
|
|
11229
|
+
renameSync(writtenPath, resultEntry.path);
|
|
11230
|
+
resultEntry.flushed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
11231
|
+
if (fileEntry) fileEntry.flushed_at = resultEntry.flushed_at;
|
|
11182
11232
|
}
|
|
11183
|
-
}
|
|
11184
|
-
|
|
11185
|
-
const
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
}
|
|
11197
|
-
];
|
|
11198
|
-
})
|
|
11199
|
-
);
|
|
11200
|
-
const hint = renderSubmitResultsHint(taskId, hintResults) ?? "";
|
|
11201
|
-
throw new Error(
|
|
11202
|
-
`Cannot mark '${taskId}' as done \u2014 ${directWrites.length} file result(s) were written directly instead of via \`workflow done --data\`:
|
|
11203
|
-
` + directWrites.join("\n") + "\n\n" + hint
|
|
11204
|
-
);
|
|
11233
|
+
} else {
|
|
11234
|
+
const config = options.config ?? { data: dataDir, technology: "html", extensions: [] };
|
|
11235
|
+
const errors = await validateResultEntry(resultEntry, value, data.schemas, config, "data");
|
|
11236
|
+
if (errors.length > 0) {
|
|
11237
|
+
validationErrors.push(...errors.map((e) => `${key}: ${e}`));
|
|
11238
|
+
resultEntry.valid = false;
|
|
11239
|
+
resultEntry.error = errors.join("; ");
|
|
11240
|
+
} else {
|
|
11241
|
+
resultEntry.value = value;
|
|
11242
|
+
resultEntry.valid = true;
|
|
11243
|
+
delete resultEntry.error;
|
|
11244
|
+
}
|
|
11245
|
+
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11205
11246
|
}
|
|
11206
11247
|
}
|
|
11207
|
-
if (
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11213
|
-
|
|
11214
|
-
|
|
11248
|
+
if (validationErrors.length > 0) {
|
|
11249
|
+
writeWorkflowAtomic(filePath, data);
|
|
11250
|
+
return {
|
|
11251
|
+
archived: false,
|
|
11252
|
+
data,
|
|
11253
|
+
response: {
|
|
11254
|
+
stage: data.current_stage ?? "unknown",
|
|
11255
|
+
validation_errors: validationErrors
|
|
11215
11256
|
}
|
|
11257
|
+
};
|
|
11258
|
+
}
|
|
11259
|
+
}
|
|
11260
|
+
if (task.result) {
|
|
11261
|
+
const directWrites = [];
|
|
11262
|
+
for (const [key, resultEntry] of Object.entries(task.result)) {
|
|
11263
|
+
if (!resultEntry.path) continue;
|
|
11264
|
+
if (resultEntry.valid !== void 0) continue;
|
|
11265
|
+
if (resultEntry.submission === "direct") continue;
|
|
11266
|
+
if (/\{[a-zA-Z]\w*\}/.test(resultEntry.path)) continue;
|
|
11267
|
+
if (existsSync(resultEntry.path)) {
|
|
11268
|
+
directWrites.push(` \xB7 result \`${key}\` at \`${resultEntry.path}\``);
|
|
11216
11269
|
}
|
|
11217
11270
|
}
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11271
|
+
if (directWrites.length > 0) {
|
|
11272
|
+
const hintResults = Object.fromEntries(
|
|
11273
|
+
Object.entries(task.result).map(([k, v]) => {
|
|
11274
|
+
const schema = v.schema;
|
|
11275
|
+
return [
|
|
11276
|
+
k,
|
|
11277
|
+
{
|
|
11278
|
+
path: v.path,
|
|
11279
|
+
submission: v.submission,
|
|
11280
|
+
flush: v.flush,
|
|
11281
|
+
$ref: typeof schema?.$ref === "string" ? schema.$ref : void 0,
|
|
11282
|
+
type: typeof schema?.type === "string" ? schema.type : void 0
|
|
11283
|
+
}
|
|
11284
|
+
];
|
|
11285
|
+
})
|
|
11286
|
+
);
|
|
11287
|
+
const hint = renderSubmitResultsHint(taskId, hintResults) ?? "";
|
|
11221
11288
|
throw new Error(
|
|
11222
|
-
`Cannot mark '${taskId}' as done \u2014
|
|
11223
|
-
` +
|
|
11289
|
+
`Cannot mark '${taskId}' as done \u2014 ${directWrites.length} file result(s) were written directly instead of via \`workflow done --data\`:
|
|
11290
|
+
` + directWrites.join("\n") + "\n\n" + hint
|
|
11224
11291
|
);
|
|
11225
11292
|
}
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11293
|
+
}
|
|
11294
|
+
if (task.result) {
|
|
11295
|
+
for (const [, resultEntry] of Object.entries(task.result)) {
|
|
11296
|
+
if (resultEntry.valid !== void 0) continue;
|
|
11297
|
+
if (resultEntry.path) continue;
|
|
11298
|
+
if (resultEntry.schema && typeof resultEntry.schema === "object" && "default" in resultEntry.schema) {
|
|
11299
|
+
resultEntry.value = resultEntry.schema.default;
|
|
11300
|
+
resultEntry.valid = true;
|
|
11301
|
+
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11304
|
+
}
|
|
11305
|
+
const hasUnresolvedPlaceholder = (f) => /\{[a-zA-Z]\w*\}/.test(f.path);
|
|
11306
|
+
const taskFiles = task.files ?? [];
|
|
11307
|
+
if (taskFiles.length > 0 && taskFiles.every((f) => hasUnresolvedPlaceholder(f)) && !taskFiles.some((f) => f.validation_result)) {
|
|
11308
|
+
throw new Error(
|
|
11309
|
+
`Cannot mark '${taskId}' as done \u2014 all file paths have unresolved placeholders and no files were written:
|
|
11310
|
+
` + taskFiles.map((f) => ` \xB7 \`${f.path}\``).join("\n")
|
|
11311
|
+
);
|
|
11312
|
+
}
|
|
11313
|
+
const notWritten = taskFiles.filter((f) => !f.validation_result && !hasUnresolvedPlaceholder(f));
|
|
11314
|
+
if (notWritten.length > 0) {
|
|
11315
|
+
throw new Error(
|
|
11316
|
+
`Cannot mark '${taskId}' as done \u2014 ${notWritten.length} file(s) not yet written:
|
|
11230
11317
|
` + notWritten.map((f) => ` \xB7 file \`${f.key}\` not yet written`).join("\n")
|
|
11318
|
+
);
|
|
11319
|
+
}
|
|
11320
|
+
const failed = (task.files ?? []).filter((f) => f.validation_result?.valid === false);
|
|
11321
|
+
if (failed.length > 0) {
|
|
11322
|
+
throw new Error(
|
|
11323
|
+
`Cannot mark '${taskId}' as done \u2014 ${failed.length} file(s) have errors:
|
|
11324
|
+
` + failed.map((f) => ` \xB7 file \`${f.key}\` has errors: ${f.validation_result?.error ?? "invalid"}`).join("\n")
|
|
11325
|
+
);
|
|
11326
|
+
}
|
|
11327
|
+
if (task.result) {
|
|
11328
|
+
const missingResults = Object.entries(task.result).filter(
|
|
11329
|
+
([, r]) => r.valid === void 0 && r.path !== void 0 && r.required !== false
|
|
11330
|
+
);
|
|
11331
|
+
if (missingResults.length > 0) {
|
|
11332
|
+
throw new Error(
|
|
11333
|
+
`Cannot mark '${taskId}' as done \u2014 ${missingResults.length} result(s) not yet written:
|
|
11334
|
+
` + missingResults.map(([k]) => ` \xB7 result \`${k}\` not yet written`).join("\n")
|
|
11231
11335
|
);
|
|
11232
11336
|
}
|
|
11233
|
-
const
|
|
11234
|
-
|
|
11337
|
+
const missingDataResults = Object.entries(task.result).filter(
|
|
11338
|
+
([, r]) => r.valid === void 0 && r.path === void 0 && r.required !== false
|
|
11339
|
+
);
|
|
11340
|
+
if (missingDataResults.length > 0) {
|
|
11235
11341
|
throw new Error(
|
|
11236
|
-
`Cannot mark '${taskId}' as done \u2014 ${
|
|
11237
|
-
` +
|
|
11342
|
+
`Cannot mark '${taskId}' as done \u2014 ${missingDataResults.length} data result(s) not yet written:
|
|
11343
|
+
` + missingDataResults.map(([k]) => ` \xB7 result \`${k}\` not yet written`).join("\n")
|
|
11238
11344
|
);
|
|
11239
11345
|
}
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
if (missingResults.length > 0) {
|
|
11245
|
-
throw new Error(
|
|
11246
|
-
`Cannot mark '${taskId}' as done \u2014 ${missingResults.length} result(s) not yet written:
|
|
11247
|
-
` + missingResults.map(([k]) => ` \xB7 result \`${k}\` not yet written`).join("\n")
|
|
11248
|
-
);
|
|
11249
|
-
}
|
|
11250
|
-
const missingDataResults = Object.entries(task.result).filter(
|
|
11251
|
-
([, r]) => r.valid === void 0 && r.path === void 0 && r.required !== false
|
|
11252
|
-
);
|
|
11253
|
-
if (missingDataResults.length > 0) {
|
|
11254
|
-
throw new Error(
|
|
11255
|
-
`Cannot mark '${taskId}' as done \u2014 ${missingDataResults.length} data result(s) not yet written:
|
|
11256
|
-
` + missingDataResults.map(([k]) => ` \xB7 result \`${k}\` not yet written`).join("\n")
|
|
11257
|
-
);
|
|
11258
|
-
}
|
|
11259
|
-
const failedResults = Object.entries(task.result).filter(([, r]) => r.valid === false);
|
|
11260
|
-
if (failedResults.length > 0) {
|
|
11261
|
-
throw new Error(
|
|
11262
|
-
`Cannot mark '${taskId}' as done \u2014 ${failedResults.length} result(s) have errors:
|
|
11346
|
+
const failedResults = Object.entries(task.result).filter(([, r]) => r.valid === false);
|
|
11347
|
+
if (failedResults.length > 0) {
|
|
11348
|
+
throw new Error(
|
|
11349
|
+
`Cannot mark '${taskId}' as done \u2014 ${failedResults.length} result(s) have errors:
|
|
11263
11350
|
` + failedResults.map(([k, r]) => ` \xB7 result \`${k}\` has errors: ${r.error ?? "invalid"}`).join("\n")
|
|
11264
|
-
|
|
11265
|
-
}
|
|
11351
|
+
);
|
|
11266
11352
|
}
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
}
|
|
11284
|
-
}
|
|
11353
|
+
}
|
|
11354
|
+
task.status = "done";
|
|
11355
|
+
if (!task.started_at) task.started_at = timestamp();
|
|
11356
|
+
task.completed_at = timestamp();
|
|
11357
|
+
if (options?.summary) task.summary = options.summary;
|
|
11358
|
+
if (loaded) {
|
|
11359
|
+
const stepName = task.step;
|
|
11360
|
+
if (stepName) {
|
|
11361
|
+
data.stage_loaded = data.stage_loaded ?? {};
|
|
11362
|
+
if (!data.stage_loaded[stepName]) {
|
|
11363
|
+
data.stage_loaded[stepName] = {
|
|
11364
|
+
task_file: loaded.task_file ?? "",
|
|
11365
|
+
rules: loaded.rules ?? [],
|
|
11366
|
+
blueprints: loaded.blueprints ?? [],
|
|
11367
|
+
config_rules: loaded.config_rules ?? [],
|
|
11368
|
+
config_instructions: loaded.config_instructions ?? [],
|
|
11369
|
+
...loaded.isolate ? { isolate: true } : {}
|
|
11370
|
+
};
|
|
11285
11371
|
}
|
|
11286
11372
|
}
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11373
|
+
}
|
|
11374
|
+
const engine = resolveWorkflowEngine();
|
|
11375
|
+
const hasGroupedStages = data.stages && typeof data.stages === "object" && !Array.isArray(data.stages);
|
|
11376
|
+
if (hasGroupedStages && data.current_stage) {
|
|
11377
|
+
const stages = data.stages;
|
|
11378
|
+
const currentStage = data.current_stage;
|
|
11379
|
+
const configForExpansion = options?.config ?? { data: dataDir, technology: "html", extensions: [] };
|
|
11380
|
+
const getMissingStageSteps = (stageName) => {
|
|
11381
|
+
const stageDef = stages[stageName];
|
|
11382
|
+
if (!stageDef?.steps) return [];
|
|
11383
|
+
const stageHasTrackedSteps = data.tasks.some((t) => t.stage === stageName && typeof t.step === "string" && t.step.length > 0) || stageDef.steps.some((step) => Boolean(data.stage_loaded?.[step]));
|
|
11384
|
+
if (!stageHasTrackedSteps) return [];
|
|
11385
|
+
return stageDef.steps.filter((step) => !data.tasks.some((t) => t.stage === stageName && t.step === step));
|
|
11386
|
+
};
|
|
11387
|
+
const expandStageIfNeeded = async (stageName) => {
|
|
11388
|
+
const missingBefore = getMissingStageSteps(stageName);
|
|
11389
|
+
if (missingBefore.length === 0) return {};
|
|
11390
|
+
if (!data.stage_loaded) return {};
|
|
11391
|
+
const stageLoaded = data.stage_loaded;
|
|
11392
|
+
const scopeEnvMap = data.env_map ?? {};
|
|
11393
|
+
const stageDef = stages[stageName];
|
|
11394
|
+
if (!stageDef) return {};
|
|
11395
|
+
let expandedFromScope;
|
|
11396
|
+
let resolverErrors;
|
|
11397
|
+
const resolveResult = await resolveStageTaskParams(stageLoaded, stageDef, data.params ?? {}, configForExpansion);
|
|
11398
|
+
data.params = resolveResult.params;
|
|
11399
|
+
if (Object.keys(resolveResult.unresolved).length > 0) {
|
|
11400
|
+
resolverErrors = Object.fromEntries(
|
|
11401
|
+
Object.entries(resolveResult.unresolved).map(([k, v]) => [k, { input: v.input, error: v.error }])
|
|
11315
11402
|
);
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11403
|
+
}
|
|
11404
|
+
const expanded = await expandTasksFromParams(
|
|
11405
|
+
stageLoaded,
|
|
11406
|
+
{ [stageName]: stageDef },
|
|
11407
|
+
resolveResult.params,
|
|
11408
|
+
data.tasks,
|
|
11409
|
+
scopeEnvMap,
|
|
11410
|
+
data.scope,
|
|
11411
|
+
options?.config
|
|
11412
|
+
);
|
|
11413
|
+
if (expanded.length > 0) {
|
|
11414
|
+
data.tasks.push(...expanded);
|
|
11415
|
+
const pluginSkillSources = options?.config ? resolvePluginSkillSources({ env: process.env, home: homedir(), config: options.config })?.sources ?? [] : [];
|
|
11416
|
+
const skillsRoot = deriveSkillsRootFromTaskFile(expanded[0]?.task_file) ?? (expanded[0]?.task_file ? dirname(expanded[0].task_file) : void 0);
|
|
11417
|
+
if (skillsRoot) {
|
|
11418
|
+
const mergedSchemas = resolveSchemasForTasks(
|
|
11419
|
+
expanded,
|
|
11420
|
+
skillsRoot,
|
|
11421
|
+
{ ...data.schemas ?? {} },
|
|
11422
|
+
pluginSkillSources
|
|
11320
11423
|
);
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
stageLoaded,
|
|
11324
|
-
{ [stageName]: stageDef },
|
|
11325
|
-
resolveResult.params,
|
|
11326
|
-
data.tasks,
|
|
11327
|
-
scopeEnvMap,
|
|
11328
|
-
data.scope,
|
|
11329
|
-
options?.config
|
|
11330
|
-
);
|
|
11331
|
-
if (expanded.length > 0) {
|
|
11332
|
-
data.tasks.push(...expanded);
|
|
11333
|
-
const skillsRoot = deriveSkillsRootFromTaskFile(expanded[0]?.task_file);
|
|
11334
|
-
if (skillsRoot) {
|
|
11335
|
-
const mergedSchemas = resolveSchemasForTasks(expanded, skillsRoot, { ...data.schemas ?? {} });
|
|
11336
|
-
if (Object.keys(mergedSchemas).length > 0) {
|
|
11337
|
-
data.schemas = mergedSchemas;
|
|
11338
|
-
}
|
|
11424
|
+
if (Object.keys(mergedSchemas).length > 0) {
|
|
11425
|
+
data.schemas = mergedSchemas;
|
|
11339
11426
|
}
|
|
11340
|
-
expandedFromScope = expanded.map((t) => ({
|
|
11341
|
-
id: t.id,
|
|
11342
|
-
step: t.step,
|
|
11343
|
-
stage: t.stage,
|
|
11344
|
-
title: t.title
|
|
11345
|
-
}));
|
|
11346
11427
|
}
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11428
|
+
expandedFromScope = expanded.map((t) => ({
|
|
11429
|
+
id: t.id,
|
|
11430
|
+
step: t.step,
|
|
11431
|
+
stage: t.stage,
|
|
11432
|
+
title: t.title
|
|
11433
|
+
}));
|
|
11434
|
+
}
|
|
11435
|
+
return { expandedFromScope, resolverErrors };
|
|
11436
|
+
};
|
|
11437
|
+
const formatResolverErrors = (resolverErrors) => {
|
|
11438
|
+
if (!resolverErrors || Object.keys(resolverErrors).length === 0) return "";
|
|
11439
|
+
const details = Object.entries(resolverErrors).map(([key, value]) => ` \xB7 ${key}: ${value.error ?? "unresolved"}`).join("\n");
|
|
11440
|
+
return `
|
|
11353
11441
|
Resolver errors:
|
|
11354
11442
|
${details}`;
|
|
11443
|
+
};
|
|
11444
|
+
const nextStepInStage = getNextStep(currentStage, task.step ?? "", data.tasks);
|
|
11445
|
+
const stageTasks = data.tasks.filter((t) => t.stage === currentStage);
|
|
11446
|
+
const stageDone = stageTasks.filter((t) => t.status === "done").length;
|
|
11447
|
+
const stageTotal = stageTasks.length;
|
|
11448
|
+
let response;
|
|
11449
|
+
if (nextStepInStage) {
|
|
11450
|
+
const nextTask = data.tasks.find((t) => t.step === nextStepInStage && t.status === "pending");
|
|
11451
|
+
if (nextTask) {
|
|
11452
|
+
nextTask.status = "in-progress";
|
|
11453
|
+
nextTask.started_at = timestamp();
|
|
11454
|
+
}
|
|
11455
|
+
response = {
|
|
11456
|
+
stage: currentStage,
|
|
11457
|
+
step_completed: task.step,
|
|
11458
|
+
next_step: nextStepInStage,
|
|
11459
|
+
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11460
|
+
stage_complete: false
|
|
11461
|
+
};
|
|
11462
|
+
writeWorkflowAtomic(filePath, data);
|
|
11463
|
+
return { archived: false, data, response };
|
|
11464
|
+
}
|
|
11465
|
+
const currentStageExpansion = await expandStageIfNeeded(currentStage);
|
|
11466
|
+
const nextTaskInCurrentStage = data.tasks.find((t) => t.stage === currentStage && t.status !== "done");
|
|
11467
|
+
if (nextTaskInCurrentStage) {
|
|
11468
|
+
if (nextTaskInCurrentStage.status === "pending") {
|
|
11469
|
+
nextTaskInCurrentStage.status = "in-progress";
|
|
11470
|
+
nextTaskInCurrentStage.started_at = timestamp();
|
|
11471
|
+
}
|
|
11472
|
+
response = {
|
|
11473
|
+
stage: currentStage,
|
|
11474
|
+
step_completed: task.step,
|
|
11475
|
+
next_step: nextTaskInCurrentStage.step ?? null,
|
|
11476
|
+
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11477
|
+
stage_complete: false,
|
|
11478
|
+
...currentStageExpansion.expandedFromScope && { expanded_tasks: currentStageExpansion.expandedFromScope },
|
|
11479
|
+
...currentStageExpansion.resolverErrors && { resolver_errors: currentStageExpansion.resolverErrors }
|
|
11355
11480
|
};
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11481
|
+
writeWorkflowAtomic(filePath, data);
|
|
11482
|
+
return { archived: false, data, response };
|
|
11483
|
+
}
|
|
11484
|
+
const missingCurrentSteps = getMissingStageSteps(currentStage);
|
|
11485
|
+
if (missingCurrentSteps.length > 0) {
|
|
11486
|
+
throw new Error(
|
|
11487
|
+
`Cannot complete stage '${currentStage}' \u2014 step(s) not materialized: ${missingCurrentSteps.join(", ")}` + formatResolverErrors(currentStageExpansion.resolverErrors)
|
|
11488
|
+
);
|
|
11489
|
+
}
|
|
11490
|
+
const scopeUpdate = collectStageResults(stageTasks);
|
|
11491
|
+
if (Object.keys(scopeUpdate).length > 0) {
|
|
11492
|
+
data.scope = { ...data.scope ?? {}, ...scopeUpdate };
|
|
11493
|
+
}
|
|
11494
|
+
let fromStage = currentStage;
|
|
11495
|
+
let nextStage = getNextStage(fromStage, stages);
|
|
11496
|
+
while (nextStage) {
|
|
11497
|
+
if (engine) {
|
|
11498
|
+
const transitionResult = await engine.onTransition(fromStage, nextStage, { data });
|
|
11499
|
+
if (transitionResult.archive) {
|
|
11500
|
+
data.current_stage = "done";
|
|
11501
|
+
const transitionResponse = {
|
|
11502
|
+
stage: "done",
|
|
11503
|
+
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11504
|
+
stage_complete: true,
|
|
11505
|
+
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate }
|
|
11506
|
+
};
|
|
11507
|
+
const held2 = holdForAfter(filePath, data, options?.after, transitionResponse);
|
|
11508
|
+
if (held2) return held2;
|
|
11509
|
+
archiveAndCascade(dataDir, name, data);
|
|
11510
|
+
return { archived: true, data, response: transitionResponse };
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11513
|
+
const unfulfilledParams = checkStageParams(nextStage, stages, data.params ?? {});
|
|
11514
|
+
if (unfulfilledParams) {
|
|
11515
|
+
data.current_stage = nextStage;
|
|
11516
|
+
const promptState = {};
|
|
11517
|
+
const interpolated = {};
|
|
11518
|
+
for (const [key, param] of Object.entries(unfulfilledParams)) {
|
|
11519
|
+
interpolated[key] = { ...param, prompt: interpolatePrompt(param.prompt, promptState) };
|
|
11366
11520
|
}
|
|
11367
|
-
response = {
|
|
11368
|
-
stage: currentStage,
|
|
11369
|
-
step_completed: task.step,
|
|
11370
|
-
next_step: nextStepInStage,
|
|
11371
|
-
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11372
|
-
stage_complete: false
|
|
11373
|
-
};
|
|
11374
11521
|
writeWorkflowAtomic(filePath, data);
|
|
11375
|
-
return { archived: false, data, response };
|
|
11522
|
+
return { archived: false, data, response: { stage: nextStage, waiting_for: interpolated } };
|
|
11376
11523
|
}
|
|
11377
|
-
const
|
|
11378
|
-
const
|
|
11379
|
-
if (
|
|
11380
|
-
if (
|
|
11381
|
-
|
|
11382
|
-
|
|
11524
|
+
const { expandedFromScope, resolverErrors } = await expandStageIfNeeded(nextStage);
|
|
11525
|
+
const nextStepInNewStage = data.tasks.find((t) => t.stage === nextStage && t.status !== "done");
|
|
11526
|
+
if (nextStepInNewStage) {
|
|
11527
|
+
if (nextStepInNewStage.status === "pending") {
|
|
11528
|
+
nextStepInNewStage.status = "in-progress";
|
|
11529
|
+
nextStepInNewStage.started_at = timestamp();
|
|
11383
11530
|
}
|
|
11531
|
+
data.current_stage = nextStage;
|
|
11384
11532
|
response = {
|
|
11385
|
-
stage:
|
|
11386
|
-
|
|
11387
|
-
|
|
11533
|
+
stage: nextStage,
|
|
11534
|
+
transition_from: currentStage,
|
|
11535
|
+
next_stage: nextStage,
|
|
11536
|
+
next_step: nextStepInNewStage.step ?? null,
|
|
11388
11537
|
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11389
|
-
stage_complete:
|
|
11390
|
-
...
|
|
11391
|
-
...
|
|
11538
|
+
stage_complete: true,
|
|
11539
|
+
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate },
|
|
11540
|
+
...expandedFromScope && { expanded_tasks: expandedFromScope },
|
|
11541
|
+
...resolverErrors && { resolver_errors: resolverErrors }
|
|
11392
11542
|
};
|
|
11393
11543
|
writeWorkflowAtomic(filePath, data);
|
|
11394
11544
|
return { archived: false, data, response };
|
|
11395
11545
|
}
|
|
11396
|
-
const
|
|
11397
|
-
if (
|
|
11546
|
+
const missingNextSteps = getMissingStageSteps(nextStage);
|
|
11547
|
+
if (missingNextSteps.length > 0) {
|
|
11398
11548
|
throw new Error(
|
|
11399
|
-
`Cannot
|
|
11549
|
+
`Cannot enter stage '${nextStage}' \u2014 step(s) not materialized: ${missingNextSteps.join(", ")}` + formatResolverErrors(resolverErrors)
|
|
11400
11550
|
);
|
|
11401
11551
|
}
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
const interpolated = {};
|
|
11416
|
-
for (const [key, param] of Object.entries(transitionResult.requires)) {
|
|
11417
|
-
interpolated[key] = { ...param, prompt: interpolatePrompt(param.prompt, promptState) };
|
|
11418
|
-
}
|
|
11419
|
-
data.current_stage = fromStage;
|
|
11420
|
-
writeWorkflowAtomic(filePath, data);
|
|
11421
|
-
return { archived: false, data, response: { stage: fromStage, waiting_for: interpolated } };
|
|
11422
|
-
}
|
|
11423
|
-
if (transitionResult.archive) {
|
|
11424
|
-
data.current_stage = "done";
|
|
11425
|
-
const transitionResponse = {
|
|
11426
|
-
stage: "done",
|
|
11427
|
-
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11428
|
-
stage_complete: true,
|
|
11429
|
-
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate }
|
|
11430
|
-
};
|
|
11431
|
-
const held2 = holdForAfter(filePath, data, options?.after, transitionResponse);
|
|
11432
|
-
if (held2) return held2;
|
|
11433
|
-
archiveAndCascade(dataDir, name, data);
|
|
11434
|
-
return { archived: true, data, response: transitionResponse };
|
|
11435
|
-
}
|
|
11436
|
-
}
|
|
11437
|
-
const unfulfilledParams = checkStageParams(nextStage, stages, data.params ?? {});
|
|
11438
|
-
if (unfulfilledParams) {
|
|
11439
|
-
data.current_stage = nextStage;
|
|
11440
|
-
const promptState = {
|
|
11441
|
-
branch: data.worktree_branch
|
|
11442
|
-
};
|
|
11443
|
-
const interpolated = {};
|
|
11444
|
-
for (const [key, param] of Object.entries(unfulfilledParams)) {
|
|
11445
|
-
interpolated[key] = { ...param, prompt: interpolatePrompt(param.prompt, promptState) };
|
|
11446
|
-
}
|
|
11447
|
-
writeWorkflowAtomic(filePath, data);
|
|
11448
|
-
return { archived: false, data, response: { stage: nextStage, waiting_for: interpolated } };
|
|
11449
|
-
}
|
|
11450
|
-
const { expandedFromScope, resolverErrors } = await expandStageIfNeeded(nextStage);
|
|
11451
|
-
const nextStepInNewStage = data.tasks.find((t) => t.stage === nextStage && t.status !== "done");
|
|
11452
|
-
if (nextStepInNewStage) {
|
|
11453
|
-
if (nextStepInNewStage.status === "pending") {
|
|
11454
|
-
nextStepInNewStage.status = "in-progress";
|
|
11455
|
-
nextStepInNewStage.started_at = timestamp();
|
|
11456
|
-
}
|
|
11457
|
-
data.current_stage = nextStage;
|
|
11458
|
-
response = {
|
|
11459
|
-
stage: nextStage,
|
|
11460
|
-
transition_from: currentStage,
|
|
11461
|
-
next_stage: nextStage,
|
|
11462
|
-
next_step: nextStepInNewStage.step ?? null,
|
|
11463
|
-
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11464
|
-
stage_complete: true,
|
|
11465
|
-
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate },
|
|
11466
|
-
...expandedFromScope && { expanded_tasks: expandedFromScope },
|
|
11467
|
-
...resolverErrors && { resolver_errors: resolverErrors }
|
|
11468
|
-
};
|
|
11469
|
-
writeWorkflowAtomic(filePath, data);
|
|
11470
|
-
return { archived: false, data, response };
|
|
11471
|
-
}
|
|
11472
|
-
const missingNextSteps = getMissingStageSteps(nextStage);
|
|
11473
|
-
if (missingNextSteps.length > 0) {
|
|
11474
|
-
throw new Error(
|
|
11475
|
-
`Cannot enter stage '${nextStage}' \u2014 step(s) not materialized: ${missingNextSteps.join(", ")}` + formatResolverErrors(resolverErrors)
|
|
11476
|
-
);
|
|
11477
|
-
}
|
|
11478
|
-
fromStage = nextStage;
|
|
11479
|
-
nextStage = getNextStage(fromStage, stages);
|
|
11552
|
+
fromStage = nextStage;
|
|
11553
|
+
nextStage = getNextStage(fromStage, stages);
|
|
11554
|
+
}
|
|
11555
|
+
data.current_stage = "done";
|
|
11556
|
+
const completionResponse = {
|
|
11557
|
+
stage: "done",
|
|
11558
|
+
stage_progress: `${stageDone}/${stageTotal}`,
|
|
11559
|
+
stage_complete: true,
|
|
11560
|
+
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate }
|
|
11561
|
+
};
|
|
11562
|
+
if (engine) {
|
|
11563
|
+
if (engine.flush) {
|
|
11564
|
+
await engine.flush(data, data.tasks);
|
|
11480
11565
|
}
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
...Object.keys(scopeUpdate).length > 0 && { scope_update: scopeUpdate }
|
|
11487
|
-
};
|
|
11488
|
-
if (engine) {
|
|
11489
|
-
if (engine.flush) {
|
|
11490
|
-
await engine.flush(data, data.tasks);
|
|
11491
|
-
}
|
|
11492
|
-
const doneResult = engine.done(data);
|
|
11493
|
-
if (doneResult.archive) {
|
|
11494
|
-
const held2 = holdForAfter(filePath, data, options?.after, completionResponse);
|
|
11495
|
-
if (held2) return held2;
|
|
11496
|
-
archiveAndCascade(dataDir, name, data);
|
|
11497
|
-
return { archived: true, data, response: completionResponse };
|
|
11498
|
-
}
|
|
11566
|
+
{
|
|
11567
|
+
const held2 = holdForAfter(filePath, data, options?.after, completionResponse);
|
|
11568
|
+
if (held2) return held2;
|
|
11569
|
+
archiveAndCascade(dataDir, name, data);
|
|
11570
|
+
return { archived: true, data, response: completionResponse };
|
|
11499
11571
|
}
|
|
11500
|
-
const held = holdForAfter(filePath, data, options?.after, completionResponse);
|
|
11501
|
-
if (held) return held;
|
|
11502
|
-
writeWorkflowAtomic(filePath, data);
|
|
11503
|
-
return { archived: false, data, response: completionResponse };
|
|
11504
11572
|
}
|
|
11505
|
-
|
|
11506
|
-
|
|
11573
|
+
const held = holdForAfter(filePath, data, options?.after, completionResponse);
|
|
11574
|
+
if (held) return held;
|
|
11575
|
+
writeWorkflowAtomic(filePath, data);
|
|
11576
|
+
return { archived: false, data, response: completionResponse };
|
|
11577
|
+
}
|
|
11578
|
+
throw new Error("Workflow has no grouped stages \u2014 all workflows require stages in frontmatter");
|
|
11507
11579
|
}
|
|
11508
11580
|
function workflowGetFile(dataDir, name, taskId, key) {
|
|
11509
11581
|
const changesDir = resolve(dataDir, "workflows", "changes", name);
|
|
@@ -11524,7 +11596,7 @@ function workflowGetFile(dataDir, name, taskId, key) {
|
|
|
11524
11596
|
const validKeys = [...fileKeys, ...resultKeys].join(", ");
|
|
11525
11597
|
throw new Error(`Unknown key '${key}' for task '${taskId}'. Valid keys: ${validKeys}`);
|
|
11526
11598
|
}
|
|
11527
|
-
const engine = resolveWorkflowEngine(
|
|
11599
|
+
const engine = resolveWorkflowEngine();
|
|
11528
11600
|
const dataWithDir = data;
|
|
11529
11601
|
dataWithDir._changesDir = changesDir;
|
|
11530
11602
|
const staged_path = engine?.getStagedPath ? engine.getStagedPath(dataWithDir, task, key) : fileEntry.path;
|
|
@@ -11554,112 +11626,110 @@ function collectStageResults(stageTasks) {
|
|
|
11554
11626
|
async function workflowResult(dataDir, name, taskId, key, content, config) {
|
|
11555
11627
|
const changesDir = resolve(dataDir, "workflows", "changes", name);
|
|
11556
11628
|
const filePath = resolve(changesDir, "tasks.yml");
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
if (
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
);
|
|
11578
|
-
}
|
|
11579
|
-
const engine = resolveWorkflowEngine(data);
|
|
11580
|
-
const dataWithDir = data;
|
|
11581
|
-
dataWithDir._changesDir = changesDir;
|
|
11582
|
-
if (!task.files?.some((f) => f.key === key)) {
|
|
11583
|
-
task.files = task.files ?? [];
|
|
11584
|
-
task.files.push({
|
|
11585
|
-
path: resultEntry.path,
|
|
11586
|
-
key,
|
|
11587
|
-
validators: resultEntry.validators ?? []
|
|
11588
|
-
});
|
|
11589
|
-
}
|
|
11590
|
-
let writtenPath;
|
|
11591
|
-
if (engine?.getStagedPath) {
|
|
11592
|
-
const staged = engine.getStagedPath(dataWithDir, task, key);
|
|
11593
|
-
writtenPath = existsSync(staged) ? staged : resultEntry.path;
|
|
11594
|
-
} else {
|
|
11595
|
-
writtenPath = resultEntry.path;
|
|
11596
|
-
}
|
|
11597
|
-
if (!existsSync(writtenPath)) {
|
|
11598
|
-
throw new Error(`External file not found at path: ${writtenPath}`);
|
|
11599
|
-
}
|
|
11600
|
-
const validationErrors2 = await validateResultEntry(resultEntry, writtenPath, data.schemas, config, "file");
|
|
11601
|
-
if (validationErrors2.length > 0) {
|
|
11602
|
-
errors.push(...validationErrors2);
|
|
11603
|
-
resultEntry.valid = false;
|
|
11604
|
-
resultEntry.error = validationErrors2.join("; ");
|
|
11605
|
-
} else {
|
|
11606
|
-
resultEntry.valid = true;
|
|
11607
|
-
delete resultEntry.error;
|
|
11608
|
-
}
|
|
11609
|
-
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11610
|
-
const fileEntry = task.files?.find((f) => f.key === key);
|
|
11611
|
-
if (fileEntry) {
|
|
11612
|
-
fileEntry.validation_result = {
|
|
11613
|
-
file: resultEntry.path,
|
|
11614
|
-
type: key,
|
|
11615
|
-
valid: resultEntry.valid,
|
|
11616
|
-
error: resultEntry.error,
|
|
11617
|
-
last_validated: resultEntry.last_validated,
|
|
11618
|
-
last_passed: resultEntry.valid ? resultEntry.last_validated : void 0,
|
|
11619
|
-
last_failed: !resultEntry.valid ? resultEntry.last_validated : void 0
|
|
11620
|
-
};
|
|
11621
|
-
}
|
|
11622
|
-
if (resultEntry.flush === "immediate" && writtenPath !== resultEntry.path) {
|
|
11623
|
-
mkdirSync(dirname(resultEntry.path), { recursive: true });
|
|
11624
|
-
renameSync(writtenPath, resultEntry.path);
|
|
11625
|
-
resultEntry.flushed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
11626
|
-
if (fileEntry) fileEntry.flushed_at = resultEntry.flushed_at;
|
|
11627
|
-
writtenPath = resultEntry.path;
|
|
11628
|
-
}
|
|
11629
|
-
writeWorkflowAtomic(filePath, data);
|
|
11630
|
-
return {
|
|
11631
|
-
valid: errors.length === 0,
|
|
11632
|
-
errors,
|
|
11633
|
-
file_path: writtenPath
|
|
11634
|
-
};
|
|
11629
|
+
const data = readWorkflow(filePath);
|
|
11630
|
+
const task = data.tasks.find((t) => t.id === taskId);
|
|
11631
|
+
if (!task) {
|
|
11632
|
+
throw new Error(`Task not found: ${taskId} (available: ${data.tasks.map((t) => t.id).join(", ")})`);
|
|
11633
|
+
}
|
|
11634
|
+
if (!task.result) {
|
|
11635
|
+
throw new Error(`Task '${taskId}' has no result declarations`);
|
|
11636
|
+
}
|
|
11637
|
+
const resultEntry = task.result[key];
|
|
11638
|
+
if (!resultEntry) {
|
|
11639
|
+
const validKeys = Object.keys(task.result).join(", ");
|
|
11640
|
+
throw new Error(`Unknown result key '${key}' for task '${taskId}'. Valid keys: ${validKeys}`);
|
|
11641
|
+
}
|
|
11642
|
+
const isFileResult = !!resultEntry.path;
|
|
11643
|
+
const errors = [];
|
|
11644
|
+
if (isFileResult) {
|
|
11645
|
+
if (resultEntry.submission !== "direct") {
|
|
11646
|
+
throw new Error(
|
|
11647
|
+
`Result '${key}' is not declared as \`submission: direct\`. Submit file results via \`workflow done --data '{"` + key + "\": ...}'` instead."
|
|
11648
|
+
);
|
|
11635
11649
|
}
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11650
|
+
const engine = resolveWorkflowEngine();
|
|
11651
|
+
const dataWithDir = data;
|
|
11652
|
+
dataWithDir._changesDir = changesDir;
|
|
11653
|
+
if (!task.files?.some((f) => f.key === key)) {
|
|
11654
|
+
task.files = task.files ?? [];
|
|
11655
|
+
task.files.push({
|
|
11656
|
+
path: resultEntry.path,
|
|
11657
|
+
key,
|
|
11658
|
+
validators: resultEntry.validators ?? []
|
|
11659
|
+
});
|
|
11660
|
+
}
|
|
11661
|
+
let writtenPath;
|
|
11662
|
+
if (engine?.getStagedPath) {
|
|
11663
|
+
const staged = engine.getStagedPath(dataWithDir, task, key);
|
|
11664
|
+
writtenPath = existsSync(staged) ? staged : resultEntry.path;
|
|
11643
11665
|
} else {
|
|
11644
|
-
|
|
11666
|
+
writtenPath = resultEntry.path;
|
|
11645
11667
|
}
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11668
|
+
if (!existsSync(writtenPath)) {
|
|
11669
|
+
throw new Error(`External file not found at path: ${writtenPath}`);
|
|
11670
|
+
}
|
|
11671
|
+
const validationErrors2 = await validateResultEntry(resultEntry, writtenPath, data.schemas, config, "file");
|
|
11672
|
+
if (validationErrors2.length > 0) {
|
|
11673
|
+
errors.push(...validationErrors2);
|
|
11649
11674
|
resultEntry.valid = false;
|
|
11650
|
-
resultEntry.error =
|
|
11675
|
+
resultEntry.error = validationErrors2.join("; ");
|
|
11651
11676
|
} else {
|
|
11652
|
-
resultEntry.value = dataValue;
|
|
11653
11677
|
resultEntry.valid = true;
|
|
11654
11678
|
delete resultEntry.error;
|
|
11655
11679
|
}
|
|
11656
11680
|
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11681
|
+
const fileEntry = task.files?.find((f) => f.key === key);
|
|
11682
|
+
if (fileEntry) {
|
|
11683
|
+
fileEntry.validation_result = {
|
|
11684
|
+
file: resultEntry.path,
|
|
11685
|
+
type: key,
|
|
11686
|
+
valid: resultEntry.valid,
|
|
11687
|
+
error: resultEntry.error,
|
|
11688
|
+
last_validated: resultEntry.last_validated,
|
|
11689
|
+
last_passed: resultEntry.valid ? resultEntry.last_validated : void 0,
|
|
11690
|
+
last_failed: !resultEntry.valid ? resultEntry.last_validated : void 0
|
|
11691
|
+
};
|
|
11692
|
+
}
|
|
11693
|
+
if (resultEntry.flush === "immediate" && writtenPath !== resultEntry.path) {
|
|
11694
|
+
mkdirSync(dirname(resultEntry.path), { recursive: true });
|
|
11695
|
+
renameSync(writtenPath, resultEntry.path);
|
|
11696
|
+
resultEntry.flushed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
11697
|
+
if (fileEntry) fileEntry.flushed_at = resultEntry.flushed_at;
|
|
11698
|
+
writtenPath = resultEntry.path;
|
|
11699
|
+
}
|
|
11657
11700
|
writeWorkflowAtomic(filePath, data);
|
|
11658
11701
|
return {
|
|
11659
11702
|
valid: errors.length === 0,
|
|
11660
|
-
errors
|
|
11703
|
+
errors,
|
|
11704
|
+
file_path: writtenPath
|
|
11661
11705
|
};
|
|
11662
|
-
}
|
|
11706
|
+
}
|
|
11707
|
+
let dataValue;
|
|
11708
|
+
if (typeof content === "string") {
|
|
11709
|
+
try {
|
|
11710
|
+
dataValue = JSON.parse(content);
|
|
11711
|
+
} catch {
|
|
11712
|
+
throw new Error(`Data result '${key}' requires valid JSON, got: ${content.slice(0, 100)}`);
|
|
11713
|
+
}
|
|
11714
|
+
} else {
|
|
11715
|
+
dataValue = content;
|
|
11716
|
+
}
|
|
11717
|
+
const validationErrors = await validateResultEntry(resultEntry, dataValue, data.schemas, config, "data");
|
|
11718
|
+
if (validationErrors.length > 0) {
|
|
11719
|
+
errors.push(...validationErrors);
|
|
11720
|
+
resultEntry.valid = false;
|
|
11721
|
+
resultEntry.error = validationErrors.join("; ");
|
|
11722
|
+
} else {
|
|
11723
|
+
resultEntry.value = dataValue;
|
|
11724
|
+
resultEntry.valid = true;
|
|
11725
|
+
delete resultEntry.error;
|
|
11726
|
+
}
|
|
11727
|
+
resultEntry.last_validated = (/* @__PURE__ */ new Date()).toISOString();
|
|
11728
|
+
writeWorkflowAtomic(filePath, data);
|
|
11729
|
+
return {
|
|
11730
|
+
valid: errors.length === 0,
|
|
11731
|
+
errors
|
|
11732
|
+
};
|
|
11663
11733
|
}
|
|
11664
11734
|
async function validateResultEntry(entry, content, schemas, config, mode) {
|
|
11665
11735
|
const errors = [];
|
|
@@ -11856,20 +11926,30 @@ function register2(workflow) {
|
|
|
11856
11926
|
|
|
11857
11927
|
// src/cli/workflow-discovery.ts
|
|
11858
11928
|
init_esm4();
|
|
11859
|
-
function
|
|
11929
|
+
function pluginSources2(sources) {
|
|
11930
|
+
return (sources ?? []).filter((s) => s.origin === "plugin");
|
|
11931
|
+
}
|
|
11932
|
+
function resolveWorkflowFile(workflowId, agentsDir, sources) {
|
|
11860
11933
|
const matches = globSync(`skills/**/workflows/${workflowId}.md`, { cwd: agentsDir, absolute: true });
|
|
11861
|
-
if (matches.length
|
|
11862
|
-
|
|
11934
|
+
if (matches.length > 0) return matches[0];
|
|
11935
|
+
for (const source2 of pluginSources2(sources)) {
|
|
11936
|
+
const found = globSync(`**/workflows/${workflowId}.md`, { cwd: source2.root, absolute: true });
|
|
11937
|
+
if (found.length > 0) return found[0];
|
|
11863
11938
|
}
|
|
11864
|
-
|
|
11939
|
+
throw new Error(`Workflow file not found for "${workflowId}". No match for skills/**/workflows/${workflowId}.md`);
|
|
11865
11940
|
}
|
|
11866
|
-
function listWorkflowDefinitions(agentsDir) {
|
|
11941
|
+
function listWorkflowDefinitions(agentsDir, sources) {
|
|
11867
11942
|
const matches = globSync(`skills/**/workflows/*.md`, { cwd: agentsDir, absolute: true });
|
|
11868
11943
|
const ids = matches.map((p) => basename(p, ".md"));
|
|
11944
|
+
for (const source2 of pluginSources2(sources)) {
|
|
11945
|
+
for (const p of globSync(`**/workflows/*.md`, { cwd: source2.root, absolute: true })) {
|
|
11946
|
+
ids.push(basename(p, ".md"));
|
|
11947
|
+
}
|
|
11948
|
+
}
|
|
11869
11949
|
return Array.from(new Set(ids)).sort();
|
|
11870
11950
|
}
|
|
11871
|
-
function loadWorkflowDefinition(workflowId, agentsDir) {
|
|
11872
|
-
const file = resolveWorkflowFile(workflowId, agentsDir);
|
|
11951
|
+
function loadWorkflowDefinition(workflowId, agentsDir, sources) {
|
|
11952
|
+
const file = resolveWorkflowFile(workflowId, agentsDir, sources);
|
|
11873
11953
|
const raw = readFileSync(file, "utf8");
|
|
11874
11954
|
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
11875
11955
|
if (!fmMatch) {
|
|
@@ -11936,8 +12016,9 @@ async function runWorkflowCreate(opts, config) {
|
|
|
11936
12016
|
const rawConfig = configPath ? load(readFileSync(configPath, "utf-8")) ?? {} : {};
|
|
11937
12017
|
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
11938
12018
|
const agentsDir = resolveSkillsRoot(configDir);
|
|
11939
|
-
const
|
|
11940
|
-
const
|
|
12019
|
+
const sources = resolveSkillSources(configDir);
|
|
12020
|
+
const workflowFilePath = resolveWorkflowFile(opts.workflow, agentsDir, sources);
|
|
12021
|
+
const resolved = await resolveAllStages(workflowFilePath, config, rawConfig, agentsDir, sources);
|
|
11941
12022
|
const wfFm = parseFrontmatter(workflowFilePath);
|
|
11942
12023
|
const wfParamsRaw = wfFm?.params;
|
|
11943
12024
|
const wfParams = wfParamsRaw ? { ...wfParamsRaw } : Object.values(resolved.expected_params).some((p) => p.resolve) ? {} : void 0;
|
|
@@ -12007,7 +12088,7 @@ async function runWorkflowCreate(opts, config) {
|
|
|
12007
12088
|
const skillsRoot = resolve(agentsDir, "skills");
|
|
12008
12089
|
for (const [key, decl] of Object.entries(resultDeclProperties)) {
|
|
12009
12090
|
if (decl.$ref && firstResult[key]) {
|
|
12010
|
-
const { typeName, schema } = resolveSchemaRef(decl.$ref, firstResolved.task_file, skillsRoot);
|
|
12091
|
+
const { typeName, schema } = resolveSchemaRef(decl.$ref, firstResolved.task_file, skillsRoot, sources);
|
|
12011
12092
|
firstSchemas[typeName] = schema;
|
|
12012
12093
|
firstResult[key].schema = schema;
|
|
12013
12094
|
}
|
|
@@ -12016,7 +12097,8 @@ async function runWorkflowCreate(opts, config) {
|
|
|
12016
12097
|
firstResult[key].schema,
|
|
12017
12098
|
firstResolved.task_file,
|
|
12018
12099
|
skillsRoot,
|
|
12019
|
-
firstSchemas
|
|
12100
|
+
firstSchemas,
|
|
12101
|
+
sources
|
|
12020
12102
|
);
|
|
12021
12103
|
}
|
|
12022
12104
|
}
|
|
@@ -12169,13 +12251,14 @@ function register3(program2) {
|
|
|
12169
12251
|
const configPath = findConfig();
|
|
12170
12252
|
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
12171
12253
|
const agentsDir = resolveSkillsRoot(configDir);
|
|
12254
|
+
const sources = resolveSkillSources(configDir);
|
|
12172
12255
|
if (!workflowId) {
|
|
12173
|
-
const ids = listWorkflowDefinitions(agentsDir);
|
|
12256
|
+
const ids = listWorkflowDefinitions(agentsDir, sources);
|
|
12174
12257
|
for (const id of ids) console.log(id);
|
|
12175
12258
|
return;
|
|
12176
12259
|
}
|
|
12177
12260
|
try {
|
|
12178
|
-
const def = loadWorkflowDefinition(workflowId, agentsDir);
|
|
12261
|
+
const def = loadWorkflowDefinition(workflowId, agentsDir, sources);
|
|
12179
12262
|
console.log(JSON.stringify(def, null, 2));
|
|
12180
12263
|
} catch (err) {
|
|
12181
12264
|
console.error(`Error: ${err.message}`);
|
|
@@ -12319,7 +12402,8 @@ function register3(program2) {
|
|
|
12319
12402
|
const configPath = findConfig();
|
|
12320
12403
|
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
12321
12404
|
const agentsDir = resolveSkillsRoot(configDir);
|
|
12322
|
-
|
|
12405
|
+
const sources = resolveSkillSources(configDir);
|
|
12406
|
+
allAfter = loadWorkflowDefinition(workflowId, agentsDir, sources).after;
|
|
12323
12407
|
parentParams = parentMeta.params ?? {};
|
|
12324
12408
|
} catch (lookupErr) {
|
|
12325
12409
|
console.warn(`Could not load after: declarations \u2014 proceeding without: ${lookupErr.message}`);
|
|
@@ -12425,19 +12509,6 @@ RESPONSE: ${JSON.stringify(response)}`);
|
|
|
12425
12509
|
process.exitCode = 1;
|
|
12426
12510
|
}
|
|
12427
12511
|
});
|
|
12428
|
-
workflow.command("merge").description("Squash-merge a workflow branch, kill preview, and archive the workflow").requiredOption("--workflow <name>", "Workflow name (e.g., debo-vision-2026-03-17-a3f7)").action((opts) => {
|
|
12429
|
-
const config = loadConfig();
|
|
12430
|
-
try {
|
|
12431
|
-
const result = workflowMerge(config.data, opts.workflow);
|
|
12432
|
-
log({ cmd: "workflow merge", args: { workflow: opts.workflow }, result: { branch: result.branch } });
|
|
12433
|
-
console.log(`\u2713 Workflow ${opts.workflow} merged and archived`);
|
|
12434
|
-
console.log(` Branch: ${result.branch} (deleted)`);
|
|
12435
|
-
console.log(` Commit: workflow: ${opts.workflow}`);
|
|
12436
|
-
} catch (err) {
|
|
12437
|
-
console.error(`Error: ${err.message}`);
|
|
12438
|
-
process.exitCode = 1;
|
|
12439
|
-
}
|
|
12440
|
-
});
|
|
12441
12512
|
register2(workflow);
|
|
12442
12513
|
}
|
|
12443
12514
|
|
|
@@ -12697,13 +12768,13 @@ function collectRefNamesFromSchema(schema) {
|
|
|
12697
12768
|
Object.values(obj).forEach(walk2);
|
|
12698
12769
|
}
|
|
12699
12770
|
}
|
|
12700
|
-
async function buildRenderContext(workflowFilePath, agentsDir) {
|
|
12771
|
+
async function buildRenderContext(workflowFilePath, agentsDir, sources) {
|
|
12701
12772
|
const config = loadConfig();
|
|
12702
12773
|
const configPath = findConfig();
|
|
12703
12774
|
const rawConfig = configPath ? load(readFileSync(configPath, "utf-8")) ?? {} : {};
|
|
12704
12775
|
const skillsRoot = resolve(agentsDir, "skills");
|
|
12705
12776
|
const envMap = buildEnvMap(config);
|
|
12706
|
-
const resolved = await resolveAllStages(workflowFilePath, config, rawConfig, agentsDir);
|
|
12777
|
+
const resolved = await resolveAllStages(workflowFilePath, config, rawConfig, agentsDir, sources);
|
|
12707
12778
|
const wfFm = parseFrontmatter(workflowFilePath) ?? {};
|
|
12708
12779
|
const workflowId = workflowFilePath.split("/").pop().replace(/\.md$/, "");
|
|
12709
12780
|
const taskBodies = /* @__PURE__ */ new Map();
|
|
@@ -12752,7 +12823,8 @@ async function buildRenderContext(workflowFilePath, agentsDir) {
|
|
|
12752
12823
|
result: resultFm,
|
|
12753
12824
|
taskFilePath: rs.task_file,
|
|
12754
12825
|
skillsRoot,
|
|
12755
|
-
envMap
|
|
12826
|
+
envMap,
|
|
12827
|
+
sources
|
|
12756
12828
|
});
|
|
12757
12829
|
const schemaObj = buildResultSchemaObject(schemaBlock.result);
|
|
12758
12830
|
outputSchemas.set(step, schemaObj);
|
|
@@ -13006,8 +13078,9 @@ function register5(program2) {
|
|
|
13006
13078
|
const configPath = findConfig();
|
|
13007
13079
|
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
13008
13080
|
const agentsDir = resolveSkillsRoot(configDir);
|
|
13009
|
-
const
|
|
13010
|
-
const
|
|
13081
|
+
const sources = resolveSkillSources(configDir);
|
|
13082
|
+
const workflowFile = resolveWorkflowFile(workflowId, agentsDir, sources);
|
|
13083
|
+
const ctx = await buildRenderContext(workflowFile, agentsDir, sources);
|
|
13011
13084
|
const md = renderPlan(ctx);
|
|
13012
13085
|
console.log(md);
|
|
13013
13086
|
} catch (err) {
|
|
@@ -13188,4 +13261,7 @@ register3(program);
|
|
|
13188
13261
|
register4(program);
|
|
13189
13262
|
register5(program);
|
|
13190
13263
|
register6(program);
|
|
13191
|
-
program.
|
|
13264
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
13265
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
13266
|
+
process.exitCode = 1;
|
|
13267
|
+
});
|