jules-orchestrator-kit 0.71.0 → 0.72.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.
@@ -36,4 +36,3 @@ To maximize the ratio of mergeable PRs vs. failed or hallucinated sessions:
36
36
  15. **Web Excellence & Frontend Guardrails:** Enforce quantitative Core Web Vitals (LCP < 1.2s, CLS < 0.05), WCAG 2.2 AA/AAA semantic accessibility, Schema.org JSON-LD compliance, and Playwright multi-viewport responsive testing.
37
37
  16. **Airtight Positive Enclosures ("Pink Elephant" Rule):** Replace long negative constraint lists with strict positive operational perimeters (`ONLY modify [Target/Module]`) to prevent attention-drift in deep context windows.
38
38
  17. **Sterile / Clinical Vocabulary Mandate:** Eradicate aggressive verbs (`kill`, `amputate`, `sabotage`, `destroy`) from prompts and policies. Use clinical equivalents (`terminate PID`, `prune code`, `mutate test logic`, `purge cache`) to prevent false-positive safety classifier trips in Google Cloud VMs.
39
-
package/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ 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.0] - 2026-09-08
9
+ *A guard is not a policy if the diff under review can rewrite the rules.*
10
+
11
+ A fifth cold-start audit, against published v0.71.0 on four polyglot repositories (`p-limit`, `itsdangerous`, `is`, `mini_markdown`). Twenty-two findings across three tiers:
12
+ - Tier 1 (Security & Isolation): uncommitted scaffold tampering, untrusted working-tree configs in staged/committed evaluation, and unisolated snapshot leaks.
13
+ - Tier 2 (Tamper Guard Hardening): root test file canonicalization, conditional assertion bypasses, de-registration detection, impossible preconditions, and uncovered new modules.
14
+ - Tier 3 (False Reds Elimination & CLI Polish): EOF markdown blank lines, cargo clippy warning defaults, multi-target Cargo aggregation, lockfile supply chain remediation diagnostics, `--strict-locks` CLI parsing, truthful waiver telemetry, dry-run evidence suppression, focused subcommand help routing, and documented repository uninstall.
15
+
16
+ ### Fixed
17
+ - **Authoritative Base Policy Resolution (`src/config.mjs`, `src/engine.mjs`)**: Staged and committed gate modes evaluated `.agent/config.yml` from the dirty working copy on disk rather than the base commit under review. An agent diff could raise `diffKb` above 75 KB or replace verification commands with no-ops. `resolveTrustedPolicy()` now extracts authoritative verification stages and security policy from `git show <base>:.agent/config.yml` (or `.agent/jules.yml`), rejecting untrusted disk edits and committed `base: HEAD` self-comparisons (F06, F07, F08).
18
+ - **Uncommitted Scaffold Integrity (`src/config.mjs`)**: `checkBootstrapPolicyIntegrity()` inspects uncommitted scaffolds in bootstrap mode, refusing configurations that replace verification with no-ops (`process.exit(0)`, `sh -c :`), lower verification profiles to `minimal`, or disable `strictTestLock` / `tamperGuard` prior to the initial commit (F06).
19
+ - **Snapshot Materialization & Ephemeral Sandbox Isolation (`src/git.mjs`, `src/engine.mjs`)**: Phase 4 verification ran directly in the dirty repository tree, allowing uncommitted edits outside the evaluated diff to leak into verification runs. `materializeSnapshot()` now extracts the staged index (`git checkout-index`) or committed revision into temporary detached worktrees with symlinked dependency trees (`node_modules`, `.venv`), ensuring hermetic execution (F10).
20
+ - **Python src-layout Invariant (`src/stack-detector.mjs`, `src/engine.mjs`)**: Packages structured under `src/` without root packaging could resolve imports to stale site-packages or fail discovery. `isSrcLayout()` now detects `src/<pkg>/` topologies and automatically prepends `src/` to `PYTHONPATH` during verification (F11).
21
+ - **Empty Test Collection Canaries (`src/ops/test-collection.mjs`, `src/guard-policy.mjs`)**: Go's `ok ... [no tests to run]` and pytest's `--collect-only` exited 0 while verifying nothing. Both are now classified as `count: 0` empty collections, activating empty-run canaries (F09).
22
+ - **Cargo Multi-Target Aggregation (`src/ops/test-collection.mjs`)**: Multi-target Cargo suites (lib unit tests + integration tests) print `running N tests` per target. `parseCollectedTests` now aggregates all target summaries via `matchAll`, preventing a 0-test unit target from hiding 58 passing integration tests (F15).
23
+ - **Cargo Clippy Oracle Defaults (`src/wizard-oracle.mjs`)**: Removed forced `-D warnings` from default `cargo clippy` candidates, preventing green external repositories with benign compiler warnings from failing verification gates (F14).
24
+ - **Truthful Waiver & Override Telemetry (`bin/agentctl.mjs`)**: Added active waiver auditing for `JULES_ALLOW_COMMAND_FILE_CHANGES`, `minTests: 0`, and non-required stage failures, and corrected `verify.required: false` explanation from `(nothing is executed)` to `(verification failures and empty test suites permitted)` (F18, F19).
25
+ - **Dry-Run Evidence Suppression (`src/engine.mjs`, `bin/agentctl.mjs`)**: Suppressed `.agent/evidence/` disk writes when `--dry-run` or `JULES_DRY_RUN=1` is active, and added visible `[DRY-RUN]` markers to `agentctl plan approve` and `agentctl session get` (F20).
26
+ - **Subcommand Help Routing (`bin/agentctl.mjs`, `src/ops/command-registry.mjs`)**: Registered descriptors for `pr harvest`, `session get`, and `plan approve`, and routed `agentctl help <subcommand>` to focused subcommand usage rather than dumping all 50 commands (F21).
27
+ - **Markdown EOF Hygiene (`JULES_RULES_TEMPLATE.md`, `.agent/rules/jules-protocol.md`)**: Stripped trailing double newlines (F13).
28
+
29
+ ### Added
30
+ - **Canonical Root Test Guard (`src/test-paths.mjs`, `src/security.mjs`)**: Root test files like `test.js` are now classified as canonical test files under `BUILTIN_PROTECT` and anti-tamper auditing (F01).
31
+ - **Conditional Assertion Guard (`src/security.mjs`)**: Ternary and conditional wrapper expressions cannot mask broken logic by rewriting assertions into conditional skips (F03).
32
+ - **De-registration & Skip Detection (`src/security.mjs`)**: Detects removed `#[test]` attributes in Rust, Go build tags, xfail decorators in Python, and body-first early returns (F04).
33
+ - **Reachable Preconditions Guard (`src/security.mjs`)**: Catches impossible preconditions like `if len(x) < 0:` placed before assertions (F05).
34
+ - **Zero-Coverage Added Module Detection (`src/coverage.mjs`)**: Brand-new code modules that receive zero test execution fail coverage thresholds rather than being ignored (F12).
35
+ - **Complete Uninstall & Undo-Init Documentation (`README.md`)**: Added full removal documentation and clean commands (`git rm -rf --ignore-unmatch ... && rm -rf .agent .agentctl`) preserving pre-existing user files (F22).
36
+ - **Cold-Start Trial Regression Suites**: Added 52 new regression tests across `test/cold-start-trial-f01-f12.test.mjs`, `test/cold-start-trial-f06-f11.test.mjs`, and `test/cold-start-trial-f13-f22.test.mjs`.
37
+
8
38
  ## [0.71.0] - 2026-09-05
