jules-orchestrator-kit 0.52.8 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -8
- package/bin/agentctl.mjs +327 -24
- package/bin/init.js +87 -74
- package/index.mjs +10 -0
- package/package.json +5 -2
- package/scripts/jules-create.mjs +5 -16
- package/scripts/jules-patch.mjs +18 -10
- package/scripts/stale-base-check.mjs +1 -1
- package/src/ci-templates.mjs +199 -0
- package/src/config-edit.mjs +138 -0
- package/src/config.mjs +39 -11
- package/src/engine.mjs +2 -21
- package/src/env-aliases.mjs +80 -0
- package/src/git.mjs +79 -0
- package/src/mcp.mjs +19 -1
- package/src/ops/cli-intent.mjs +29 -0
- package/src/ops/command-registry.mjs +19 -10
- package/src/ops/doctor-registry.mjs +75 -32
- package/src/ops/ide-scaffold.mjs +22 -16
- package/src/ops/next-step.mjs +38 -7
- package/src/ops/verify-output.mjs +38 -0
- package/src/profiles.mjs +188 -0
- package/src/provider-readiness.mjs +284 -0
- package/src/security.mjs +42 -1
- package/src/tui.mjs +14 -3
- package/src/wizard-init.mjs +88 -12
- package/src/wizard-task.mjs +22 -5
- package/src/ops/doctor-planner.mjs +0 -220
- package/src/ops/receipts.mjs +0 -171
- package/src/ops/swarm-actions.mjs +0 -156
- package/src/ops/task-actions.mjs +0 -214
- package/src/ops/transaction.mjs +0 -278
- package/src/ux/capabilities.mjs +0 -180
- package/src/ux/diff-viewer.mjs +0 -289
- package/src/ux/layout.mjs +0 -195
- package/src/ux/log-viewer.mjs +0 -110
- package/src/ux/palette.mjs +0 -179
- package/src/ux/queue-model.mjs +0 -206
- package/src/ux/renderer.mjs +0 -188
- package/src/ux/swarm-model.mjs +0 -146
- package/src/ux/terminal-session.mjs +0 -215
- package/src/ux/widgets.mjs +0 -323
- /package/src/{ux/key-decoder.mjs → key-decoder.mjs} +0 -0
package/bin/agentctl.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
|
+
import { applyEnvAliases } from "../src/env-aliases.mjs";
|
|
7
|
+
import { selectFailureOutput } from "../src/ops/verify-output.mjs";
|
|
6
8
|
import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
|
|
7
9
|
import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
|
|
8
10
|
import { acquireLock, releaseLock, lockStatus, getQueueDir } from "../src/state.mjs";
|
|
@@ -11,6 +13,12 @@ import { reapOrphanedIntents, reapStaleMutexDirs } from "../src/journal.mjs";
|
|
|
11
13
|
import { KIT_VERSION } from "../src/version.mjs";
|
|
12
14
|
import { budgetStatus, listOpenReservations, releaseOpenReservations, resolveConcurrency } from "../src/budget.mjs";
|
|
13
15
|
|
|
16
|
+
// Vendor-neutral env spellings are filled in before any module reads
|
|
17
|
+
// process.env, so `AGENT_API_KEY` works everywhere `JULES_API_KEY` does. A
|
|
18
|
+
// value already present under the legacy name always wins — see
|
|
19
|
+
// src/env-aliases.mjs.
|
|
20
|
+
applyEnvAliases(process.env);
|
|
21
|
+
|
|
14
22
|
const args = process.argv.slice(2);
|
|
15
23
|
const command = args[0];
|
|
16
24
|
|
|
@@ -35,7 +43,7 @@ export function formatBudgetLine(b) {
|
|
|
35
43
|
|
|
36
44
|
export function printHelp() {
|
|
37
45
|
console.log(`
|
|
38
|
-
|
|
46
|
+
agentctl v${VERSION} — Universal Agent Orchestrator & Safety Gatekeeper
|
|
39
47
|
|
|
40
48
|
Usage: agentctl <command> [options]
|
|
41
49
|
|
|
@@ -55,6 +63,10 @@ Commands:
|
|
|
55
63
|
clean Clean stale branches, worktrees, locks, and ledgers
|
|
56
64
|
lock <action> Manage mutex locks (acquire | release | status)
|
|
57
65
|
doctor Run system diagnostics and stack resolution checks
|
|
66
|
+
providers List agent providers and whether this machine can reach them (--json)
|
|
67
|
+
provider set <name> Switch the active provider in .agent/config.yml
|
|
68
|
+
profile Show or set the verification profile (--list, --set <name>, --json)
|
|
69
|
+
ci init Generate a stack-aware CI gate workflow (--target github|gitlab, --force)
|
|
58
70
|
bootstrap Bootstrap zero-test repository with verification oracle
|
|
59
71
|
review-repair Parse PR review comments and synthesize OODA repair tasks
|
|
60
72
|
dashboard Start local HTTP telemetry and audit dashboard
|
|
@@ -185,9 +197,7 @@ function printVerifyFailure(failure) {
|
|
|
185
197
|
if (failure.command) console.log(` - Command: ${failure.command}`);
|
|
186
198
|
for (const d of failure.diagnostics || []) console.log(` - ${d}`);
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
// fallback for the runners that report everything there.
|
|
190
|
-
const output = (failure.stderr || "").trim() || (failure.stdout || "").trim();
|
|
200
|
+
const output = selectFailureOutput(failure);
|
|
191
201
|
if (!output) return;
|
|
192
202
|
const lines = output.split("\n");
|
|
193
203
|
const tail = lines.slice(-VERIFY_OUTPUT_TAIL_LINES);
|
|
@@ -409,6 +419,13 @@ async function main() {
|
|
|
409
419
|
if (p.findings) {
|
|
410
420
|
p.findings.forEach((f) => console.log(` - [${f.severity}] ${f.type}: ${f.description}`));
|
|
411
421
|
}
|
|
422
|
+
// `git_resolution` reports its cause in `error` and nothing else.
|
|
423
|
+
// Never rendering it meant an unresolvable base branch printed a bare
|
|
424
|
+
// "Phase [GIT_RESOLUTION] : ❌ FAIL" and the operator had to re-run
|
|
425
|
+
// with --json to find out what the tool had objected to.
|
|
426
|
+
if (!p.ok && p.error) {
|
|
427
|
+
console.log(` - Error: ${p.error}`);
|
|
428
|
+
}
|
|
412
429
|
if (!p.ok && p.failure) {
|
|
413
430
|
printVerifyFailure(p.failure);
|
|
414
431
|
}
|
|
@@ -417,13 +434,49 @@ async function main() {
|
|
|
417
434
|
console.log(`Overall Result: ${res.ok ? "APPROVED (Exit 0)" : `REJECTED (Exit ${res.code})`}\n`);
|
|
418
435
|
if (!res.ok) {
|
|
419
436
|
const failedPhase = res.phases.find((p) => !p.ok)?.phase;
|
|
437
|
+
// The secret scanner also carries the integrity findings, so exit 6
|
|
438
|
+
// alone cannot pick the hint: a weakened assertion and a leaked key
|
|
439
|
+
// arrive under the same phase and the same code.
|
|
440
|
+
const secretsPhase = res.phases.find((p) => p.phase === "secrets" && !p.ok);
|
|
441
|
+
const findingTypes = new Set((secretsPhase?.findings || []).map((f) => f.type));
|
|
442
|
+
const INTEGRITY_TYPES = new Set([
|
|
443
|
+
"TEST_TAMPERING_DETECTED",
|
|
444
|
+
"EDGE_RUNTIME_VIOLATION",
|
|
445
|
+
"CROSS_PACKAGE_BOUNDARY_VIOLATION",
|
|
446
|
+
]);
|
|
447
|
+
const onlyIntegrityFindings =
|
|
448
|
+
findingTypes.size > 0 && [...findingTypes].every((t) => INTEGRITY_TYPES.has(t));
|
|
449
|
+
|
|
420
450
|
// Exit 3 is also what a strictTestLock tamper verdict returns, so the
|
|
421
451
|
// code alone cannot pick the hint — a scope remediation for a rewritten
|
|
422
452
|
// test file sends the operator to the wrong flag entirely.
|
|
423
453
|
if (res.repairs) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
454
|
+
// A repair loop that never reached the agent is not an exhausted
|
|
455
|
+
// repair loop. Telling someone their tests could not be fixed when
|
|
456
|
+
// the provider rejected the credential sends them to read test
|
|
457
|
+
// output that was never produced.
|
|
458
|
+
const providerFailure =
|
|
459
|
+
res.repairs.finalStatus === "PROVIDER_INFRASTRUCTURE_FAILURE" ||
|
|
460
|
+
(res.repairs.attempts || []).every((a) => a && a.ok === false && a.error);
|
|
461
|
+
const firstError = (res.repairs.attempts || []).find((a) => a && a.error)?.error || res.repairs.error;
|
|
462
|
+
if (providerFailure && firstError) {
|
|
463
|
+
console.log(`💡 Remediation Hint (Exit 4 — the repair agent never ran):`);
|
|
464
|
+
console.log(` • The provider rejected the dispatch, so no repair was attempted.`);
|
|
465
|
+
console.log(` • Provider error: ${firstError}`);
|
|
466
|
+
console.log(` • Check the provider is usable: agentctl providers`);
|
|
467
|
+
console.log(` • The verification failure above is unchanged — fix it locally, or retry once the provider works.\n`);
|
|
468
|
+
} else {
|
|
469
|
+
console.log(`💡 Remediation Hint (Exit 4 OODA Repair Exhausted):`);
|
|
470
|
+
console.log(` • Automated self-repair could not pass tests cleanly.`);
|
|
471
|
+
if (firstError) console.log(` • Last attempt error: ${firstError}`);
|
|
472
|
+
console.log(` • Review error fingerprints via: agentctl doctor\n`);
|
|
473
|
+
}
|
|
474
|
+
} else if (onlyIntegrityFindings) {
|
|
475
|
+
console.log(`💡 Remediation Hint (Exit ${res.code} Test Integrity Violation — no secret was found):`);
|
|
476
|
+
console.log(` • The diff weakens or removes verification rather than leaking a credential.`);
|
|
477
|
+
console.log(` • Restore the assertion the finding names. A requirement that is not met belongs`);
|
|
478
|
+
console.log(` RED with a stated reason, not silenced.`);
|
|
479
|
+
console.log(` • Nothing needs rotating: this exit code is shared with the secret scanner.\n`);
|
|
427
480
|
} else if (res.flakyVerdict?.verdict === "QUARANTINED") {
|
|
428
481
|
console.log(`💡 Remediation Hint (Exit 8 Flaky Test Quarantined):`);
|
|
429
482
|
console.log(` • This command has alternated between pass and fail across recent runs.`);
|
|
@@ -436,9 +489,38 @@ async function main() {
|
|
|
436
489
|
console.log(` • The stage above exited non-zero. Reproduce it locally, then re-run the gate.`);
|
|
437
490
|
console.log(` • To let agentctl attempt the repair loop itself, pass: agentctl gate --fix\n`);
|
|
438
491
|
} else if (res.code === 3) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
492
|
+
// The same violation has two very different causes. Right after
|
|
493
|
+
// `init`, every offending path is a file the tool itself just wrote
|
|
494
|
+
// and has not committed — advising --allow-protected there teaches
|
|
495
|
+
// the newcomer to bypass the gate on their first run, when what
|
|
496
|
+
// they need is `git commit`.
|
|
497
|
+
const scopeFiles = (res.phases.find((p) => p.phase === "scope")?.violations || [])
|
|
498
|
+
.map((v) => v.file)
|
|
499
|
+
.filter(Boolean);
|
|
500
|
+
const { partitionTracked } = await import("../src/git.mjs");
|
|
501
|
+
const { tracked, untracked } = partitionTracked(root, scopeFiles);
|
|
502
|
+
const allNewScaffolding =
|
|
503
|
+
untracked.length > 0 &&
|
|
504
|
+
tracked.length === 0 &&
|
|
505
|
+
untracked.every((f) => /^(\.agent\/|AGENTS\.md$|SPEC\.md$|CONSTRAINTS\.md$|DESIGN\.md$)/.test(f));
|
|
506
|
+
|
|
507
|
+
if (allNewScaffolding) {
|
|
508
|
+
console.log(`💡 Remediation Hint (Exit ${res.code} — these are the files init just wrote):`);
|
|
509
|
+
console.log(` • The agent manifest and guardrails are gate-protected on purpose: an agent must`);
|
|
510
|
+
console.log(` not edit the rules it is governed by. Uncommitted, they read as exactly that.`);
|
|
511
|
+
console.log(` • Commit them once and the gate goes green:`);
|
|
512
|
+
console.log(` git add ${untracked.join(" ")} && git commit -m "chore: add agent config"\n`);
|
|
513
|
+
} else {
|
|
514
|
+
console.log(`💡 Remediation Hint (Exit ${res.code} Scope Violation):`);
|
|
515
|
+
console.log(` • To allow protected files in this run, pass: agentctl gate --allow-protected`);
|
|
516
|
+
console.log(` • Or remove protected/denied paths from the diff before dispatching.\n`);
|
|
517
|
+
}
|
|
518
|
+
} else if (failedPhase === "git_resolution") {
|
|
519
|
+
console.log(`💡 Remediation Hint (Exit ${res.code} Base Branch Unresolvable):`);
|
|
520
|
+
console.log(` • The gate compares your work against a base branch, and this one is not reachable.`);
|
|
521
|
+
console.log(` • This repository's branches: git branch -a`);
|
|
522
|
+
console.log(` • Point the gate at the right one: agentctl check --base <branch>`);
|
|
523
|
+
console.log(` • Or record it once in .agent/config.yml as: base_branch: <branch>\n`);
|
|
442
524
|
} else if (res.code === 5) {
|
|
443
525
|
console.log(`💡 Remediation Hint (Exit 5 Diff Payload Overflow):`);
|
|
444
526
|
console.log(` • Total diff exceeds ${config.limits?.diffKb || 75} KB limit.`);
|
|
@@ -544,7 +626,11 @@ async function main() {
|
|
|
544
626
|
|
|
545
627
|
const root = resolveRoot();
|
|
546
628
|
const minCoverage = values.min ? parseFloat(values.min) : (values["min-coverage"] ? parseFloat(values["min-coverage"]) : 100);
|
|
547
|
-
|
|
629
|
+
// `config.baseBranch` was skipped here while every other gate honoured it,
|
|
630
|
+
// so `agentctl coverage` alone died on "Cannot resolve base reference
|
|
631
|
+
// main" in any repository not on `main`.
|
|
632
|
+
const coverageBase = values.base || config.baseBranch || "main";
|
|
633
|
+
const diffStr = diffText(root, coverageBase, values.mode || "working-tree");
|
|
548
634
|
|
|
549
635
|
const covRes = runV8Coverage(values.cmd, { root });
|
|
550
636
|
const report = calculateDiffCoverage(covRes.coverageByFile, diffStr, { root, minCoverage });
|
|
@@ -552,7 +638,7 @@ async function main() {
|
|
|
552
638
|
if (values.json) {
|
|
553
639
|
console.log(JSON.stringify({ ...report, testPass: covRes.ok }, null, 2));
|
|
554
640
|
} else {
|
|
555
|
-
console.log(`\n📊 V8 Native Diff Coverage Report (Base: ${
|
|
641
|
+
console.log(`\n📊 V8 Native Diff Coverage Report (Base: ${coverageBase})`);
|
|
556
642
|
console.log(`------------------------------------------------------------------`);
|
|
557
643
|
console.log(` Target Min Coverage : ${report.minCoverage}%`);
|
|
558
644
|
console.log(` Achieved Coverage : ${report.score}%`);
|
|
@@ -1151,7 +1237,13 @@ async function main() {
|
|
|
1151
1237
|
case "doctor": {
|
|
1152
1238
|
const { values } = parseArgs({
|
|
1153
1239
|
args: args.slice(1),
|
|
1154
|
-
options: {
|
|
1240
|
+
options: {
|
|
1241
|
+
json: { type: "boolean", short: "j" },
|
|
1242
|
+
// `--probe` was advertised in the command registry and declared
|
|
1243
|
+
// nowhere, so `runDoctorChecks` never saw `activeProbe` and the flag
|
|
1244
|
+
// was silently inert.
|
|
1245
|
+
probe: { type: "boolean" },
|
|
1246
|
+
},
|
|
1155
1247
|
allowPositionals: true,
|
|
1156
1248
|
strict: false,
|
|
1157
1249
|
});
|
|
@@ -1160,7 +1252,7 @@ async function main() {
|
|
|
1160
1252
|
// them: this command printed a hand-written summary, so findings like a
|
|
1161
1253
|
// git-tracked .env were computed, tested, and never shown to anyone.
|
|
1162
1254
|
const { runDoctorChecks } = await import("../src/ops/doctor-registry.mjs");
|
|
1163
|
-
const report = await runDoctorChecks({ root });
|
|
1255
|
+
const report = await runDoctorChecks({ root, activeProbe: Boolean(values.probe) });
|
|
1164
1256
|
|
|
1165
1257
|
if (values.json) {
|
|
1166
1258
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -1180,7 +1272,11 @@ async function main() {
|
|
|
1180
1272
|
const icon = { pass: "✅", warn: "⚠️ ", fail: "❌", skip: "⏭️ ", unknown: "❔" };
|
|
1181
1273
|
for (const r of report.results) {
|
|
1182
1274
|
console.log(` ${icon[r.status] || "•"} ${r.title}`);
|
|
1183
|
-
|
|
1275
|
+
// A passing row normally speaks for itself. Provider readiness does
|
|
1276
|
+
// not: green there means "a binary was found", and read as "the
|
|
1277
|
+
// provider works" it sends someone into a dispatch that cannot succeed.
|
|
1278
|
+
// Such a row asks to be spelled out even when it passes.
|
|
1279
|
+
if (r.status !== "pass" || r.alwaysShowSummary) {
|
|
1184
1280
|
console.log(` ${r.summary}`);
|
|
1185
1281
|
for (const fix of r.remediation || []) console.log(` → ${fix.summary}`);
|
|
1186
1282
|
}
|
|
@@ -1193,6 +1289,178 @@ async function main() {
|
|
|
1193
1289
|
break;
|
|
1194
1290
|
}
|
|
1195
1291
|
|
|
1292
|
+
case "provider":
|
|
1293
|
+
case "providers": {
|
|
1294
|
+
// `agentctl providers` used to tell the operator to run
|
|
1295
|
+
// `agentctl init --provider <name>` to switch — which restarts the whole
|
|
1296
|
+
// onboarding wizard, plan question included, to change one line.
|
|
1297
|
+
if (args[1] === "set") {
|
|
1298
|
+
const target = args[2];
|
|
1299
|
+
if (!target) {
|
|
1300
|
+
console.error(`❌ Usage: agentctl provider set <name> (see: agentctl providers)`);
|
|
1301
|
+
process.exit(2);
|
|
1302
|
+
}
|
|
1303
|
+
const { setConfigProvider } = await import("../src/config-edit.mjs");
|
|
1304
|
+
const res = setConfigProvider(root, target);
|
|
1305
|
+
if (!res.ok) {
|
|
1306
|
+
console.error(`❌ ${res.error}`);
|
|
1307
|
+
process.exit(1);
|
|
1308
|
+
}
|
|
1309
|
+
const { probeProvider } = await import("../src/provider-readiness.mjs");
|
|
1310
|
+
const probe = probeProvider(target);
|
|
1311
|
+
console.log(`✅ Provider set to '${target}' in ${res.file}`);
|
|
1312
|
+
console.log(` ${probe.ready ? "Ready" : `Not ready — ${probe.remedy}`}`);
|
|
1313
|
+
if (!probe.known) console.log(` Note: '${target}' is not a built-in preset, so its readiness cannot be checked.`);
|
|
1314
|
+
console.log(` The manifest is gate-protected — commit it so the next check does not read it as an agent edit.`);
|
|
1315
|
+
process.exit(0);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const { values } = parseArgs({
|
|
1319
|
+
args: args.slice(1),
|
|
1320
|
+
options: { json: { type: "boolean", short: "j" } },
|
|
1321
|
+
allowPositionals: true,
|
|
1322
|
+
});
|
|
1323
|
+
const { detectAvailableProviders, probeProvider } = await import("../src/provider-readiness.mjs");
|
|
1324
|
+
const probes = detectAvailableProviders();
|
|
1325
|
+
const active = config.provider || "jules";
|
|
1326
|
+
const activeProbe = probeProvider(active);
|
|
1327
|
+
|
|
1328
|
+
if (values.json) {
|
|
1329
|
+
console.log(JSON.stringify({ active, activeReady: activeProbe.ready, providers: probes }, null, 2));
|
|
1330
|
+
process.exit(0);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
console.log(`\n🔌 Agent providers\n`);
|
|
1334
|
+
for (const p of probes) {
|
|
1335
|
+
const marker = p.name === active ? "▸" : " ";
|
|
1336
|
+
console.log(` ${marker} ${p.ready ? "✅" : "⬜"} ${p.name.padEnd(14)} ${p.label}`);
|
|
1337
|
+
console.log(` ${p.reason}`);
|
|
1338
|
+
if (!p.ready) console.log(` Fix: ${p.remedy}`);
|
|
1339
|
+
}
|
|
1340
|
+
if (!probes.some((p) => p.name === active)) {
|
|
1341
|
+
console.log(` ▸ ⬜ ${active.padEnd(14)} ${activeProbe.label}`);
|
|
1342
|
+
console.log(` ${activeProbe.reason}`);
|
|
1343
|
+
}
|
|
1344
|
+
console.log(`\n Active: ${active} (provider: in ${config._file || ".agent/config.yml"})`);
|
|
1345
|
+
console.log(` Switch: agentctl provider set <name> · every gate below works with no provider at all`);
|
|
1346
|
+
console.log(` Note: for the CLI providers, "ready" means the binary is on PATH — it does not prove`);
|
|
1347
|
+
console.log(` the CLI is signed in. A dispatch is the first thing that can tell you that.\n`);
|
|
1348
|
+
process.exit(activeProbe.ready ? 0 : 1);
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
case "profile": {
|
|
1353
|
+
const { values } = parseArgs({
|
|
1354
|
+
args: args.slice(1),
|
|
1355
|
+
options: {
|
|
1356
|
+
list: { type: "boolean", short: "l" },
|
|
1357
|
+
set: { type: "string" },
|
|
1358
|
+
json: { type: "boolean", short: "j" },
|
|
1359
|
+
},
|
|
1360
|
+
allowPositionals: true,
|
|
1361
|
+
});
|
|
1362
|
+
const { PROFILE_NAMES, PROFILE_DESCRIPTIONS, buildProfileStages, buildDefaultStages } = await import("../src/profiles.mjs");
|
|
1363
|
+
|
|
1364
|
+
if (values.set) {
|
|
1365
|
+
const name = String(values.set).toLowerCase();
|
|
1366
|
+
if (!PROFILE_NAMES.includes(name)) {
|
|
1367
|
+
console.error(`❌ Unknown profile '${values.set}'. Choose one of: ${PROFILE_NAMES.join(", ")}`);
|
|
1368
|
+
process.exit(2);
|
|
1369
|
+
}
|
|
1370
|
+
const { setVerificationProfile } = await import("../src/config-edit.mjs");
|
|
1371
|
+
const res = setVerificationProfile(root, name);
|
|
1372
|
+
if (!res.ok) {
|
|
1373
|
+
console.error(`❌ ${res.error}`);
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
console.log(`✅ Verification profile set to '${name}' in ${res.file}`);
|
|
1377
|
+
console.log(` The manifest is gate-protected — commit it so the next check does not read it as an agent edit.`);
|
|
1378
|
+
process.exit(0);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (values.list) {
|
|
1382
|
+
if (values.json) {
|
|
1383
|
+
console.log(JSON.stringify(PROFILE_NAMES.map((n) => ({ name: n, description: PROFILE_DESCRIPTIONS[n] })), null, 2));
|
|
1384
|
+
} else {
|
|
1385
|
+
console.log(`\n🎚️ Verification profiles\n`);
|
|
1386
|
+
for (const n of PROFILE_NAMES) console.log(` ${n.padEnd(10)} ${PROFILE_DESCRIPTIONS[n]}`);
|
|
1387
|
+
console.log(`\n Set with: agentctl profile --set max\n`);
|
|
1388
|
+
}
|
|
1389
|
+
process.exit(0);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const stack = detectStack(root).stack;
|
|
1393
|
+
const activeProfile = config.verify.profile || "(none — running the built-in pipeline)";
|
|
1394
|
+
const plan = config.verify.profile
|
|
1395
|
+
? buildProfileStages(config.verify.profile, { stack, verify: config.verify })
|
|
1396
|
+
: { profile: null, stages: config.verify.stages || buildDefaultStages(config.verify), skipped: [] };
|
|
1397
|
+
|
|
1398
|
+
if (values.json) {
|
|
1399
|
+
console.log(JSON.stringify({ profile: config.verify.profile, stack, stages: plan.stages, skipped: plan.skipped }, null, 2));
|
|
1400
|
+
process.exit(0);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
console.log(`\n🎚️ Verification profile: ${activeProfile} (stack: ${stack})\n`);
|
|
1404
|
+
if (plan.stages.length === 0) {
|
|
1405
|
+
console.log(` No stages resolved — no verification command is configured or detectable.`);
|
|
1406
|
+
console.log(` Set verify.test in .agent/config.yml, or run: agentctl bootstrap`);
|
|
1407
|
+
}
|
|
1408
|
+
for (const st of plan.stages) {
|
|
1409
|
+
const what = st.assert ? `assert:${st.assert}` : st.cmd;
|
|
1410
|
+
console.log(` ${String(st.id).padEnd(14)} ${what}`);
|
|
1411
|
+
}
|
|
1412
|
+
for (const sk of plan.skipped) {
|
|
1413
|
+
console.log(` ${String(sk.id).padEnd(14)} — skipped: ${sk.reason}`);
|
|
1414
|
+
}
|
|
1415
|
+
console.log(`\n Change with: agentctl profile --set ${config.verify.profile === "max" ? "standard" : "max"}\n`);
|
|
1416
|
+
process.exit(0);
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
case "ci": {
|
|
1421
|
+
const sub = args[1];
|
|
1422
|
+
if (sub !== "init") {
|
|
1423
|
+
console.error(`❌ Unknown 'ci' action '${sub || ""}'. Usage: agentctl ci init [--target github|gitlab] [--force]`);
|
|
1424
|
+
process.exit(2);
|
|
1425
|
+
}
|
|
1426
|
+
const { values } = parseArgs({
|
|
1427
|
+
args: args.slice(2),
|
|
1428
|
+
options: {
|
|
1429
|
+
target: { type: "string", default: "github" },
|
|
1430
|
+
force: { type: "boolean", short: "f" },
|
|
1431
|
+
"dry-run": { type: "boolean", short: "d" },
|
|
1432
|
+
json: { type: "boolean", short: "j" },
|
|
1433
|
+
},
|
|
1434
|
+
allowPositionals: true,
|
|
1435
|
+
});
|
|
1436
|
+
const { writeCiWorkflow } = await import("../src/ci-templates.mjs");
|
|
1437
|
+
const res = writeCiWorkflow(root, {
|
|
1438
|
+
target: values.target,
|
|
1439
|
+
force: values.force,
|
|
1440
|
+
dryRun: values["dry-run"],
|
|
1441
|
+
config,
|
|
1442
|
+
version: VERSION,
|
|
1443
|
+
stack: detectStack(root),
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
if (values.json) {
|
|
1447
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1448
|
+
process.exit(res.ok ? 0 : 1);
|
|
1449
|
+
}
|
|
1450
|
+
if (!res.ok) {
|
|
1451
|
+
console.error(`❌ ${res.error}`);
|
|
1452
|
+
process.exit(1);
|
|
1453
|
+
}
|
|
1454
|
+
console.log(`✅ ${res.written ? "Wrote" : "Would write"} ${res.file}`);
|
|
1455
|
+
console.log(` Stack: ${res.stack} · runtime setup: ${res.setupSummary}`);
|
|
1456
|
+
console.log(` The workflow runs: agentctl check --mode committed`);
|
|
1457
|
+
if (!res.written && !values["dry-run"]) {
|
|
1458
|
+
console.log(` (unchanged — pass --force to overwrite)`);
|
|
1459
|
+
}
|
|
1460
|
+
process.exit(0);
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1196
1464
|
case "bootstrap": {
|
|
1197
1465
|
const { values } = parseArgs({
|
|
1198
1466
|
args: args.slice(1),
|
|
@@ -1250,6 +1518,8 @@ async function main() {
|
|
|
1250
1518
|
"no-interactive": { type: "boolean" },
|
|
1251
1519
|
yes: { type: "boolean", short: "y" },
|
|
1252
1520
|
tier: { type: "string", short: "t" },
|
|
1521
|
+
provider: { type: "string" },
|
|
1522
|
+
profile: { type: "string" },
|
|
1253
1523
|
json: { type: "boolean", short: "j" },
|
|
1254
1524
|
"dry-run": { type: "boolean", short: "d" },
|
|
1255
1525
|
force: { type: "boolean", short: "f" },
|
|
@@ -1267,6 +1537,10 @@ async function main() {
|
|
|
1267
1537
|
// re-run. Undefined lets the wizard seed the menu from the existing
|
|
1268
1538
|
// config and fall back to FALLBACK_TIER when there is nothing to seed.
|
|
1269
1539
|
tier: values.tier,
|
|
1540
|
+
// Both undefined by default so the wizard can detect a provider the
|
|
1541
|
+
// machine can actually reach and keep the profile already in config.
|
|
1542
|
+
provider: values.provider,
|
|
1543
|
+
profile: values.profile,
|
|
1270
1544
|
allowDefaults: true,
|
|
1271
1545
|
});
|
|
1272
1546
|
|
|
@@ -1282,6 +1556,17 @@ async function main() {
|
|
|
1282
1556
|
} else {
|
|
1283
1557
|
console.log(`✅ Onboarding complete! Manifest generated at ${res.configPath}`);
|
|
1284
1558
|
console.log(` Tier: ${res.plan.tier.toUpperCase()} (${res.plan.limits.concurrency} worker(s), ${res.plan.limits.daily_tasks} daily tasks)`);
|
|
1559
|
+
{
|
|
1560
|
+
const { probeProvider } = await import("../src/provider-readiness.mjs");
|
|
1561
|
+
const probe = probeProvider(res.plan.provider);
|
|
1562
|
+
console.log(` Provider : ${probe.name} ${probe.ready ? "(ready)" : `(not ready — ${probe.remedy})`}`);
|
|
1563
|
+
const { buildProfileStages, describeProfilePlan } = await import("../src/profiles.mjs");
|
|
1564
|
+
const planned = buildProfileStages(res.plan.profile, {
|
|
1565
|
+
stack: res.plan.stack,
|
|
1566
|
+
verify: { ...res.plan.verify, unit: res.plan.verify.test },
|
|
1567
|
+
});
|
|
1568
|
+
console.log(` Verification Profile : ${res.plan.profile} → ${describeProfilePlan(planned)}`);
|
|
1569
|
+
}
|
|
1285
1570
|
if (res.plan.verify.test) {
|
|
1286
1571
|
console.log(` Verification Test Command : "${res.plan.verify.test}"`);
|
|
1287
1572
|
} else {
|
|
@@ -1341,8 +1626,15 @@ async function main() {
|
|
|
1341
1626
|
allowPositionals: true,
|
|
1342
1627
|
});
|
|
1343
1628
|
|
|
1344
|
-
const
|
|
1345
|
-
const isInteractive =
|
|
1629
|
+
const { resolveWizardInteractivity } = await import("../src/ops/cli-intent.mjs");
|
|
1630
|
+
const isInteractive = resolveWizardInteractivity({
|
|
1631
|
+
interactive: values.interactive,
|
|
1632
|
+
nonInteractive: values["non-interactive"] || values["no-interactive"],
|
|
1633
|
+
yes: values.yes,
|
|
1634
|
+
// A title and a prompt together state the whole task; there is
|
|
1635
|
+
// nothing left for the wizard to ask.
|
|
1636
|
+
fullySpecified: Boolean(values.title && resolvePromptInput(values, positionals)),
|
|
1637
|
+
});
|
|
1346
1638
|
|
|
1347
1639
|
const { runTaskCreateWizard } = await import("../src/wizard-task.mjs");
|
|
1348
1640
|
const res = await runTaskCreateWizard(root, {
|
|
@@ -1357,12 +1649,17 @@ async function main() {
|
|
|
1357
1649
|
requirePlanApproval: values["require-plan-approval"],
|
|
1358
1650
|
repoless: values.repoless,
|
|
1359
1651
|
interactive: isInteractive,
|
|
1652
|
+
dryRun: values["dry-run"],
|
|
1360
1653
|
});
|
|
1361
1654
|
|
|
1362
1655
|
if (values.json) {
|
|
1363
1656
|
console.log(JSON.stringify(res, null, 2));
|
|
1364
1657
|
} else {
|
|
1365
|
-
console.log(
|
|
1658
|
+
console.log(
|
|
1659
|
+
res.dryRun
|
|
1660
|
+
? `🧪 Dry run — envelope synthesized and validated, nothing written (would be ${res.taskFile})`
|
|
1661
|
+
: `✅ Task synthesized & queued at ${res.taskFile}`
|
|
1662
|
+
);
|
|
1366
1663
|
console.log(` Task ID : ${res.plan.taskId}`);
|
|
1367
1664
|
console.log(` Title : ${res.plan.title}`);
|
|
1368
1665
|
if (res.plan.role) console.log(` Role : ${res.plan.role}`);
|
|
@@ -2178,26 +2475,32 @@ async function main() {
|
|
|
2178
2475
|
const subAction = args[1];
|
|
2179
2476
|
if (subAction === "init") {
|
|
2180
2477
|
const { scaffoldIdeConfig } = await import("../src/ops/ide-scaffold.mjs");
|
|
2181
|
-
const { values } = parseArgs({
|
|
2478
|
+
const { values, positionals } = parseArgs({
|
|
2182
2479
|
args: args.slice(2),
|
|
2183
2480
|
options: {
|
|
2184
|
-
|
|
2481
|
+
// No `default: "all"` here. parseArgs sets a default eagerly, so
|
|
2482
|
+
// `values.target` was always truthy and the `|| args[2]` fallback
|
|
2483
|
+
// below could never be reached — `agentctl mcp init cursor`, the
|
|
2484
|
+
// spelling --help advertises, silently scaffolded Cursor, VS Code
|
|
2485
|
+
// and Claude Desktop alike. The default belongs at the end of the
|
|
2486
|
+
// resolution chain, not at the start of it.
|
|
2487
|
+
target: { type: "string", short: "t" },
|
|
2185
2488
|
json: { type: "boolean", short: "j" },
|
|
2186
2489
|
"dry-run": { type: "boolean", short: "d" },
|
|
2187
2490
|
},
|
|
2188
2491
|
allowPositionals: true,
|
|
2189
2492
|
});
|
|
2190
2493
|
|
|
2191
|
-
const target = values.target ||
|
|
2494
|
+
const target = values.target || positionals[0] || "all";
|
|
2192
2495
|
try {
|
|
2193
|
-
const res = scaffoldIdeConfig(target, { root });
|
|
2496
|
+
const res = scaffoldIdeConfig(target, { root, dryRun: values["dry-run"] });
|
|
2194
2497
|
if (values.json) {
|
|
2195
2498
|
console.log(JSON.stringify(res, null, 2));
|
|
2196
2499
|
} else {
|
|
2197
|
-
console.log(`\n🔌 IDE MCP Config Scaffolded Successfully!`);
|
|
2500
|
+
console.log(res.dryRun ? `\n🧪 IDE MCP Config — dry run, nothing written` : `\n🔌 IDE MCP Config Scaffolded Successfully!`);
|
|
2198
2501
|
console.log(` Target : ${res.target}`);
|
|
2199
2502
|
for (const item of res.results) {
|
|
2200
|
-
console.log(` - ${item.target.toUpperCase()} : ${item.file}`);
|
|
2503
|
+
console.log(` - ${item.target.toUpperCase()} : ${item.file}${res.dryRun ? " (would write)" : ""}`);
|
|
2201
2504
|
}
|
|
2202
2505
|
console.log("");
|
|
2203
2506
|
}
|