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/CHANGELOG.md +27 -1
- package/JULES_RULES_TEMPLATE.md +3 -0
- package/README.md +4 -2
- package/bin/agentctl.mjs +395 -23
- package/package.json +1 -1
- package/scripts/ci-scope-guard.mjs +226 -23
- package/scripts/run-tests.mjs +5 -1
- package/src/bidi-guard.mjs +190 -15
- package/src/ci-templates.mjs +184 -21
- package/src/config.mjs +40 -1
- package/src/engine.mjs +342 -48
- package/src/evidence.mjs +22 -5
- package/src/git.mjs +15 -0
- package/src/migrate.mjs +493 -0
- package/src/ops/command-registry.mjs +93 -4
- package/src/ops/doctor-registry.mjs +184 -32
- package/src/ops/pr-harvest.mjs +22 -2
- package/src/ops/toolchain-diagnostics.mjs +253 -0
- package/src/ops/tui-menu.mjs +192 -0
- package/src/process-tree.mjs +146 -0
- package/src/prompt-guard.mjs +9 -0
- package/src/provider.mjs +14 -0
- package/src/scaffold.mjs +49 -1
- package/src/scope-guard.mjs +73 -5
- package/src/secret-scanner.mjs +14 -2
- package/src/security.mjs +41 -30
- package/src/self-audit.mjs +237 -3
- package/src/state.mjs +123 -13
- package/src/uninstall.mjs +227 -0
- package/src/wizard-init.mjs +11 -0
- package/src/wizard-task.mjs +17 -22
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
|
|
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
|
-
|
|
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 || "
|
|
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
|
-
|
|
695
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1686
|
+
if (allLocks.length === 0) {
|
|
1591
1687
|
console.log("Active Locks: 0 (no held locks)");
|
|
1592
1688
|
} else {
|
|
1593
|
-
console.log(`Active Locks (${
|
|
1594
|
-
for (const l of
|
|
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
|
-
|
|
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
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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.
|
|
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",
|