jules-orchestrator-kit 0.52.8 → 0.53.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # jules-orchestrator-kit
4
4
 
5
- ### Task orchestration and automated verification harness for Google Jules
5
+ ### Task orchestration and automated verification harness for coding agents
6
6
 
7
7
  <br/>
8
8
 
@@ -24,6 +24,7 @@
24
24
 
25
25
  <p align="center">
26
26
  <a href="#quickstart">Quickstart</a> &nbsp;•&nbsp;
27
+ <a href="#any-repository">Any Repository</a> &nbsp;•&nbsp;
27
28
  <a href="#overview">Overview</a> &nbsp;•&nbsp;
28
29
  <a href="#target-workflows">Target Workflows</a> &nbsp;•&nbsp;
29
30
  <a href="#triage-guidelines">Triage</a> &nbsp;•&nbsp;
@@ -57,8 +58,8 @@ npx jules-orchestrator-kit init
57
58
  ```
58
59
 
59
60
  ```bash
60
- # 2. Commit what init wrote — .agent/config.yml is on the gate's deny list by
61
- # design, so leaving it uncommitted makes the first gate reject your tree
61
+ # 2. Commit what init wrote — .agent/config.yml is protected by BUILTIN_PROTECT,
62
+ # so leaving it uncommitted makes the first gate reject your tree
62
63
  git add .agent AGENTS.md SPEC.md CONSTRAINTS.md .gitignore && git commit -m "chore: add agent config"
63
64
  ```
64
65
 
@@ -67,6 +68,20 @@ git add .agent AGENTS.md SPEC.md CONSTRAINTS.md .gitignore && git commit -m "cho
67
68
  npx jules-orchestrator-kit task create
68
69
  ```
69
70
 
71
+ `init` reads the repository, not a template: it detects the stack, picks a
72
+ provider this machine can actually reach, and generates a CI workflow for the
73
+ toolchain the project uses. Nothing about your setup is assumed.
74
+
75
+ ```bash
76
+ # Which agents can this machine dispatch to, and what is missing for the rest?
77
+ npx jules-orchestrator-kit providers
78
+ ```
79
+
80
+ ```bash
81
+ # How hard should the gate verify agent work? (minimal | standard | max)
82
+ npx jules-orchestrator-kit profile --set max
83
+ ```
84
+
70
85
  > [!TIP]
71
86
  > **Not sure what to run next?**
72
87
  > `agentctl` with no arguments reads the repository state and prints the single
@@ -87,6 +102,44 @@ npx jules-orchestrator-kit task create
87
102
 
88
103
  <br/>
89
104
 
105
+ <a id="any-repository"></a>
106
+ ## Using It In Any Repository
107
+
108
+ Four things differ between projects, and the kit resolves each one from the
109
+ repository rather than from a template.
110
+
111
+ | What differs | How it is resolved | Inspect / override |
112
+ | :--- | :--- | :--- |
113
+ | **The stack** | `detectStack()` recognises 24+ ecosystems (Cargo, Go, Python/Django, Maven/Gradle, .NET, PHP/Laravel, Ruby, Elixir, Swift, Flutter/Dart, CMake, Bun, Deno, Node + Turbo/pnpm/Nx workspaces) and derives the setup, lint, test and build commands from the manifest it finds. | `agentctl doctor` · `verify:` in `.agent/config.yml` |
114
+ | **The agent** | `provider:` selects Google Jules (hosted REST), the Claude Code CLI, the Codex CLI or the Gemini CLI. Readiness means a credential for the hosted one and a binary on `PATH` for the local ones — never both. | `agentctl providers` · `agentctl init --provider <name>` |
115
+ | **How hard to verify** | `verify.profile` expands at load time into a stage pipeline that skips gates the runtime cannot support, and says which and why. | `agentctl profile` · `agentctl profile --set max` |
116
+ | **Where CI runs** | A workflow is *generated* for the detected stack — the project's toolchain plus Node for the CLI — not copied from this repository. | `agentctl ci init [--target github\|gitlab]` |
117
+
118
+ ### Verification profiles
119
+
120
+ | Profile | Runs | Use when |
121
+ | :--- | :--- | :--- |
122
+ | `minimal` | setup → tests | The suite is slow, the stack is unfamiliar, or it is day one. |
123
+ | `standard` | setup → lint → tests → build → anti-tamper on the diff | The everyday gate. Scaffolded by default. |
124
+ | `max` | everything above → mutation scoring → V8 diff coverage *(Node runtimes only)* → 3-pass flakiness probe | The change is consequential, or an agent has been getting green too easily. |
125
+
126
+ Nothing in a profile is Node-specific by assumption. Gates a runtime cannot
127
+ support are skipped with a stated reason rather than failing the diff — a Cargo
128
+ repository on `max` runs mutation and stability probing and is never asked for
129
+ `NODE_V8_COVERAGE`.
130
+
131
+ ### No provider? Still useful
132
+
133
+ Every gate below runs locally with no API key, no CLI and no network:
134
+ `agentctl check`, `mutate`, `coverage`, `probe`, `assert`, `evidence`, `rules`,
135
+ `doctor`. The provider is only needed to *dispatch* work, not to verify it.
136
+
137
+ <br/>
138
+
139
+ ---
140
+
141
+ <br/>
142
+
90
143
  <a id="overview"></a>
