jules-orchestrator-kit 0.5.1 → 0.5.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/.env.example CHANGED
@@ -1,17 +1,63 @@
1
1
  # Google Jules Orchestration Kit - Environment Configuration Example
2
2
  # Copy this file to .env and populate with your credentials/settings.
3
3
 
4
+ # --- Authentication & Core Settings ---
4
5
  # Google Jules API Key (Required for direct REST API dispatches)
5
6
  JULES_API_KEY=your_google_jules_api_key_here
6
7
 
8
+ # Alias for API key (fallback if JULES_API_KEY is not set)
9
+ # GEMINI_API_KEY=your_gemini_api_key_here
10
+
11
+ # Override the Jules REST API URL
12
+ # JULES_API_URL=https://jules.googleapis.com/v1alpha/sessions
13
+
7
14
  # Target GitHub Repository (Format: owner/repo)
8
- JULES_REPO=FullThrottle83/jules-orchestrator-kit
15
+ JULES_REPO=owner/repo
16
+
17
+ # Set to "true" or "1" to run in repoless/serverless mode
18
+ # JULES_REPOLESS=false
19
+
20
+ # Enable Dry-Run mode to simulate payload dispatch without making API calls
21
+ # JULES_DRY_RUN=false
9
22
 
23
+ # --- Execution Scope & Git ---
10
24
  # Base Branch for PR Audits & Merge-Base Calculations (Default: main)
11
25
  BASE_BRANCH=main
12
26
 
13
- # Swarm Concurrency Limit (Maximum parallel dispatches, default: 3)
27
+ # PR Head Branch (Used by CI to dynamically target branches during OODA repair)
28
+ # GITHUB_HEAD_REF=my-feature-branch
29
+
30
+ # Root directory of the project (Auto-assigned during swarm executions)
31
+ # JULES_PROJECT_ROOT=/path/to/project
32
+
33
+ # --- Swarm & Concurrency ---
34
+ # Maximum parallel dispatches for swarm runs (Default: 3)
14
35
  JULES_SWARM_CONCURRENCY=3
15
36
 
16
- # Swarm Dispatch Stagger Interval in Milliseconds (Default: 1500)
37
+ # Dispatch Stagger Interval in Milliseconds (Default: 1500)
17
38
  JULES_SWARM_STAGGER_MS=1500
39
+
40
+ # Rate-limit for the jules:queue command (Default: 500)
41
+ # JULES_PACE_MS=500
42
+
43
+ # Use Git Worktrees instead of cloning for swarm isolation
44
+ # JULES_USE_WORKTREES=false
45
+
46
+ # Current slot index for partitioning tasks (Swarm mode)
47
+ # JULES_SLOT_INDEX=1
48
+
49
+ # Total number of slots for partitioning tasks (Swarm mode)
50
+ # JULES_SLOT_TOTAL=3
51
+
52
+ # --- CI & Output ---
53
+ # Is this running in a CI environment? (Changes log output and fail-fast behaviors)
54
+ # CI=true
55
+
56
+ # Allow OODA Auto-Repair even in CI environments
57
+ # ALLOW_AUTO_REPAIR=true
58
+
59
+ # GitHub Actions Step Summary File Path
60
+ # GITHUB_STEP_SUMMARY=/path/to/step_summary.md
61
+
62
+ # Disable color in terminal output
63
+ # NO_COLOR=true
@@ -27,8 +27,8 @@ jobs:
27
27
  echo "Checking files modified in PR..."
28
28
  MODIFIED_FILES=$(git diff --name-only $BASE_SHA $HEAD_SHA)
29
29
 
30
- # Read protected paths
31
- PROTECTED_PATHS=$(node -e "const paths = require('./.agent/protected-paths.json').protected; console.log(paths.join(' '));")
30
+ # Read protected paths from origin/main
31
+ PROTECTED_PATHS=$(node -e "const paths = JSON.parse(require('child_process').execSync('git show origin/main:.agent/protected-paths.json', {encoding:'utf8'})).protected; console.log(paths.join(' '));")
32
32
 
33
33
  echo "Protected patterns: $PROTECTED_PATHS"
34
34
 
@@ -9,6 +9,9 @@ on:
9
9
  jobs:
10
10
  audit-and-test:
11
11
  runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ node-version: ['18.x', '20.x', '22.x', '24.x']
