@mutagent/evaluator 0.1.0-alpha.3 → 0.1.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/.claude/skills/mutagent-evaluator/SKILL.md +19 -7
  2. package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +2 -2
  3. package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +65 -1
  4. package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +1 -0
  5. package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +20 -7
  6. package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +1 -1
  7. package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +16 -0
  8. package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +14 -7
  9. package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +18 -2
  10. package/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +285 -0
  11. package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +45 -9
  12. package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +63 -8
  13. package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +1 -1
  14. package/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
  15. package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1 -1
  16. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +21 -6
  17. package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +12 -2
  18. package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +114 -18
  19. package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +6 -2
  20. package/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
  21. package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +1 -1
  22. package/bin/mutagent-cli.mjs +698 -7
  23. package/package.json +3 -3
@@ -5170,11 +5170,15 @@ var OperationIntent = {
5170
5170
  Evals: "evals",
5171
5171
  Dataset: "dataset"
5172
5172
  };
5173
+ var SourceFormat = {
5174
+ Unitf: "unitf"
5175
+ };
5173
5176
  var QuerySourceSchema = Type.Object({
5174
5177
  endpoint_ref: Type.Optional(Type.String()),
5175
5178
  credential_ref: Type.Optional(Type.String()),
5176
5179
  paths: Type.Optional(Type.Array(Type.String())),
5177
- root: Type.Optional(Type.String())
5180
+ root: Type.Optional(Type.String()),
5181
+ format: Type.Optional(Type.Literal(SourceFormat.Unitf))
5178
5182
  }, { additionalProperties: false });