91
144
  ## Overview
92
145
 
@@ -137,13 +190,17 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
137
190
 
138
191
  ## Core Capabilities
139
192
 
193
+ * **Provider-Agnostic:** Dispatches to Google Jules (hosted REST), the Claude Code CLI, the OpenAI Codex CLI or the Gemini CLI. `agentctl providers` probes each one — a credential for the hosted provider, a binary on `PATH` for the local ones — and every verification gate works with no provider configured at all.
194
+ * **Vendor-Neutral Configuration:** Every `JULES_*` environment variable also answers to an `AGENT_*` spelling (`AGENT_API_KEY`, `AGENT_REPO`, `AGENT_SWARM_CONCURRENCY`). The legacy name always wins where both are set, so adding an alias cannot change a working setup.
195
+ * **One-Word Verification Depth:** `verify.profile: minimal | standard | max` expands at load time into a stack-aware pipeline — `max` adds mutation scoring, flakiness probing and, where the runtime emits it, V8 diff coverage. A Cargo repository is never asked for `NODE_V8_COVERAGE`.
196
+ * **Generated, Not Copied, CI:** `agentctl ci init` writes a GitHub Actions or GitLab job carrying the toolchain the detected stack needs (`setup-python`, `setup-go`, `setup-java`, …) plus Node for the CLI itself.
140
197
  * **Zero Runtime Dependencies:** Built exclusively on Node.js 20+ built-in modules (`node:fs`, `node:child_process`, `node:crypto`, `node:path`, `node:http`, `node:tty`, `node:test`).
141
198
  * **Cross-Platform Parity:** Verified 100% green across Linux, macOS (Darwin), and Windows on Node 20, 22, and 24.
142
199
  * **Autonomous Self-Healing Loop:** Captures test stderr/stdout, fingerprints error traces, and feeds structured context back into automated repair turns (up to 3 attempts) before human escalation.
143
200
  * **Fail-Closed Security & Secret Redaction:** Evaluates explicit Deny rules before Allow rules against canonicalized, case-folded paths. Redacts high-entropy keys and base64-encoded credentials (such as Kubernetes `Secret` manifests).
144
201
  * **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors, with a `node --check` syntax-verification gate that transparently escalates a FAST-tier result to the primary provider if it left broken JS on disk.
145
202
  * **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
146
- * **Verified Test Suite:** Tested with **828 unit tests across 91 suites passing in < 10.0s**.
203
+ * **Verified Test Suite:** Tested with **797 unit tests across 98 suites passing in < 12.0s**.
147
204
 
148
205
  <br/>
149
206
 
@@ -158,7 +215,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
158
215
 
159
216
  | Command | Usage | Description | Exit Codes |
160
217
  | :--- | :--- | :--- | :--- |
161
- | `init` | `agentctl init [--interactive] [--tier pro] [--force]` | Interactive onboarding wizard & stack detector. Generates `.agent/config.yml` and scaffolds `AGENTS.md`, the role prompts, the guardrails and the runtime `.gitignore` entries. Existing files are preserved unless `--force`. | `0` (Created) |
218
+ | `init` | `agentctl init [--interactive] [--tier pro] [--provider <name>] [--profile <name>] [--force]` | Interactive onboarding wizard & stack detector. Generates `.agent/config.yml` and scaffolds `AGENTS.md`, the role prompts, the guardrails and the runtime `.gitignore` entries. Existing files are preserved unless `--force`. | `0` (Created) |
162
219
  | `budget` | `agentctl budget [--by-user] [--json] [reset]` | Reports rolling 24h task budget, quota headroom, and per-developer task attribution without external auth servers. | `0` (Status), `2` (Arg Error) |
163
220
  | `task create` | `agentctl task create [<prompt>] [--title <t>] [-p <prompt>] [-f <file>] [--template <id>] [--role <name>] [--tier fast\|complex]` | Interactively authors & scopes falsifiable task envelopes with secret scrubbing, preflight gate checks, and DAG dependency wiring. | `0` (Queued), `1` (Secret/Unfalsifiable) |
164
221
  | `task template` | `agentctl task template [<id>] [--list] [--json]` | Lists and synthesizes pre-calibrated task envelopes (Web, Deep Think, Universal & Agent Hardening: `web-cwv`, `web-wcag`, `web-seo`, `web-playwright`, `agent-dead-code-audit`, `web-flaky-heal`, `web-i18n`, `web-ai-access`, `agent-qa-mutation`, `agent-ci-falsify`, `agent-service-isolate`, `agent-error-paths`, `agent-security-audit`, `agent-dep-audit`, `agent-doc-drift`, `agent-config-audit`, `agent-api-contract`, `deep-debug`, `deep-feature`, `deep-optimize`, `deep-harden`). | `0` (Listed/Synthesized) |
@@ -169,6 +226,9 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
169
226
  | `retry` | `agentctl retry <sessionId> [--role <role>] [--with-failure] [--json]` | Fetches error traces and activity logs from a failed session and synthesizes a targeted OODA retry dispatch. | `0` (Dispatched), `1` (Error) |
