jules-orchestrator-kit 0.38.1 → 0.38.2

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
@@ -131,7 +131,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
131
131
  * **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).
132
132
  * **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors.
133
133
  * **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
134
- * **Verified Test Suite:** Tested with **569 unit tests across 81 suites passing in < 10.0s**.
134
+ * **Verified Test Suite:** Tested with **572 unit tests across 81 suites passing in < 10.0s**.
135
135
 
136
136
  <br/>
137
137
 
@@ -151,7 +151,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
151
151
  | `task template` | `agentctl task template [<id>] [--list] [--json]` | Lists and synthesizes pre-calibrated web task envelopes (`web-cwv`, `web-wcag`, `web-seo`, `web-playwright`, `web-flaky-heal`, `web-i18n`, `web-ai-access`). | `0` (Listed/Synthesized) |
152
152
  | `dispatch` | `agentctl dispatch [-p <prompt>] [-f <file>] [-r <role>] [-t <tier>] [--auto-pr] [--repoless] [--dry-run]` | Dispatches autonomous task to the active provider with payload limits and role prompt resolution. | `0` (Dispatched), `1` (Error) |
153
153
  | `doctor` | `agentctl doctor [--json]` | Diagnostic DAG check runner & automated transactional self-repair engine. | `0` (Healthy), `1` (Failures) |