9
39
  *A blanket is not a check, and silence is not a suite.*
10
40
 
@@ -124,4 +124,3 @@ HARD CONSTRAINTS:
124
124
  - BEFORE opening the PR: Run `git fetch origin <base> && git rebase origin/<base>`, then re-verify. If the rebase leaves an empty diff, the work already landed — do NOT submit.
125
125
  - Remove any scratch files you created for debugging before submitting. Do not delete files that are part of the project.
126
126
  ```
127
-
package/README.md CHANGED
@@ -208,7 +208,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
208
208
  * **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).
209
209
  * **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.
210
210
  * **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
211
- * **Verified Test Suite:** Tested with **1299 unit tests across 174 suites**, green on every supported platform.
211
+ * **Verified Test Suite:** Tested with **1399 unit tests across 195 suites**, green on every supported platform.
212
212
 
213
213
  <br/>
214
214
 
@@ -448,6 +448,8 @@ 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
+ | **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)* |
452
+ | **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)* |
451
453
  | **Diagnostics That Reach the Operator** | `src/security.mjs`, `src/engine.mjs`, `bin/agentctl.mjs` | Secret findings name the file and line, a failed verify stage reports its command, exit code and output, and `queue`/`swarm` name each failed task and exit `1` rather than reporting success for a run that dispatched nothing. | **v0.41.1** *(Shipped)* |
452
454
  | **One Scaffolding Path & First-Install Fixes** | `src/scaffold.mjs`, `src/security.mjs` | `agentctl init` and `jules-init` scaffold from one source and write the runtime `.gitignore` entries, so the kit's own bookkeeping no longer reaches its own gate; a lockfile bump no longer fails closed as a secret leak. | **v0.41.1** *(Shipped)* |
453
455
  | **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)* |
@@ -473,6 +475,59 @@ const result = await fast.dispatch({ prompt: "Fix a typo." }, { root: process.cw
473
475
 
474
476
  <br/>
475
477
 
478
+ ## 🧹 Complete Uninstall / Removing the Kit (Undo Init)
479
+
480
+ If you need to completely remove `jules-orchestrator-kit` from a repository after running `agentctl init`, follow the procedure below. Note that `agentctl clean` is an operational maintenance command (cleaning ephemeral locks, temporary worktrees, and evidence caches), not an uninstaller.
481
+
482
+ ### 1. Generated Assets & Manifest
483
+
484
+ `agentctl init` / `scaffoldRepoAssets()` writes the following project files and directories:
485
+ - **Core configuration and rules:** `.agent/config.yml` (or `.agent/jules.yml`), `.agent/rules/`, `.agent/prompts/`, `.agent/workflows/`, and `AGENTS.md`.
486
+ - **System contracts:** `SPEC.md`, `CONSTRAINTS.md` (and optional `DESIGN.md`).
487
+ - **Queue runtime stub:** `.agent/jules-queue/README.md`.
488
+ - **Optional IDE & CI integrations:** `.github/workflows/agent-gate.yml`, `.gitlab-ci.agent-gate.yml`, and `.cursor/rules/jules.mdc`.
489
+
490
+ ### 2. Runtime State & Working Trees
491
+
492
+ During execution, the kit produces untracked runtime artifacts in:
493
+ - `.agent/evidence/` — Cryptographic evidence manifests and stage run recordings.
494
+ - `.agent/state/` — Flaky test ledgers, budget trackers, and escalation queues.
495
+ - `.agent/worktrees/` — Isolated snapshot worktrees used by the verification sandbox.
496
+ - `.agent/history/` and `.agent/handovers/` — Local agent session memories.
497
+
498
+ ### 3. Removal Procedure (Preserving Pre-Existing User Files)
499
+
500
+ To completely undo `init` and restore your working tree to its exact original state:
501
+
502
+ ```bash
503
+ # 1. Remove tracked orchestrator assets (skips any files that were not scaffolded)
504
+ git rm -rf --ignore-unmatch \
505
+ .agent \
506
+ AGENTS.md \
507
+ SPEC.md \
508
+ CONSTRAINTS.md \
509
+ DESIGN.md \
510
+ .github/workflows/agent-gate.yml \
511
+ .gitlab-ci.agent-gate.yml \
512
+ .cursor/rules/jules.mdc
513
+
514
+ # 2. Remove untracked runtime directories and temporary caches
515
+ rm -rf .agent .agentctl
516
+
517
+ # 3. Clean up .gitignore additions
518
+ # Revert the appended "# Jules Orchestrator runtime state & credentials" block from .gitignore
519
+ git checkout .gitignore # If .gitignore had no other unstaged changes, or edit by hand
520
+
521
+ # 4. Optional: Uninstall global CLI package
522
+ npm uninstall -g jules-orchestrator-kit
523
+ ```
524
+
525
+ <br/>
526
+
527
+ ---
528
+
529
+ <br/>
530
+
476
531
  ## 📖 Documentation & External References
477
532
 
478
533
  - [**System Architecture & Pipeline Overview**](./docs/architecture.md) — Comprehensive technical sequence diagrams and control plane specifications.
package/ROADMAP_V1.md CHANGED
@@ -11,14 +11,30 @@ The **jules-orchestrator-kit** is the zero-dependency safety gatekeeper and self
11
11
  ## 📌 Release Milestones Overview
12
12
 
13
13
  ```