170
227
  | `prune` | `agentctl prune [--age 7d] [--state <state>] [--delete] [--yes] [--json]` | Queries and batch-archives or deletes stale/completed sessions via Jules v1alpha API to keep workspaces clean. | `0` (Cleaned) |
171
228
  | `pr harvest` | `agentctl pr harvest [--tier r0,r1] [--limit <n>] [--auto] [--allow-no-checks] [--dry-run]` | Discovers open agent PRs, evaluates CI checks & risk tiers, and auto-squashes green low-risk changes autonomously. A PR reporting **no** CI checks is skipped unless `--allow-no-checks` is passed, and an unavailable changed-file list blocks rather than classifying as low risk. | `0` (Triaged/Merged), `1` (Error) |
229
+ | `providers` | `agentctl providers [--json]` | Probes every built-in provider and reports which ones this machine can dispatch to, what each one is missing, and which is active. | `0` (Active provider ready), `1` (Not ready) |
230
+ | `profile` | `agentctl profile [--list] [--set minimal\|standard\|max] [--json]` | Shows the verification stages the configured profile expands to on this stack, or writes a new profile into `.agent/config.yml` without disturbing comments. | `0` (Shown/Set), `2` (Unknown profile) |
231
+ | `ci init` | `agentctl ci init [--target github\|gitlab] [--force] [--dry-run] [--json]` | Generates a stack-aware CI gate workflow (`.github/workflows/agent-gate.yml` or `.gitlab-ci.agent-gate.yml`) that runs `agentctl check --mode committed`. Refuses to overwrite without `--force`. | `0` (Written/Skipped), `1` (Write error), `2` (Unknown target) |
172
232
  | `doctor` | `agentctl doctor [--json]` | Diagnostic DAG check runner & automated transactional self-repair engine. | `0` (Healthy), `1` (Failures) |
173
233
  | `queue` | `agentctl queue [--dag] [--concurrency <n>] [--dry-run] [--json]` | Consumes and executes task envelopes in `.agent/jules-queue/` with Kahn's DAG dependency resolution. Non-task files (manifests, `README.md`) are skipped, and `--dry-run` previews without moving anything. | `0` (Complete) |
174
234
  | `swarm` | `agentctl swarm [--json]` | Runs parallel multi-agent swarm across worker slots with PID liveness detection. | `0` (Complete) |
@@ -222,7 +282,6 @@ verify:
222
282
  scope:
223
283
  deny:
224
284
  - ".github/**"
225
- - ".agent/config.yml"
226
285
  - "keys/**"
227
286
 
228
287
  # Plan tier. Defaults to `free` when unset — the kit will not assume you are