154
- | `queue` | `agentctl queue [--dag] [--concurrency <n>] [--dry-run] [--json]` | Consumes and executes task envelopes in `.agent/jules-queue/` with Kahn's DAG dependency resolution. | `0` (Complete) |
154
+ | `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) |
155
155
  | `swarm` | `agentctl swarm [--json]` | Runs parallel multi-agent swarm across worker slots with PID liveness detection. | `0` (Complete) |
156
156
  | `gate` / `audit`| `agentctl gate --mode working-tree [--json]` | Runs security, secret scanning, and verification gates against working tree or branch. | `0` (Approved), `3` (Scope), `5` (Diff >75K), `6` (Secret) |
157
157
  | `rollback` | `agentctl rollback [sessionId \| --latest]` | Restores exact commit, uncommitted files, and cleans orphan task worktrees from pre-flight checkpoints. | `0` (Restored), `1` (Error) |
@@ -319,6 +319,7 @@ console.log(classification.tier); // "fast" | "complex"
319
319
 
320
320
  | Feature | Module / Command | Architectural Description | Status |
321
321
  | :--- | :--- | :--- | :---: |
322
+ | **Queue Runner Fidelity** | `src/dag-engine.mjs`, `src/engine.mjs` | Queue selection is by task shape rather than file extension, so manifests and READMEs are skipped instead of dispatched, and `--dry-run` leaves the queue untouched. | **v0.38.2** *(Shipped)* |
322
323
  | **Release Gate Enforcement & Wizard Smoke Test** | `.github/workflows/jules-audit.yml`, `scripts/release.mjs`, `test/wizard-smoke.test.mjs` | Doc-sync gate runs in CI rather than by hand, releases block on a green CI matrix for `HEAD`, per-test deadlines turn a hang into a failure, and the real `init` wizard is driven end to end over a fake TTY. | **v0.38.1** *(Shipped)* |
323
324
  | **Multi-OS CI Matrix & TUI Hardening** | `scripts/run-tests.mjs`, `src/state.mjs`, `src/git.mjs` | Automated 9-job CI matrix across Linux, macOS, and Windows on Node 20/22/24 with raw-mode TUI resilience and native Windows command quoting. | **v0.38.0** *(Shipped)* |
324
325
  | **Base64 Secret Detection & Budget Fix** | `src/security.mjs`, `src/budget.mjs` | Secret scanner decodes base64 before matching structured patterns (K8s secrets), and `budget reset` preserves confirmed provider sessions. | **v0.37.0** *(Shipped)* |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.38.1",
3
+ "version": "0.38.2",
4
4
  "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for Google Jules (jules) autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -417,9 +417,42 @@ export function resolveAffectedTests(modifiedFiles = [], options = {}) {
417
417
  * @param {Object} [options.config]
418
418
  * @returns {Promise<{ processed: number, results: Array<any> }>}
419
419
  */
420
+ /**
421
+ * Decides whether a queue-directory entry is a task this runner should execute.
422
+ *
423
+ * The DAG runner accepts more shapes than `isTaskFile` does — `.json` and
424
+ * `.task` envelopes as well as Markdown — but "every `.json` in the queue
425
+ * folder" also swept up manifests (swarm run files, indexes). A manifest has no
426
+ * `prompt`, so the whole file became the prompt and a large one blew the
427
+ * provider payload limit. Shape decides, not the extension.
428
+ *
429
+ * @param {string} fileName
430
+ * @param {string} content
431
+ * @param {Function} isTaskFile - `isTaskFile` from engine.mjs, passed in to avoid a circular import.
432
+ * @returns {boolean}
433
+ */
434
+ function isDagTaskFile(fileName, content, isTaskFile) {
435
+ if (fileName.endsWith(".task")) return true;
436
+ if (fileName.endsWith(".md")) return isTaskFile(fileName, content);
437
+ if (fileName.endsWith(".json")) {
438
+ let parsed;
439
+ try {
440
+ parsed = JSON.parse(content);
441
+ } catch (_) {
442
+ return false;
443
+ }
444
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
445
+ // A manifest *describes* tasks; a task envelope *is* one.
446
+ if (Array.isArray(parsed.tasks) || Array.isArray(parsed.envelopes)) return false;
447
+ return typeof parsed.prompt === "string" && parsed.prompt.length > 0;
448
+ }
449
+ return false;
450
+ }
451
+
420
452
  export async function executeQueueDag(root = process.cwd(), options = {}) {
421
453
  const { readdirSync, renameSync, mkdirSync } = await import("node:fs");
422
454
  const { join, basename } = await import("node:path");
455
+ const { isTaskFile } = await import("./engine.mjs");
423
456
 
424
457
  const queueDir = join(root, ".agent", "jules-queue");
425
458
  const fallbackQueueDir = join(root, ".agent", "queue");
@@ -429,7 +462,7 @@ export async function executeQueueDag(root = process.cwd(), options = {}) {
429
462
  if (!existsSync(actualQueueDir)) {
430
463
  return { processed: 0, results: [] };
431
464
  }
432
- if (!existsSync(completedDir)) {
465
+ if (!existsSync(completedDir) && !options.dryRun) {
433
466
  try {
434
467
  mkdirSync(completedDir, { recursive: true });
435
468
  } catch (_) {}
@@ -452,6 +485,7 @@ export async function executeQueueDag(root = process.cwd(), options = {}) {
452
485
  if (basename(file) !== file) continue;
453
486
  const fullPath = join(actualQueueDir, file);
454
487
  const content = readFileSync(fullPath, "utf-8");
488
+ if (!isDagTaskFile(file, content, isTaskFile)) continue;
455
489
  let taskId = file.replace(/\.(md|json|task)$/, "");
456
490
  let dependsOn = [];
457
491
  let title = taskId;
@@ -508,13 +542,17 @@ export async function executeQueueDag(root = process.cwd(), options = {}) {
508
542
  results.push({ file: t.file, taskId: t.taskId, ok: false, status: session.status, error: session.error, session });
509
543
  throw new Error(`DAG Task '${t.taskId}' failed: ${session.error || session.status || "Unknown error"}`);
510
544
  } else {
511
- const dstPath = join(completedDir, t.file);
512
- if (existsSync(srcPath)) {
513
- if (!existsSync(dstPath)) {
514
- renameSync(srcPath, dstPath);
545
+ // A dry run simulates; it must leave the queue exactly as it found it,
546
+ // or the second `--dry-run` sees an empty queue.
547
+ if (!options.dryRun) {
548
+ const dstPath = join(completedDir, t.file);
549
+ if (existsSync(srcPath)) {
550
+ if (!existsSync(dstPath)) {
551
+ renameSync(srcPath, dstPath);
552
+ }
515
553
  }
516
554
  }
517
- results.push({ file: t.file, taskId: t.taskId, ok: true, session });
555
+ results.push({ file: t.file, taskId: t.taskId, ok: true, dryRun: Boolean(options.dryRun), session });
518
556
  }
519
557
  },
520
558
  });
package/src/engine.mjs CHANGED
@@ -824,7 +824,7 @@ export async function run(tasksOrOpts = {}, opts = {}) {
824
824
 
825
825
  const queueDir = getQueueDir(root);
826
826
  const completedDir = join(queueDir, "completed");
827
- ensureDir(completedDir);
827
+ if (!isDry) ensureDir(completedDir);
828
828
 
829
829
  let filesToProcess = [];
830
830
  if (Array.isArray(tasks)) {
@@ -858,10 +858,17 @@ export async function run(tasksOrOpts = {}, opts = {}) {
858
858
  return;
859
859
  }
860
860
 
861
- const session = await dispatch(task, { root, config, dryRun: isDry });
861
+ // options.provider mirrors dispatch()'s existing injection point so a
862
+ // caller can exercise the real queue lifecycle without a live provider.
863
+ const session = await dispatch(task, { root, config, dryRun: isDry, provider: options.provider });
862
864
 
863
865
  if (session && session.ok === false) {
864
866
  results.push({ file: fileName, ok: false, status: session.status, error: session.error, session });
867
+ } else if (isDry) {
868
+ // A dry run simulates; it must leave the queue exactly as it found
869
+ // it, or the second `--dry-run` sees an empty queue. Nothing
870
+ // completed either, so nothing is written to the ledger.
871
+ results.push({ file: fileName, ok: true, dryRun: true, session });
865
872
  } else {
866
873
  const dstPath = join(completedDir, fileName);
867
874
  if (existsSync(srcPath)) {