jules-orchestrator-kit 0.74.0 → 0.75.1

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/bin/agentctl.mjs CHANGED
@@ -5,10 +5,11 @@ import { readFileSync, existsSync, readdirSync, statSync, renameSync, mkdirSync
5
5
  import { join, resolve, relative } from "node:path";
6
6
  import { applyEnvAliases } from "../src/env-aliases.mjs";
7
7
  import { selectFailureOutput } from "../src/ops/verify-output.mjs";
8
+ import { diagnoseVerifyFailure } from "../src/ops/toolchain-diagnostics.mjs";
8
9
  import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
9
10
  import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
10
- import { acquireLock, releaseLock, lockStatus, getQueueDir } from "../src/state.mjs";
11
- import { parseEnvelopeHeader } from "../src/envelope.mjs";
11
+ import { acquireLock, releaseLock, reapStaleLocks, lockStatus, getQueueDir } from "../src/state.mjs";
12
+ import { parseEnvelopeHeader, validateEnvelope } from "../src/envelope.mjs";
12
13
  import { worktreePrune } from "../src/git.mjs";
13
14
  import { reapOrphanedIntents, reapStaleMutexDirs } from "../src/journal.mjs";
14
15
  import { KIT_VERSION } from "../src/version.mjs";
@@ -89,7 +90,8 @@ export function printHelp() {
89
90
  lines.push(" --prompt-file, -f Read the prompt from a file (-f is --fix on task optimize)");
90
91
  lines.push(" --role, -r Specify specialist agent role (auditor | performance | security | hygiene | resilience | types | debugger | testing | e2e | database | docs | a11y)");
91
92
  lines.push(" --tier Force routing tier when router.enabled (fast | complex) — see .agent/config.yml router:");
92
- lines.push(" --check-premise Verify task goal/oracle passes locally before burning API budget");
93
+ lines.push(" --check-premise Skip only when an explicit --goal-check proves the task complete");
94
+ lines.push(" --goal-check <cmd> Objective-specific command; distinct from --verify-cmd");
93
95
  lines.push(" --dag Execute queue tasks via DAG dependency resolution");
94
96
  lines.push(" --dry-run, -d Simulate action without making API calls or modifying git");
95
97
  lines.push(" --mode, -m Gate evaluation mode (working-tree | committed | staged)");
@@ -268,6 +270,11 @@ async function main() {
268
270
  // tool; a newcomer cannot tell which entry is step one, and guessing wrong
269
271
  // costs them a confusing failure instead of a hint.
270
272
  if (!command) {
273
+ if (args.includes("-i") || args.includes("--interactive")) {
274
+ const { runTuiMenu } = await import("../src/ops/tui-menu.mjs");
275
+ await runTuiMenu(resolveRoot());
276
+ process.exit(0);
277
+ }
271
278
  const { resolveNextStep, renderNextStep } = await import("../src/ops/next-step.mjs");
272
279
  const cwd = process.cwd();
273
280
  let budgetLine = "";
@@ -308,9 +315,14 @@ async function main() {
308
315
  // Init is a configuration boundary, not an operational maintenance command.
309
316
  // In particular, --dry-run must not create .agent/ indirectly by reaping
310
317
  // journal intents or mutex directories before init itself gets control.
311
- if (command !== "init") {
318
+ // init, uninstall and migrate must not create kit state as a side-effect of
319
+ // startup maintenance — dry-run and "nothing installed" paths need a clean tree.
320
+ if (command !== "init" && command !== "uninstall" && command !== "migrate") {
312
321
  reapOrphanedIntents(root);
313
322
  reapStaleMutexDirs(root);
323
+ if (command !== "lock") {
324
+ reapStaleLocks(root);
325
+ }
314
326
  }
315
327
  const config = loadConfig(root);
316
328
 
@@ -331,7 +343,9 @@ async function main() {
331
343
  "require-plan-approval": { type: "boolean" },
332
344
  "auto-approve-plans": { type: "boolean" },
333
345
  "auto-approve": { type: "boolean" },
346
+ "auto-reply": { type: "boolean" },
334
347
  "check-premise": { type: "boolean" },
348
+ "goal-check": { type: "string" },
335
349
  idempotent: { type: "boolean" },
336
350
  author: { type: "string" },
337
351
  "verify-cmd": { type: "string", short: "v" },
@@ -383,6 +397,7 @@ async function main() {
383
397
  }
384
398
 
385
399
  const autoApprove = Boolean(values["auto-approve-plans"] || values["auto-approve"]);
400
+ const autoReply = Boolean(values["auto-reply"] || values.autoReply);
386
401
  const task = {
387
402
  title: values.title || "CLI Dispatch Task",
388
403
  prompt: promptContent,
@@ -393,7 +408,10 @@ async function main() {
393
408
  repoless: values.repoless,
394
409
  autoPr: values["auto-pr"],
395
410
  requirePlanApproval: autoApprove ? false : values["require-plan-approval"],
411
+ autoApprovePlan: autoApprove,
412
+ autoReply,
396
413
  checkPremise: values["check-premise"] || values.idempotent,
414
+ goalCheck: values["goal-check"],
397
415
  author: values.author,
398
416
  verifyCmd: values["verify-cmd"] || values.verify,
399
417
  };
@@ -407,13 +425,15 @@ async function main() {
407
425
  source: values.source,
408
426
  branch: values.branch,
409
427
  checkPremise: values["check-premise"] || values.idempotent,
428
+ autoApprovePlan: autoApprove,
429
+ autoReply,
410
430
  });
411
431
  if (values.json) {
412
432
  console.log(JSON.stringify({ ok: true, session }, null, 2));
413
433
  } else {
414
434
  if (session.status === "ALREADY_SATISFIED" || session.skipped) {
415
435
  console.log(`\n⚡ Task Already Satisfied (skipped dispatch):`);
416
- console.log(` Reason: ${session.reason || "Verification oracle already passing on base branch."}`);
436
+ console.log(` Reason: ${session.reason || "Explicit goal check passed on a clean revision."}`);
417
437
  } else if (values["dry-run"]) {
418
438
  // The dry run reached the provider adapter and stopped short of the
419
439
  // call. Printing the same "Dispatched Successfully!" banner as a
@@ -424,9 +444,16 @@ async function main() {
424
444
  console.log(` Title : ${task.title}`);
425
445
  console.log(` Provider : ${session.provider || config.provider || "jules"}`);
426
446
  if (task.role) console.log(` Role : ${task.role}`);
447
+ if (task.branch) console.log(` Branch : ${task.branch}`);
448
+ if (task.verifyCmd) console.log(` Verify Cmd : ${task.verifyCmd}`);
427
449
  if (session._routeTier) {
428
450
  console.log(` Router Tier : ${session._routeTier} (${session._routeReason || "n/a"})`);
429
451
  }
452
+ if (task.prompt) {
453
+ console.log(`\n--- Task Payload Preview ---`);
454
+ console.log(String(task.prompt).trim());
455
+ console.log(`----------------------------`);
456
+ }
430
457
  console.log(`\n Re-run without --dry-run to dispatch for real.`);
431
458
  } else {
432
459
  console.log(`\n✅ Task Dispatched Successfully!`);
@@ -690,9 +717,14 @@ async function main() {
690
717
  console.log(` • Or set it by hand: verify.test in ${config._file || ".agent/config.yml"}`);
691
718
  console.log(` • Scope- and secret-scanning only, on purpose? Set verify.required: false there.\n`);
692
719
  } else if (failedPhase === "verify" || failedPhase === "evidence") {
720
+ const verifyFailure =
721
+ res.phases.find((p) => (p.phase === "verify" || p.phase === "evidence") && !p.ok)?.failure || {};
722
+ const diagnosis = diagnoseVerifyFailure(verifyFailure);
693
723
  console.log(`💡 Remediation Hint (Exit ${res.code} Verification Failed):`);
694
- console.log(` • The stage above exited non-zero. Reproduce it locally, then re-run the gate.`);
695
- console.log(` • To start an explicit repair workflow, pipe the failing command's output to: agentctl repair\n`);
724
+ for (const line of diagnosis.lines) {
725
+ console.log(` • ${line}`);
726
+ }
727
+ console.log("");
696
728
  } else if (res.code === 3) {
697
729
  // The same violation has two very different causes. Right after
698
730
  // `init`, every offending path is a file the tool itself just wrote
@@ -992,6 +1024,8 @@ async function main() {
992
1024
  cmd: { type: "string", short: "c" },
993
1025
  task: { type: "boolean", short: "t" },
994
1026
  author: { type: "string" },
1027
+ "auto-approve": { type: "boolean" },
1028
+ "auto-reply": { type: "boolean" },
995
1029
  json: { type: "boolean", short: "j" },
996
1030
  "dry-run": { type: "boolean" },
997
1031
  },
@@ -1038,17 +1072,59 @@ async function main() {
1038
1072
  }
1039
1073
  process.exit(0);
1040
1074
  } else {
1041
- console.log(`\n🔧 Dispatching OODA Repair Loop for captured failure trace...`);
1075
+ if (!values.json) {
1076
+ console.log(`\n🔧 Dispatching OODA Repair Loop for captured failure trace...`);
1077
+ }
1042
1078
  const repairRes = await repair(
1043
1079
  { stderr: cleanTrace, command: values.cmd || "verify" },
1044
- { root, dryRun: values["dry-run"], author: values.author }
1080
+ {
1081
+ root,
1082
+ dryRun: values["dry-run"],
1083
+ author: values.author,
1084
+ autoApprove: values["auto-approve"],
1085
+ autoReply: values["auto-reply"],
1086
+ }
1045
1087
  );
1046
1088
 
1047
1089
  if (values.json) {
1048
- console.log(JSON.stringify(repairRes, null, 2));
1090
+ process.stdout.write(JSON.stringify(repairRes, null, 2) + "\n", () => {
1091
+ process.exit(repairRes.ok ? 0 : 1);
1092
+ });
1093
+ setTimeout(() => process.exit(repairRes.ok ? 0 : 1), 1000).unref();
1094
+ break;
1049
1095
  } else {
1050
1096
  console.log(`------------------------------------------------------------------`);
1051
1097
  console.log(`Repair Status: ${repairRes.ok ? "RESOLVED (Exit 0)" : `FAILED (${repairRes.finalStatus} — Exit 1)`}`);
1098
+ if (!repairRes.ok && Array.isArray(repairRes.attempts) && repairRes.attempts.length > 0) {
1099
+ console.log(`\nRepair attempt review:`);
1100
+ if (repairRes.repairDir) {
1101
+ console.log(` Retained under: ${repairRes.repairDir}`);
1102
+ }
1103
+ for (const attempt of repairRes.attempts) {
1104
+ const phase =
1105
+ attempt.phase ||
1106
+ attempt.diagnostics?.phase ||
1107
+ (attempt.verified === true ? "verified" : attempt.verified === false ? "verify" : "unknown");
1108
+ const errRaw = attempt.error || attempt.diagnostics?.error || "";
1109
+ const concise = String(errRaw).split("\n")[0].slice(0, 200);
1110
+ const diff = typeof attempt.diff === "string" ? attempt.diff : "";
1111
+ const fileCount = [...diff.matchAll(/^diff --git /gm)].length;
1112
+ const lineCount = diff
1113
+ .split("\n")
1114
+ .filter((line) => (line.startsWith("+") && !line.startsWith("+++")) || (line.startsWith("-") && !line.startsWith("---")))
1115
+ .length;
1116
+ console.log(` #${attempt.n}: phase=${phase} ok=${attempt.ok}${concise ? ` error=${JSON.stringify(concise)}` : ""}`);
1117
+ if (attempt.diff !== undefined) {
1118
+ console.log(` diff: ${fileCount} file(s), ${lineCount} changed line(s)`);
1119
+ }
1120
+ if (attempt.diagnostics?.patchPath) {
1121
+ console.log(` patch: ${attempt.diagnostics.patchPath}`);
1122
+ }
1123
+ if (attempt.diagnostics?.diagnosticsPath) {
1124
+ console.log(` diagnostics: ${attempt.diagnostics.diagnosticsPath}`);
1125
+ }
1126
+ }
1127
+ }
1052
1128
  }
1053
1129
 
1054
1130
  process.exit(repairRes.ok ? 0 : 1);
@@ -1517,6 +1593,7 @@ async function main() {
1517
1593
  options: {
1518
1594
  ttl: { type: "string" },
1519
1595
  pid: { type: "string" },
1596
+ "dry-run": { type: "boolean", short: "d" },
1520
1597
  json: { type: "boolean", short: "j" },
1521
1598
  },
1522
1599
  allowPositionals: true,
@@ -1581,22 +1658,49 @@ async function main() {
1581
1658
  console.log(`❌ Lock for ${taskId} not found or release failed`);
1582
1659
  process.exit(1);
1583
1660
  }
1661
+ } else if (action === "reap" || action === "prune") {
1662
+ const dryRun = Boolean(lockValues["dry-run"]);
1663
+ const res = reapStaleLocks(root, { dryRun });
1664
+ if (lockValues.json) {
1665
+ console.log(JSON.stringify(res, null, 2));
1666
+ process.exit(0);
1667
+ }
1668
+ if (res.reapedCount === 0) {
1669
+ console.log("✅ No stale locks found.");
1670
+ } else {
1671
+ const verb = dryRun ? "Would reap" : "Reaped";
1672
+ console.log(`✅ ${verb} ${res.reapedCount} stale lock(s):`);
1673
+ for (const item of res.reaped) {
1674
+ console.log(` • ${item.taskId || item.file} (${item.reason})`);
1675
+ }
1676
+ }
1677
+ process.exit(0);
1584
1678
  } else {
1585
- const locks = lockStatus(root);
1679
+ const allLocks = lockStatus(root);
1680
+ const activeLocks = allLocks.filter((l) => l.live);
1681
+ const staleLocks = allLocks.filter((l) => !l.live);
1586
1682
  if (lockValues.json) {
1587
- console.log(JSON.stringify({ ok: true, locks }, null, 2));
1683
+ console.log(JSON.stringify({ ok: true, locks: allLocks, activeCount: activeLocks.length, staleCount: staleLocks.length }, null, 2));
1588
1684
  process.exit(0);
1589
1685
  }
1590
- if (locks.length === 0) {
1686
+ if (allLocks.length === 0) {
1591
1687
  console.log("Active Locks: 0 (no held locks)");
1592
1688
  } else {
1593
- console.log(`Active Locks (${locks.length}):`);
1594
- for (const l of locks) {
1689
+ console.log(`Active Locks (${activeLocks.length}):`);
1690
+ for (const l of activeLocks) {
1595
1691
  const files = Array.isArray(l.files) ? l.files.join(", ") : (l.files || "");
1596
1692
  const expires = l.expiresAt ? `expires ${l.expiresAt}` : (l.ownerPid ? `pid ${l.ownerPid}` : "active");
1597
1693
  console.log(` • taskId: ${l.taskId || l.id} [${l.agent || "unknown"}] (${expires})`);
1598
1694
  if (files) console.log(` Paths: ${files}`);
1599
1695
  }
1696
+ if (staleLocks.length > 0) {
1697
+ console.log(`\nStale Locks (${staleLocks.length}):`);
1698
+ for (const l of staleLocks) {
1699
+ const reason = l.leased ? "expired lease" : `dead pid ${l.pid || "unknown"}`;
1700
+ console.log(` • taskId: ${l.taskId || l.id} [${l.agent || "unknown"}] (${reason})`);
1701
+ }
1702
+ console.log(` 💡 Run \`agentctl lock reap\` to clean up stale locks.`);
1703
+ }
1600
1704
  }
1601
1705
  }
1602
1706
  process.exit(0);
@@ -1791,7 +1895,7 @@ async function main() {
1791
1895
  case "ci": {
1792
1896
  const sub = args[1];
1793
1897
  if (sub !== "init") {
1794
- console.error(`❌ Unknown 'ci' action '${sub || ""}'. Usage: agentctl ci init [--target github|gitlab] [--force]`);
1898
+ console.error(`❌ Unknown 'ci' action '${sub || ""}'. Usage: agentctl ci init [--target github|gitlab] [--force] [--with-automerge]`);
1795
1899
  process.exit(2);
1796
1900
  }
1797
1901
  const { values } = parseArgs({
@@ -1801,6 +1905,7 @@ async function main() {
1801
1905
  force: { type: "boolean", short: "f" },
1802
1906
  "dry-run": { type: "boolean", short: "d" },
1803
1907
  json: { type: "boolean", short: "j" },
1908
+ "with-automerge": { type: "boolean" },
1804
1909
  },
1805
1910
  allowPositionals: true,
1806
1911
  });
@@ -1812,6 +1917,7 @@ async function main() {
1812
1917
  config,
1813
1918
  version: VERSION,
1814
1919
  stack: detectStack(root),
1920
+ withAutomerge: values["with-automerge"],
1815
1921
  });
1816
1922
 
1817
1923
  if (values.json) {
@@ -1822,9 +1928,13 @@ async function main() {
1822
1928
  console.error(`❌ ${res.error}`);
1823
1929
  process.exit(1);
1824
1930
  }
1825
- console.log(`✅ ${res.written ? "Wrote" : "Would write"} ${res.file}`);
1931
+ const filesDesc = Array.isArray(res.files) ? res.files.join(", ") : res.file;
1932
+ console.log(`✅ ${res.written ? "Wrote" : "Would write"} ${filesDesc}`);
1826
1933
  console.log(` Stack: ${res.stack} · runtime setup: ${res.setupSummary}`);
1827
1934
  console.log(` The workflow runs: agentctl check --mode committed`);
1935
+ if (values["with-automerge"]) {
1936
+ console.log(` Auto-merge: enabled for authorized agent pull requests via workflow_run`);
1937
+ }
1828
1938
  if (!res.written && !values["dry-run"]) {
1829
1939
  console.log(` (unchanged — pass --force to overwrite)`);
1830
1940
  }
@@ -1921,6 +2031,15 @@ async function main() {
1921
2031
  break;
1922
2032
  }
1923
2033
 
2034
+ case "menu":
2035
+ case "tui":
2036
+ case "ui": {
2037
+ const { runTuiMenu } = await import("../src/ops/tui-menu.mjs");
2038
+ await runTuiMenu(root);
2039
+ process.exit(0);
2040
+ break;
2041
+ }
2042
+
1924
2043
  case "init": {
1925
2044
  const { values } = parseArgs({
1926
2045
  args: args.slice(1),
@@ -2099,6 +2218,7 @@ async function main() {
2099
2218
  yes: { type: "boolean", short: "y" },
2100
2219
  json: { type: "boolean", short: "j" },
2101
2220
  "dry-run": { type: "boolean", short: "d" },
2221
+ "allow-protected": { type: "boolean" },
2102
2222
  },
2103
2223
  allowPositionals: true,
2104
2224
  });
@@ -2129,6 +2249,7 @@ async function main() {
2129
2249
  repoless: values.repoless,
2130
2250
  interactive: isInteractive,
2131
2251
  dryRun: values["dry-run"],
2252
+ allowProtected: Boolean(values["allow-protected"]),
2132
2253
  });
2133
2254
 
2134
2255
  if (values.json) {
@@ -2255,8 +2376,87 @@ async function main() {
2255
2376
  console.log(`--------------------------------------------------\n`);
2256
2377
  }
2257
2378
  process.exit(analysis.isFalsifiable ? 0 : 1);
2379
+ } else if (subCommand === "validate") {
2380
+ const { values, positionals } = parseArgs({
2381
+ args: args.slice(2),
2382
+ options: {
2383
+ json: { type: "boolean", short: "j" },
2384
+ },
2385
+ allowPositionals: true,
2386
+ });
2387
+
2388
+ const target = positionals[0];
2389
+ if (!target) {
2390
+ console.error("Usage: agentctl task validate <path-to-envelope.md|json> [--json]");
2391
+ process.exit(1);
2392
+ }
2393
+
2394
+ const targetPath = resolve(root, target);
2395
+ if (!existsSync(targetPath)) {
2396
+ console.error(`Error: Envelope file not found: ${target}`);
2397
+ process.exit(1);
2398
+ }
2399
+
2400
+ let payload = null;
2401
+ try {
2402
+ const content = readFileSync(targetPath, "utf-8");
2403
+ if (targetPath.endsWith(".json") || target.toLowerCase().endsWith(".json")) {
2404
+ payload = JSON.parse(content);
2405
+ } else {
2406
+ payload = parseEnvelopeHeader(content);
2407
+ if (!payload || typeof payload !== "object") {
2408
+ try {
2409
+ payload = JSON.parse(content);
2410
+ } catch {
2411
+ payload = null;
2412
+ }
2413
+ }
2414
+ }
2415
+ } catch (err) {
2416
+ console.error(`Error: Failed to read or parse envelope: ${err.message}`);
2417
+ process.exit(1);
2418
+ }
2419
+
2420
+ if (!payload || typeof payload !== "object") {
2421
+ console.error("❌ TASK ENVELOPE VALIDATION FAILED:");
2422
+ console.error(" - File does not contain a valid JSON payload or task envelope frontmatter.");
2423
+ process.exit(1);
2424
+ }
2425
+
2426
+ const res = validateEnvelope(payload, { root });
2427
+ const result = {
2428
+ ok: res.ok === true,
2429
+ file: target,
2430
+ errors: Array.isArray(res.errors) ? res.errors : [],
2431
+ warnings: Array.isArray(res.warnings) ? res.warnings : [],
2432
+ };
2433
+
2434
+ if (values.json) {
2435
+ console.log(JSON.stringify(result, null, 2));
2436
+ } else if (!result.ok) {
2437
+ console.error("❌ TASK ENVELOPE VALIDATION FAILED:");
2438
+ for (const err of result.errors) {
2439
+ console.error(` - ${err}`);
2440
+ }
2441
+ if (result.warnings.length > 0) {
2442
+ console.warn("⚠️ TASK ENVELOPE WARNINGS:");
2443
+ for (const w of result.warnings) {
2444
+ console.warn(` - ${w}`);
2445
+ }
2446
+ }
2447
+ } else {
2448
+ if (result.warnings.length > 0) {
2449
+ console.warn("⚠️ TASK ENVELOPE WARNINGS:");
2450
+ for (const w of result.warnings) {
2451
+ console.warn(` - ${w}`);
2452
+ }
2453
+ }
2454
+ console.log(`✅ Task envelope validated successfully: ${target}`);
2455
+ }
2456
+
2457
+ process.exit(result.ok ? 0 : 1);
2258
2458
  } else {
2259
- console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create, agentctl task optimize, agentctl task template`);
2459
+ console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create, agentctl task optimize, agentctl task template, agentctl task validate`);
2260
2460
  process.exit(1);
2261
2461
  }
2262
2462
  break;
@@ -2279,7 +2479,7 @@ async function main() {
2279
2479
  version: VERSION,
2280
2480
  root,
2281
2481
  pendingTasks: files.length,
2282
- activeLocks: lockStatus(root).length,
2482
+ activeLocks: lockStatus(root, { activeOnly: true }).length,
2283
2483
  budget,
2284
2484
  }, null, 2));
2285
2485
  process.exit(0);
@@ -2288,7 +2488,7 @@ async function main() {
2288
2488
  console.log(`--------------------------------------------------`);
2289
2489
  console.log(` Project Root : ${root}`);
2290
2490
  console.log(` Pending Tasks : ${files.length}`);
2291
- console.log(` Active VFS Locks : ${lockStatus(root).length}`);
2491
+ console.log(` Active VFS Locks : ${lockStatus(root, { activeOnly: true }).length}`);
2292
2492
  console.log(` Daily Budget : ${formatBudgetLine(budget)}`);
2293
2493
  console.log(`--------------------------------------------------\n`);
2294
2494
  process.exit(0);
@@ -3332,10 +3532,182 @@ async function main() {
3332
3532
  break;
3333
3533
  }
3334
3534
 
3335
- default:
3336
- console.error(`Unknown command: ${command}`);
3337
- printHelp();
3535
+ case "migrate": {
3536
+ const { values } = parseArgs({
3537
+ args: args.slice(1),
3538
+ options: {
3539
+ "dry-run": { type: "boolean", short: "d" },
3540
+ yes: { type: "boolean", short: "y" },
3541
+ json: { type: "boolean", short: "j" },
3542
+ },
3543
+ allowPositionals: true,
3544
+ });
3545
+
3546
+ const dryRun = Boolean(values["dry-run"]);
3547
+ const confirmed = Boolean(values.yes);
3548
+ const asJson = Boolean(values.json);
3549
+
3550
+ if (!dryRun && !confirmed) {
3551
+ if (asJson) {
3552
+ console.error(JSON.stringify({
3553
+ ok: false,
3554
+ error: "Refusing to migrate without --yes. Pass --dry-run to preview, or --yes to confirm.",
3555
+ }));
3556
+ } else {
3557
+ console.error("❌ Refusing to migrate without --yes. Pass --dry-run to preview, or --yes to confirm.");
3558
+ }
3559
+ process.exit(1);
3560
+ }
3561
+
3562
+ const { executeMigration } = await import("../src/migrate.mjs");
3563
+ const res = executeMigration(root, { dryRun });
3564
+
3565
+ if (!res.ok) {
3566
+ if (asJson) {
3567
+ console.log(JSON.stringify(res, null, 2));
3568
+ } else {
3569
+ console.error(`❌ ${res.error || "Migration failed."}`);
3570
+ }
3571
+ process.exit(1);
3572
+ }
3573
+
3574
+ if (asJson) {
3575
+ const payload = {
3576
+ ok: true,
3577
+ dryRun: Boolean(res.dryRun),
3578
+ migrated: res.migrated,
3579
+ warnings: res.warnings || [],
3580
+ };
3581
+ if (res.message) payload.message = res.message;
3582
+ console.log(JSON.stringify(payload, null, 2));
3583
+ process.exit(0);
3584
+ }
3585
+
3586
+ if (res.migrated.length === 0 && (!res.warnings || res.warnings.length === 0)) {
3587
+ console.log(`✅ ${res.message || "Repository is already in canonical v1 format."}`);
3588
+ process.exit(0);
3589
+ }
3590
+
3591
+ if (dryRun) {
3592
+ console.log("🧪 Dry run — no files modified.");
3593
+ if (res.migrated.length === 0) {
3594
+ console.log(" Nothing to migrate.");
3595
+ } else {
3596
+ console.log(" Would migrate:");
3597
+ for (const item of res.migrated) {
3598
+ console.log(` - [${item.type}] ${item.path}`);
3599
+ for (const change of item.changes || []) console.log(` • ${change}`);
3600
+ }
3601
+ }
3602
+ } else {
3603
+ console.log("✅ Migration applied.");
3604
+ for (const item of res.migrated) {
3605
+ console.log(` - [${item.type}] ${item.path}`);
3606
+ for (const change of item.changes || []) console.log(` • ${change}`);
3607
+ }
3608
+ }
3609
+
3610
+ if (res.warnings?.length) {
3611
+ console.log("⚠️ Warnings:");
3612
+ for (const w of res.warnings) console.log(` - ${w}`);
3613
+ }
3614
+
3615
+ process.exit(0);
3616
+ break;
3617
+ }
3618
+
3619
+ case "uninstall": {
3620
+ const { values } = parseArgs({
3621
+ args: args.slice(1),
3622
+ options: {
3623
+ "dry-run": { type: "boolean", short: "d" },
3624
+ yes: { type: "boolean", short: "y" },
3625
+ force: { type: "boolean", short: "f" },
3626
+ json: { type: "boolean", short: "j" },
3627
+ },
3628
+ allowPositionals: true,
3629
+ });
3630
+
3631
+ const dryRun = Boolean(values["dry-run"]);
3632
+ const confirmed = Boolean(values.yes);
3633
+ const force = Boolean(values.force);
3634
+ const asJson = Boolean(values.json);
3635
+
3636
+ if (!dryRun && !confirmed) {
3637
+ if (asJson) {
3638
+ console.error(JSON.stringify({
3639
+ ok: false,
3640
+ error: "Refusing to uninstall without --yes. Pass --dry-run to preview, or --yes to confirm.",
3641
+ }));
3642
+ } else {
3643
+ console.error("❌ Refusing to uninstall without --yes. Pass --dry-run to preview, or --yes to confirm.");
3644
+ }
3645
+ process.exit(1);
3646
+ }
3647
+
3648
+ const { uninstallKit } = await import("../src/uninstall.mjs");
3649
+ const res = uninstallKit(root, { dryRun, force });
3650
+
3651
+ if (!res.ok) {
3652
+ if (asJson) {
3653
+ console.log(JSON.stringify(res, null, 2));
3654
+ } else {
3655
+ console.error(`❌ ${res.error || "Uninstall failed."}`);
3656
+ }
3657
+ process.exit(1);
3658
+ }
3659
+
3660
+ if (asJson) {
3661
+ if (dryRun) {
3662
+ console.log(JSON.stringify({ ok: true, dryRun: true, willRemove: res.willRemove }, null, 2));
3663
+ } else {
3664
+ console.log(JSON.stringify({ ok: true, removed: res.removed }, null, 2));
3665
+ }
3666
+ process.exit(0);
3667
+ }
3668
+
3669
+ if (dryRun) {
3670
+ console.log("🧪 Dry run — no files modified.");
3671
+ console.log(" Would remove / clean:");
3672
+ for (const item of res.willRemove) console.log(` - ${item}`);
3673
+ if (force && (!res.willRemove.some((p) => p.startsWith(".agent/rules") || p.startsWith(".agent/prompts") || p.startsWith(".agent/workflows") || p.startsWith(".agent/jules-queue")))) {
3674
+ console.log(" (no legacy scaffold directories present)");
3675
+ }
3676
+ } else {
3677
+ console.log("✅ Kit configuration uninstalled.");
3678
+ console.log(" Removed / cleaned:");
3679
+ for (const item of res.removed) console.log(` - ${item}`);
3680
+ }
3681
+ process.exit(0);
3682
+ break;
3683
+ }
3684
+
3685
+ default: {
3686
+ const knownTopCommands = [
3687
+ ...new Set(
3688
+ COMMAND_REGISTRY.flatMap((c) => [c.path[0], ...(c.shortcuts || [])]).filter(Boolean)
3689
+ ),
3690
+ "help",
3691
+ "version",
3692
+ ];
3693
+ const { levenshteinDistance } = await import("../src/task-optimizer.mjs");
3694
+ let closest = null;
3695
+ let minDistance = Infinity;
3696
+ for (const known of knownTopCommands) {
3697
+ const dist = levenshteinDistance(command.toLowerCase(), known.toLowerCase());
3698
+ if (dist < minDistance) {
3699
+ minDistance = dist;
3700
+ closest = known;
3701
+ }
3702
+ }
3703
+ if (closest && minDistance <= 3) {
3704
+ console.error(`Unknown command: "${command}". Did you mean "${closest}"?`);
3705
+ } else {
3706
+ console.error(`Unknown command: "${command}".`);
3707
+ }
3708
+ console.error(`Run 'agentctl --help' to see all available commands.`);
3338
3709
  process.exit(1);
3710
+ }
3339
3711
  }
3340
3712
  }
3341
3713
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.74.0",
3
+ "version": "0.75.1",
4
4
  "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for autonomous coding agents \u2014 Google Jules, Claude Code, Codex and Gemini CLI.",
5
5
  "repository": {
6
6
  "type": "git",