14
- v0.71.0 (Current Stable) ──► v0.72.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
15
- (A Blanket Is Not A Check) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
14
+ v0.72.0 (Current Stable) ──► v0.73.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
15
+ (Cold-Start Hardened Kernel) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
16
16
  ```
17
17
 
18
18
  ---
19
19
 
20
- ## ✅ Shipped Milestones (v0.20.0 – v0.71.0)
21
-
20
+ ## ✅ Shipped Milestones (v0.20.0 – v0.72.0)
21
+
22
+
23
+ ### v0.72.0: Cold-Start Hardened Kernel & Tamper Defense
24
+ - [x] **Canonical Root Test Guard (F01)** — `test.js` at repository root is inside the tamper guard.
25
+ - [x] **Conditional Expectation Guard (F03)** — ternary and conditional assertions cannot mask broken logic.
26
+ - [x] **De-registration & Skip Detection (F04)** — removed `#[test]`, build tags, xfail decorators, and body-first early returns are caught.
27
+ - [x] **Reachable Preconditions (F05)** — impossible guard conditions (`len < 0`) cannot neutralise assertions.
28
+ - [x] **Uncommitted Scaffold Integrity (F06)** — rejects uncommitted scaffolds that disable verification or lower profiles.
29
+ - [x] **Trusted Base Policy Resolution (F07)** — authoritative verification stages resolved from base commit (`git show <base>:.agent/config.yml`), never trusting uncommitted edits under review.
30
+ - [x] **Committed Base Branch Integrity (F08)** — rejects `--base HEAD` in committed mode.
31
+ - [x] **Empty Test Collection Canaries (F09)** — empty collections (Go `[no tests to run]`, pytest `--collect-only`, no-op scripts) recognized as 0 tests.
32
+ - [x] **Snapshot Worktree Isolation (F10)** — isolates staged index and committed revisions in ephemeral worktrees with symlinked dependencies.
33
+ - [x] **Python src-layout Invariant (F11)** — automatically injects `PYTHONPATH=src` for package layouts.
34
+ - [x] **Zero-Coverage Added Module Detection (F12)** — untracked/unexecuted new files fail coverage.
35
+ - [x] **Clean Scaffold & Oracle Tuning (F13–F15)** — markdown newline hygiene, cargo clippy without `-D warnings`, and multi-target Cargo aggregation.
36
+ - [x] **Supply Chain Diagnostics & Waiver Telemetry (F16–F20)** — lockfile tamper hints, `--strict-locks` flag, waiver auditing, and dry-run evidence suppression.
37
+ - [x] **Targeted Help & Complete Uninstall (F21–F22)** — targeted subcommand help routing and documented full removal procedure.
22
38
 