12
15
  steps:
13
16
  - name: Checkout repository
14
17
  uses: actions/checkout@v4
@@ -18,7 +21,10 @@ jobs:
18
21
  - name: Setup Node.js
19
22
  uses: actions/setup-node@v4
20
23
  with:
21
- node-version: "20"
24
+ node-version: ${{ matrix.node-version }}
25
+
26
+ - name: Syntax Check
27
+ run: node --check scripts/*.mjs bin/*.js
22
28
 
23
29
  - name: Run Unit Tests
24
30
  run: npm test
package/README.md CHANGED
@@ -60,7 +60,7 @@ npm run jules:status
60
60
  - If tests failed, Jules fixed the code and re-ran tests.
61
61
  - You'll receive a Pull Request with working, verified code.
62
62
 
63
- > 🔐 **New in v0.3.0 (The Epistemic Bridge)**: The init script generates a cryptographic Handshake Token (`.agent/JULES_WEB_SETUP.md`). Paste this into the Jules Web UI to sync your environment perfectly.
63
+ > 🔐 **New in v0.5.0 (The Epistemic Bridge)**: The init script generates a cryptographic Handshake Token (`.agent/JULES_WEB_SETUP.md`). Paste this into the Jules Web UI to sync your environment perfectly.
64
64
 
65
65
  ---
66
66
 
@@ -193,7 +193,7 @@ allow_paths: []
193
193
  | Feature | What It Does | Example |
194
194
  | --------------------- | -------------------------------------------- | ------------------------------------- |
195
195
  | **Automatic Testing** | Runs test suite against every AI change | `test_cmd: "npm test"` |
196
- | **Self-Fixing** | Jules automatically corrects failed tests | Retries up to 4 times before blocking |
196
+ | **Self-Fixing** | Jules automatically corrects failed tests | Retries up to 3 times before blocking |
197
197
  | **Secret Protection** | Hides API keys, passwords, tokens | Entropy > 3.6, length ≥ 20 |
198
198
  | **Path Restrictions** | Blocks changes to sensitive files | `.env`, `*.pem`, `.github/**` |
199
199
  | **Scope Boundaries** | Prevents changes outside task scope | `scope: ["src/auth/**"]` |
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
+ "description": "Orchestration kit for running Google Jules autonomous agents.",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "git+https://github.com/FullThrottle83/jules-orchestrator-kit.git"
@@ -56,6 +57,6 @@
56
57
  "mcp",
57
58
  "automation"
58
59
  ],
59
- "author": "Google Open Source",
60
+ "author": "FullThrottle83",
60
61
  "license": "MIT"
61
62
  }
@@ -41,7 +41,9 @@ function loadEnv() {
41
41
  }
42
42
  }
43
43
  }
44
- loadEnv();
44
+ if (isMainModule) {
45
+ loadEnv();
46
+ }
45
47
 
46
48
  // 0.2 Pre-Flight Secret Redaction Gate (RegEx + Env Denylist)
47
49
  export function redactSecrets(text) {
@@ -52,7 +54,7 @@ export function redactSecrets(text) {
52
54
  for (const [envKey, envVal] of Object.entries(process.env)) {
53
55
  if (
54
56
  envVal &&
55
- envVal.length >= 6 &&
57
+ envVal.length >= 20 &&
56
58
  /KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH/i.test(envKey)
57
59
  ) {
58
60
  if (sanitized.includes(envVal)) {
@@ -155,7 +157,7 @@ if (isMainModule) {
155
157
  if (fs.existsSync(possiblePath)) {
156
158
  try {
157
159
  const verifiedPath = path.resolve(process.cwd(), possiblePath);
158
- if (verifiedPath.startsWith(process.cwd())) {
160
+ if (!path.relative(process.cwd(), verifiedPath).startsWith('..')) {
159
161
  const fd = fs.openSync(verifiedPath, "r");
160
162
  try {
161
163
  const stat = fs.fstatSync(fd);
@@ -269,10 +271,11 @@ async function executeDispatch() {
269
271
  "X-Goog-Api-Key": apiKey,
270
272
  "Content-Type": "application/json"
271
273
  },
272
- body: JSON.stringify(payload)
274
+ body: JSON.stringify(payload),
275
+ signal: AbortSignal.timeout(30000)
273
276
  });
274
277
 
275
- if (res.ok || res.status === 429) {
278
+ if (res.ok || res.status === 429 || res.status === 400 || res.status === 401) {
276
279
  break;
277
280
  }
278
281
 
@@ -300,8 +303,17 @@ async function executeDispatch() {
300
303
  }
301
304
 
302
305
  if (res.status === 429) {
303
- log.error(`❌ Jules REST API Rate Limit Exceeded (HTTP 429). Will NOT fallback to CLI.`);
304
- process.exit(1);
306
+ const retryAfter = res.headers.get("Retry-After");
307
+ const err = new Error(`❌ Jules REST API Rate Limit Exceeded (HTTP 429). Retry after: ${retryAfter || 'unknown'}`);
308
+ err.code = 1;
309
+ throw err;
310
+ }
311
+
312
+ if (res.status === 400 || res.status === 401) {
313
+ const errText = await res.text().catch(() => "");
314
+ const err = new Error(`❌ Jules REST API returned HTTP ${res.status}: ${errText}`);
315
+ err.code = 1;
316
+ throw err;
305
317
  }
306
318
 
307
319
  if (!res.ok) {
@@ -350,8 +362,9 @@ function dispatchViaCli(targetRepo, payloadFile, title) {
350
362
  execFileSync("jules", args, { input: promptToSend, stdio: ["pipe", "inherit", "inherit"] });
351
363
  log.success(`Successfully dispatched task "${title}" to Jules CLI.`);
352
364
  } catch (error) {
353
- log.error(`Failed to dispatch task to Jules CLI: ${error.message}`);
354
- process.exit(1);
365
+ const err = new Error(`Failed to dispatch task to Jules CLI: ${error.message}`);
366
+ err.code = 1;
367
+ throw err;
355
368
  }
356
369
  }
357
370
 
@@ -11,10 +11,9 @@ function runGitCommand(args, ignoreError = false) {
11
11
  try {
12
12
  return execFileSync("git", args, { encoding: "utf-8" }).trim();
13
13
  } catch (error) {
14
- if (ignoreError) return "";
15
- log.error(`Git command failed: git ${args.join(" ")}`);
16
- log.error(error.message);
17
- process.exit(1);
14
+ const err = new Error(`Git command failed: git ${args.join(" ")}\n${error.message}`);
15
+ err.code = 1;
16
+ throw err;
18
17
  }
19
18
  }
20
19
 
@@ -153,16 +152,18 @@ export function runSelfAudit() {
153
152
  const targetBranch = process.env.BASE_BRANCH || "main";
154
153
  const SAFE_BRANCH = /^[a-zA-Z0-9._\/-]+$/;
155
154
  if (!SAFE_BRANCH.test(targetBranch)) {
156
- log.error(`FATAL: Invalid BASE_BRANCH "${targetBranch}". Must match ^[a-zA-Z0-9._\\/-]+$`);
157
- process.exit(2);
155
+ const err = new Error(`FATAL: Invalid BASE_BRANCH "${targetBranch}". Must match ^[a-zA-Z0-9._\\/-]+$`);
156
+ err.code = 2;
157
+ throw err;
158
158
  }
159
159
  log.info(`Target Branch: ${targetBranch}`);
160
160
 
161
161
  try {
162
162
  execFileSync("git", ["--version"], { stdio: "ignore" });
163
163
  } catch {
164
- log.error("FATAL: git is not installed or not in PATH.");
165
- process.exit(2);
164
+ const err = new Error("FATAL: git is not installed or not in PATH.");
165
+ err.code = 2;
166
+ throw err;
166
167
  }
167
168
 
168
169
  if (process.env.CI) {
@@ -180,8 +181,9 @@ export function runSelfAudit() {
180
181
  const mergeBase = runGitCommand(["merge-base", "HEAD", mainRef], true);
181
182
 
182
183
  if (!mergeBase) {
183
- log.error(`FATAL: Could not compute merge-base with ${mainRef}. Make sure git history is unshallowed.`);
184
- process.exit(2);
184
+ const err = new Error(`FATAL: Could not compute merge-base with ${mainRef}. Make sure git history is unshallowed.`);
185
+ err.code = 2;
186
+ throw err;
185
187
  }
186
188
 
187
189
  log.info(`Merge-Base Hash: ${mergeBase}`);
@@ -211,10 +213,9 @@ export function runSelfAudit() {
211
213
  });
212
214
 
213
215
  if (violations.length > 0) {
214
- log.error("RESTRICTED FILE VIOLATION DETECTED!");
215
- log.error("Jules PR attempted to modify forbidden system files:");
216
- violations.forEach((v) => log.error(` - ${v}`));
217
- process.exit(3);
216
+ const err = new Error("RESTRICTED FILE VIOLATION DETECTED!");
217
+ err.code = 3;
218
+ throw err;
218
219
  }
219
220
  log.success("Restricted File Boundary Check: PASSED");
220
221
 
@@ -254,7 +255,7 @@ export function runSelfAudit() {
254
255
 
255
256
  if (!resolvedCmds.testCmd && !resolvedCmds.buildCmd) {
256
257
  log.info("No build or test scripts found. Running git status check.");
257
- runCommand("git status");
258
+ execSync("git status", { stdio: "inherit" });
258
259
  }
259
260
 
260
261
  const durationMs = Date.now() - startTime;
@@ -270,45 +271,10 @@ export function runSelfAudit() {
270
271
  log.error("OODA SELF-HEALING FEEDBACK LOG:");
271
272
  log.dim(failureLog.slice(-1500));
272
273
 
273
- // OODA Circuit Breaker: Fingerprint failure trace using SHA-256
274
- const normalizedError = failureLog
275
- .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/g, "TIMESTAMP")
276
- .replace(/0x[0-9a-fA-F]+/g, "HEX")
277
- .replace(/\d+/g, "NUM")
278
- .trim();
279
- const errorHash = crypto.createHash("sha256").update(normalizedError).digest("hex");
280
-
281
- const circuitFile = path.resolve(process.cwd(), ".agent/history/ooda-circuit.json");
282
- let prevCircuit = { hash: "", count: 0 };
283
- if (fs.existsSync(circuitFile)) {
284
- try {
285
- prevCircuit = JSON.parse(fs.readFileSync(circuitFile, "utf-8"));
286
- } catch (_) {}
287
- }
288
-
289
- if (prevCircuit.hash === errorHash && prevCircuit.count >= 1) {
290
- log.error("❌ OODA CIRCUIT BREAKER TRIPPED: Consecutive identical failure detected.");
291
- log.error("Agent generated code with identical error trace twice in a row. Aborting auto-repair.");
292
- try {
293
- fs.writeFileSync(circuitFile, JSON.stringify({ hash: "", count: 0 }));
294
- } catch (_) {}
295
- process.exit(4);
296
- }
297
-
298
- try {
299
- fs.writeFileSync(
300
- circuitFile,
301
- JSON.stringify({
302
- hash: errorHash,
303
- count: prevCircuit.hash === errorHash ? prevCircuit.count + 1 : 1,
304
- updatedAt: new Date().toISOString()
305
- })
306
- );
307
- } catch (_) {}
308
-
309
274
  if (process.env.CI && !process.env.ALLOW_AUTO_REPAIR) {
310
- log.error("CI environment detected and ALLOW_AUTO_REPAIR is not set. Failing fast to save CI minutes.");
311
- process.exit(4);
275
+ const err = new Error("CI environment detected and ALLOW_AUTO_REPAIR is not set. Failing fast to save CI minutes.");
276
+ err.code = 4;
277
+ throw err;
312
278
  }
313
279
 
314
280
  let oodaRetries = 0;
@@ -342,28 +308,29 @@ export function runSelfAudit() {
342
308
  } else {
343
309
  log.error("❌ OODA Auto-Repair exhausted maximum retries (3). Giving up.");
344
310
  }
345
-
346
- process.exit(4);
311
+ const err = new Error("OODA Auto-Repair failed or exhausted.");
312
+ err.code = 4;
313
+ throw err;
347
314
  }
348
315
 
349
316
  log.success("🎉 JULES PR SELF-AUDIT PASSED SUCCESSFULLY!");
350
317
  }
351
318
 
352
319
  export function runPreflightSandbox() {
353
- log.header("Initializing Pre-Flight Sandbox (Steril Simulering)...");
320
+ log.header("Initializing Pre-Flight Sandbox (Sterile Simulation)...");
354
321
 
355
322
  const runId = crypto.randomBytes(4).toString("hex");
356
323
  const sandboxDir = path.join(os.tmpdir(), `jules-preflight-${runId}`);
357
324
  const tempArchive = path.join(os.tmpdir(), `jules-archive-${runId}.tar`);
358
325
 
359
326
  try {
360
- log.step("[1/4]", `Skapar isolerad miljö i ${sandboxDir}...`);
327
+ log.step("[1/5]", `Creating isolated environment in ${sandboxDir}...`);
361
328
  fs.mkdirSync(sandboxDir, { recursive: true });
362
329
 
363
330
  execSync(`git archive HEAD -o "${tempArchive}"`);
364
331
  execSync(`tar -xf "${tempArchive}" -C "${sandboxDir}"`);
365
332
 
366
- log.step("[2/4]", "Steriliserar miljövariabler...");
333
+ log.step("[2/5]", "Sterilizing environment variables...");
367
334
  const sterileEnv = { ...process.env };
368
335
  const stripKeys = ["DATABASE_URL", "NPM_TOKEN", "GITHUB_TOKEN", "GH_TOKEN", "AWS_ACCESS_KEY_ID", "STRIPE_TEST_KEY"];
369
336
  for (const key of stripKeys) {
@@ -372,11 +339,21 @@ export function runPreflightSandbox() {
372
339
  }
373
340
  }
374
341
 
375
- log.step("[3/4]", "Löser beroenden i sandlådan...");
342
+ log.step("[3/5]", "Installing dependencies in sandbox (npm ci)...");
343
+ const packageJsonPath = path.join(sandboxDir, "package.json");
344
+ if (fs.existsSync(packageJsonPath)) {
345
+ try {
346
+ execSync("npm ci --ignore-scripts", { cwd: sandboxDir, stdio: "ignore" });
347
+ } catch (e) {
348
+ log.warn("npm ci failed or wasn't needed. Proceeding...");
349
+ }
350
+ }
351
+
352
+ log.step("[4/5]", "Resolving execution boundary in sandbox...");
376
353
  const resolvedCmds = resolveWorkspaceExecutionBoundary([], sandboxDir);
377
354
 
378
355
  if (resolvedCmds.testCmd || resolvedCmds.buildCmd) {
379
- log.step("[4/4]", `Kör verifiering: ${resolvedCmds.testCmd || resolvedCmds.buildCmd}`);
356
+ log.step("[5/5]", `Running verification: ${resolvedCmds.testCmd || resolvedCmds.buildCmd}`);
380
357
  execSync(resolvedCmds.testCmd || resolvedCmds.buildCmd, {
381
358
  cwd: sandboxDir,
382
359
  env: sterileEnv,
@@ -384,14 +361,15 @@ export function runPreflightSandbox() {
384
361
  });
385
362
  log.success("Pre-Flight Passed. Zero epistemic drift detected.");
386
363
  } else {
387
- log.info("Inga test/bygg-kommandon hittades för verifiering.");
364
+ log.info("No test/build commands found for verification.");
388
365
  log.success("Pre-Flight Passed (No-Op).");
389
366
  }
390
367
  } catch (err) {
391
- log.error("\nPRE-FLIGHT MISSLYCKADES!");
392
- log.error("Agenten skulle förmodligen krascha (The 5-Minute Drop-off) i Web UI på grund av saknade beroenden.");
393
- log.error(err.message);
394
- process.exit(1);
368
+ log.error("\nPRE-FLIGHT FAILED!");
369
+ log.error("The agent would likely crash (The 5-Minute Drop-off) in the Web UI due to missing dependencies.");
370
+ const wrappedErr = new Error(err.message);
371
+ wrappedErr.code = 1;
372
+ throw wrappedErr;
395
373
  } finally {
396
374
  try {
397
375
  fs.rmSync(sandboxDir, { recursive: true, force: true });
@@ -406,10 +384,15 @@ export function runPreflightSandbox() {
406
384
 
407
385
  // Execute when invoked directly from CLI
408
386
  if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
409
- if (process.argv.includes("--preflight")) {
410
- runPreflightSandbox();
411
- } else {
412
- runSelfAudit();
387
+ try {
388
+ if (process.argv.includes("--preflight")) {
389
+ runPreflightSandbox();
390
+ } else {
391
+ runSelfAudit();
392
+ }
393
+ } catch (err) {
394
+ if (err.message) log.error(err.message);
395
+ process.exit(err.code || 1);
413
396
  }
414
397
  }
415
398