jules-orchestrator-kit 0.67.0 → 0.69.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/AGENTS.md +135 -0
- package/CHANGELOG.md +852 -0
- package/README.md +7 -3
- package/ROADMAP_V1.md +210 -0
- package/bin/agentctl.mjs +9 -0
- package/package.json +5 -2
- package/scripts/doc-sync-check.mjs +9 -0
- package/src/config.mjs +8 -2
- package/src/engine.mjs +44 -3
- package/src/git.mjs +39 -5
- package/src/guard-policy.mjs +136 -0
- package/src/ops/test-collection.mjs +21 -0
- package/src/security.mjs +196 -43
- package/src/stack-detector.mjs +56 -1
- package/src/wizard-init.mjs +60 -4
package/README.md
CHANGED
|
@@ -68,7 +68,8 @@ git add .agent AGENTS.md SPEC.md CONSTRAINTS.md .gitignore && git commit -m "cho
|
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
# 3. Author a scoped, verified task envelope with guardrails & secret scrubbing
|
|
71
|
-
|
|
71
|
+
# Interactive by default. Pass the prompt to skip straight to review:
|
|
72
|
+
npx jules-orchestrator-kit task create -p "Refactor the invoice module"
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
`init` reads the repository, not a template: it detects the stack, picks a
|
|
@@ -207,7 +208,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
207
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).
|
|
208
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.
|
|
209
210
|
* **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
|
|
210
|
-
* **Verified Test Suite:** Tested with **
|
|
211
|
+
* **Verified Test Suite:** Tested with **1223 unit tests across 173 suites**, green on every supported platform.
|
|
211
212
|
|
|
212
213
|
<br/>
|
|
213
214
|
|
|
@@ -240,7 +241,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
240
241
|
| `doctor` | `agentctl doctor [--probe] [--json]` | Diagnostic check runner. `--probe` additionally starts the configured provider's CLI to confirm it answers, rather than only finding it on `PATH`. | `0` (Healthy), `1` (Failures) |
|
|
241
242
|
| `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) |
|
|
242
243
|
| `swarm` | `agentctl swarm [--json]` | Runs parallel multi-agent swarm across worker slots with PID liveness detection. | `0` (Complete) |
|
|
243
|
-
| `check` / `gate` / `audit`| `agentctl check [--mode working-tree] [--fix] [--allow-protected] [--allow-test-change <kind>] [--json] [--json-report <path>]` | Runs security, secret scanning, rules budget audit, and tiered verification gates (with declarative assertion support) against working tree or branch. | `0` (Approved), `1` (Budget/Arg), `3` (Scope), `4` (Verify), `5` (Diff >75K), `6` (Secret), `8` (Flaky) |
|
|
244
|
+
| `check` / `gate` / `audit`| `agentctl check [--mode working-tree] [--fix] [--allow-protected] [--allow-test-change <kind>] [--json] [--json-report <path>]` | Runs security, secret scanning, rules budget audit, and tiered verification gates (with declarative assertion support) against working tree or branch. | `0` (Approved), `1` (Budget/Arg), `3` (Scope), `4` (Verify), `5` (Diff >75K), `6` (Secret **or** test integrity), `8` (Flaky) |
|
|
244
245
|
| `mutate` / `mutation` | `agentctl mutate [--min-score <n>] [--max-mutants <n>] [--cmd <testCmd>] [--json]` | Runs zero-dependency diff mutation testing harness on changed hunks with operator inversion and safety rollback. | `0` (Passed), `1` (Score Low) |
|
|
245
246
|
| `coverage` | `agentctl coverage [--min <pct>] [--cmd <testCmd>] [--base <ref>] [--json]` | Runs native zero-dependency V8 diff coverage check against added diff lines. | `0` (Passed), `1` (Low Coverage) |
|
|
246
247
|
| `probe` / `stability` | `agentctl probe [--repeat <n>] [--min <passRate>] [--cmd <testCmd>] [--json]` | Probes test suite flakiness across N consecutive iterations with oscillation detection. | `0` (Passed), `1` (Flaky) |
|
|
@@ -285,6 +286,9 @@ branchPrefix: "agent/" # Prefix for task branches
|
|
|
285
286
|
verify:
|
|
286
287
|
test: "npm test"
|
|
287
288
|
build: "npm run build"
|
|
289
|
+
timeout_ms: 300000 # Per-stage kill time in ms (default 300000)
|
|
290
|
+
minTests: 1 # Floor for "the suite actually ran" (0 disables)
|
|
291
|
+
required: true # false = this repo uses only the scope/secret phases
|
|
288
292
|
|
|
289
293
|
# Scope protection rules (Deny-first evaluation)
|
|
290
294
|
scope:
|
package/ROADMAP_V1.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# 🗺️ Jules Orchestrator Kit — Roadmap to v1.0 & Beyond
|
|
2
|
+
|
|
3
|
+
The **jules-orchestrator-kit** is the zero-dependency safety gatekeeper and self-healing engineering kernel for autonomous coding agents — **Google Jules**, **Claude Code**, **Codex** and the **Gemini CLI** — in any repository and any stack.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Zero Runtime Dependencies is a strict core invariant.**
|
|
7
|
+
> Every feature on this roadmap is built strictly with native Node.js 20+ built-in modules (`node:fs`, `node:child_process`, `node:crypto`, `node:http`, `node:readline`, `node:test`).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📌 Release Milestones Overview
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
v0.69.0 (Current Stable) ──► v0.70.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
|
|
15
|
+
(Read, Not Just Counted) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✅ Shipped Milestones (v0.20.0 – v0.69.0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### v0.69.0: Read, Not Just Counted
|
|
24
|
+
- [x] **Expected-Value-First Assertions (`src/security.mjs`)** — JUnit and PHPUnit document the order the guard read as prose.
|
|
25
|
+
- [x] **A Regex Is An Expected Value (`src/security.mjs`)** — a rewritten pattern was neither a change nor a loss.
|
|
26
|
+
- [x] **Renaming A Test Is Not Tampering (`src/security.mjs`)** — on the one-line form, the name blanked into the expectation.
|
|
27
|
+
- [x] **An Environment Prefix Runs (`src/git.mjs`)** — `PYTHONPATH=src pytest` never started, and was reported as a failure.
|
|
28
|
+
- [x] **`node -e ""` Is A Placeholder (`src/stack-detector.mjs`)** — the same no-op, spelled to look like work.
|
|
29
|
+
- [x] **`init` Rejects An Oracle That Proves Nothing (`src/wizard-init.mjs`)** — silence at setup is silence at every gate after it.
|
|
30
|
+
|
|
31
|
+
### v0.68.0: Not An Approval Either
|
|
32
|
+
- [x] **An Unreadable Dialect Blocks (`src/security.mjs`)** — the guard said it had not checked, and approved anyway.
|
|
33
|
+
- [x] **A Command That Cannot Fail Is Not An Oracle (`src/engine.mjs`)** — `task create` already refused what the gate accepted.
|
|
34
|
+
- [x] **chai And node-tap (`src/security.mjs`)** — a dot chain where RSpec has a space, and a receiver named `ct`.
|
|
35
|
+
- [x] **A Reformat Is Not A Weakening (`src/security.mjs`)** — the weakening count was still line-based.
|
|
36
|
+
- [x] **`bootstrap` Declines (`src/stack-detector.mjs`)** — rather than writing an oracle that asserts its own impossibility.
|
|
37
|
+
|
|
38
|
+
### v0.67.0: A Move Is Not A Deletion
|
|
39
|
+
- [x] **In-Body Skips (`src/security.mjs`)** — a test could be silenced with `self.skipTest()`, the form unittest's own documentation uses.
|
|
40
|
+
- [x] **Cross-File Moves (`src/security.mjs`)** — refactoring produced CRITICAL findings for assertions that still run.
|
|
41
|
+
- [x] **The Published Package Explains Itself (`scripts/run-tests.mjs`)** — `npm test` crashed with a raw ENOENT in an installed copy.
|
|
42
|
+
- [x] **The Label Names What Failed (`bin/agentctl.mjs`)** — a deleted assertion was reported under a bare SECRETS heading.
|
|
43
|
+
|
|
44
|
+
### v0.66.0: Saying Nothing Is Not Saying Approved
|
|
45
|
+
- [x] **An Assertion Is A Statement (`src/security.mjs`)** — a rewritten expected value whose keyword sat on a context line collected five green phases.
|
|
46
|
+
- [x] **Line Comments Are Stripped (`src/security.mjs`)** — they never were; `pending` was left at the comment and the copy after the loop put it back.
|
|
47
|
+
- [x] **The Boundary Reaches The Operator (`src/security.mjs`)** — the warning was wired to a function the gate does not call.
|
|
48
|
+
- [x] **Evidence Before Rules (`src/memory.mjs`)** — a hardcoded sentence was being injected into every prompt as if it were a fix.
|
|
49
|
+
|
|
50
|
+
### v0.65.0: The First Hour
|
|
51
|
+
- [x] **The Quickstart No Longer Rejects Its Own Output (`src/engine.mjs`)** — `init` wrote the scaffold, told the user to commit it, and the gate called that commit a scope violation while advising `--allow-protected`.
|
|
52
|
+
- [x] **A Stated Count Outranks A Phrase (`src/ops/test-collection.mjs`)** — a healthy 190-test TAP suite was rejected as empty because a skipped fixture printed `no tests found`.
|
|
53
|
+
- [x] **`init` Probes On The Headless Path (`src/wizard-init.mjs`)** — `--yes` means "do not ask me", not "do not check".
|
|
54
|
+
- [x] **`verify.minTests` Reaches The Floor (`src/engine.mjs`)** — the failure message named a lever that was connected to nothing.
|
|
55
|
+
|
|
56
|
+
### v0.64.0: What The Guard Could Not Read
|
|
57
|
+
- [x] **The Package Is Checked The Way It Is Installed (`scripts/package-integrity-check.mjs`)** — the tarball's own import graph, resolved inside the tarball. The previous release shipped the activation-coverage check without the contract it imports.
|
|
58
|
+
- [x] **Five Silent Ecosystems (`src/security.mjs`)** — JUnit, RSpec, PHPUnit, Minitest and XCTest all returned `PASS` on a rewritten expected value, and `assertEqual` had been working only by accident.
|
|
59
|
+
- [x] **`UNREADABLE` (`src/security.mjs`)** — coverage ending is fine; coverage ending silently is not.
|
|
60
|
+
- [x] **Contracts For The Opposite Failure (`src/guard-policy.mjs`)** — ten ordinary edits that must produce no finding, because a guard that flags everything is switched off within a week.
|
|
61
|
+
|
|
62
|
+
### v0.63.0: Can It Still Go Red?
|
|
63
|
+
- [x] **Activation Coverage (`scripts/guard-reach-check.mjs`)** — Blocking in CI and in the release. Eleven known-bad canaries across six ecosystems must each still produce the finding they name; four hand-written mutants of the applicability predicate must each break at least one. Runs in 88 ms.
|
|
64
|
+
- [x] **A Hand-Written Policy Contract (`src/guard-policy.mjs`)** — Derived from what the tool advertises, never from the regexes that implement it, so the implementation cannot be its own oracle.
|
|
65
|
+
- [x] **Verdicts Carry Their Denominator (`src/security.mjs`)** — `checkTestTampering` reports `inputsSeen` and a `PASS`/`FAIL`/`NOT_APPLICABLE` status, so "checked and clean" is no longer byte-identical to "nothing was checked".
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### v0.62.0: No Denominator, No Claim
|
|
69
|
+
- [x] **A File Is Not A Claim (`src/stack-detector.mjs`)** — `make test` requires the Makefile to declare the target; `app.json` requires a package.json beside it; a `test` script that is `echo … && exit 0` is a placeholder, not an oracle.
|
|
70
|
+
- [x] **The Generated Fallback Oracle Can Fail (`src/stack-detector.mjs`)** — It checks that every source file parses instead of asserting that the working directory exists, and refuses to pass with nothing to check.
|
|
71
|
+
- [x] **Lockfiles And Toolchain Pins Are Protected (`src/config.mjs`)** — `package.json` was protected and `package-lock.json` was not. Plus `.envrc`, `.git-credentials`, cloud credential trees and CodeBuild.
|
|
72
|
+
- [x] **The Sixth Spelling Of "Where Are The Tests" (`src/evidence.mjs`)** — Go co-located tests and every monorepo produced `fileCount: 0`, which switched `strictTestLock` off in silence.
|
|
73
|
+
- [x] **Seven More Runners In The Collection Floor** — Maven, Gradle, PHPUnit, RSpec, dotnet, XCTest, ctest, ExUnit.
|
|
74
|
+
- [x] **100% Of Nothing Is Not 100% (`src/coverage.mjs`)** — Diff coverage reports `scored: false` where V8 measured nothing, instead of the best possible number.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### v0.61.0: Green Against Nothing
|
|
78
|
+
- [x] **Collection Floor (`src/ops/test-collection.mjs`)** — A verification command that exits 0 having collected zero tests is no longer a pass. The count is read from the runner's own summary across node:test, pytest, cargo, jest, vitest, mocha and go; an unrecognised runner states no count and is not failed for it.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### v0.60.0: A Guard Worth Reading
|
|
82
|
+
- [x] **Per-Check Overrides (`--allow-test-change <kind>`)** — Answering one tamper finding no longer silences the five other checks in the bundle. `--allow-test-modifications` remains as the blunt form.
|
|
83
|
+
- [x] **Unchanged Assertions Cancel Before Pairing (`src/security.mjs`)** — Reordering or moving an assertion is no longer reported as two rewritten expectations.
|
|
84
|
+
- [x] **A Reworded Message Is Not A Rewritten Expectation (`src/security.mjs`)** — Message-position string arguments are compared apart from value positions, so improving the wording of a failure is silent while `assert.equal(name(), "Alice")` → `"Bob"` still fires.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### v0.59.0: One Answer Per Question
|
|
88
|
+
- [x] **One Test-Path Classifier (`src/test-paths.mjs`)** — Five modules had five definitions of "is this a test file?"; the tamper guard's could not see `tests/test_calc.py`, so every check in it was off for the standard pytest, Rust and RSpec layouts. All five now share `isTestPath`.
|
|
89
|
+
- [x] **Statement-Level Expectation Pairing (`src/security.mjs`)** — Assertions are reassembled from the diff's two images before pairing, so wrapping one across lines no longer walks through the guard. Contributed as PR #14 by an external agent; verified and merged.
|
|
90
|
+
- [x] **Numeric Literals Beyond Decimal (`src/security.mjs`)** — Hex, binary, octal, exponent and underscored literals normalise, so `0xFF` → `0xFE` is a rewritten expectation like any other.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### v0.58.0: Locks That Lock
|
|
94
|
+
- [x] **A CLI Lock Is A Lease (`src/state.mjs`, `bin/agentctl.mjs`)** — `lock acquire` no longer witnesses its own exiting process, so two agents can no longer both hold the same file. `--ttl` bounds it; `--pid` binds it to a real long-lived process instead.
|
|
95
|
+
- [x] **A URL No Longer Disables The Secret Scanner (`src/security.mjs`)** — URLs, data: URIs and integrity hashes are stripped from a line rather than skipping the whole line, and a URL's userinfo and query values are scanned on their own.
|
|
96
|
+
- [x] **Untracked Symlinks Are Judged By Their Target (`src/git.mjs`)** — Working-tree mode resolves links git has never seen, and a symlink is rendered as its target *path* rather than read through.
|
|
97
|
+
- [x] **Rewritten Expectations Are Reported (`src/security.mjs`)** — An assertion whose literal changed while its shape did not is `ASSERTION_EXPECTATION_CHANGED`, with `--allow-test-modifications` as the documented override.
|
|
98
|
+
- [x] **Scope Guard Covers Every Forge (`src/config.mjs`)** — GitLab, CircleCI, Jenkins, Azure, Travis, Drone, Buildkite and Woodpecker definitions are denied alongside `.github/**`; build and test-runner configuration is protected.
|
|
99
|
+
- [x] **Python Runs As A Module (`src/stack-detector.mjs`)** — `python -m pytest` under whichever interpreter name this machine has, so an ordinary layout is not rejected on first contact.
|
|
100
|
+
- [x] **Build Artifacts Do Not Read As Tampering (`src/evidence.mjs`)** — Caches a test run writes itself are excluded from the integrity hash.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
### v0.57.0: Honest Reporting
|
|
104
|
+
- [x] **Stack-Native TDD Oracles (`src/ops/tdd-generator.mjs`)** — Generated in the runner's own language, with a RED check that requires the assertion to have actually run rather than accepting any non-zero exit.
|
|
105
|
+
- [x] **JSON Envelopes Reach The DAG Runner (`src/dag-engine.mjs`)** — `isDagTaskFile` is exported and used for the CLI's count, so a queue of `.json` envelopes is no longer reported as empty.
|
|
106
|
+
- [x] **`swarm` Honours Its Flags (`bin/agentctl.mjs`)** — `--json`, `--dry-run` and `--concurrency` are parsed, and the registry describes a dispatcher rather than an inspector.
|
|
107
|
+
- [x] **No Score Where Nothing Was Measured (`src/mutation.mjs`)** — An empty mutant population reports `null` and a reason instead of a vacuous 100%.
|
|
108
|
+
- [x] **New Files Are Visible To Every Gate (`src/git.mjs`)** — The synthetic diff for an untracked file carries a `@@` header, so mutation and diff coverage can place its lines at all.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
### v0.56.0: Wired, Not Just Shipped
|
|
112
|
+
- [x] **File-Aware Locking (`src/state.mjs`)** — `acquireLock()` compares the requested paths against every live lock instead of only the task id.
|
|
113
|
+
- [x] **Checkpoints That Exist (`src/engine.mjs`, `src/session-ops.mjs`)** — Taken before a dispatch and before `patch --apply`, so `agentctl rollback` finally has something to restore.
|
|
114
|
+
- [x] **Reachable Monorepo Scoping (`verify.scope: affected`)** — The boundary resolver is called by the gate, opt-in, defaulted on for repositories detected as monorepos and widening again when a shared file changes.
|
|
115
|
+
- [x] **Process-Group Reaping For The Test Runner (`scripts/run-tests.mjs`)** — An interrupted run takes its whole tree with it instead of orphaning every process below the first level. A first instance of the v0.60.0 guillotine, applied where the leak was actually observed.
|
|
116
|
+
- [x] **Live-Path Corrections (`src/provider.mjs`, `src/engine.mjs`)** — `listSources()` works against the real API, and Node's test-runner context no longer masks a failing verification command.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### v0.55.0: Verification Integrity
|
|
120
|
+
- [x] **Assertion Weakening Detection (`src/security.mjs`)** — A specific expectation swapped for a vague one is reported as `ASSERTION_WEAKENED`, closing the one-out-one-in bypass that counting assertions could never see.
|
|
121
|
+
- [x] **Symlink-Aware Scope (`src/git.mjs`, `src/engine.mjs`)** — Added links are judged by the path they resolve to as well as their own name, without following anything on disk.
|
|
122
|
+
- [x] **Source-Bound Evidence (`src/evidence.mjs`)** — Manifests attest to the source tree, not only the tests, and the integrity hash finally sees test files that live at the repository root.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
### v0.54.1: The Gate Fails Closed
|
|
126
|
+
- [x] **No Oracle, No Approval (`src/engine.mjs`)** — A change that ran zero verification commands is rejected rather than approved; `verify.required: false` is the deliberate opt-out, read from the base commit.
|
|
127
|
+
- [x] **Binary Payload Inspection (`src/security.mjs`, `src/git.mjs`)** — Files git renders as "Binary files ... differ" are read directly for structured credentials, closing the NUL-byte bypass, and their real size is charged against the payload ceiling.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
### v0.54.0: First-Run Friction — Honest Errors, Detected Defaults
|
|
131
|
+
- [x] **Base Branch Detection (`src/git.mjs`)** — `detectDefaultBranch()` resolves `origin/HEAD`, a local `main`/`master`, then the checked-out branch, replacing the hardcoded `main` that failed the first gate in every `master` or `develop` repository.
|
|
132
|
+
- [x] **Diagnosable Gate Output (`bin/agentctl.mjs`, `src/ops/verify-output.mjs`)** — Phase errors are rendered instead of swallowed, and a failing stage's stdout is no longer discarded because the spawn wrapper wrote one line to stderr.
|
|
133
|
+
- [x] **Truthful Remediation Hints (`bin/agentctl.mjs`)** — Test tampering no longer advises rotating API keys, and a provider that refused the dispatch is no longer reported as an exhausted repair loop.
|
|
134
|
+
- [x] **Provider-First Onboarding & `agentctl provider set` (`src/wizard-init.mjs`, `src/config-edit.mjs`)** — The wizard asks which agent before asking about one vendor's plans, and switching providers no longer means re-running onboarding.
|
|
135
|
+
- [x] **Named Targets And Flags Actually Take Effect (`bin/agentctl.mjs`, `src/ops/ide-scaffold.mjs`, `src/ops/cli-intent.mjs`)** — `mcp init <target>`, `task create --title/--prompt` and `doctor --probe` all did something other than what they said; each now does what it says, or is no longer advertised.
|
|
136
|
+
- [x] **Onboarding Trap Distinguished From Overreach (`src/git.mjs`)** — `partitionTracked()` tells uncommitted scaffolding apart from an agent editing its own rules, so the first-run exit 3 advises committing rather than bypassing the gate.
|
|
137
|
+
- [x] **Rehearsals Write Nothing (`src/wizard-task.mjs`)** — `task create --dry-run` synthesizes and validates the envelope without queueing it.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### v0.53.0: Universal Portability — Any Stack, Any Agent, Any CI
|
|
141
|
+
- [x] **Provider Readiness Probe (`src/provider-readiness.mjs`, `agentctl providers`)** — Per-provider capability descriptors: a credential for the hosted `jules` adapter, a `PATH` binary for the `claude-code`/`codex`/`gemini-flash` exec adapters, with a cross-platform PATHEXT-aware resolver that spawns nothing.
|
|
142
|
+
- [x] **Vendor-Neutral Environment Spellings (`src/env-aliases.mjs`)** — Every `JULES_*` knob also answers to `AGENT_*`, normalised once at CLI entry, with the legacy name always winning.
|
|
143
|
+
- [x] **Verification Profiles (`src/profiles.mjs`, `agentctl profile`)** — `minimal | standard | max` expanded at load time into a stack-aware pipeline that skips unsupported gates with a stated reason rather than failing the diff.
|
|
144
|
+
- [x] **Generated Stack-Aware CI (`src/ci-templates.mjs`, `agentctl ci init`)** — GitHub Actions and GitLab jobs carrying the detected stack's toolchain, replacing the copy of this repository's own nine-way Node matrix.
|
|
145
|
+
- [x] **Consumer Repository Hygiene (`bin/init.js`)** — `init` no longer copies the kit's twenty internal scripts into the target repository, and both entry points now write the same manifest pair.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
### v0.52.x: Entropy Hardening, Egress Auditing & CI Defense
|
|
149
|
+
- [x] **Binary Asset Classification Guard (`src/security.mjs`)** — Restricts binary asset skipping to tokens $\ge 256$ chars, preventing length $\pmod 4 = 0$ keys from bypassing entropy checks.
|
|
150
|
+
- [x] **CI Runtime & Egress Security (`.github/workflows/*.yml`)** — Linux eBPF runtime monitoring and network egress auditing via StepSecurity.
|
|
151
|
+
- [x] **GitHub Actions Supply Chain Hardening (`.github/`)** — Action SHA pinning, least-privilege `contents: read`, and automated Zizmor CI gate.
|
|
152
|
+
- [x] **Assertion Removal & Anti-Tamper Guard (`src/security.mjs`)** — Detects test deletion bypass (`ASSERTION_REMOVAL`) and vacuous assertion weakening.
|
|
153
|
+
- [x] **Shannon Entropy Diff Scanner (`src/security.mjs`)** — High-entropy token detection (>4.5 entropy, $\ge 24$ chars) on added diff lines.
|
|
154
|
+
- [x] **Jules API Patch Ingestion & Edge Webhooks (`src/session-ops.mjs`, `src/webhook.mjs`)** — API changeset extraction and Webhook `Uint8Array` Edge runtime parity.
|
|
155
|
+
- [x] **Zero-Test Oracle Bootstrapping & Git Remote Origin Auto-Detection (`src/stack-detector.mjs`, `src/git.mjs`)** — Auto-configuration for virgin repositories.
|
|
156
|
+
|
|
157
|
+
### v0.51.0: Mechanical Falsification, AST Mutation & V8 Coverage
|
|
158
|
+
- [x] **Diff-Hunk Mutation Testing Engine (`src/mutation.mjs`, `agentctl mutate`)** — Transactional operator inversion with automatic shadow disk rollback.
|
|
159
|
+
- [x] **Native V8 Diff Coverage Enforcer (`src/coverage.mjs`, `agentctl coverage`)** — Zero-dependency V8 coverage mapping against added `+` hunks.
|
|
160
|
+
- [x] **Test-Oscillation Cycle Detector (`src/remediation.mjs`, `src/engine.mjs`)** — Halts multi-test oscillation cycles in OODA repair loops.
|
|
161
|
+
- [x] **Flakiness Stability Prober & Event Loop Delay Guard (`src/stability.mjs`, `src/perf.mjs`)** — Multi-pass repetition probing and Big-O event loop delay monitor.
|
|
162
|
+
|
|
163
|
+
### v0.40.0 – v0.50.0: Architecture Governance & Universal Envelopes
|
|
164
|
+
- [x] **Universal Stack-Agnostic Task Templates (`src/web-templates.mjs`)** — Pre-calibrated templates for Cargo, Go, Python, PHP, .NET, Java, and Node.
|
|
165
|
+
- [x] **Specialist Agent Roles (`.agent/prompts/`)** — Eight stack-neutral specialist personas with dynamic command hydration.
|
|
166
|
+
- [x] **All-In-One CI Verification Gate (`agentctl check`)** — Unified security, scope, payload, and stack test gatekeeper.
|
|
167
|
+
- [x] **Stack-Tailored Contract Scaffolding (`src/scaffold.mjs`)** — Automatic `SPEC.md`, `CONSTRAINTS.md`, and `DESIGN.md` generation on init.
|
|
168
|
+
|
|
169
|
+
### v0.30.0 – v0.39.0: Budget Provenance, Terminal UX & Autonomous Resilience
|
|
170
|
+
- [x] **Rolling 24h Budget Ledger & Limit Provenance (`src/budget.mjs`, `agentctl budget`)** — Multi-user attribution, rolling window accounting, and graceful estimate warnings.
|
|
171
|
+
- [x] **Terminal UI Engine (`src/tui.mjs`, `src/key-decoder.mjs`)** — Zero-dependency interactive selection, multi-select, secret masking, and raw keyboard decoding.
|
|
172
|
+
- [x] **Multi-OS CI Matrix Parity** — 100% green suite across Linux, macOS Darwin, and Windows CMD/PowerShell on Node 20, 22, and 24.
|
|
173
|
+
- [x] **Atomic Git Checkpoint & Rollback (`src/ops/checkpoint.mjs`, `agentctl rollback`)** — Pre-flight working tree snapshots and 1-command git restore.
|
|
174
|
+
- [x] **Documentation Sync Gate (`scripts/doc-sync-check.mjs`, `npm run jules:doc-sync`)** — Mechanical enforcement preventing documentation drift across manifests, roadmaps, and tests.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🎯 Target Milestones (v0.60.0 & v1.0.0)
|
|
179
|
+
|
|
180
|
+
### v0.60.0: Distributed File Leases & Preemptive DAG Scheduling
|
|
181
|
+
- [ ] **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.
|
|
182
|
+
- [ ] **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.
|
|
183
|
+
- [ ] **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.
|
|
184
|
+
- [ ] **Unicode Trojan Source & Homoglyph Fencing (`src/security.mjs`)** — Deterministic token scanner using V8 Unicode Property Escapes (`\p{Script=...}`) and NFKC normalization to block invisible Bidi overrides (CVE-2021-42574) and mixed-script homoglyphs.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 🏁 Target Milestone v1.0.0: The Production-Grade Autonomous Engineering Kernel
|
|
189
|
+
*Focus: Long-term API stability, cryptographic compliance, and enterprise deployment guarantees.*
|
|
190
|
+
|
|
191
|
+
- [ ] **Cryptographic Compliance & SOC2 Audit Exporter (`agentctl audit export`)**:
|
|
192
|
+
- Export tamper-evident, signed JSON-LD / SPDX receipts of all agent activities linked to the SHA-256 telemetry ledger.
|
|
193
|
+
- *Foundation shipped:* `agentctl evidence generate|verify|show` (`src/evidence.mjs`) already produces SHA-256 evidence manifests with test-tamper locking.
|
|
194
|
+
- [ ] **Zero-Dependency Core Freezing & Stability Guarantee**:
|
|
195
|
+
- 100% API stability for `index.mjs` SDK exports, CLI exit codes (0–8), and configuration schema (`.agent/config.yml`).
|
|
196
|
+
- [ ] **High-Concurrency Swarm Benchmarking (500+ Daily Sessions)**:
|
|
197
|
+
- Stress testing with 50+ concurrent worker slots across 100k+ file repositories with zero lock contention or memory leaks.
|
|
198
|
+
- [ ] **Comprehensive Multi-Language Enterprise Test Matrix**:
|
|
199
|
+
- Automated CI test fixtures for polyglot environments (Node, Python, Go, Rust, .NET, PHP, Java, Flutter).
|
|
200
|
+
- [ ] **OODA Attempt Diff Retention & Inspection (`.agent/state/ooda/*.patch`, `agentctl patch --attempt <n>`)**:
|
|
201
|
+
- Retains intermediate working tree diffs and failure traces across OODA repair turns so developers can inspect failed hypotheses when an agent exhausts its retry budget.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🔮 Post-1.0 Long-Term Horizon (v1.x+)
|
|
206
|
+
|
|
207
|
+
- **Proactive Telemetry Ingestion (Type III Situational Awareness)**: Ingest dev-server crash logs, APM traces, and Playwright test artifacts into auto-synthesized task envelopes for background diagnosis.
|
|
208
|
+
- **Cross-Repository Swarm Orchestration**: Orchestrate breaking API contract changes across multiple distinct git repositories with atomic synchronization.
|
|
209
|
+
- **Multimodal Visual Verification Loop**: Direct integration with headless browser video/screenshot streams for autonomous visual regression repairs.
|
|
210
|
+
- **Wasm-Powered Structural AST Invariant Engine**: In-memory WebAssembly tree-sitter bindings (zero npm dependencies) for deep multi-language semantic AST verification.
|
package/bin/agentctl.mjs
CHANGED
|
@@ -383,6 +383,7 @@ async function main() {
|
|
|
383
383
|
committed: { type: "boolean" },
|
|
384
384
|
fix: { type: "boolean" },
|
|
385
385
|
"allow-protected": { type: "boolean" },
|
|
386
|
+
"allow-unreadable-tests": { type: "boolean" },
|
|
386
387
|
// The tamper guard has always had an override — `allowTestModifications`
|
|
387
388
|
// — and it was reachable only from JavaScript. So a legitimate change
|
|
388
389
|
// of spec, which necessarily rewrites what a test expects, hit a
|
|
@@ -413,6 +414,7 @@ async function main() {
|
|
|
413
414
|
mode: selectedMode,
|
|
414
415
|
fix: values.fix,
|
|
415
416
|
allowProtected: values["allow-protected"],
|
|
417
|
+
allowUnreadableTests: values["allow-unreadable-tests"],
|
|
416
418
|
allowTestModifications: values["allow-test-modifications"],
|
|
417
419
|
allowTestChanges: values["allow-test-change"],
|
|
418
420
|
jsonReport: values["json-report"],
|
|
@@ -1611,6 +1613,13 @@ async function main() {
|
|
|
1611
1613
|
console.log(` Stack detected : ${res.stack}`);
|
|
1612
1614
|
console.log(` Oracle TestCmd : ${res.testCmd}`);
|
|
1613
1615
|
console.log(` Config written : ${res.configPath}`);
|
|
1616
|
+
} else if (res.reason === "NO_ORACLE_POSSIBLE") {
|
|
1617
|
+
// Declining is a usable answer. Writing a config whose very first run
|
|
1618
|
+
// asserts its own impossibility is not: it left the user following
|
|
1619
|
+
// the gate's own repair advice into a dead end.
|
|
1620
|
+
console.error(`❌ No oracle could be generated for this repository.`);
|
|
1621
|
+
console.error(` ${res.detail}`);
|
|
1622
|
+
process.exit(1);
|
|
1614
1623
|
} else {
|
|
1615
1624
|
console.log(`ℹ️ Repository already has a verification oracle (${res.testCmd}). Use --force to overwrite.`);
|
|
1616
1625
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.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",
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
"JULES_RULES_TEMPLATE.md",
|
|
33
33
|
".agent/rules/",
|
|
34
34
|
".agent/prompts/",
|
|
35
|
-
".agent/workflows/"
|
|
35
|
+
".agent/workflows/",
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"ROADMAP_V1.md",
|
|
38
|
+
"AGENTS.md"
|
|
36
39
|
],
|
|
37
40
|
"scripts": {
|
|
38
41
|
"init": "node bin/init.js",
|
|
@@ -331,6 +331,15 @@ function compareSemver(a, b) {
|
|
|
331
331
|
|
|
332
332
|
// ---- CLI ----
|
|
333
333
|
const isMain = process.argv[1] && process.argv[1].endsWith("doc-sync-check.mjs");
|
|
334
|
+
// This gate compares the documentation against the test suite, which the
|
|
335
|
+
// published package does not contain. Run from an installed copy it reported
|
|
336
|
+
// six failures about files that were never meant to be there.
|
|
337
|
+
if (isMain && !existsSync(join(process.cwd(), "test"))) {
|
|
338
|
+
console.error("Documentation sync is a release gate for the repository, not a check on an installed package.");
|
|
339
|
+
console.error("It compares README/ROADMAP/CHANGELOG against the test suite, which is not shipped.");
|
|
340
|
+
console.error("Clone the repository to run it. To check an installed copy, run: npm run guard-reach");
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
334
343
|
if (isMain) {
|
|
335
344
|
const argv = process.argv.slice(2);
|
|
336
345
|
const flag = (name) => {
|
package/src/config.mjs
CHANGED
|
@@ -626,7 +626,13 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
626
626
|
const rawTeardown = parsed.teardown_cmd ?? parsed.verify?.teardown;
|
|
627
627
|
const rawBuild = parsed.build_cmd ?? parsed.verify?.build;
|
|
628
628
|
const rawUnit = parsed.verify?.unit;
|
|
629
|
-
|
|
629
|
+
// Five minutes, matching the value `init` writes and the README documents.
|
|
630
|
+
// The engine carries the same number as a fallback, but this loader always
|
|
631
|
+
// supplies one — so the fallback never fired, and every repository without
|
|
632
|
+
// an explicit `timeout_ms` kept the old one-minute limit while the
|
|
633
|
+
// changelog said otherwise. The default has to live where the value is
|
|
634
|
+
// resolved, not where it is consumed.
|
|
635
|
+
const verifyTimeoutMs = parsed.verify?.timeoutMs ?? parsed.verify?.timeout_ms ?? 300_000;
|
|
630
636
|
const autoVerify = resolveVerify(root);
|
|
631
637
|
|
|
632
638
|
const rawTier = String(process.env.JULES_TIER || parsed.tier || FALLBACK_TIER).toLowerCase();
|
|
@@ -697,7 +703,7 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
697
703
|
// Named so `doctor` can tell an operator that the gate they enabled is
|
|
698
704
|
// running one fewer check than they think, and why.
|
|
699
705
|
profileSkipped: profilePlan?.skipped ?? [],
|
|
700
|
-
timeoutMs: Number.isFinite(Number(verifyTimeoutMs)) ? Number(verifyTimeoutMs) :
|
|
706
|
+
timeoutMs: Number.isFinite(Number(verifyTimeoutMs)) ? Number(verifyTimeoutMs) : 300_000,
|
|
701
707
|
},
|
|
702
708
|
evidence: {
|
|
703
709
|
enabled: parsed.evidence?.enabled ?? true,
|
package/src/engine.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadConfig, parseYaml, normalizeScope } from "./config.mjs";
|
|
2
2
|
import { isTestPath } from "./test-paths.mjs";
|
|
3
|
+
import { isPlaceholderTestScript } from "./stack-detector.mjs";
|
|
3
4
|
import { checkCollectionFloor } from "./ops/test-collection.mjs";
|
|
4
5
|
import { checkScope, scanDiff, scanBinaryPayloads, redactSecrets } from "./security.mjs";
|
|
5
6
|
import { changedFiles, diffBytes, diffText, binaryDiffEntries, symlinkChanges, showFromOrigin, runCmd } from "./git.mjs";
|
|
@@ -214,6 +215,7 @@ export async function gate(opts = {}) {
|
|
|
214
215
|
: parsed.verify?.min_tests !== undefined
|
|
215
216
|
? parsed.verify.min_tests
|
|
216
217
|
: config.verify.minTests,
|
|
218
|
+
tamperGuard: parsed.verify?.tamperGuard || parsed.verify?.tamper_guard || config.verify.tamperGuard,
|
|
217
219
|
scope: parsed.verify?.scope || config.verify.scope || "global",
|
|
218
220
|
timeoutMs: parsed.verify?.timeoutMs || parsed.verify?.timeout_ms || config.verify.timeoutMs,
|
|
219
221
|
};
|
|
@@ -328,6 +330,11 @@ export async function gate(opts = {}) {
|
|
|
328
330
|
root,
|
|
329
331
|
allowTestModifications: opts.allowTestModifications === true,
|
|
330
332
|
allowTestChanges: opts.allowTestChanges,
|
|
333
|
+
// Read from the base commit like every other trusted field: a repository
|
|
334
|
+
// opts out of the tamper guard deliberately and on the record, and an
|
|
335
|
+
// uncommitted edit must not be able to do it.
|
|
336
|
+
tamperGuard: trustedVerify.tamperGuard,
|
|
337
|
+
allowUnreadableTests: opts.allowUnreadableTests === true,
|
|
331
338
|
});
|
|
332
339
|
// A binary file reaches the scanner as one summary line, so its contents were
|
|
333
340
|
// never looked at — a NUL byte in front of a token was enough to hide it.
|
|
@@ -386,7 +393,11 @@ export async function gate(opts = {}) {
|
|
|
386
393
|
}
|
|
387
394
|
|
|
388
395
|
let flakyVerdictResult = null;
|
|
389
|
-
|
|
396
|
+
// Five minutes. A minute was too short for an ordinary suite — a mid-sized
|
|
397
|
+
// Node repository takes longer than that on cold caches — and the timeout
|
|
398
|
+
// surfaced as a plain non-zero verification, so the gate told the user their
|
|
399
|
+
// tests failed when it had killed them. Raise it with verify.timeout_ms.
|
|
400
|
+
const verifyTimeout = trustedVerify.timeoutMs || 300_000;
|
|
390
401
|
|
|
391
402
|
// Monorepo scoping: run the suites the change can actually break.
|
|
392
403
|
//
|
|
@@ -588,7 +599,22 @@ export async function gate(opts = {}) {
|
|
|
588
599
|
: { ok: true, count: null, runner: null, reason: null };
|
|
589
600
|
const emptySuite = !collectionFloor.ok;
|
|
590
601
|
|
|
591
|
-
|
|
602
|
+
// A command that cannot fail is not an oracle, wherever it is configured.
|
|
603
|
+
//
|
|
604
|
+
// `task create` already refuses one — "Unfalsifiable Task Rejected: Task
|
|
605
|
+
// must include a non-trivial verification test/build command" — while the
|
|
606
|
+
// gate approved against `verify.test: "true"` and printed an advisory. The
|
|
607
|
+
// same kit disagreed with itself about the same string. The collection
|
|
608
|
+
// floor cannot catch this: `true` states no count, and failing on "I could
|
|
609
|
+
// not tell" would break every unlisted runner. But a command that is
|
|
610
|
+
// *recognisably* a placeholder is not an unlisted runner.
|
|
611
|
+
const placeholderOracle =
|
|
612
|
+
verificationRequired &&
|
|
613
|
+
!missingOracle &&
|
|
614
|
+
Boolean(testResult?.command) &&
|
|
615
|
+
isPlaceholderTestScript(testResult.command);
|
|
616
|
+
|
|
617
|
+
const verifyOk = !failingCmd && !testTampered && !missingOracle && !emptySuite && !placeholderOracle;
|
|
592
618
|
|
|
593
619
|
// What actually broke. Without this the verify phase reported `ok: false` and
|
|
594
620
|
// nothing else — not the stage, not the exit code, not a line of output — so
|
|
@@ -635,7 +661,22 @@ export async function gate(opts = {}) {
|
|
|
635
661
|
"The gate approves a change because verification passed. Zero stages executed is not a pass.",
|
|
636
662
|
],
|
|
637
663
|
}
|
|
638
|
-
:
|
|
664
|
+
: placeholderOracle
|
|
665
|
+
? {
|
|
666
|
+
stageId: "placeholder-oracle",
|
|
667
|
+
command: testResult?.command || null,
|
|
668
|
+
exitCode: 0,
|
|
669
|
+
stdout: "",
|
|
670
|
+
stderr:
|
|
671
|
+
`The verification command is ${JSON.stringify(testResult.command)}, which cannot fail. ` +
|
|
672
|
+
"Nothing about this change was checked, so approving it would certify nothing. " +
|
|
673
|
+
"Point verify.test at a command that runs this repository's tests, or — if this repository " +
|
|
674
|
+
"intentionally uses only the scope and secret phases — set verify.required: false, which says so.",
|
|
675
|
+
diagnostics: [
|
|
676
|
+
"`agentctl task create` already refuses a task with this command; the gate now agrees with it.",
|
|
677
|
+
],
|
|
678
|
+
}
|
|
679
|
+
: emptySuite
|
|
639
680
|
? {
|
|
640
681
|
stageId: "empty-suite",
|
|
641
682
|
command: testResult?.command || null,
|
package/src/git.mjs
CHANGED
|
@@ -15,6 +15,10 @@ export class GateError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// `NAME=value` at the head of a command line: a POSIX environment prefix,
|
|
19
|
+
// not the name of a program.
|
|
20
|
+
const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
21
|
+
|
|
18
22
|
const DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes
|
|
19
23
|
const DEFAULT_MAX_BUFFER = 10 * 1024 * 1024; // 10 MB
|
|
20
24
|
|
|
@@ -139,6 +143,8 @@ export function runCmd(command, opts = {}) {
|
|
|
139
143
|
let args = [];
|
|
140
144
|
let useShell = false;
|
|
141
145
|
let shellCmd = "";
|
|
146
|
+
/** @type {Record<string,string>|null} */
|
|
147
|
+
let envOverlay = null;
|
|
142
148
|
// True when `args` came from splitting a whitespace-separated string, which
|
|
143
149
|
// means no element can itself contain whitespace. See the Windows note below.
|
|
144
150
|
let tokenized = false;
|
|
@@ -153,6 +159,24 @@ export function runCmd(command, opts = {}) {
|
|
|
153
159
|
shellCmd = trimmed;
|
|
154
160
|
} else {
|
|
155
161
|
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
162
|
+
|
|
163
|
+
// A POSIX command may be prefixed with environment assignments, and
|
|
164
|
+
// half the Python world writes its test command exactly that way:
|
|
165
|
+
// `PYTHONPATH=src python3 -m pytest`. execFileSync took the first token
|
|
166
|
+
// as the program and failed with `spawnSync PYTHONPATH=src ENOENT` —
|
|
167
|
+
// which the gate then reported as a failed verification, so the user
|
|
168
|
+
// was told their tests broke when the command had never started.
|
|
169
|
+
//
|
|
170
|
+
// Peeling the assignments into the child's environment behaves the same
|
|
171
|
+
// on every platform. Handing the string to a shell would not: cmd.exe
|
|
172
|
+
// has no such syntax, so the Windows matrix would keep failing.
|
|
173
|
+
while (tokens.length > 1 && ENV_ASSIGNMENT.test(tokens[0])) {
|
|
174
|
+
const token = tokens.shift();
|
|
175
|
+
const eq = token.indexOf("=");
|
|
176
|
+
if (!envOverlay) envOverlay = {};
|
|
177
|
+
envOverlay[token.slice(0, eq)] = token.slice(eq + 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
156
180
|
binary = tokens[0] || "";
|
|
157
181
|
args = tokens.slice(1);
|
|
158
182
|
tokenized = true;
|
|
@@ -177,10 +201,12 @@ export function runCmd(command, opts = {}) {
|
|
|
177
201
|
// wrapped in cmd.exe with every element quoted per the C-runtime + cmd.exe
|
|
178
202
|
// rules, which preserves argv exactly while still letting the shell resolve
|
|
179
203
|
// the `.cmd` shim (P-07).
|
|
204
|
+
const childEnv = envOverlay ? { ...(opts.env || process.env), ...envOverlay } : opts.env || process.env;
|
|
205
|
+
|
|
180
206
|
const winShim = tokenized && process.platform === "win32";
|
|
181
207
|
const winSpawn =
|
|
182
208
|
!useShell && !winShim && process.platform === "win32"
|
|
183
|
-
? resolveWindowsSpawn(binary, args,
|
|
209
|
+
? resolveWindowsSpawn(binary, args, childEnv)
|
|
184
210
|
: null;
|
|
185
211
|
|
|
186
212
|
if (!binary && !useShell) {
|
|
@@ -194,7 +220,7 @@ export function runCmd(command, opts = {}) {
|
|
|
194
220
|
cwd,
|
|
195
221
|
encoding: "utf-8",
|
|
196
222
|
stdio: ["ignore", "pipe", "pipe"],
|
|
197
|
-
env:
|
|
223
|
+
env: childEnv,
|
|
198
224
|
timeout,
|
|
199
225
|
maxBuffer,
|
|
200
226
|
})
|
|
@@ -204,7 +230,7 @@ export function runCmd(command, opts = {}) {
|
|
|
204
230
|
shell: winShim,
|
|
205
231
|
windowsVerbatimArguments: Boolean(winSpawn && winSpawn.verbatim),
|
|
206
232
|
stdio: ["ignore", "pipe", "pipe"],
|
|
207
|
-
env:
|
|
233
|
+
env: childEnv,
|
|
208
234
|
timeout,
|
|
209
235
|
maxBuffer,
|
|
210
236
|
});
|
|
@@ -218,8 +244,16 @@ export function runCmd(command, opts = {}) {
|
|
|
218
244
|
let stdout = (err.stdout || "").toString().trim();
|
|
219
245
|
let stderr = (err.stderr || err.message || "").toString().trim();
|
|
220
246
|
|
|
221
|
-
if (isTimeout
|
|
222
|
-
|
|
247
|
+
if (isTimeout) {
|
|
248
|
+
// Node's own message for this is `spawnSync sh ETIMEDOUT`, which already
|
|
249
|
+
// contains the token the old guard tested for — so the explanation was
|
|
250
|
+
// skipped exactly when it was needed, and the user was left with five
|
|
251
|
+
// words that name neither the limit nor the way to raise it.
|
|
252
|
+
stderr =
|
|
253
|
+
`Command execution timed out after ${timeout}ms (ETIMEDOUT). ` +
|
|
254
|
+
`The command was killed, not failed: raise verify.timeout_ms in ` +
|
|
255
|
+
`.agent/config.yml if this suite legitimately takes longer.` +
|
|
256
|
+
(stderr ? "\n" + stderr : "");
|
|
223
257
|
}
|
|
224
258
|
if (isNobufs && !stderr.includes("ENOBUFS")) {
|
|
225
259
|
stderr = `Command output buffer exceeded limit of ${maxBuffer} bytes (ENOBUFS)${stderr ? "\n" + stderr : ""}`;
|