23
39
  ### v0.71.0: A Blanket Is Not A Check
24
40
  - [x] **Silence Is Not A Suite (`src/ops/test-collection.mjs`, `src/wizard-init.mjs`)** — a command that claims to run tests and prints nothing ran none; a static gate that prints nothing did its job.
@@ -190,9 +206,9 @@ The **jules-orchestrator-kit** is the zero-dependency safety gatekeeper and self
190
206
 
191
207
  ---
192
208
 
193
- ## 🎯 Target Milestones (v0.60.0 & v1.0.0)
209
+ ## 🎯 Target Milestones (v0.73.0 & v1.0.0)
194
210
 
195
- ### v0.60.0: Distributed File Leases & Preemptive DAG Scheduling
211
+ ### v0.73.0: Distributed File Leases & Preemptive DAG Scheduling
196
212
  - [ ] **Atomic Filesystem Lease & Heartbeat Protocol (`src/engine.mjs`, `src/flaky-ledger.mjs`)** — Directory-mutex file leasing with heartbeat timestamps, stale-lock detection via PID liveness inspection, and tombstone rotation without third-party daemons or Redis.
197
213
  - [ ] **Preemptive Task Cancellation & Interface Fingerprints (`src/dag-engine.mjs`)** — Automatically aborts and yields downstream swarm tasks when upstream exported symbol interfaces diverge from their cryptographic SHA-256 fingerprints.