5179
5183
  var QueryFiltersSchema = Type.Object({
5180
5184
  sessionId: Type.Optional(Type.String()),
@@ -5449,6 +5453,7 @@ function parseNdjson(text) {
5449
5453
  // ../mutagent-tools/src/adapters/local-ndjson.ts
5450
5454
  function collect(ctx) {
5451
5455
  const src = ctx.query.source ?? {};
5456
+ const passthrough = src.format === "unitf";
5452
5457
  const files = discoverFiles({ paths: src.paths, root: src.root });
5453
5458
  const traces = [];
5454
5459
  let malformed = 0;
@@ -5465,6 +5470,13 @@ function collect(ctx) {
5465
5470
  const parsed = parseNdjson(text);
5466
5471
  malformed += parsed.malformed;
5467
5472
  for (const rec of parsed.records) {
5473
+ if (passthrough) {
5474
+ if (validateUnifiedTrace(rec).ok)
5475
+ traces.push(rec);
5476
+ else
5477
+ malformed++;
5478
+ continue;
5479
+ }
5468
5480
  const t = mapGenericRecord(rec, "local-ndjson");
5469
5481
  if (t)
5470
5482
  traces.push(t);
@@ -7560,6 +7572,16 @@ function buildAttributeBag(hit) {
7560
7572
  if (hit.error_message !== undefined && hit.error_message !== null && hit.error_message !== "") {
7561
7573
  bag["error.message"] = hit.error_message;
7562
7574
  }
7575
+ const CC_CONTENT_COLS = ["user_prompt", "user_prompt_length", "tool_name", "full_command", "file_path", "tool_use_id"];
7576
+ for (const c of CC_CONTENT_COLS) {
7577
+ const v = hit[c];
7578
+ if (v !== undefined && v !== null && v !== "" && bag[c] === undefined)
7579
+ bag[c] = v;
7580
+ }
7581
+ const userPrompt = hit["user_prompt"];
7582
+ if (typeof userPrompt === "string" && userPrompt !== "" && bag["gen_ai.prompt"] === undefined && bag["gen_ai.input.messages"] === undefined) {
7583
+ bag["gen_ai.prompt"] = userPrompt;
7584
+ }
7563
7585
  return bag;
7564
7586
  }
7565
7587
  function toOtelAttribute(key, value) {
@@ -7689,7 +7711,9 @@ var ASSISTANT_RESPONSE_KEYS = [
7689
7711
  "assistant_response",
7690
7712
  "completion",
7691
7713
  "output_text",
7692
- "message"
7714
+ "message",
7715
+ "text",
7716
+ "content"
7693
7717
  ];
7694
7718
  function eventTimeToIso(v) {
7695
7719
  if (typeof v === "string" && v.length > 0) {
@@ -7816,6 +7840,23 @@ function eventToSpan(hit, sessionId, index) {
7816
7840
  span.attributes = { model };
7817
7841
  break;
7818
7842
  }
7843
+ case "assistant_response": {
7844
+ span.kind = "llm";
7845
+ span.role = "assistant";
7846
+ const response = firstStr(hit, ASSISTANT_RESPONSE_KEYS);
7847
+ if (response !== undefined)
7848
+ span.output = response;
7849
+ const tok = eventTokens(hit);
7850
+ if (tok !== undefined)
7851
+ span.tokens = tok;
7852
+ const cost = num3(hit.cost_usd);
7853
+ if (cost !== undefined)
7854
+ span.costUsd = cost;
7855
+ const model = str3(hit.model);
7856
+ if (model !== undefined)
7857
+ span.attributes = { model };
7858
+ break;
7859
+ }
7819
7860
  case "tool_result": {
7820
7861
  span.kind = "tool";
7821
7862
  span.role = "tool";
@@ -8924,12 +8965,14 @@ function buildQuery(sub, positionals, flags) {
8924
8965
  const root = getStr(flags, "root");
8925
8966
  const endpointRef = getStr(flags, "endpoint-ref");
8926
8967
  const credRef = getStr(flags, "credential-ref");
8927
- if (paths || root || endpointRef || credRef) {
8968
+ const unitfPassthrough = getStr(flags, "format") === "unitf";
8969
+ if (paths || root || endpointRef || credRef || unitfPassthrough) {
8928
8970
  q.source = {
8929
8971
  ...paths ? { paths } : {},
8930
8972
  ...root ? { root } : {},
8931
8973
  ...endpointRef ? { endpoint_ref: endpointRef } : {},
8932
- ...credRef ? { credential_ref: credRef } : {}
8974
+ ...credRef ? { credential_ref: credRef } : {},
8975
+ ...unitfPassthrough ? { format: "unitf" } : {}
8933
8976
  };
8934
8977
  }
8935
8978
  const exportPath = getStr(flags, "export");
@@ -8988,6 +9031,7 @@ var FLAGS_HELP = [
8988
9031
  ["--limit <n>", "cap the result count"],
8989
9032
  ["--page-size <n> / --cursor <c>", "pagination"],
8990
9033
  ["--paths <file> / --root <dir>", "local source discovery (repeat --paths; --root pins a dir)"],
9034
+ ["--format unitf", "local files are ALREADY UniTF \u2014 skip normalize, pass through (validate only)"],
8991
9035
  ["--endpoint-ref / --credential-ref", "env-var NAMES for a remote source (never raw secrets)"],
8992
9036
  ["--export <path.jsonl>", "write UniTF JSONL + <path>.manifest.json (the handover artifacts)"],
8993
9037
  ["--in <unitf.jsonl>", "`select`: UniTF JSONL input (repeatable; `--in -` reads stdin)"],
@@ -9211,8 +9255,645 @@ function runSelect(flags, io) {
9211
9255
  return 0;
9212
9256
  }
9213
9257
 
9214
- // ../mutagent-tools/src/cli/index.ts
9258
+ // ../mutagent-tools/src/cli/apply.ts
9259
+ import { spawnSync } from "child_process";
9260
+ import { createHash as createHash3, randomUUID } from "crypto";
9261
+ import {
9262
+ existsSync as existsSync4,
9263
+ mkdirSync as mkdirSync2,
9264
+ readFileSync as readFileSync4,
9265
+ rmSync,
9266
+ writeFileSync as writeFileSync2
9267
+ } from "fs";
9268
+ import { dirname as dirname2 } from "path";
9269
+
9270
+ // ../mutagent-tools/src/apply/registry.ts
9271
+ var REGISTRY2 = new Map;
9272
+ function registerTargetAdapter(adapter) {
9273
+ REGISTRY2.set(adapter.id, adapter);
9274
+ }
9275
+ function getTargetAdapter(id) {
9276
+ const a = REGISTRY2.get(id);
9277
+ if (!a) {
9278
+ const known = [...REGISTRY2.keys()].sort().join(", ") || "(none registered)";
9279
+ throw new Error(`no target adapter registered for id '${id}'. Registered: ${known}.`);
9280
+ }
9281
+ return a;
9282
+ }
9283
+ function clearTargetAdapters() {
9284
+ REGISTRY2.clear();
9285
+ }
9286
+
9287
+ // ../mutagent-tools/src/apply/types.ts
9288
+ var AdapterId = {
9289
+ WorktreePr: "worktree-pr",
9290
+ Rest: "rest",
9291
+ VendorCli: "vendor-cli",
9292
+ ReportOnly: "report-only"
9293
+ };
9294
+ var ApplyKind = {
9295
+ CodePr: "code-pr",
9296
+ Markdown: "markdown",
9297
+ CloudDeploy: "cloud-deploy",
9298
+ ReportOnly: "report-only"
9299
+ };
9300
+
9301
+ // ../mutagent-tools/src/apply/binding.ts
9302
+ function adapterIdForKind(kind, opts) {
9303
+ switch (kind) {
9304
+ case ApplyKind.CodePr:
9305
+ case ApplyKind.Markdown:
9306
+ return AdapterId.WorktreePr;
9307
+ case ApplyKind.CloudDeploy:
9308
+ return opts?.platformTransport === "vendor-cli" ? AdapterId.VendorCli : AdapterId.Rest;
9309
+ case ApplyKind.ReportOnly:
9310
+ return AdapterId.ReportOnly;
9311
+ default: {
9312
+ const _never = kind;
9313
+ throw new Error(`no adapter bound for apply.kind '${String(_never)}'`);
9314
+ }
9315
+ }
9316
+ }
9317
+ function kindRunsBuild(kind) {
9318
+ return kind === ApplyKind.CodePr;
9319
+ }
9320
+
9321
+ // ../mutagent-tools/src/apply/audit.ts
9322
+ function auditPath(projectRoot, applyId) {
9323
+ return `${projectRoot}/.mutagent/apply/${applyId}/apply-audit.json`;
9324
+ }
9325
+ var REQUIRED_FIELDS = [
9326
+ "applyId",
9327
+ "transport",
9328
+ "kind",
9329
+ "mode",
9330
+ "fromRev",
9331
+ "toRev",
9332
+ "staleHash",
9333
+ "revertToken",
9334
+ "producedAt"
9335
+ ];
9336
+ function missingAuditFields(record) {
9337
+ return REQUIRED_FIELDS.filter((f) => {
9338
+ const v = record[f];
9339
+ return v === undefined || v === null || v === "";
9340
+ });
9341
+ }
9342
+ function writeAudit(record, ctx) {
9343
+ const missing = missingAuditFields(record);
9344
+ if (missing.length > 0) {
9345
+ throw new Error(`apply-audit is incomplete \u2014 missing: ${missing.join(", ")} (0-blind-writes contract, V-O6)`);
9346
+ }
9347
+ const path = auditPath(ctx.projectRoot, record.applyId);
9348
+ ctx.io.mkdirp(`${ctx.projectRoot}/.mutagent/apply/${record.applyId}`);
9349
+ ctx.io.writeFile(path, JSON.stringify(record, null, 2) + `
9350
+ `);
9351
+ }
9352
+
9353
+ // ../mutagent-tools/src/apply/diff.ts
9354
+ function edorderKey(edits) {
9355
+ return [...edits].sort((a, b) => a.path.localeCompare(b.path));
9356
+ }
9357
+ function concatCurrent(edits, read) {
9358
+ return edorderKey(edits).map((e) => `=== ${e.path} ===
9359
+ ${read(e.path) ?? ""}`).join(`
9360
+ `);
9361
+ }
9362
+ function concatProposed(edits) {
9363
+ return edorderKey(edits).map((e) => `=== ${e.path} ===
9364
+ ${e.contents ?? "<deleted>"}`).join(`
9365
+ `);
9366
+ }
9367
+ function lineDiff(current, proposed) {
9368
+ const a = current.split(`
9369
+ `);
9370
+ const b = proposed.split(`
9371
+ `);
9372
+ const out = [];
9373
+ const max = Math.max(a.length, b.length);
9374
+ for (let i = 0;i < max; i++) {
9375
+ const cur = a[i];
9376
+ const prop = b[i];
9377
+ if (cur === prop)
9378
+ continue;
9379
+ if (cur !== undefined)
9380
+ out.push(`- ${cur}`);
9381
+ if (prop !== undefined)
9382
+ out.push(`+ ${prop}`);
9383
+ }
9384
+ return out.join(`
9385
+ `);
9386
+ }
9387
+ function fileChangeDiff(change, read) {
9388
+ const edits = change.edits ?? [];
9389
+ const current = concatCurrent(edits, read);
9390
+ const proposed = concatProposed(edits);
9391
+ return { current, proposed, diff: lineDiff(current, proposed) };
9392
+ }
9393
+
9394
+ // ../mutagent-tools/src/apply/adapters/report-only.ts
9395
+ var reportOnlyAdapter = {
9396
+ id: AdapterId.ReportOnly,
9397
+ live: true,
9398
+ async read(change, ctx) {
9399
+ const content = (change.edits ?? []).map((e) => ctx.io.readFile(`${change.targetRoot}/${e.path}`) ?? "").join(`
9400
+ `);
9401
+ return { rev: "report-only", content, hash: ctx.io.hash(content), exists: true };
9402
+ },
9403
+ async dryRun(change, current, ctx) {
9404
+ const { diff } = fileChangeDiff(change, (p) => ctx.io.readFile(`${change.targetRoot}/${p}`));
9405
+ return {
9406
+ diff,
9407
+ staleHash: current.hash,
9408
+ wouldWrite: false,
9409
+ notes: ["report-only: no write will be performed"]
9410
+ };
9411
+ },
9412
+ async apply(change, current, ctx) {
9413
+ const applyId = ctx.io.uid();
9414
+ return { applyId, newRev: current.rev, notes: ["report-only: no write performed"] };
9415
+ },
9416
+ async rollback(audit) {
9417
+ return {
9418
+ applyId: audit.applyId,
9419
+ restoredRev: audit.fromRev,
9420
+ notes: ["report-only: nothing to roll back"]
9421
+ };
9422
+ },
9423
+ emitAudit(record, ctx) {
9424
+ writeAudit(record, ctx);
9425
+ }
9426
+ };
9427
+
9428
+ // ../mutagent-tools/src/apply/adapters/worktree-pr.ts
9429
+ function sh(ctx, cmd, args, cwd, input) {
9430
+ const r = ctx.io.shell(cmd, args, { cwd, input });
9431
+ if (r.code !== 0) {
9432
+ throw new Error(`\`${cmd} ${args.join(" ")}\` failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);
9433
+ }
9434
+ return r;
9435
+ }
9436
+ function headRev(ctx, cwd) {
9437
+ const r = ctx.io.shell("git", ["rev-parse", "HEAD"], { cwd });
9438
+ return r.code === 0 ? r.stdout.trim() : "unknown";
9439
+ }
9440
+ function parsePrUrl(stdout) {
9441
+ const m = stdout.match(/https?:\/\/\S+/);
9442
+ return m ? m[0] : undefined;
9443
+ }
9444
+ var worktreePrAdapter = {
9445
+ id: AdapterId.WorktreePr,
9446
+ live: true,
9447
+ async read(change, ctx) {
9448
+ const edits = change.edits ?? [];
9449
+ const content = concatCurrent(edits, (p) => ctx.io.readFile(`${change.targetRoot}/${p}`));
9450
+ const exists = edits.some((e) => ctx.io.exists(`${change.targetRoot}/${e.path}`));
9451
+ return { rev: headRev(ctx, change.targetRoot), content, hash: ctx.io.hash(content), exists };
9452
+ },
9453
+ async dryRun(change, current, ctx) {
9454
+ const { diff } = fileChangeDiff(change, (p) => ctx.io.readFile(`${change.targetRoot}/${p}`));
9455
+ const proposed = concatProposed(change.edits ?? []);
9456
+ return {
9457
+ diff,
9458
+ staleHash: current.hash,
9459
+ wouldWrite: proposed !== current.content,
9460
+ notes: kindRunsBuild(change.kind) ? ["code-pr: lint + typecheck will run in the worktree"] : ["markdown: no build step"]
9461
+ };
9462
+ },
9463
+ async apply(change, current, ctx) {
9464
+ const edits = change.edits ?? [];
9465
+ if (edits.length === 0)
9466
+ throw new Error("worktree-PR apply: no edits provided");
9467
+ const now = concatCurrent(edits, (p) => ctx.io.readFile(`${change.targetRoot}/${p}`));
9468
+ if (ctx.io.hash(now) !== current.hash) {
9469
+ throw new Error("worktree-PR apply: target changed since read (stale-hash mismatch) \u2014 re-run dryRun");
9470
+ }
9471
+ const applyId = ctx.io.uid();
9472
+ const branch = `apply/${applyId}`;
9473
+ const wtdir = `${ctx.projectRoot}/.mutagent/apply/${applyId}/worktree`;
9474
+ ctx.io.mkdirp(`${ctx.projectRoot}/.mutagent/apply/${applyId}`);
9475
+ sh(ctx, "git", ["worktree", "add", "-b", branch, wtdir, "HEAD"], change.targetRoot);
9476
+ try {
9477
+ for (const e of edits) {
9478
+ const target = `${wtdir}/${e.path}`;
9479
+ if (e.contents === null)
9480
+ ctx.io.deleteFile(target);
9481
+ else {
9482
+ ctx.io.mkdirp(dirOf(target));
9483
+ ctx.io.writeFile(target, e.contents);
9484
+ }
9485
+ }
9486
+ if (kindRunsBuild(change.kind)) {
9487
+ sh(ctx, "bun", ["run", "lint"], wtdir);
9488
+ sh(ctx, "bun", ["run", "typecheck"], wtdir);
9489
+ }
9490
+ sh(ctx, "git", ["add", "-A"], wtdir);
9491
+ sh(ctx, "git", ["commit", "-m", change.title], wtdir);
9492
+ const newRev = headRev(ctx, wtdir);
9493
+ sh(ctx, "git", ["push", "-u", "origin", branch], wtdir);
9494
+ const pr = sh(ctx, "gh", ["pr", "create", "--base", "main", "--title", change.title, "--body", change.body ?? change.title], wtdir);
9495
+ return { applyId, newRev, ref: parsePrUrl(pr.stdout), notes: [`branch ${branch}`] };
9496
+ } finally {
9497
+ ctx.io.shell("git", ["worktree", "remove", "--force", wtdir], { cwd: change.targetRoot });
9498
+ }
9499
+ },
9500
+ async rollback(audit, ctx) {
9501
+ const branch = `rollback/${audit.applyId}`;
9502
+ const repoRoot = ctx.projectRoot;
9503
+ const wtdir = `${ctx.projectRoot}/.mutagent/apply/${audit.applyId}/rollback-worktree`;
9504
+ ctx.io.mkdirp(`${ctx.projectRoot}/.mutagent/apply/${audit.applyId}`);
9505
+ sh(ctx, "git", ["worktree", "add", "-b", branch, wtdir, "HEAD"], repoRoot);
9506
+ try {
9507
+ sh(ctx, "git", ["revert", "--no-edit", audit.revertToken], wtdir);
9508
+ const restoredRev = headRev(ctx, wtdir);
9509
+ sh(ctx, "git", ["push", "-u", "origin", branch], wtdir);
9510
+ const pr = sh(ctx, "gh", ["pr", "create", "--base", "main", "--title", `Revert ${audit.applyId}`, "--body", `Reverts ${audit.toRev}`], wtdir);
9511
+ return { applyId: audit.applyId, restoredRev, ref: parsePrUrl(pr.stdout), notes: [`revert of ${audit.toRev}`] };
9512
+ } finally {
9513
+ ctx.io.shell("git", ["worktree", "remove", "--force", wtdir], { cwd: repoRoot });
9514
+ }
9515
+ },
9516
+ emitAudit(record, ctx) {
9517
+ writeAudit(record, ctx);
9518
+ }
9519
+ };
9520
+ function dirOf(path) {
9521
+ const i = path.lastIndexOf("/");
9522
+ return i <= 0 ? path : path.slice(0, i);
9523
+ }
9524
+
9525
+ // ../mutagent-tools/src/apply/adapters/rest.ts
9526
+ function authHeaders(change) {
9527
+ const h = { "content-type": "application/json" };
9528
+ if (change.credentialToken)
9529
+ h["authorization"] = `Bearer ${change.credentialToken}`;
9530
+ return h;
9531
+ }
9532
+ function assertOk(r, what) {
9533
+ if (r.status < 200 || r.status >= 300) {
9534
+ throw new Error(`REST ${what} failed (HTTP ${r.status}): ${r.body.slice(0, 200)}`);
9535
+ }
9536
+ return r;
9537
+ }
9538
+ function extractRev(body) {
9539
+ try {
9540
+ const j = JSON.parse(body);
9541
+ for (const k of ["activeRev", "rev", "revision", "version", "id"]) {
9542
+ const v = j[k];
9543
+ if (typeof v === "string" && v)
9544
+ return v;
9545
+ if (typeof v === "number")
9546
+ return String(v);
9547
+ }
9548
+ } catch {}
9549
+ return "";
9550
+ }
9551
+ var restAdapter = {
9552
+ id: AdapterId.Rest,
9553
+ live: true,
9554
+ async read(change, ctx) {
9555
+ const r = assertOk(await ctx.io.http("GET", change.targetRoot, { headers: authHeaders(change) }), "GET");
9556
+ const rev = extractRev(r.body) || "unknown";
9557
+ return { rev, content: r.body, hash: ctx.io.hash(r.body), exists: r.status !== 404 };
9558
+ },
9559
+ async dryRun(change, current, ctx) {
9560
+ const proposed = JSON.stringify(change.config ?? {}, null, 2);
9561
+ return {
9562
+ diff: lineDiff(current.content, proposed),
9563
+ staleHash: current.hash,
9564
+ wouldWrite: ctx.io.hash(proposed) !== current.hash,
9565
+ notes: ["cloud-rest: non-destructive create-rev + activate"]
9566
+ };
9567
+ },
9568
+ async apply(change, current, ctx) {
9569
+ const applyId = ctx.io.uid();
9570
+ const created = assertOk(await ctx.io.http("POST", `${change.targetRoot}/revisions`, {
9571
+ headers: authHeaders(change),
9572
+ body: JSON.stringify(change.config ?? {})
9573
+ }), "POST create-rev");
9574
+ const newRev = extractRev(created.body);
9575
+ if (!newRev)
9576
+ throw new Error("REST apply: create-rev returned no rev id");
9577
+ assertOk(await ctx.io.http("PATCH", change.targetRoot, {
9578
+ headers: authHeaders(change),
9579
+ body: JSON.stringify({ activeRev: newRev })
9580
+ }), "PATCH activate");
9581
+ return { applyId, newRev, ref: newRev, notes: [`activated ${newRev} (prior ${current.rev} retained)`] };
9582
+ },
9583
+ async rollback(audit, ctx) {
9584
+ if (!audit.target)
9585
+ throw new Error("REST rollback: audit has no target url");
9586
+ assertOk(await ctx.io.http("PATCH", audit.target, {
9587
+ body: JSON.stringify({ activeRev: audit.revertToken }),
9588
+ headers: { "content-type": "application/json" }
9589
+ }), "PATCH re-activate");
9590
+ return {
9591
+ applyId: audit.applyId,
9592
+ restoredRev: audit.revertToken,
9593
+ ref: audit.revertToken,
9594
+ notes: [`re-activated ${audit.revertToken}; ${audit.toRev} retained`]
9595
+ };
9596
+ },
9597
+ emitAudit(record, ctx) {
9598
+ writeAudit(record, ctx);
9599
+ }
9600
+ };
9601
+
9602
+ // ../mutagent-tools/src/apply/adapters/vendor-cli.ts
9603
+ function sh2(ctx, cmd, args) {
9604
+ const r = ctx.io.shell(cmd, args);
9605
+ if (r.code !== 0) {
9606
+ throw new Error(`\`${cmd} ${args.join(" ")}\` failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);
9607
+ }
9608
+ return r;
9609
+ }
9610
+ function parseHandle(stdout) {
9611
+ const m = stdout.match(/(?:version|rev|revision|handle)[:=\s]+(\S+)/i) ?? stdout.match(/\bv\d+\b/) ?? stdout.trim().split(`
9612
+ `).pop()?.match(/(\S+)$/);
9613
+ return (Array.isArray(m) ? m[1] ?? m[0] : "") || "";
9614
+ }
9615
+ function vendorBin(change) {
9616
+ if (!change.vendorBin)
9617
+ throw new Error("vendor-CLI: change.vendorBin is required (the deploy binary name)");
9618
+ return change.vendorBin;
9619
+ }
9620
+ var vendorCliAdapter = {
9621
+ id: AdapterId.VendorCli,
9622
+ live: true,
9623
+ async read(change, ctx) {
9624
+ const bin = vendorBin(change);
9625
+ const r = ctx.io.shell(bin, ["get", change.targetRoot]);
9626
+ const content = r.stdout;
9627
+ return { rev: parseHandle(content) || "unknown", content, hash: ctx.io.hash(content), exists: r.code === 0 };
9628
+ },
9629
+ async dryRun(change, current, ctx) {
9630
+ const proposed = JSON.stringify(change.config ?? {}, null, 2);
9631
+ return {
9632
+ diff: lineDiff(current.content, proposed),
9633
+ staleHash: current.hash,
9634
+ wouldWrite: ctx.io.hash(proposed) !== current.hash,
9635
+ notes: [`vendor-CLI (${change.vendorBin ?? "?"}): shell deploy \u2192 new version handle`]
9636
+ };
9637
+ },
9638
+ async apply(change, current, ctx) {
9639
+ const applyId = ctx.io.uid();
9640
+ const bin = vendorBin(change);
9641
+ const cfgPath = `${ctx.projectRoot}/.mutagent/apply/${applyId}/deploy-config.json`;
9642
+ ctx.io.mkdirp(`${ctx.projectRoot}/.mutagent/apply/${applyId}`);
9643
+ ctx.io.writeFile(cfgPath, JSON.stringify(change.config ?? {}, null, 2));
9644
+ const r = sh2(ctx, bin, ["deploy", change.targetRoot, "--config", cfgPath]);
9645
+ const newRev = parseHandle(r.stdout);
9646
+ if (!newRev)
9647
+ throw new Error("vendor-CLI apply: deploy returned no version handle");
9648
+ return { applyId, newRev, ref: newRev, notes: [`deployed ${newRev} (prior ${current.rev} retained)`] };
9649
+ },
9650
+ async rollback(audit, ctx) {
9651
+ const bin = (audit.note ?? "").match(/vendor-CLI \(([^)]+)\)/)?.[1];
9652
+ const [binName, target] = (audit.target ?? "").split("|");
9653
+ const resolvedBin = bin && bin !== "?" ? bin : binName;
9654
+ if (!resolvedBin || !target)
9655
+ throw new Error("vendor-CLI rollback: audit.target must be 'bin|url'");
9656
+ sh2(ctx, resolvedBin, ["rollback", target, "--to", audit.revertToken]);
9657
+ return {
9658
+ applyId: audit.applyId,
9659
+ restoredRev: audit.revertToken,
9660
+ ref: audit.revertToken,
9661
+ notes: [`vendor rollback to ${audit.revertToken}; ${audit.toRev} retained`]
9662
+ };
9663
+ },
9664
+ emitAudit(record, ctx) {
9665
+ writeAudit(record, ctx);
9666
+ }
9667
+ };
9668
+ // ../mutagent-tools/src/apply/target-select.ts
9669
+ var TargetSelectionOutcome = {
9670
+ Resolved: "resolved",
9671
+ NeedsDisambiguation: "needs-disambiguation",
9672
+ NoTarget: "no-target"
9673
+ };
9674
+ function selectApplyTarget(candidates, pick) {
9675
+ const uniq = [...new Set(candidates.filter((c) => c.trim().length > 0))];
9676
+ if (uniq.length === 0) {
9677
+ return { outcome: TargetSelectionOutcome.NoTarget };
9678
+ }
9679
+ if (uniq.length === 1) {
9680
+ return { outcome: TargetSelectionOutcome.Resolved, target: uniq[0] };
9681
+ }
9682
+ if (pick !== undefined && uniq.includes(pick)) {
9683
+ return { outcome: TargetSelectionOutcome.Resolved, target: pick };
9684
+ }
9685
+ return {
9686
+ outcome: TargetSelectionOutcome.NeedsDisambiguation,
9687
+ candidates: uniq,
9688
+ reason: pick !== undefined ? `--pick '${pick}' is not one of the ${uniq.length} candidate targets` : `${uniq.length} candidate targets present and no explicit --pick \u2014 refusing to default`
9689
+ };
9690
+ }
9691
+
9692
+ // ../mutagent-tools/src/apply/index.ts
9693
+ function registerBuiltinAdapters2() {
9694
+ clearTargetAdapters();
9695
+ registerTargetAdapter(reportOnlyAdapter);
9696
+ registerTargetAdapter(worktreePrAdapter);
9697
+ registerTargetAdapter(restAdapter);
9698
+ registerTargetAdapter(vendorCliAdapter);
9699
+ }
9700
+ async function runApplyFlow(change, ctx, mode) {
9701
+ const adapterId = adapterIdForKind(change.kind, { platformTransport: change.platformTransport });
9702
+ const adapter = getTargetAdapter(adapterId);
9703
+ const read = await adapter.read(change, ctx);
9704
+ const dryRun = await adapter.dryRun(change, read, ctx);
9705
+ if (mode === "dry-run") {
9706
+ return { adapterId, read, dryRun };
9707
+ }
9708
+ const applyResult = await adapter.apply(change, read, ctx);
9709
+ const audit = {
9710
+ applyId: applyResult.applyId,
9711
+ transport: adapterId,
9712
+ kind: change.kind,
9713
+ subject: change.subject,
9714
+ target: change.targetRoot,
9715
+ mode: change.kind === ApplyKind.ReportOnly ? "report-only" : "apply",
9716
+ fromRev: read.rev,
9717
+ toRev: applyResult.newRev,
9718
+ staleHash: dryRun.staleHash,
9719
+ revertToken: read.rev,
9720
+ ref: applyResult.ref,
9721
+ producedAt: ctx.io.producedAt,
9722
+ note: (applyResult.notes ?? []).join("; ") || undefined
9723
+ };
9724
+ adapter.emitAudit(audit, ctx);
9725
+ return { adapterId, read, dryRun, applyResult, audit };
9726
+ }
9727
+ async function runRollbackFlow(audit, ctx) {
9728
+ const adapter = getTargetAdapter(audit.transport);
9729
+ const rollback = await adapter.rollback(audit, ctx);
9730
+ const record = {
9731
+ applyId: `${audit.applyId}-rollback`,
9732
+ transport: audit.transport,
9733
+ kind: audit.kind,
9734
+ subject: audit.subject,
9735
+ target: audit.target,
9736
+ mode: "rollback",
9737
+ fromRev: audit.toRev,
9738
+ toRev: rollback.restoredRev,
9739
+ staleHash: audit.staleHash,
9740
+ revertToken: audit.toRev,
9741
+ ref: rollback.ref,
9742
+ producedAt: ctx.io.producedAt,
9743
+ note: (rollback.notes ?? []).join("; ") || undefined
9744
+ };
9745
+ adapter.emitAudit(record, ctx);
9746
+ return { rollback, audit: record };
9747
+ }
9748
+
9749
+ // ../mutagent-tools/src/cli/apply.ts
9215
9750
  function usage(err) {
9751
+ err("mutagent-cli apply \u2014 shared write transport (worktree-PR \xB7 REST \xB7 vendor-CLI)");
9752
+ err("");
9753
+ err("usage: mutagent-cli apply --kind <code-pr|markdown|cloud-deploy|report-only> --target <root>");
9754
+ err(" (--edit <relpath>=<srcfile>)... | --edits-json <file> # file transports");
9755
+ err(" [--config-json <file>] # platform transports");
9756
+ err(" --title <t> [--body <b>] [--subject <name>]");
9757
+ err(" [--platform-transport rest|vendor-cli] [--vendor-bin <bin>] [--token-ref <ENV>]");
9758
+ err(" [--dry-run | --commit] # default: --dry-run");
9759
+ err(" mutagent-cli apply --rollback <apply-audit.json>");
9760
+ err("");
9761
+ err(" read() + dryRun() are always safe. apply() writes ONLY on --commit, AFTER the caller's gate.");
9762
+ }
9763
+ function buildApplyIo(base) {
9764
+ return {
9765
+ now: base.now,
9766
+ producedAt: base.producedAt,
9767
+ out: base.out,
9768
+ err: base.err,
9769
+ readFile: (p) => existsSync4(p) ? readFileSync4(p, "utf8") : undefined,
9770
+ writeFile: (p, c) => {
9771
+ mkdirSync2(dirname2(p), { recursive: true });
9772
+ writeFileSync2(p, c);
9773
+ },
9774
+ deleteFile: (p) => {
9775
+ if (existsSync4(p))
9776
+ rmSync(p, { force: true });
9777
+ },
9778
+ exists: (p) => existsSync4(p),
9779
+ mkdirp: (p) => mkdirSync2(p, { recursive: true }),
9780
+ shell: (cmd, args, opts) => {
9781
+ const r = spawnSync(cmd, args, {
9782
+ cwd: opts?.cwd,
9783
+ input: opts?.input,
9784
+ encoding: "utf8"
9785
+ });
9786
+ return {
9787
+ stdout: r.stdout ?? "",
9788
+ stderr: r.stderr ?? (r.error ? r.error.message : ""),
9789
+ code: r.status ?? (r.error ? 1 : 0)
9790
+ };
9791
+ },
9792
+ http: async (method, url, opts) => {
9793
+ const res = await fetch(url, { method, headers: opts?.headers, body: opts?.body });
9794
+ const body = await res.text();
9795
+ return { status: res.status, body };
9796
+ },
9797
+ hash: (s) => createHash3("sha256").update(s).digest("hex").slice(0, 16),
9798
+ uid: () => randomUUID().slice(0, 8)
9799
+ };
9800
+ }
9801
+ function buildEdits(flags, cwd) {
9802
+ const editsJson = getStr(flags, "edits-json");
9803
+ if (editsJson) {
9804
+ const raw = readFileSync4(resolveRel(editsJson, cwd), "utf8");
9805
+ const parsed = JSON.parse(raw);
9806
+ if (!Array.isArray(parsed))
9807
+ throw new Error("--edits-json must be a JSON array of {path, contents}");
9808
+ return parsed;
9809
+ }
9810
+ const specs = getList(flags, "edit") ?? [];
9811
+ return specs.map((spec) => {
9812
+ const eq = spec.indexOf("=");
9813
+ if (eq < 0)
9814
+ throw new Error(`--edit expects '<relpath>=<srcfile>' (got '${spec}')`);
9815
+ const path = spec.slice(0, eq);
9816
+ const src = spec.slice(eq + 1);
9817
+ return { path, contents: readFileSync4(resolveRel(src, cwd), "utf8") };
9818
+ });
9819
+ }
9820
+ function resolveRel(p, cwd) {
9821
+ return p.startsWith("/") ? p : `${cwd}/${p}`;
9822
+ }
9823
+ var VALID_KINDS = new Set(Object.values(ApplyKind));
9824
+ async function runApply(argv, base, cwd) {
9825
+ const { flags } = parseArgs(argv);
9826
+ if (getBool(flags, "help") || argv.length === 0) {
9827
+ usage(base.err);
9828
+ return argv.length === 0 ? 1 : 0;
9829
+ }
9830
+ registerBuiltinAdapters2();
9831
+ const io = buildApplyIo(base);
9832
+ const ctx = { io, projectRoot: cwd };
9833
+ const rollbackPath = getStr(flags, "rollback");
9834
+ if (rollbackPath) {
9835
+ const auditRaw = readFileSync4(resolveRel(rollbackPath, cwd), "utf8");
9836
+ const audit = JSON.parse(auditRaw);
9837
+ const { rollback, audit: record } = await runRollbackFlow(audit, ctx);
9838
+ base.out(`\u2705 rollback: restored ${rollback.restoredRev}${rollback.ref ? ` (${rollback.ref})` : ""}`);
9839
+ base.out(` audit: .mutagent/apply/${record.applyId}/apply-audit.json`);
9840
+ return 0;
9841
+ }
9842
+ const kind = getStr(flags, "kind");
9843
+ if (!kind || !VALID_KINDS.has(kind)) {
9844
+ base.err(`apply: --kind must be one of ${[...VALID_KINDS].join(" | ")}`);
9845
+ return 1;
9846
+ }
9847
+ const candidates = getList(flags, "target") ?? [];
9848
+ const selection = selectApplyTarget(candidates, getStr(flags, "pick"));
9849
+ if (selection.outcome === TargetSelectionOutcome.NoTarget) {
9850
+ base.err("apply: --target is required");
9851
+ return 1;
9852
+ }
9853
+ if (selection.outcome === TargetSelectionOutcome.NeedsDisambiguation) {
9854
+ base.err(`apply: ambiguous target \u2014 ${selection.reason}`);
9855
+ base.err(` candidates: ${selection.candidates.join(", ")}`);
9856
+ base.err(" re-run with an explicit --pick <target> (the session must confirm which target to bind)");
9857
+ return 2;
9858
+ }
9859
+ const target = selection.target;
9860
+ const title = getStr(flags, "title") ?? "mutagent apply";
9861
+ const tokenRef = getStr(flags, "token-ref");
9862
+ const change = {
9863
+ kind,
9864
+ targetRoot: target,
9865
+ title,
9866
+ body: getStr(flags, "body"),
9867
+ subject: getStr(flags, "subject"),
9868
+ platformTransport: getStr(flags, "platform-transport"),
9869
+ vendorBin: getStr(flags, "vendor-bin"),
9870
+ credentialToken: tokenRef ? process.env[tokenRef] : undefined
9871
+ };
9872
+ if (kind === ApplyKind.CodePr || kind === ApplyKind.Markdown || kind === ApplyKind.ReportOnly) {
9873
+ change.edits = buildEdits(flags, cwd);
9874
+ }
9875
+ const configJson = getStr(flags, "config-json");
9876
+ if (configJson)
9877
+ change.config = JSON.parse(readFileSync4(resolveRel(configJson, cwd), "utf8"));
9878
+ const mode = getBool(flags, "commit") ? "commit" : "dry-run";
9879
+ const result = await runApplyFlow(change, ctx, mode);
9880
+ base.out(`transport: ${result.adapterId} \xB7 kind: ${kind} \xB7 from-rev: ${result.read.rev}`);
9881
+ base.out(`dry-run: ${result.dryRun.wouldWrite ? "WOULD write" : "no change"} \xB7 stale-hash: ${result.dryRun.staleHash}`);
9882
+ if (result.dryRun.diff)
9883
+ base.out(`--- diff ---
9884
+ ${result.dryRun.diff}`);
9885
+ if (mode === "dry-run") {
9886
+ base.out("(dry-run \u2014 no write; re-run with --commit after the caller's gate)");
9887
+ return 0;
9888
+ }
9889
+ const a = result.audit;
9890
+ base.out(`\u2705 applied: ${a?.fromRev} \u2192 ${a?.toRev}${result.applyResult?.ref ? ` (${result.applyResult.ref})` : ""}`);
9891
+ base.out(` audit: .mutagent/apply/${a?.applyId}/apply-audit.json (revert-token: ${a?.revertToken})`);
9892
+ return 0;
9893
+ }
9894
+
9895
+ // ../mutagent-tools/src/cli/index.ts
9896
+ function usage2(err) {
9216
9897
  err("mutagent-cli \u2014 MutagenT infra CLI");
9217
9898
  err("");
9218
9899
  err("usage: mutagent-cli <command> [args]");
@@ -9229,6 +9910,13 @@ function usage(err) {
9229
9910
  err(" trace select <--in <unitf.jsonl>|-> [--select <preset>] [--signal <flags>]");
9230
9911
  err(" [--signal-mode and|or] [--min-worthiness <0..1>] [--export <path.jsonl>]");
9231
9912
  err("");
9913
+ err(" apply --kind <code-pr|markdown|cloud-deploy|report-only> --target <root>");
9914
+ err(" (--edit <relpath>=<src>)... | --edits-json <f> | --config-json <f>");
9915
+ err(" --title <t> [--body <b>] [--subject <n>] [--platform-transport rest|vendor-cli]");
9916
+ err(" [--vendor-bin <bin>] [--token-ref <ENV>] [--dry-run | --commit]");
9917
+ err(" apply --rollback <apply-audit.json>");
9918
+ err(" the SHARED write transport \u2014 the caller gates BEFORE --commit (no approval logic here).");
9919
+ err("");
9232
9920
  err(" see references/TRACES-CLI.md for the full contract + UniTF format.");
9233
9921
  }
9234
9922
  async function main() {
@@ -9246,14 +9934,17 @@ async function main() {
9246
9934
  err
9247
9935
  };
9248
9936
  if (command === undefined || command === "--help" || command === "-h" || command === "help") {
9249
- usage(err);
9937
+ usage2(err);
9250
9938
  return command === undefined ? 1 : 0;
9251
9939
  }
9252
9940
  if (command === "trace") {
9253
9941
  return runTrace(rest, io, process.cwd());
9254
9942
  }
9943
+ if (command === "apply") {
9944
+ return runApply(rest, io, process.cwd());
9945
+ }
9255
9946
  err(`mutagent-cli: unknown command '${command}'`);
9256
- usage(err);
9947
+ usage2(err);
9257
9948
  return 1;
9258
9949
  }
9259
9950
  main().then((code) => process.exit(code)).catch((e) => {