@@ -391,7 +450,7 @@ const result = await fast.dispatch({ prompt: "Fix a typo." }, { root: process.cw
391
450
  | **Warm Session Resumption & PR Bundler** | `src/provider.mjs`, `src/engine.mjs` | Multi-turn warm session context streaming via `POST /v1alpha/sessions/{id}:sendMessage` & evidence PR descriptions. | **v0.31.0** *(Shipped)* |
392
451
  | **TDD Harness & Prompt Falsifiability Linter** | `agentctl test-gen`, `agentctl task optimize` | Automated RED-state test generator, `scope.deny` test locking, and prompt testability linter with fuzzy path resolution. | **v0.31.0** *(Shipped)* |
393
452
  | **Atomic Git Checkpoint & Rollback** | `agentctl rollback` (`src/ops/checkpoint.mjs`) | Pre-flight git HEAD/stash snapshotting, atomic rollback restoration, and 10-session pruning rotation. | **v0.31.0** *(Shipped)* |
394
- | **Interactive UX Engine & TUI Engine** | `src/ux/` (`capabilities`, `key-decoder`, `renderer`, `layout`, `widgets`) | Zero-dependency terminal capabilities detector, sequence key decoder, virtual frame renderer, and widgets. | **v0.30.0** *(Shipped)* |
453
+ | **Terminal UI Engine** | `src/tui.mjs`, `src/key-decoder.mjs` | Zero-dependency terminal capabilities detector, sequence key decoder, and interactive prompt widgets. | **v0.30.0** *(Shipped)* |
395
454
  | **PR Review Auto-Remediation Loop** | `agentctl review-repair` (`src/review-repair.mjs`) | Ingests GitHub PR review comments (`CHANGES_REQUESTED`), extracts line/file context, and dispatches automated repair turns. | **v0.27.0** *(Shipped)* |
396
455
 
397
456
  </details>
package/bin/agentctl.mjs CHANGED
@@ -3,6 +3,7 @@
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";
6
7
  import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
7
8
  import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
8
9
  import { acquireLock, releaseLock, lockStatus, getQueueDir } from "../src/state.mjs";
@@ -11,6 +12,12 @@ import { reapOrphanedIntents, reapStaleMutexDirs } from "../src/journal.mjs";
11
12
  import { KIT_VERSION } from "../src/version.mjs";
12
13
  import { budgetStatus, listOpenReservations, releaseOpenReservations, resolveConcurrency } from "../src/budget.mjs";
13
14
 
15
+ // Vendor-neutral env spellings are filled in before any module reads
16
+ // process.env, so `AGENT_API_KEY` works everywhere `JULES_API_KEY` does. A
17
+ // value already present under the legacy name always wins — see
18
+ // src/env-aliases.mjs.
19
+ applyEnvAliases(process.env);
20
+
14
21
  const args = process.argv.slice(2);
15
22
  const command = args[0];
16
23
 
@@ -35,7 +42,7 @@ export function formatBudgetLine(b) {
35
42
 
36
43
  export function printHelp() {
37
44
  console.log(`
38
- 🚀 agentctl v${VERSION} — Universal Agent Orchestrator & Safety Gatekeeper
45
+ agentctl v${VERSION} — Universal Agent Orchestrator & Safety Gatekeeper
39
46
 
40
47
  Usage: agentctl <command> [options]
41
48
 
@@ -55,6 +62,9 @@ Commands:
55
62
  clean Clean stale branches, worktrees, locks, and ledgers
56
63
  lock <action> Manage mutex locks (acquire | release | status)
57
64
  doctor Run system diagnostics and stack resolution checks
65
+ providers List agent providers and whether this machine can reach them (--json)
66
+ profile Show or set the verification profile (--list, --set <name>, --json)
67
+ ci init Generate a stack-aware CI gate workflow (--target github|gitlab, --force)
58
68
  bootstrap Bootstrap zero-test repository with verification oracle
59
69
  review-repair Parse PR review comments and synthesize OODA repair tasks
60
70
  dashboard Start local HTTP telemetry and audit dashboard
@@ -1193,6 +1203,150 @@ async function main() {
1193
1203
  break;
1194
1204
  }
1195
1205
 
1206
+ case "providers": {
1207
+ const { values } = parseArgs({
1208
+ args: args.slice(1),
1209
+ options: { json: { type: "boolean", short: "j" } },
1210
+ allowPositionals: true,
1211
+ });
1212
+ const { detectAvailableProviders, probeProvider } = await import("../src/provider-readiness.mjs");
1213
+ const probes = detectAvailableProviders();
1214
+ const active = config.provider || "jules";
1215
+ const activeProbe = probeProvider(active);
1216
+
1217
+ if (values.json) {
1218
+ console.log(JSON.stringify({ active, activeReady: activeProbe.ready, providers: probes }, null, 2));
1219
+ process.exit(0);
1220
+ }
1221
+
1222
+ console.log(`\n🔌 Agent providers\n`);
1223
+ for (const p of probes) {
1224
+ const marker = p.name === active ? "▸" : " ";
1225
+ console.log(` ${marker} ${p.ready ? "✅" : "⬜"} ${p.name.padEnd(14)} ${p.label}`);
1226
+ console.log(` ${p.reason}`);
1227
+ if (!p.ready) console.log(` Fix: ${p.remedy}`);
1228
+ }
1229
+ if (!probes.some((p) => p.name === active)) {
1230
+ console.log(` ▸ ⬜ ${active.padEnd(14)} ${activeProbe.label}`);
1231
+ console.log(` ${activeProbe.reason}`);
1232
+ }
1233
+ console.log(`\n Active: ${active} (provider: in ${config._file || ".agent/config.yml"})`);
1234
+ console.log(` Switch: agentctl init --provider <name> · every gate below works with no provider at all\n`);
1235
+ process.exit(activeProbe.ready ? 0 : 1);
1236
+ break;
1237
+ }
1238
+
1239
+ case "profile": {
1240
+ const { values } = parseArgs({
1241
+ args: args.slice(1),
1242
+ options: {
1243
+ list: { type: "boolean", short: "l" },
1244
+ set: { type: "string" },
1245
+ json: { type: "boolean", short: "j" },
1246
+ },
1247
+ allowPositionals: true,
1248
+ });
1249
+ const { PROFILE_NAMES, PROFILE_DESCRIPTIONS, buildProfileStages, buildDefaultStages } = await import("../src/profiles.mjs");
1250
+
1251
+ if (values.set) {
1252
+ const name = String(values.set).toLowerCase();
1253
+ if (!PROFILE_NAMES.includes(name)) {
1254
+ console.error(`❌ Unknown profile '${values.set}'. Choose one of: ${PROFILE_NAMES.join(", ")}`);
1255
+ process.exit(2);
1256
+ }
1257
+ const { setVerificationProfile } = await import("../src/profiles-io.mjs");
1258
+ const res = setVerificationProfile(root, name);
1259
+ if (!res.ok) {
1260
+ console.error(`❌ ${res.error}`);
1261
+ process.exit(1);
1262
+ }
1263
+ console.log(`✅ Verification profile set to '${name}' in ${res.file}`);
1264
+ process.exit(0);
1265
+ }
1266
+
1267
+ if (values.list) {
1268
+ if (values.json) {
1269
+ console.log(JSON.stringify(PROFILE_NAMES.map((n) => ({ name: n, description: PROFILE_DESCRIPTIONS[n] })), null, 2));
1270
+ } else {
1271
+ console.log(`\n🎚️ Verification profiles\n`);
1272
+ for (const n of PROFILE_NAMES) console.log(` ${n.padEnd(10)} ${PROFILE_DESCRIPTIONS[n]}`);
1273
+ console.log(`\n Set with: agentctl profile --set max\n`);
1274
+ }
1275
+ process.exit(0);
1276
+ }
1277
+
1278
+ const stack = detectStack(root).stack;
1279
+ const activeProfile = config.verify.profile || "(none — running the built-in pipeline)";
1280
+ const plan = config.verify.profile
1281
+ ? buildProfileStages(config.verify.profile, { stack, verify: config.verify })
1282
+ : { profile: null, stages: config.verify.stages || buildDefaultStages(config.verify), skipped: [] };
1283
+
1284
+ if (values.json) {
1285
+ console.log(JSON.stringify({ profile: config.verify.profile, stack, stages: plan.stages, skipped: plan.skipped }, null, 2));
1286
+ process.exit(0);
1287
+ }
1288
+
1289
+ console.log(`\n🎚️ Verification profile: ${activeProfile} (stack: ${stack})\n`);
1290
+ if (plan.stages.length === 0) {
1291
+ console.log(` No stages resolved — no verification command is configured or detectable.`);
1292
+ console.log(` Set verify.test in .agent/config.yml, or run: agentctl bootstrap`);
1293
+ }
1294
+ for (const st of plan.stages) {
1295
+ const what = st.assert ? `assert:${st.assert}` : st.cmd;
1296
+ console.log(` ${String(st.id).padEnd(14)} ${what}`);
1297
+ }
1298
+ for (const sk of plan.skipped) {
1299
+ console.log(` ${String(sk.id).padEnd(14)} — skipped: ${sk.reason}`);
1300
+ }
1301
+ console.log(`\n Change with: agentctl profile --set ${config.verify.profile === "max" ? "standard" : "max"}\n`);
1302
+ process.exit(0);
1303
+ break;
1304
+ }
1305
+
1306
+ case "ci": {
1307
+ const sub = args[1];
1308
+ if (sub !== "init") {
1309
+ console.error(`❌ Unknown 'ci' action '${sub || ""}'. Usage: agentctl ci init [--target github|gitlab] [--force]`);
1310
+ process.exit(2);
1311
+ }
1312
+ const { values } = parseArgs({
1313
+ args: args.slice(2),
1314
+ options: {
1315
+ target: { type: "string", default: "github" },
1316
+ force: { type: "boolean", short: "f" },
1317
+ "dry-run": { type: "boolean", short: "d" },
1318
+ json: { type: "boolean", short: "j" },
1319
+ },
1320
+ allowPositionals: true,
1321
+ });
1322
+ const { writeCiWorkflow } = await import("../src/ci-templates.mjs");
1323
+ const res = writeCiWorkflow(root, {
1324
+ target: values.target,
1325
+ force: values.force,
1326
+ dryRun: values["dry-run"],
1327
+ config,
1328
+ version: VERSION,
1329
+ stack: detectStack(root),
1330
+ });
1331
+
1332
+ if (values.json) {
1333
+ console.log(JSON.stringify(res, null, 2));
1334
+ process.exit(res.ok ? 0 : 1);
1335
+ }
1336
+ if (!res.ok) {
1337
+ console.error(`❌ ${res.error}`);
1338
+ process.exit(1);
1339
+ }
1340
+ console.log(`✅ ${res.written ? "Wrote" : "Would write"} ${res.file}`);
1341
+ console.log(` Stack: ${res.stack} · runtime setup: ${res.setupSummary}`);
1342
+ console.log(` The workflow runs: agentctl check --mode committed`);
1343
+ if (!res.written && !values["dry-run"]) {
1344
+ console.log(` (unchanged — pass --force to overwrite)`);
1345
+ }
1346
+ process.exit(0);
1347
+ break;
1348
+ }
1349
+
1196
1350
  case "bootstrap": {
1197
1351
  const { values } = parseArgs({
1198
1352
  args: args.slice(1),
@@ -1250,6 +1404,8 @@ async function main() {
1250
1404
  "no-interactive": { type: "boolean" },
1251
1405
  yes: { type: "boolean", short: "y" },
1252
1406
  tier: { type: "string", short: "t" },
1407
+ provider: { type: "string" },
1408
+ profile: { type: "string" },
1253
1409
  json: { type: "boolean", short: "j" },
1254
1410
  "dry-run": { type: "boolean", short: "d" },
1255
1411
  force: { type: "boolean", short: "f" },
@@ -1267,6 +1423,10 @@ async function main() {
1267
1423
  // re-run. Undefined lets the wizard seed the menu from the existing
1268
1424
  // config and fall back to FALLBACK_TIER when there is nothing to seed.
1269
1425
  tier: values.tier,
1426
+ // Both undefined by default so the wizard can detect a provider the
1427
+ // machine can actually reach and keep the profile already in config.
1428
+ provider: values.provider,
1429
+ profile: values.profile,
1270
1430
  allowDefaults: true,
1271
1431
  });
1272
1432
 
@@ -1282,6 +1442,17 @@ async function main() {
1282
1442
  } else {
1283
1443
  console.log(`✅ Onboarding complete! Manifest generated at ${res.configPath}`);
1284
1444
  console.log(` Tier: ${res.plan.tier.toUpperCase()} (${res.plan.limits.concurrency} worker(s), ${res.plan.limits.daily_tasks} daily tasks)`);
1445
+ {
1446
+ const { probeProvider } = await import("../src/provider-readiness.mjs");
1447
+ const probe = probeProvider(res.plan.provider);
1448
+ console.log(` Provider : ${probe.name} ${probe.ready ? "(ready)" : `(not ready — ${probe.remedy})`}`);
1449
+ const { buildProfileStages, describeProfilePlan } = await import("../src/profiles.mjs");
1450
+ const planned = buildProfileStages(res.plan.profile, {
1451
+ stack: res.plan.stack,
1452
+ verify: { ...res.plan.verify, unit: res.plan.verify.test },
1453
+ });
1454
+ console.log(` Verification Profile : ${res.plan.profile} → ${describeProfilePlan(planned)}`);
1455
+ }
1285
1456
  if (res.plan.verify.test) {
1286
1457
  console.log(` Verification Test Command : "${res.plan.verify.test}"`);
1287
1458
  } else {
package/bin/init.js CHANGED
@@ -8,6 +8,8 @@ import readline from "node:readline/promises";
8
8
  import zlib from "node:zlib";
9
9
  import crypto from "node:crypto";
10
10
  import { resolveProjectCommands } from "../scripts/command-resolver.mjs";
11
+ import { KIT_VERSION } from "../src/version.mjs";
12
+ import { detectStack } from "../src/config.mjs";
11
13
 
12
14
  const __filename = fileURLToPath(import.meta.url);
13
15
  const __dirname = path.dirname(__filename);
@@ -52,7 +54,7 @@ Options:
52
54
  process.exit(0);
53
55
  }
54
56
 
55
- console.log("\n🚀 Initializing Google Jules Orchestration Kit...\n");
57
+ console.log("\n🚀 Initializing agent orchestrator kit...\n");
56
58
  console.log(`📁 Target Directory: ${targetDir}`);
57
59
  if (isForce) console.log("⚠️ Force mode enabled (existing files will be overwritten).");
58
60
 
@@ -129,65 +131,62 @@ for (const item of scaffolded.created) {
129
131
 
130
132
  const agentDir = path.join(targetDir, ".agent");
131
133
 
132
- // Scaffold .agent/jules.yml
134
+ // Scaffold the manifest pair.
135
+ //
136
+ // This entry point used to hand-roll a thinner `.agent/jules.yml` while
137
+ // `agentctl init` wrote a `.agent/config.yml` the runtime actually reads, so
138
+ // which of the two scaffolders you happened to run decided whether the
139
+ // repository had a provider, a tier and a verification profile at all. Both
140
+ // now go through `planInit`.
141
+ const { planInit } = await import("../src/wizard-init.mjs");
142
+ const initPlan = planInit(targetDir, {
143
+ testCmd: detected.testCmd,
144
+ buildCmd: detected.buildCmd,
145
+ lintCmd: detected.lintCmd,
146
+ baseBranch: answerBranchVal || process.env.BASE_BRANCH || undefined,
147
+ });
148
+
149
+ const configPath = path.join(agentDir, "config.yml");
150
+ if (!fs.existsSync(configPath) || isForce) {
151
+ fs.writeFileSync(configPath, initPlan.configYaml, "utf-8");
152
+ console.log(`✅ Created: .agent/config.yml (provider: ${initPlan.provider}, profile: ${initPlan.profile})`);
153
+ }
154
+
133
155
  const yamlConfigPath = path.join(agentDir, "jules.yml");
134
156
  if (!fs.existsSync(yamlConfigPath) || isForce) {
135
- const yamlContent = `# Google Jules Repository Configuration (Version 2)
136
- version: 2
137
- test_cmd: "${detected.testCmd || "npm test"}"
138
- build_cmd: "${detected.buildCmd || "npm run build"}"
139
- forbidden_paths: [".github/**", "**/.env*", "**/*.pem", "**/lock-manager*"]
140
- `;
141
- fs.writeFileSync(yamlConfigPath, yamlContent, "utf-8");
157
+ fs.writeFileSync(yamlConfigPath, initPlan.julesYaml, "utf-8");
142
158
  console.log("✅ Created: .agent/jules.yml");
143
159
  }
144
160
 
145
- // Scaffold .github/workflows/jules-audit.yml
146
- const githubWorkflowsDir = path.join(targetDir, ".github/workflows");
147
- const auditWfSource = path.join(kitRoot, ".github/workflows/jules-audit.yml");
148
- const auditWfTarget = path.join(githubWorkflowsDir, "jules-audit.yml");
149
-
150
- if (fs.existsSync(auditWfSource)) {
151
- if (!fs.existsSync(githubWorkflowsDir)) {
152
- fs.mkdirSync(githubWorkflowsDir, { recursive: true });
153
- }
154
- if (!fs.existsSync(auditWfTarget) || isForce) {
155
- fs.copyFileSync(auditWfSource, auditWfTarget);
156
- console.log("✅ Added GitHub Actions workflow: .github/workflows/jules-audit.yml");
157
- }
161
+ // 3b. Generate a CI gate workflow for *this* repository's stack.
162
+ //
163
+ // This used to copy the kit's own audit workflow verbatim: a nine-way Node
164
+ // matrix running `npm install`, `npm test` and a script path that exists only
165
+ // inside the kit. In a Rust, Python or Go repository it was red on the first
166
+ // push for reasons that had nothing to do with that repository's code.
167
+ const { writeCiWorkflow } = await import("../src/ci-templates.mjs");
168
+ const ciRes = writeCiWorkflow(targetDir, {
169
+ target: "github",
170
+ force: isForce,
171
+ // `resolveProjectCommands` reports a human-readable source string; the CI
172
+ // generator needs the machine-readable stack id.
173
+ stack: detectStack(targetDir),
174
+ config: { baseBranch: answerBranchVal || process.env.BASE_BRANCH || "main" },
175
+ version: KIT_VERSION,
176
+ });
177
+ if (ciRes.ok && ciRes.written) {
178
+ console.log(`✅ Generated CI gate for '${ciRes.stack}': ${ciRes.file}`);
179
+ } else if (ciRes.ok) {
180
+ console.log(`↩️ Kept existing ${ciRes.file} (use --force to regenerate)`);
158
181
  }
159
182
 
160
- // 4. Copy scripts/ directory with PER-FILE existence guard
161
- const targetScriptsDir = path.join(targetDir, "scripts");
162
- if (!fs.existsSync(targetScriptsDir)) {
163
- fs.mkdirSync(targetScriptsDir, { recursive: true });
164
- }
165
-
166
- const sourceScriptsDir = path.join(kitRoot, "scripts");
167
- let copiedCount = 0;
168
- let skippedCount = 0;
169
-
170
- if (fs.existsSync(sourceScriptsDir)) {
171
- const scriptFiles = fs.readdirSync(sourceScriptsDir);
172
- const excludeFiles = new Set(["release.mjs"]);
173
- scriptFiles.forEach((file) => {
174
- if (excludeFiles.has(file)) return;
175
- const srcFile = path.join(sourceScriptsDir, file);
176
- const destFile = path.join(targetScriptsDir, file);
177
- if (!fs.existsSync(destFile) || isForce) {
178
- fs.copyFileSync(srcFile, destFile);
179
- try {
180
- fs.chmodSync(destFile, 0o755);
181
- } catch (err) {
182
- console.warn(`⚠️ Could not set executable permissions on ${file}:`, err.message);
183
- }
184
- copiedCount++;
185
- } else {
186
- skippedCount++;
187
- }
188
- });
189
- console.log(`✅ Copied ${copiedCount} orchestration scripts to ./scripts/ (${skippedCount} skipped, use --force to overwrite)`);
190
- }
183
+ // 4. The kit's own scripts/ directory is NOT copied here.
184
+ //
185
+ // It used to be — twenty orchestration scripts dropped into the target repo's
186
+ // scripts/ folder, where they collided with the project's own files, aged
187
+ // independently of the installed kit, and duplicated commands that `agentctl`
188
+ // already exposes. Everything they did is reachable through the CLI, which is
189
+ // what the injected package.json entries below point at.
191
190
 
192
191
  // 5. Optionally inject scripts into target package.json if present
193
192
  const targetPkgPath = path.join(targetDir, "package.json");
@@ -196,18 +195,22 @@ if (fs.existsSync(targetPkgPath) && targetDir !== kitRoot) {
196
195
  const pkg = JSON.parse(fs.readFileSync(targetPkgPath, "utf-8"));
197
196
  pkg.scripts = pkg.scripts || {};
198
197
  let updated = false;
199
- const julesScripts = {
200
- "jules:dispatch": "agentctl dispatch",
201
- "jules:queue": "agentctl queue",
202
- "jules:create": "agentctl task create",
203
- "jules:status": "agentctl status",
204
- "jules:audit": "agentctl gate",
205
- "jules:scan": "agentctl scan",
206
- "jules:swarm": "agentctl swarm",
207
- "jules:nightly": "agentctl clean"
198
+ // Vendor-neutral names: these run `agentctl`, which dispatches to whichever
199
+ // provider the repository selected. `jules:*` entries from an older init are
200
+ // left in place — removing scripts a project may reference in CI is not
201
+ // this command's business.
202
+ const agentScripts = {
203
+ "agent:gate": "agentctl check",
204
+ "agent:dispatch": "agentctl dispatch",
205
+ "agent:queue": "agentctl queue",
206
+ "agent:create": "agentctl task create",
207
+ "agent:status": "agentctl status",
208
+ "agent:doctor": "agentctl doctor",
209
+ "agent:swarm": "agentctl swarm",
210
+ "agent:clean": "agentctl clean"
208
211
  };
209
212
 
210
- for (const [key, val] of Object.entries(julesScripts)) {
213
+ for (const [key, val] of Object.entries(agentScripts)) {
211
214
  if (!pkg.scripts[key] || isForce) {
212
215
  pkg.scripts[key] = val;
213
216
  updated = true;
@@ -216,7 +219,7 @@ if (fs.existsSync(targetPkgPath) && targetDir !== kitRoot) {
216
219
 
217
220
  if (updated) {
218
221
  fs.writeFileSync(targetPkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
219
- console.log("✅ Added Jules commands to package.json");
222
+ console.log("✅ Added agent:* commands to package.json");
220
223
  }
221
224
  } catch (err) {
222
225
  console.warn("⚠️ Failed to inject helper scripts into target package.json:", err.message);
@@ -229,7 +232,7 @@ if (scaffolded.gitignore.length > 0) {
229
232
  console.log(`✅ Added ${scaffolded.gitignore.length} runtime state entries to .gitignore`);
230
233
  }
231
234
 
232
- console.log("\n🎉 Google Jules Orchestration Kit successfully initialized!");
235
+ console.log("\n🎉 Agent orchestrator kit initialized.");
233
236
 
234
237
  // 6. Generate Encoded Workspace Manifest (JULES_WEB_SETUP.md)
235
238
  const agentState = {
@@ -272,13 +275,23 @@ ${payloadToken}
272
275
 
273
276
  fs.writeFileSync(path.join(targetDir, ".agent", "JULES_WEB_SETUP.md"), setupMdContent, "utf-8");
274
277
 
275
- console.log("\n🔗 Google Jules Setup Complete");
276
- console.log(" Your local agent configurations are ready.");
277
- console.log(` 👉 Encoded Workspace Manifest: \x1b[36m${payloadToken}\x1b[0m`);
278
- console.log("\n (A backup was written to .agent/JULES_WEB_SETUP.md)");
278
+ // The encoded manifest is a Google Jules convenience — it pastes the detected
279
+ // workspace into the Jules web UI. It is written unconditionally because it
280
+ // costs nothing and a repository can change provider later, but it is only
281
+ // *announced* when Jules is the provider this repo actually selected;
282
+ // otherwise it is one more Google-specific string in the way of someone who
283
+ // chose a different agent.
284
+ if (initPlan.provider === "jules") {
285
+ console.log("\n🔗 Google Jules workspace manifest");
286
+ console.log(` 👉 Paste into the Jules web UI: \x1b[36m${payloadToken}\x1b[0m`);
287
+ console.log(" (A copy was written to .agent/JULES_WEB_SETUP.md)");
288
+ } else {
289
+ console.log(`\n Provider: ${initPlan.provider}. A Google Jules workspace manifest was also written to .agent/JULES_WEB_SETUP.md if you ever switch.`);
290
+ }
279
291
  console.log("\nNext Steps:");
280
- console.log(" 1. See official documentation at https://jules.google");
281
- console.log(" 2. Set environment variables: JULES_REPO=\"owner/repo\"");
282
- console.log(" 3. Scaffold a new task: npm run jules:create \"My Feature\"");
283
- console.log(" 4. Dispatch the queue: npm run jules:queue");
284
- console.log(" 5. Run pre-merge audit: npm run jules:audit\n");
292
+ console.log(" 1. Pick a provider: agentctl providers (jules | claude-code | codex | gemini)");
293
+ console.log(" 2. Pick a gate depth: agentctl profile --list (minimal | standard | max)");
294
+ console.log(" 3. Scaffold a task: agentctl task create");
295
+ console.log(" 4. Dispatch the queue: agentctl queue");
296
+ console.log(" 5. Run the pre-merge gate: agentctl check\n");
297
+ console.log(" Not sure? Run `agentctl` with no arguments — it reads the repo and names one step.\n");
package/index.mjs CHANGED
@@ -36,6 +36,16 @@ export {
36
36
  parseRetryAfter,
37
37
  } from "./src/provider.mjs";
38
38
  export { resolveRoutedProvider } from "./src/router.mjs";
39
+ export {
40
+ PROVIDER_DESCRIPTORS,
41
+ PROVIDER_PREFERENCE,
42
+ whichBinary,
43
+ probeProvider,
44
+ detectAvailableProviders,
45
+ suggestProvider,
46
+ } from "./src/provider-readiness.mjs";
47
+ export { ENV_ALIASES, applyEnvAliases, describeEnvVar } from "./src/env-aliases.mjs";
48
+ export { PROFILE_NAMES, PROFILE_DESCRIPTIONS, buildProfileStages, describeProfilePlan } from "./src/profiles.mjs";
39
49
  export {
40
50
  detectPolyglotStack,
41
51
  resolveWorkspaceBoundary,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.52.8",
4
- "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for Google Jules (jules) autonomous agents.",
3
+ "version": "0.53.0",
4
+ "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for autonomous coding agents — Google Jules, Claude Code, Codex and Gemini CLI.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/FullThrottle83/jules-orchestrator-kit.git"
@@ -63,6 +63,9 @@
63
63
  "keywords": [
64
64
  "jules",
65
65
  "google-jules",
66
+ "claude-code",
67
+ "codex",
68
+ "gemini-cli",
66
69
  "ai-agent",
67
70
  "orchestrator",
68
71
  "mcp",