198
214
  - [ ] **POSIX/Win32 Process Group Guillotine (`src/engine.mjs`)** — Tree teardown via `process.kill(-pid, 'SIGKILL')` on POSIX and `taskkill /T /F /PID` on Windows to eliminate orphaned dev-servers and background watchers.
package/bin/agentctl.mjs CHANGED
@@ -211,6 +211,23 @@ async function main() {
211
211
  process.exit(0);
212
212
  }
213
213
 
214
+ if (command === "help") {
215
+ const target = args[1];
216
+ if (!target || target === "--help" || target === "-h") {
217
+ printHelp();
218
+ process.exit(0);
219
+ }
220
+ const { getCommandDescriptor, formatCommandHelp } = await import("../src/ops/command-registry.mjs");
221
+ const subSub = args[2] && !args[2].startsWith("-") ? `${target} ${args[2]}` : target;
222
+ const desc = getCommandDescriptor(subSub) || getCommandDescriptor(target);
223
+ if (desc) {
224
+ console.log(formatCommandHelp(desc));
225
+ process.exit(0);
226
+ }
227
+ console.log(`\nUsage: agentctl ${args.slice(1).join(" ")} [options]\n\nFor general help, run: agentctl --help\n`);
228
+ process.exit(0);
229
+ }
230
+
214
231
  // Bare `agentctl` answers "what do I do next" rather than dumping thirty
215
232
  // commands. The help text is a reference for people who already know the
216
233
  // tool; a newcomer cannot tell which entry is step one, and guessing wrong
@@ -248,7 +265,7 @@ async function main() {
248
265
  console.log(formatCommandHelp(desc));
249
266
  process.exit(0);
250
267
  }
251
- printHelp();
268
+ console.log(`\nUsage: agentctl ${command}${subArgs[0] && !subArgs[0].startsWith("-") ? " " + subArgs[0] : ""} [options]\n\nFor general help, run: agentctl --help\n`);
252
269
  process.exit(0);
253
270
  }
254
271
 
