sverka 0.2.22 → 0.2.24

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/main.ts","../src/output.ts"],"mappings":";;iBAGiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;EACf,MAAM;EACN,UAAU;EACV,MAAM;EACN,UAAU;;EAEV,MAAM;;;qBAIK;;;;;;YAOD,mBAAmB,uBAAuB;;YAG1C;;qBAWC,iBAAiB;WACnB,MAAM;WACN,UAAU;WACD;EAEhB,YAAA,iBACA,MAAM,cACN,UAAU,UACV;;;;;UChCa;EACf,SAAS;;;;;;;wBAuZW,KAAK,gBAAgB,OAAO,WAAW;;;;KC7ajD,aAAa;;;;;;qBAOZ,+BAA+B;;EAM9B,YAAA,KAAK,WAAW,KAAK,WAAW,iBAAe;EAO3D,MAAM;EAKN,UAAU;EAKV,MAAM;EAIN,UAAU;;EAKV,MAAM;;;;;;wBAwDQ,mBACd,QAAQ,aACR,KAAK,WACL,KAAK,YACJ;;;;;wBAYa,iBACd,QAAQ,aACR,MAAM,eACL"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/main.ts","../src/output.ts"],"mappings":";;iBAGiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;EACf,MAAM;EACN,UAAU;EACV,MAAM;EACN,UAAU;;EAEV,MAAM;;;qBAIK;;;;;;YAOD,mBAAmB,uBAAuB;;YAG1C;;qBAWC,iBAAiB;WACnB,MAAM;WACN,UAAU;WACD;EAEhB,YAAA,iBACA,MAAM,cACN,UAAU,UACV;;;;;UC9Ba;EACf,SAAS;;;;;;;wBA6cW,KAAK,gBAAgB,OAAO,WAAW;;;;KCrejD,aAAa;;;;;;qBAOZ,+BAA+B;;EAM9B,YAAA,KAAK,WAAW,KAAK,WAAW,iBAAe;EAO3D,MAAM;EAKN,UAAU;EAKV,MAAM;EAIN,UAAU;;EAKV,MAAM;;;;;;wBAwDQ,mBACd,QAAQ,aACR,KAAK,WACL,KAAK,YACJ;;;;;wBAYa,iBACd,QAAQ,aACR,MAAM,eACL"}
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import { mkdir, open, readFile, realpath, writeFile } from "node:fs/promises";
10
10
  import { Entry, Pipeline, Project, ShellStep, collectConstructWarnings, manual, synthesize, validateGraph } from "@sverka/workflow";
11
11
  import { bindRunPlan, createPlanner } from "@sverka/sdk";
12
12
  import { DEFAULT_POLICY, createBuiltinResolver, evaluatePolicy, loadBaseline, normalizeSarif, serializeSarif, synthesizeCheckSteps } from "@sverka/verification";
13
- import { createDockerDriver, createEngine, createHostDriver } from "@sverka/runtime";
13
+ import { AGENT_ENV, collectAgentWrites, createDockerDriver, createEngine, createHostDriver, noAgentDriverError, resolveAgentDrivers, resolveAgentEngine } from "@sverka/runtime";
14
14
  import { spawnSync } from "node:child_process";
15
15
  import { watch } from "chokidar";
16
16
  import { compileGithub, compileGitlab } from "@sverka/compiler";
