jules-orchestrator-kit 0.72.1 → 0.72.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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.72.2] - 2026-09-08
9
+ *A test suite that writes to stderr did not write silence.*
10
+
11
+ Fixes stream preservation in child process execution, stack-detection build defaults, and CLI flag parity uncovered during Bun + TypeScript trial execution:
12
+ - **Child Process Stream Fidelity & Stderr Preservation (`src/git.mjs`)**: `runCmd()` used `execSync`/`execFileSync` which hardcoded `stderr: ""` on zero exit status. Test runners that print progress and summary statistics to `stderr` (notably `bun test`) had their results silenced, causing `parseCollectedTests` to report unverified test counts. `runCmd()` now invokes `spawnSync` natively, capturing both `stdout` and `stderr` streams regardless of exit status.
13
+ - **Conditional Build Command Resolution (`src/stack-detector.mjs`)**: Bun and Deno detectors previously assigned `buildCmd: "bun run build"` and `buildCmd: "deno task build"` unconditionally upon discovering `bunfig.toml` or `deno.json`. In pure zero-dependency projects lacking a `build` script/task, `agentctl gate` failed in the build stage. `detectPolyglotStack()` now checks `package.json` (or `deno.json`) for explicit build scripts before populating `buildCmd`.
14
+ - **CLI Ergonomics & Alias Parity (`bin/agentctl.mjs`)**: Added `--verify` as a first-class alias for `--verify-cmd` across `agentctl task create`, `task template`, and `task optimize`.
15
+
8
16
  ## [0.72.1] - 2026-09-08
9
17
  *A diff cannot judge what it cannot see.*
10
18
 