@@ -376,7 +393,7 @@ async function main() {
376
393
  const { values } = parseArgs({
377
394
  args: args.slice(1),
378
395
  options: {
379
- base: { type: "string", short: "b", default: config.baseBranch || "main" },
396
+ base: { type: "string", short: "b" },
380
397
  mode: { type: "string", short: "m", default: "working-tree" },
381
398
  "working-tree": { type: "boolean" },
382
399
  staged: { type: "boolean" },
@@ -384,6 +401,7 @@ async function main() {
384
401
  fix: { type: "boolean" },
385
402
  "allow-protected": { type: "boolean" },
386
403
  "allow-unreadable-tests": { type: "boolean" },
404
+ "strict-locks": { type: "boolean" },
387
405
  // The tamper guard has always had an override — `allowTestModifications`
388
406
  // — and it was reachable only from JavaScript. So a legitimate change
389
407
  // of spec, which necessarily rewrites what a test expects, hit a
@@ -417,13 +435,38 @@ async function main() {
417
435
  allowUnreadableTests: values["allow-unreadable-tests"],
418
436
  allowTestModifications: values["allow-test-modifications"],
419
437
  allowTestChanges: values["allow-test-change"],
438
+ strictLocks: values["strict-locks"],
439
+ dryRun: values["dry-run"],
420
440
  jsonReport: values["json-report"],
421
441
  });
422
442
 
443
+ const overrides = [];
444
+ const julesCmdAllow = process.env.JULES_ALLOW_COMMAND_FILE_CHANGES === "true" || process.env.JULES_ALLOW_COMMAND_FILE_CHANGES === "1";
445
+ const agentCmdAllow = process.env.AGENT_ALLOW_COMMAND_FILE_CHANGES === "true" || process.env.AGENT_ALLOW_COMMAND_FILE_CHANGES === "1";
446
+ if (values["allow-protected"] || julesCmdAllow || agentCmdAllow) {
447
+ const via = values["allow-protected"] ? "--allow-protected" : (julesCmdAllow ? "JULES_ALLOW_COMMAND_FILE_CHANGES" : "AGENT_ALLOW_COMMAND_FILE_CHANGES");
448
+ overrides.push(`${via} (protected paths permitted)`);
449
+ }
450
+ if (values["allow-unreadable-tests"]) overrides.push("--allow-unreadable-tests (unreadable dialect permitted)");
451
+ if (values["allow-test-modifications"]) overrides.push("--allow-test-modifications (every tamper check waived)");
452
+ if (values["allow-test-change"]) {
453
+ const kinds = [].concat(values["allow-test-change"]).join(", ");
454
+ overrides.push(`--allow-test-change ${kinds} (tamper check waived: ${kinds})`);
455
+ }
456
+ if (config?.verify?.required === false) overrides.push("verify.required: false (verification failures and empty test suites permitted)");
457
+ if (config?.verify?.minTests === 0 || config?.verify?.min_tests === 0 || config?.minTests === 0 || config?.min_tests === 0) overrides.push("verify.minTests: 0 (test collection floor disabled)");
458
+ if (config?.verify?.tamperGuard === "warn") overrides.push('verify.tamperGuard: "warn" (unreadable dialects report only)');
459
+ const optionalFails = (res.phases?.find((p) => p.phase === "verify")?.executionRecords || []).filter((r) => r.required === false && !r.ok);
460
+ for (const r of optionalFails) {
461
+ overrides.push(`stage.${r.id || r.name}: required: false (failing optional stage permitted)`);
462
+ }
463
+
423
464
  if (values.json) {
465
+ res.overrides = overrides;
424
466
  console.log(JSON.stringify(res, null, 2));
425
467
  } else {
426
- console.log(`\n🛡️ agentctl Safety Gate Audit Results (Base: ${values.base}, Mode: ${selectedMode})`);
468
+ const reportedBase = res.base || values.base || "main";
469
+ console.log(`\n🛡️ agentctl Safety Gate Audit Results (Base: ${reportedBase}, Mode: ${selectedMode})`);
427
470
  console.log(`-----------------------------------------------------`);
428
471
  // A loosened run must not be able to pass for a strict one.
429
472
  //
@@ -435,16 +478,6 @@ async function main() {
435
478
  // check had been waived, which makes the waiver invisible exactly where
436
479
  // it matters most. Printed before the phases, and on approval as well
437
480
  // as rejection, because an approval is the case where it is load-bearing.
438
- const overrides = [];
439
- if (values["allow-protected"]) overrides.push("--allow-protected (protected paths permitted)");
440
- if (values["allow-unreadable-tests"]) overrides.push("--allow-unreadable-tests (unreadable dialect permitted)");
441
- if (values["allow-test-modifications"]) overrides.push("--allow-test-modifications (every tamper check waived)");
442
- if (values["allow-test-change"]) {
443
- const kinds = [].concat(values["allow-test-change"]).join(", ");
444
- overrides.push(`--allow-test-change ${kinds} (tamper check waived: ${kinds})`);
445
- }
446
- if (config.verify?.required === false) overrides.push("verify.required: false (nothing is executed)");
447
- if (config.verify?.tamperGuard === "warn") overrides.push('verify.tamperGuard: "warn" (unreadable dialects report only)');
448
481
  if (overrides.length > 0) {
449
482
  console.log(` ⚠️ OVERRIDES ACTIVE — this run is not a strict pass:`);
450
483
  for (const o of overrides) console.log(` • ${o}`);
@@ -607,9 +640,15 @@ async function main() {
607
640
  console.log(` • Commit them once and the gate goes green:`);
608
641
  console.log(` git add ${untracked.join(" ")} && git commit -m "chore: add agent config"\n`);
609
642
  } else {
643
+ const hasLockfileViolation = scopeFiles.some((f) => /(?:^|\/)(?:package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|Cargo\.lock|go\.sum|uv\.lock|poetry\.lock|Pipfile\.lock|composer\.lock)$/.test(f));
610
644
  console.log(`💡 Remediation Hint (Exit ${res.code} Scope Violation):`);
611
- console.log(` • To allow protected files in this run, pass: agentctl gate --allow-protected`);
612
- console.log(` • Or remove protected/denied paths from the diff before dispatching.\n`);
645
+ if (hasLockfileViolation) {
646
+ console.log(` • Lockfiles are protected against supply-chain tampering. Routine dependency`);
647
+ console.log(` updates are permitted using the maintainer waiver: agentctl gate --allow-protected\n`);
648
+ } else {
649
+ console.log(` • To allow protected files in this run, pass: agentctl gate --allow-protected`);
650
+ console.log(` • Or remove protected/denied paths from the diff before dispatching.\n`);
651
+ }
613
652
  }
614
653
  } else if (failedPhase === "git_resolution") {
615
654
  console.log(`💡 Remediation Hint (Exit ${res.code} Base Branch Unresolvable):`);
@@ -2252,7 +2291,7 @@ async function main() {
2252
2291
  if (values.json) {
2253
2292
  console.log(JSON.stringify(res, null, 2));
2254
2293
  } else {
2255
- console.log(`\n✅ Plan Approved Successfully!`);
2294
+ console.log(values["dry-run"] ? `\n[DRY-RUN] Plan Approved Successfully (Simulation)!` : `\n✅ Plan Approved Successfully!`);
2256
2295
  console.log(` Session ID : ${res.id}`);
2257
2296
  console.log(` Status : ${res.status}\n`);
2258
2297
  }
@@ -2289,7 +2328,7 @@ async function main() {
2289
2328
  if (values.json) {
2290
2329
  console.log(JSON.stringify(res, null, 2));
2291
2330
  } else {
2292
- console.log(`\n✅ Plan Approved Successfully!`);
2331
+ console.log(values["dry-run"] ? `\n[DRY-RUN] Plan Approved Successfully (Simulation)!` : `\n✅ Plan Approved Successfully!`);
2293
2332
  console.log(` Session ID : ${res.id}`);
2294
2333
  console.log(` Status : ${res.status}\n`);
2295
2334
  }
@@ -2325,7 +2364,7 @@ async function main() {
2325
2364
  if (values.json) {
2326
2365
  console.log(JSON.stringify(res, null, 2));
2327
2366
  } else {
2328
- console.log(`\n📋 Remote Session Status:`);
2367
+ console.log(values["dry-run"] ? `\n📋 [DRY-RUN] Remote Session Status (Simulation):` : `\n📋 Remote Session Status:`);
2329
2368
  console.log(` Session ID : ${res.id}`);
2330
2369
  console.log(` Status : ${res.status}\n`);
2331
2370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
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",
@@ -75,9 +75,14 @@ function canaryDiff(c) {
75
75
  const lines = [`--- a/${c.file}`, `+++ b/${c.file}`, "@@ -1,20 +1,20 @@", ` ${ctx}`];
76
76
  // Unchanged lines the change sits inside. An assertion whose keyword is on
77
77
  // one of these is the shape the line-level denominator could not see.
78
- for (const l of c.lead || []) lines.push(` ${l}`);
79
- for (const l of c.removed) lines.push(`-${l}`);
80
- for (const l of c.added) lines.push(`+${l}`);
78
+ // Entries may contain newlines (a multi-line fixture); each physical line
79
+ // carries its own diff prefix, exactly as git emits it.
80
+ const each = (arr, prefix) => {
81
+ for (const l of arr) for (const part of String(l).split("\n")) lines.push(`${prefix}${part}`);
82
+ };
83
+ each(c.lead || [], " ");
84
+ each(c.removed, "-");
85
+ each(c.added, "+");
81
86
  lines.push(` ${ctx}`);
82
87
  return lines.join("\n");
83
88
  }
@@ -138,17 +143,35 @@ const canaryResults = new Map();
138
143
  const silent = [];
139
144
  const noDenominator = [];
140
145
  const noAssertions = [];
146
+ // Findings whose own evidence is a comment line: Go build constraints are
147
+ // comments to the compiler, so a constraint-only diff contributes no
148
+ // *examined* code lines — the finding's denominator is the file it sits in,
149
+ // and `filesSeen` carries that. Requiring `inputsSeen` here would ask the
150
+ // counter to count comments, which is exactly what it must skip.
151
+ const COMMENT_LINE_FINDING = /^skip-injection\/go-build/;
141
152
  for (const c of [...TAMPER_CANARIES, ...MULTILINE_CANARIES]) {
142
153
  const res = checkTestTampering(canaryDiff(c));
143
154
  const hit = (res.violations || []).some((v) => v.type === c.expect);
144
155
  canaryResults.set(c.id, hit);
145
156
  if (!hit) silent.push(`${c.id} expected ${c.expect}, got ${JSON.stringify((res.violations || []).map((v) => v.type))}`);
146
157
  // A finding with no denominator is the shape this script exists to reject.
147
- if (hit && !(res.inputsSeen > 0)) noDenominator.push(c.id);
158
+ if (hit && !(res.inputsSeen > 0) && !COMMENT_LINE_FINDING.test(c.id)) noDenominator.push(c.id);
159
+ if (hit && COMMENT_LINE_FINDING.test(c.id) && !(res.filesSeen > 0)) noDenominator.push(c.id);
148
160
  // Counting lines was not enough: a JUnit diff reported one input examined
149
161
  // and a clean PASS while every assertion in it went unrecognised. A rule
150
162
  // about assertions has to say how many assertions it actually read.
151
- if (hit && c.expect !== "TEST_SKIP_INJECTION" && !(res.assertionsSeen > 0)) {
163
+ //
164
+ // A finding about test *execution* rather than assertion content —
165
+ // skips, xfail/cfg/build-constraint exclusion, de-registration by rename
166
+ // or attribute removal — may legitimately be the only change in the
167
+ // diff, with no assertion line on either side. Asserting an
168
+ // `assertionsSeen` there is a denominator the finding does not have; the
169
+ // assertions a dead-tagged test holds are in context, not in the edit.
170
+ const assertionFinding =
171
+ c.expect !== "TEST_SKIP_INJECTION" &&
172
+ c.expect !== "TEST_DEREGISTERED" &&
173
+ !/^(skip-injection|deregistration)\//.test(c.id);
174
+ if (hit && assertionFinding && !(res.assertionsSeen > 0)) {
152
175
  noAssertions.push(`${c.id} (${res.assertionsSeen} assertions parsed)`);
153
176
  }
154
177
  }