@@ -190,17 +190,31 @@ async function registerConfigHooks() {
190
190
  /**
191
191
  * Find a sverka.config.ts or sverka.config.js file in the given root.
192
192
  * Returns the absolute path or null if not found.
193
+ * Spec 37/54: when no config exists, fall back to `.sverka/*.md` files
194
+ * (markdown authoring) — sorted, first file wins.
193
195
  */
194
196
  async function findConfig(root) {
195
197
  for (const candidate of [
196
198
  "sverka.config.ts",
199
+ "sverka.config.mts",
197
200
  "sverka.config.js",
198
201
  "sverka.config.mjs"
199
202
  ]) {
200
203
  const path = join(root, candidate);
201
204
  if (existsSync(path)) return path;
202
205
  }
203
- return null;
206
+ return findMarkdownConfig(root);
207
+ }
208
+ /** First `.sverka/*.md` file under root (sorted), or null. */
209
+ function findMarkdownConfig(root) {
210
+ const dir = join(root, ".sverka");
211
+ let names;
212
+ try {
213
+ names = readdirSync(dir).filter((n) => n.endsWith(".md")).sort();
214
+ } catch {
215
+ return null;
216
+ }
217
+ return names.length > 0 ? join(dir, names[0]) : null;
204
218
  }
205
219
  /**
206
220
  * Resolve the effective config path from global flags.
@@ -236,6 +250,12 @@ function assertConstructLike(value) {
236
250
  async function loadConfig(configPath) {
237
251
  const absPath = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath);
238
252
  if (!existsSync(absPath)) throw new CliError(`config not found: ${absPath}`, "MISSING_ARG", ExitCode.UsageError);
253
+ if (absPath.endsWith(".md")) try {
254
+ const { loadMarkdownFile } = await import("@sverka/sdk");
255
+ return await loadMarkdownFile(absPath);
256
+ } catch (e) {
257
+ throw new CliError(`failed to load config: ${e instanceof Error ? e.message : String(e)}`, "SDK_ERROR", ExitCode.RuntimeError, e);
258
+ }
239
259
  let mod;
240
260
  try {
241
261
  await registerConfigHooks();
@@ -975,6 +995,7 @@ async function runCommand(args, global, output, start) {
975
995
  const artifactDir = join(global.root, ".sverka", "artifacts");
976
996
  const { events, runStatus, renderer } = await consumeEvents(createEngine({
977
997
  drivers: buildDrivers(executor),
998
+ agentDrivers: resolveAgentDrivers(process$1.env),
978
999
  maxConcurrent: args.jobs ?? 4
979
1000
  }), plan, {
980
1001
  workspace: global.root,
@@ -2265,6 +2286,218 @@ async function uiCommand(args, global, output, _start) {
2265
2286
  }
2266
2287
  }
2267
2288
  //#endregion
2289
+ //#region src/commands/agent.ts
2290
+ const RESULT_FILE = "agent-result.json";
2291
+ const WRITES_FILE$1 = "sverka-writes.json";
2292
+ async function agentCommand(args, global, output, _start) {
2293
+ const env = process$1.env;
2294
+ const engine = resolveAgentEngine(env);
2295
+ const prompt = env[AGENT_ENV.prompt];
2296
+ if (prompt === void 0 || prompt === "") throw new CliError(`sverka agent: ${AGENT_ENV.prompt} is not set — the compiled agent job provides it`, "MISSING_ARG", ExitCode.UsageError);
2297
+ const outDir = args.outputDir ?? global.root;
2298
+ const mentions = parseMentionList(env[AGENT_ENV.mention]);
2299
+ const commentBody = env["COMMENT_BODY"];
2300
+ if (mentions.length > 0 && commentBody !== void 0 && !mentions.some((m) => commentBody.includes(m))) {
2301
+ output.writeLine(`sverka agent: comment does not contain a configured mention (${mentions.join(", ")}) — skipping`);
2302
+ await writeArtifacts(outDir, {
2303
+ text: "",
2304
+ finishReason: "stop"
2305
+ });
2306
+ return ExitCode.Success;
2307
+ }
2308
+ const driver = resolveAgentDrivers(env).find((d) => d.canExecute(engine));
2309
+ if (driver === void 0) throw noAgentDriverError(engine, `sverka agent, engine '${engine}'`);
2310
+ const model = env[AGENT_ENV.model];
2311
+ const maxTokens = env[AGENT_ENV.maxTokens];
2312
+ const request = {
2313
+ engine,
2314
+ prompt,
2315
+ ...model !== void 0 ? { model } : {},
2316
+ ...maxTokens !== void 0 ? { maxTokens: parseMaxTokens(maxTokens) } : {}
2317
+ };
2318
+ output.debug(`sverka agent: engine=${engine} driver=${driver.name}`);
2319
+ const result = await driver.executeAgent(request);
2320
+ if (result.finishReason === "error") {
2321
+ output.errorLine("sverka agent: agent finished with reason 'error'");
2322
+ return ExitCode.RuntimeError;
2323
+ }
2324
+ await writeArtifacts(outDir, result, collectAgentWrites(result));
2325
+ if (result.text !== "") output.writeLine(result.text);
2326
+ return ExitCode.Success;
2327
+ }
2328
+ /**
2329
+ * SVERKA_MENTION carries one mention string, or a JSON array when several
2330
+ * comment entries with different mentions reach the same job.
2331
+ */
2332
+ function parseMentionList(raw) {
2333
+ if (raw === void 0) return [];
2334
+ try {
2335
+ const parsed = JSON.parse(raw);
2336
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((m) => typeof m === "string")) return parsed;
2337
+ } catch {}
2338
+ return [raw];
2339
+ }
2340
+ function parseMaxTokens(raw) {
2341
+ const n = Number(raw);
2342
+ if (!Number.isInteger(n) || n <= 0) throw new CliError(`sverka agent: ${AGENT_ENV.maxTokens} must be a positive integer, got '${raw}'`, "INVALID_FLAG", ExitCode.UsageError);
2343
+ return n;
2344
+ }
2345
+ async function writeArtifacts(dir, result, writes = []) {
2346
+ await writeFile(join(dir, RESULT_FILE), JSON.stringify(result, null, 2), "utf-8");
2347
+ await writeFile(join(dir, WRITES_FILE$1), JSON.stringify({ writes }, null, 2), "utf-8");
2348
+ }
2349
+ //#endregion
2350
+ //#region src/commands/apply.ts
2351
+ const WRITES_FILE = "sverka-writes.json";
2352
+ const DECLARATIONS_ENV = "SVERKA_WRITE_DECLARATIONS";
2353
+ /** Write kinds `sverka apply` can execute. Everything else fails loudly. */
2354
+ const SUPPORTED_WRITE_KINDS = /* @__PURE__ */ new Set(["comment"]);
2355
+ async function applyCommand(args, global, output, _start) {
2356
+ const provider = args.provider ?? detectProvider();
2357
+ const writes = await loadWrites(args.file ?? join(global.root, WRITES_FILE));
2358
+ const declarations = loadDeclarations();
2359
+ const applied = [];
2360
+ for (const write of writes) {
2361
+ if (!declarations.some((d) => d.kind === write.kind)) throw new CliError(`sverka apply: write kind '${write.kind}' is not declared in permissions.write (declarations: ${declarations.map((d) => d.kind).join(", ") || "none"})`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2362
+ if (!SUPPORTED_WRITE_KINDS.has(write.kind)) throw new CliError(`sverka apply: write kind '${write.kind}' is declared but not supported by sverka apply (supported: ${[...SUPPORTED_WRITE_KINDS].join(", ")})`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2363
+ applied.push(await applyWrite(provider, write, declarations));
2364
+ }
2365
+ const summary = applied.length > 0 ? ` — ${applied.join("; ")}` : "";
2366
+ output.writeLine(`sverka apply: applied ${applied.length} write(s) via ${provider}${summary}`);
2367
+ return ExitCode.Success;
2368
+ }
2369
+ function detectProvider() {
2370
+ if (process$1.env["GITLAB_CI"] !== void 0 || process$1.env["CI_API_V4_URL"]) return "gitlab";
2371
+ if (process$1.env["GITHUB_ACTIONS"] !== void 0) return "github";
2372
+ throw new CliError("sverka apply: --provider is required when CI provider cannot be detected (gitlab|github)", "MISSING_ARG", ExitCode.UsageError);
2373
+ }
2374
+ async function loadWrites(file) {
2375
+ let raw;
2376
+ try {
2377
+ raw = await readFile(file, "utf-8");
2378
+ } catch {
2379
+ throw new CliError(`sverka apply: writes artifact not found: ${file} — the agent job must produce ${WRITES_FILE}`, "MISSING_ARG", ExitCode.RuntimeError);
2380
+ }
2381
+ let parsed;
2382
+ try {
2383
+ parsed = JSON.parse(raw);
2384
+ } catch (e) {
2385
+ throw new CliError(`sverka apply: ${WRITES_FILE} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2386
+ }
2387
+ const items = extractWritesItems(parsed);
2388
+ if (items === void 0) throw new CliError(`sverka apply: ${WRITES_FILE} must be an object with a 'writes' array`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2389
+ return items.map((item, i) => {
2390
+ if (typeof item !== "object" || item === null || typeof item.kind !== "string") throw new CliError(`sverka apply: writes[${i}] must be an object with a string 'kind'`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2391
+ return item;
2392
+ });
2393
+ }
2394
+ /** The artifact is either a bare array or an object with a 'writes' array. */
2395
+ function extractWritesItems(parsed) {
2396
+ if (Array.isArray(parsed)) return parsed;
2397
+ if (typeof parsed !== "object" || parsed === null) return void 0;
2398
+ const w = parsed.writes;
2399
+ return Array.isArray(w) ? w : void 0;
2400
+ }
2401
+ function loadDeclarations() {
2402
+ const raw = process$1.env[DECLARATIONS_ENV];
2403
+ if (raw === void 0 || raw === "") throw new CliError(`sverka apply: ${DECLARATIONS_ENV} is not set — the __apply job declares it from permissions.write`, "MISSING_ARG", ExitCode.RuntimeError);
2404
+ let parsed;
2405
+ try {
2406
+ parsed = JSON.parse(raw);
2407
+ } catch (e) {
2408
+ throw new CliError(`sverka apply: ${DECLARATIONS_ENV} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2409
+ }
2410
+ if (!Array.isArray(parsed)) throw new CliError(`sverka apply: ${DECLARATIONS_ENV} must be a JSON array of write declarations`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2411
+ return parsed;
2412
+ }
2413
+ async function applyWrite(provider, write, declarations) {
2414
+ if (write.kind === "comment") return provider === "gitlab" ? applyGitlabComment(write, declarations) : applyGithubComment(write);
2415
+ throw new CliError(`sverka apply: write kind '${write.kind}' is not supported`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2416
+ }
2417
+ function requireBody(write) {
2418
+ if (typeof write.body !== "string" || write.body === "") throw new CliError("sverka apply: comment write requires a non-empty 'body' string", "PACKAGE_ERROR", ExitCode.RuntimeError);
2419
+ return write.body;
2420
+ }
2421
+ /**
2422
+ * The object kind a comment write targets. `write.on` wins when present
2423
+ * (and must be a known kind — anything else fails loudly rather than
2424
+ * silently falling back); otherwise the triggering context decides: a
2425
+ * forwarded MR_IID means the comment fired on a merge request.
2426
+ */
2427
+ function resolveCommentTarget(write) {
2428
+ if (write.on === "issue" || write.on === "merge_request") return write.on;
2429
+ if (write.on !== void 0) throw new CliError(`sverka apply: comment write 'on' must be issue|merge_request, got '${JSON.stringify(write.on)}'`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2430
+ return process$1.env["MR_IID"] !== void 0 ? "merge_request" : "issue";
2431
+ }
2432
+ /**
2433
+ * Resolve the target iid for a comment write. When the triggering context
2434
+ * supplies an iid (the webhook forwarded MR_IID/ISSUE_IID), it wins — a
2435
+ * write.iid that disagrees is model output trying to redirect the comment
2436
+ * to another target, which fails loudly.
2437
+ */
2438
+ function requireIid(write, envKeys) {
2439
+ const contextRaw = envKeys.map((k) => process$1.env[k]).find((v) => v !== void 0 && v !== "");
2440
+ if (contextRaw !== void 0) {
2441
+ const ctx = Number(contextRaw);
2442
+ if (!Number.isInteger(ctx) || ctx <= 0) throw new CliError(`sverka apply: context iid '${contextRaw}' is not a positive integer`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2443
+ if (write.iid !== void 0 && Number(write.iid) !== ctx) throw new CliError(`sverka apply: write iid '${JSON.stringify(write.iid)}' does not match the triggering context iid ${ctx}`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2444
+ return ctx;
2445
+ }
2446
+ if (write.iid === void 0) throw new CliError(`sverka apply: comment write needs a target iid (${envKeys.join(" or ")} env var, or an 'iid' field)`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2447
+ const n = Number(write.iid);
2448
+ if (!Number.isInteger(n) || n <= 0) throw new CliError(`sverka apply: comment write iid must be a positive integer, got '${JSON.stringify(write.iid)}'`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2449
+ return n;
2450
+ }
2451
+ async function applyGitlabComment(write, declarations) {
2452
+ const { api, project, token } = gitlabApplyEnv();
2453
+ const body = requireBody(write);
2454
+ const on = resolveCommentTarget(write);
2455
+ if (!declarations.some((d) => d.kind === "comment" && d.target === on)) throw new CliError(`sverka apply: comment write targets '${on}' but no declaration declares it (declared comment targets: ${declarations.filter((d) => d.kind === "comment").map((d) => d.target).join(", ") || "none"})`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2456
+ const iid = requireIid(write, on === "merge_request" ? ["MR_IID"] : ["ISSUE_IID", "SVERKA_ISSUE_IID"]);
2457
+ await postJson(`${api}${on === "merge_request" ? `/projects/${encodeURIComponent(project)}/merge_requests/${iid}/notes` : `/projects/${encodeURIComponent(project)}/issues/${iid}/notes`}`, token, { body }, "PRIVATE-TOKEN");
2458
+ return `comment on ${on} !${iid}`;
2459
+ }
2460
+ /** Provider-supplied env for a GitLab apply call — all CI-controlled. */
2461
+ function gitlabApplyEnv() {
2462
+ const env = process$1.env;
2463
+ const api = env["CI_API_V4_URL"] ?? "https://gitlab.com/api/v4";
2464
+ const project = env["CI_PROJECT_ID"];
2465
+ if (project === void 0 || project === "") throw new CliError("sverka apply: CI_PROJECT_ID is not set — run inside a GitLab job or set it explicitly", "MISSING_ARG", ExitCode.RuntimeError);
2466
+ const token = env["SVERKA_APPLY_TOKEN"] || env["GITLAB_TOKEN"];
2467
+ if (token === void 0 || token === "") throw new CliError("sverka apply: SVERKA_APPLY_TOKEN is not set — scope it to the 'sverka-apply' environment (see engdocs/user/gitlab/agentic.md)", "MISSING_ARG", ExitCode.RuntimeError);
2468
+ return {
2469
+ api,
2470
+ project,
2471
+ token
2472
+ };
2473
+ }
2474
+ async function applyGithubComment(write) {
2475
+ const env = process$1.env;
2476
+ const api = env["GITHUB_API_URL"] ?? "https://api.github.com";
2477
+ const repo = env["GITHUB_REPOSITORY"];
2478
+ if (repo === void 0 || repo === "") throw new CliError("sverka apply: GITHUB_REPOSITORY is not set — run inside a GitHub Actions job or set it explicitly", "MISSING_ARG", ExitCode.RuntimeError);
2479
+ const token = env["SVERKA_APPLY_TOKEN"] || env["GH_TOKEN"] || env["GITHUB_TOKEN"];
2480
+ if (token === void 0 || token === "") throw new CliError("sverka apply: GH_TOKEN/GITHUB_TOKEN is not set", "MISSING_ARG", ExitCode.RuntimeError);
2481
+ const body = requireBody(write);
2482
+ const issue = requireIid(write, ["SVERKA_ISSUE_IID", "ISSUE_IID"]);
2483
+ await postJson(`${api}/repos/${repo}/issues/${issue}/comments`, token, { body }, "Bearer");
2484
+ return `comment on issue #${issue}`;
2485
+ }
2486
+ async function postJson(url, token, body, auth) {
2487
+ const headers = { "content-type": "application/json" };
2488
+ if (auth === "PRIVATE-TOKEN") headers["PRIVATE-TOKEN"] = token;
2489
+ else headers["authorization"] = `Bearer ${token}`;
2490
+ const res = await fetch(url, {
2491
+ method: "POST",
2492
+ headers,
2493
+ body: JSON.stringify(body)
2494
+ });
2495
+ if (!res.ok) {
2496
+ const text = await res.text().catch(() => "");
2497
+ throw new CliError(`sverka apply: provider request failed (${res.status} ${res.url}): ${text.slice(0, 300)}`, "PACKAGE_ERROR", ExitCode.RuntimeError);
2498
+ }
2499
+ }
2500
+ //#endregion
2268
2501
  //#region src/main.ts
2269
2502
  /** Build GlobalFlags from parsed yargs options. */
2270
2503
  function buildGlobalFlags(parsed) {
@@ -2347,13 +2580,9 @@ function addPolicyCommand(y) {
2347
2580
  demandOption: true
2348
2581
  }).option("baseline", { type: "string" });
2349
2582
  }
2350
- /** Configure the synth subcommand options. */
2583
+ /** Configure the synth subcommand options. Alias for compile — same flags. */
2351
2584
  function addSynthCommand(y) {
2352
- return y.option("target", {
2353
- type: "string",
2354
- demandOption: true,
2355
- choices: ["github", "gitlab"]
2356
- });
2585
+ return addCompileCommand(y);
2357
2586
  }
2358
2587
  /** Configure the compile subcommand options. */
2359
2588
  function addCompileCommand(y) {
@@ -2433,7 +2662,17 @@ function buildParser() {
2433
2662
  type: "boolean",
2434
2663
  alias: "v",
2435
2664
  default: false
2436
- }).command("init", "Create a sverka.config.ts", addInitCommand).command("validate", "Validate a sverka config").command("plan", "Bind Entry + inputs → Run Plan", addPlanCommand).command("graph", "Display the Definition Graph").command("run", "Execute the workflow locally", addRunCommand).command("discover", "Discover and display project context").command("check", "Resolve proposed checks → StepDefinitions").command("policy", "Evaluate findings against policy", addPolicyCommand).command("synth", "Alias for compile — compile to a target CI YAML", addSynthCommand).command("compile", "Compile to a target CI YAML", addCompileCommand).command("mcp-server", "Expose Sverka as an MCP server (stdio)").command("doctor", "Diagnose environment and dependencies").command("view [file]", "Open the latest run report, or view SARIF findings (TUI/HTML)", addViewCommand).command("ui", "Start local web dashboard server", addUiCommand).demandCommand(1, "No command given").strict().fail((msg, err) => {
2665
+ }).command("init", "Create a sverka.config.ts", addInitCommand).command("validate", "Validate a sverka config").command("plan", "Bind Entry + inputs → Run Plan", addPlanCommand).command("graph", "Display the Definition Graph").command("run", "Execute the workflow locally", addRunCommand).command("discover", "Discover and display project context").command("check", "Resolve proposed checks → StepDefinitions").command("policy", "Evaluate findings against policy", addPolicyCommand).command("synth", "Alias for compile — compile to a target CI YAML", addSynthCommand).command("compile", "Compile to a target CI YAML", addCompileCommand).command("mcp-server", "Expose Sverka as an MCP server (stdio)").command("doctor", "Diagnose environment and dependencies").command("view [file]", "Open the latest run report, or view SARIF findings (TUI/HTML)", addViewCommand).command("ui", "Start local web dashboard server", addUiCommand).command("agent", "Run a single agent step from SVERKA_AGENT_* env (CI agent jobs)", (y) => y.option("output-dir", {
2666
+ type: "string",
2667
+ describe: "Directory for agent-result.json + sverka-writes.json (default: root)"
2668
+ })).command("apply", "Validate + apply agent writes artifact (CI __apply jobs)", (y) => y.option("provider", {
2669
+ type: "string",
2670
+ choices: ["gitlab", "github"],
2671
+ describe: "CI provider (auto-detected from CI env when omitted)"
2672
+ }).option("file", {
2673
+ type: "string",
2674
+ describe: "Path to sverka-writes.json (default: <root>/sverka-writes.json)"
2675
+ })).demandCommand(1, "No command given").strict().fail((msg, err) => {
2437
2676
  if (err) throw err;
2438
2677
  throw new CliError(msg, "UNKNOWN_COMMAND", ExitCode.UsageError);
2439
2678
  });
@@ -2455,6 +2694,8 @@ async function dispatch(command, parsed, global, output, start) {
2455
2694
  case "doctor": return doctorCommand(global, output, start);
2456
2695
  case "view": return dispatchView(parsed, global, output, start);
2457
2696
  case "ui": return dispatchUi(parsed, global, output, start);
2697
+ case "agent": return dispatchAgent(parsed, global, output, start);
2698
+ case "apply": return dispatchApply(parsed, global, output, start);
2458
2699
  default: throw new CliError(`unknown command: ${command}`, "UNKNOWN_COMMAND", ExitCode.UsageError);
2459
2700
  }
2460
2701
  }
@@ -2499,7 +2740,11 @@ function dispatchPolicy(parsed, global, output, start) {
2499
2740
  return policyCommand(args, global, output, start);
2500
2741
  }
2501
2742
  function dispatchSynth(parsed, global, output, start) {
2502
- return synthCommand({ target: parsed.target === "gitlab" ? "gitlab" : "github" }, global, output, start);
2743
+ const args = { target: parsed.target === "gitlab" ? "gitlab" : "github" };
2744
+ if (typeof parsed.output === "string") args.output = parsed.output;
2745
+ if (typeof parsed.outputDir === "string") args.outputDir = parsed.outputDir;
2746
+ if (parsed.pin === true) args.pin = true;
2747
+ return synthCommand(args, global, output, start);
2503
2748
  }
2504
2749
  function dispatchCompile(parsed, global, output, start) {
2505
2750
  const args = { target: parsed.target === "gitlab" ? "gitlab" : "github" };
@@ -2520,6 +2765,17 @@ function dispatchUi(parsed, global, output, start) {
2520
2765
  if (typeof parsed.host === "string") args.host = parsed.host;
2521
2766
  return uiCommand(args, global, output, start);
2522
2767
  }
2768
+ function dispatchAgent(parsed, global, output, start) {
2769
+ const args = {};
2770
+ if (typeof parsed.outputDir === "string") args.outputDir = parsed.outputDir;
2771
+ return agentCommand(args, global, output, start);
2772
+ }
2773
+ function dispatchApply(parsed, global, output, start) {
2774
+ const args = {};
2775
+ if (parsed.provider === "gitlab" || parsed.provider === "github") args.provider = parsed.provider;
2776
+ if (typeof parsed.file === "string") args.file = parsed.file;
2777
+ return applyCommand(args, global, output, start);
2778
+ }
2523
2779
  /**
2524
2780
  * The main CLI entry point.
2525
2781
  * @param argv Command-line arguments (excluding node and script path).