package/README.md CHANGED
@@ -448,6 +448,7 @@ const result = await fast.dispatch({ prompt: "Fix a typo." }, { root: process.cw
448
448
 
449
449
  | Feature | Module / Command | Architectural Description | Status |
450
450
  | :--- | :--- | :--- | :---: |
451
+ | **Child Streams & Polyglot Build Detection** | `src/git.mjs`, `src/stack-detector.mjs`, `bin/agentctl.mjs` | Native `spawnSync` execution in `runCmd()` preserving stderr stream on status 0 (supporting Bun test output), conditional `buildCmd` resolution for Bun/Deno scripts, and `--verify` alias parity. | **v0.72.2** *(Shipped)* |
451
452
  | **Staged Diff Fidelity & Indentation Dead Guards** | `src/git.mjs`, `src/security.mjs` | Query cached index in staged mode (`git diff --cached <base>`), detect literal falsity dead guards (`if False:`, `if (false)`, `if 0:`), and support indentation-aware block traversal for Python test suites. | **v0.72.1** *(Shipped)* |
452
453
  | **Cold-Start Hardened Kernel & Tamper Defense** | `src/config.mjs`, `src/engine.mjs`, `src/git.mjs`, `src/security.mjs` | Full remediation of 22 cold-start audit findings (F01–F22): authoritative base policy resolution, ephemeral snapshot worktree isolation, canonical root test tamper guard, conditional assertion defense, multi-target Cargo test aggregation, Python src-layout injection, and complete repository uninstall documentation. | **v0.72.0** *(Shipped)* |
453
454
  | **Silence Is Not A Suite & Scaffolding Linter Fixes** | `src/ops/test-collection.mjs`, `src/wizard-init.mjs`, `src/config.mjs` | Reject zero-output test suite commands, quote-aware YAML parser with scalar emission, test de-registration detection (`TEST_DEREGISTERED`), and active waiver telemetry banner. | **v0.71.0** *(Shipped)* |
package/ROADMAP_V1.md CHANGED
@@ -11,13 +11,18 @@ The **jules-orchestrator-kit** is the zero-dependency safety gatekeeper and self
11
11
  ## 📌 Release Milestones Overview
12
12
 
13
13
  ```
14
- v0.72.1 (Current Stable) ──► v0.73.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
15
- (Staged Diff & Dead Guards) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
14
+ v0.72.2 (Current Stable) ──► v0.73.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
15
+ (Child Streams & Bun/Deno) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
16
16
  ```
17
17
 
18
18
  ---
19
19
 
20
- ## ✅ Shipped Milestones (v0.20.0 – v0.72.1)
20
+ ## ✅ Shipped Milestones (v0.20.0 – v0.72.2)
21
+
22
+ ### v0.72.2: Child Process Stream Fidelity & Polyglot Build Detection
23
+ - [x] **Child Process Stream Fidelity (`src/git.mjs`)** — `runCmd()` invokes native `spawnSync`, preserving both `stdout` and `stderr` streams on exit 0 so test runners emitting summaries to stderr (`bun test`) are fully recognized by `parseCollectedTests`.
24
+ - [x] **Conditional Polyglot Build Resolution (`src/stack-detector.mjs`)** — Bun and Deno projects without declared build scripts default to `buildCmd: ""`, preventing `agentctl gate` false reds during pure script verification.
25
+ - [x] **CLI Flag Parity (`bin/agentctl.mjs`)** — `--verify` supported alongside `--verify-cmd` across `agentctl task create`, `task template`, and `task optimize`.
21
26
 
22
27
  ### v0.72.1: Staged Mode Diff Fidelity & Indentation-Aware Tamper Defense
23
28
  - [x] **Staged Mode Diff Fidelity (`src/git.mjs`)** — `diffText` in staged mode queries `git diff --cached <base>`, ensuring staged additions on feature branches are visible to secret and tamper scanners.
package/bin/agentctl.mjs CHANGED
@@ -1854,6 +1854,7 @@ async function main() {
1854
1854
  template: { type: "string" },
1855
1855
  depends: { type: "string" },
1856
1856
  "depends-on": { type: "string" },
1857
+ verify: { type: "string" },
1857
1858
  "verify-cmd": { type: "string", short: "v" },
1858
1859
  "auto-pr": { type: "boolean" },
1859
1860
  "require-plan-approval": { type: "boolean" },
@@ -1886,7 +1887,7 @@ async function main() {
1886
1887
  tier: values.tier,
1887
1888
  template: values.template,
1888
1889
  dependsOn: values["depends-on"] || values.depends,
1889
- verifyCmd: values["verify-cmd"],
1890
+ verifyCmd: values["verify-cmd"] || values.verify,
1890
1891
  autoPr: values["auto-pr"],
1891
1892
  requirePlanApproval: values["require-plan-approval"],
1892
1893
  repoless: values.repoless,
@@ -1916,6 +1917,7 @@ async function main() {
1916
1917
  options: {
1917
1918
  list: { type: "boolean", short: "l" },
1918
1919
  json: { type: "boolean", short: "j" },
1920
+ verify: { type: "string" },
1919
1921
  "verify-cmd": { type: "string", short: "v" },
1920
1922
  "dry-run": { type: "boolean", short: "d" },
1921
1923
  },
@@ -1949,7 +1951,7 @@ async function main() {
1949
1951
  process.exit(1);
1950
1952
  }
1951
1953
 
1952
- const envelope = synthesizeWebEnvelope(templateName, {}, { verifyCmd: values["verify-cmd"] });
1954
+ const envelope = synthesizeWebEnvelope(templateName, {}, { verifyCmd: values["verify-cmd"] || values.verify });
1953
1955
  if (values.json) {
1954
1956
  console.log(JSON.stringify({ ok: true, ...envelope }, null, 2));
1955
1957
  } else {
@@ -1971,6 +1973,7 @@ async function main() {
1971
1973
  dir: { type: "string", short: "d" },
1972
1974
  web: { type: "boolean", short: "w" },
1973
1975
  json: { type: "boolean", short: "j" },
1976
+ verify: { type: "string" },
1974
1977
  "verify-cmd": { type: "string", short: "v" },
1975
1978
  "dry-run": { type: "boolean" },
1976
1979
  },
@@ -1982,7 +1985,7 @@ async function main() {
1982
1985
  const promptText = resolvePromptInput(values, positionals);
1983
1986
 
1984
1987
  if (values.fix) {
1985
- const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"], web: values.web });
1988
+ const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] || values.verify, web: values.web });
1986
1989
  if (values.json) {
1987
1990
  console.log(JSON.stringify(opt, null, 2));
1988
1991
  } else {
@@ -1991,7 +1994,7 @@ async function main() {
1991
1994
  process.exit(0);
1992
1995
  }
1993
1996
 
1994
- const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
1997
+ const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] || values.verify });
1995
1998
  if (values.json) {
1996
1999
  console.log(JSON.stringify(analysis, null, 2));
1997
2000
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.72.1",
3
+ "version": "0.72.2",
4
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",
package/src/git.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { execFileSync, execSync } from "node:child_process";
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
2
  import { readFileSync, existsSync, statSync, lstatSync, readlinkSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
3
3
  import { join, delimiter } from "node:path";
4
4
  import { tmpdir } from "node:os";
@@ -216,16 +216,17 @@ export function runCmd(command, opts = {}) {
216
216
  }
217
217
 
218
218
  try {
219
- const stdout = useShell
220
- ? execSync(shellCmd, {
219
+ const ret = useShell
220
+ ? spawnSync(shellCmd, {
221
221
  cwd,
222
222
  encoding: "utf-8",
223
+ shell: true,
223
224
  stdio: ["ignore", "pipe", "pipe"],
224
225
  env: childEnv,
225
226
  timeout,
226
227
  maxBuffer,
227
228
  })
228
- : execFileSync(winSpawn ? winSpawn.file : binary, winSpawn ? winSpawn.args : args, {
229
+ : spawnSync(winSpawn ? winSpawn.file : binary, winSpawn ? winSpawn.args : args, {
229
230
  cwd,
230
231
  encoding: "utf-8",
231
232
  shell: winShim,
@@ -236,20 +237,14 @@ export function runCmd(command, opts = {}) {
236
237
  maxBuffer,
237
238
  });
238
239
 
239
- return { status: 0, stdout: String(stdout || "").trim(), stderr: "" };
240
- } catch (err) {
241
- const isTimeout = err.code === "ETIMEDOUT" || (err.signal === "SIGTERM" && err.killed);
242
- const isNobufs = err.code === "ENOBUFS" || (err.message && err.message.includes("maxBuffer"));
240
+ const isTimeout = Boolean(ret.error && (ret.error.code === "ETIMEDOUT" || (ret.signal === "SIGTERM" && ret.error.killed)));
241
+ const isNobufs = Boolean(ret.error && (ret.error.code === "ENOBUFS" || (ret.error.message && ret.error.message.includes("maxBuffer"))));
243
242
 
244
- const status = err.status || (isTimeout ? 124 : 1);
245
- let stdout = (err.stdout || "").toString().trim();
246
- let stderr = (err.stderr || err.message || "").toString().trim();
243
+ const status = typeof ret.status === "number" ? ret.status : (isTimeout ? 124 : 1);
244
+ let stdout = (ret.stdout || "").toString().trim();
245
+ let stderr = (ret.stderr || (ret.error && ret.error.message) || "").toString().trim();
247
246
 
248
247
  if (isTimeout) {
249
- // Node's own message for this is `spawnSync sh ETIMEDOUT`, which already
250
- // contains the token the old guard tested for — so the explanation was
251
- // skipped exactly when it was needed, and the user was left with five
252
- // words that name neither the limit nor the way to raise it.
253
248
  stderr =
254
249
  `Command execution timed out after ${timeout}ms (ETIMEDOUT). ` +
255
250
  `The command was killed, not failed: raise verify.timeout_ms in ` +
@@ -260,17 +255,31 @@ export function runCmd(command, opts = {}) {
260
255
  stderr = `Command output buffer exceeded limit of ${maxBuffer} bytes (ENOBUFS)${stderr ? "\n" + stderr : ""}`;
261
256
  }
262
257
 
263
- if (opts.ignoreError) {
264
- return { status, stdout, stderr };
265
- }
258
+ if (ret.error || status !== 0) {
259
+ if (opts.ignoreError) {
260
+ return { status, stdout, stderr };
261
+ }
266
262
 
267
- if (isTimeout) {
268
- throw new GateError(`Command execution timed out (ETIMEDOUT): ${useShell ? shellCmd : binary}`, { code: status });
269
- }
270
- if (isNobufs) {
271
- throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${useShell ? shellCmd : binary}`, { code: status });
263
+ if (isTimeout) {
264
+ throw new GateError(`Command execution timed out (ETIMEDOUT): ${useShell ? shellCmd : binary}`, { code: status });
265
+ }
266
+ if (isNobufs) {
267
+ throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${useShell ? shellCmd : binary}`, { code: status });
268
+ }
269
+
270
+ const err = ret.error || new Error(`Command failed with status ${status}: ${useShell ? shellCmd : binary}`);
271
+ err.status = status;
272
+ err.code = status;
273
+ err.stdout = stdout;
274
+ err.stderr = stderr;
275
+ throw err;
272
276
  }
273
277
 
278
+ return { status: 0, stdout, stderr };
279
+ } catch (err) {
280
+ if (opts.ignoreError && !(err instanceof GateError)) {
281
+ return { status: err.status || 1, stdout: (err.stdout || "").toString().trim(), stderr: (err.stderr || err.message || "").toString().trim() };
282
+ }
274
283
  throw err;
275
284
  }
276
285
  }
@@ -475,11 +475,27 @@ export function detectPolyglotStack(projectRoot = process.cwd()) {
475
475
  : existsSync(join(projectRoot, "bun.lockb"))
476
476
  ? "bun.lockb"
477
477
  : "bun.lock";
478
- return { ...container, stack: "bun", setupCmd, testCmd: "bun test", buildCmd: "bun run build", triggerFile };
478
+ let buildCmd = "";
479
+ if (existsSync(join(projectRoot, "package.json"))) {
480
+ try {
481
+ const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
482
+ if (pkg && typeof pkg === "object" && pkg.scripts && pkg.scripts.build) {
483
+ buildCmd = "bun run build";
484
+ }
485
+ } catch (_) {}
486
+ }
487
+ return { ...container, stack: "bun", setupCmd, testCmd: "bun test", buildCmd, triggerFile };
479
488
  }
480
489
  if (existsSync(join(projectRoot, "deno.json")) || existsSync(join(projectRoot, "deno.jsonc"))) {
481
490
  const triggerFile = existsSync(join(projectRoot, "deno.json")) ? "deno.json" : "deno.jsonc";
482
- return { ...container, stack: "deno", setupCmd, testCmd: "deno test", buildCmd: "deno task build", triggerFile };
491
+ let buildCmd = "";
492
+ try {
493
+ const denoConfig = JSON.parse(readFileSync(join(projectRoot, triggerFile), "utf-8"));
494
+ if (denoConfig && typeof denoConfig === "object" && denoConfig.tasks && denoConfig.tasks.build) {
495
+ buildCmd = "deno task build";
496
+ }
497
+ } catch (_) {}
498
+ return { ...container, stack: "deno", setupCmd, testCmd: "deno test", buildCmd, triggerFile };
483
499
  }
484
500
  if (existsSync(join(projectRoot, "package.json"))) {
485
501
  const hasPnpm = existsSync(join(projectRoot, "pnpm-lock.yaml"));