jules-orchestrator-kit 0.8.1 → 0.9.3

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.
Files changed (50) hide show
  1. package/.agent/jules-queue/README.md +4 -4
  2. package/.agent/prompts/Bolt.md +18 -0
  3. package/.agent/prompts/Overseer.md +18 -0
  4. package/.agent/prompts/Sentinel.md +18 -0
  5. package/.agent/prompts/Task_Template.md +26 -0
  6. package/.agent/rules/dynamic-guardrails.json +0 -4
  7. package/.github/workflows/jules-audit.yml +16 -5
  8. package/JULES_RULES_TEMPLATE.md +7 -2
  9. package/README.md +113 -21
  10. package/bin/agentctl.mjs +274 -0
  11. package/bin/init.js +1 -1
  12. package/index.mjs +34 -6
  13. package/package.json +22 -6
  14. package/scripts/asset-integrity-check.mjs +35 -0
  15. package/scripts/command-resolver.mjs +37 -214
  16. package/scripts/jules-cleanup.mjs +23 -174
  17. package/scripts/jules-create.mjs +12 -51
  18. package/scripts/jules-dispatch.mjs +103 -349
  19. package/scripts/jules-merge-swarm.mjs +30 -0
  20. package/scripts/jules-nightly.mjs +7 -124
  21. package/scripts/jules-patch.mjs +23 -0
  22. package/scripts/jules-queue-runner.mjs +28 -203
  23. package/scripts/jules-scan-todos.mjs +31 -125
  24. package/scripts/jules-self-audit.mjs +76 -578
  25. package/scripts/jules-status.mjs +42 -65
  26. package/scripts/jules-swarm.mjs +27 -232
  27. package/scripts/jules-webhook-receiver.mjs +70 -0
  28. package/scripts/lock-manager.mjs +11 -170
  29. package/scripts/risk-tier.mjs +29 -0
  30. package/scripts/rules-lint.mjs +20 -0
  31. package/scripts/stale-base-check.mjs +26 -0
  32. package/scripts/utils.mjs +118 -252
  33. package/scripts/validate-envelope.mjs +44 -0
  34. package/src/asset_integrity.mjs +76 -0
  35. package/src/config.mjs +325 -0
  36. package/src/engine.mjs +217 -0
  37. package/src/envelope.mjs +105 -0
  38. package/src/git.mjs +127 -0
  39. package/src/provider.mjs +179 -0
  40. package/src/risk.mjs +120 -0
  41. package/src/rules_budget.mjs +77 -0
  42. package/src/security.mjs +212 -0
  43. package/src/state.mjs +209 -0
  44. package/src/webhook.mjs +138 -0
  45. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -63
  46. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -34
  47. package/.github/social-preview.png +0 -0
  48. package/.github/workflows/agent-scope-guard.yml +0 -57
  49. package/.github/workflows/jules-nightly.yml +0 -21
  50. package/.github/workflows/publish.yml +0 -24
@@ -1,607 +1,105 @@
1
- import { execSync, execFileSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import crypto from "node:crypto";
5
- import os from "node:os";
6
- import { fileURLToPath } from "node:url";
7
- import { resolveWorkspaceExecutionBoundary } from "./command-resolver.mjs";
8
- import { log, logToHistory, redactSecrets, hasHighConfidenceSecret, hasLowConfidenceSecret } from "./utils.mjs";
1
+ #!/usr/bin/env node
9
2
 
10
- function runGitCommand(args, ignoreError = false) {
11
- try {
12
- return execFileSync("git", args, { encoding: "utf-8" }).trim();
13
- } catch (error) {
14
- if (ignoreError) return "";
15
- const err = new Error(`Git command failed: git ${args.join(" ")}\n${error.message}`);
16
- err.code = 1;
17
- throw err;
18
- }
19
- }
3
+ /**
4
+ * Backward compatibility shim for jules-self-audit.mjs in v0.9.0.
5
+ * Delegates execution to src/engine.mjs gate().
6
+ */
7
+
8
+ import { gate } from "../src/engine.mjs";
9
+ import { loadConfig, parseYaml } from "../src/config.mjs";
10
+ import { matchesGlob } from "../src/security.mjs";
20
11
 
21
12
  export function matchGlob(filepath, globPattern) {
22
- const cleanPath = filepath.replace(/\\/g, "/").replace(/^\.\//, "");
23
- // Normalize consecutive glob wildcards (e.g., "**/**" -> "**")
24
- const normalizedGlob = globPattern.replace(/\\/g, "/").replace(/(?:\*\*\/)+/g, "**/");
25
- const segments = normalizedGlob.split("/");
26
- const regexParts = [];
27
- for (let i = 0; i < segments.length; i++) {
28
- const seg = segments[i];
29
- if (seg === "**") {
30
- regexParts.push("**");
31
- } else {
32
- regexParts.push(
33
- seg
34
- .replace(/[.+^${}()|[\]\\]/g, "\\$&")
35
- .replace(/\*/g, "[^/]*")
36
- .replace(/\?/g, "[^/]")
37
- );
38
- }
39
- }
40
- let patternStr = regexParts.join("/");
41
- patternStr = patternStr.replace(/(?:^|\/)\*\*(?:\/|$)/g, (m) => {
42
- if (m === "**") return ".*";
43
- if (m === "/**/") return "(?:/|/.*/)";
44
- if (m === "/**") return "(?:/.*)?";
45
- if (m === "**/") return "(?:.*/)?";
46
- return ".*";
47
- });
48
- patternStr = patternStr.replace(/\*\*/g, ".*");
49
- return new RegExp(`^${patternStr}$`, "i").test(cleanPath);
13
+ return matchesGlob(filepath, globPattern);
50
14
  }
51
15
 
16
+ export const EXECUTION_CONFIG_FILES = ["package.json", "tsconfig.json", "Cargo.toml", "go.mod", "pyproject.toml", "vite.config.ts", "jest.config.js", ".npmrc"];
17
+ export const RESTRICTED_AGENT_FILES = ["AGENTS.md", "JULES_RULES_TEMPLATE.md"];
18
+ export const COMMAND_DEFINING_FILES = ["package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".agent/jules.yml", ".agent/config.yml"];
52
19
 
53
- export const EXECUTION_CONFIG_FILES = [
54
- ".npmrc",
55
- ".yarnrc",
56
- ".yarnrc.yml",
57
- "pnpmfile.js",
58
- "pnpmfile.cjs",
59
- ".pnpmfile.cjs",
60
- "jest.config.js",
61
- "jest.config.cjs",
62
- "jest.config.mjs",
63
- "jest.config.ts",
64
- "vitest.config.js",
65
- "vitest.config.cjs",
66
- "vitest.config.mjs",
67
- "vitest.config.ts",
68
- "playwright.config.js",
69
- "playwright.config.ts",
70
- "vite.config.js",
71
- "vite.config.ts",
72
- "webpack.config.js",
73
- "webpack.config.cjs",
74
- "webpack.config.mjs",
75
- "rollup.config.js",
76
- "rollup.config.mjs",
77
- "esbuild.config.js",
78
- "next.config.js",
79
- "next.config.mjs",
80
- "next.config.ts",
81
- "babel.config.js",
82
- "babel.config.cjs",
83
- "babel.config.json",
84
- ".babelrc",
85
- "tsconfig.json",
86
- "tsconfig.*.json",
87
- "deno.json",
88
- "deno.jsonc",
89
- "bunfig.toml"
90
- ];
91
-
92
- export const RESTRICTED_AGENT_FILES = [
93
- "AGENTS.md",
94
- "JULES_RULES_TEMPLATE.md",
95
- ".agent/rules/**",
96
- ".agent/workflows/**",
97
- ".agent/jules.yml",
98
- "jules.config.json"
99
- ];
100
-
101
- export const COMMAND_DEFINING_FILES = [
102
- "package.json",
103
- "package-lock.json",
104
- "pnpm-lock.yaml",
105
- "pnpm-workspace.yaml",
106
- "yarn.lock",
107
- "bun.lockb",
108
- "turbo.json",
109
- "nx.json",
110
- "lerna.json",
111
- "rush.json",
112
- "Cargo.toml",
113
- "Cargo.lock",
114
- "go.mod",
115
- "go.sum",
116
- "pyproject.toml",
117
- "setup.py",
118
- "requirements.txt",
119
- "Makefile",
120
- "build.gradle",
121
- "build.gradle.kts",
122
- "pom.xml",
123
- ".agent/jules.yml",
124
- "jules.config.json"
125
- ];
126
-
127
- /**
128
- * Lightweight Zero-Dependency YAML Pattern Extractor
129
- * Extracts forbidden_paths and allow_paths arrays from jules.yml.
130
- * Supported subset:
131
- * - Flow style arrays: forbidden_paths: ["path1", "path2"]
132
- * - Block style arrays: forbidden_paths:\n - path1\n - path2
133
- * Limitations: Does not support multiline strings (|/>), nested dicts, or inline comments.
134
- */
135
20
  export function loadForbiddenPatterns(configContent = "") {
136
- const defaultForbidden = [
137
- ".github/**",
138
- "**/secrets/**",
139
- "**/*.pem",
140
- "**/lock-manager/**",
141
- "scripts/jules-*",
142
- ".agent/jules.yml"
143
- ];
144
- if (!configContent) return defaultForbidden;
145
-
146
- // Flow style: forbidden_paths: [...]
147
- const flowMatch = configContent.match(/forbidden_paths:\s*\[([^\]]+)\]/);
148
- if (flowMatch) {
149
- const parsed = flowMatch[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
150
- return Array.from(new Set([...defaultForbidden, ...parsed]));
151
- }
152
-
153
- // Block style: forbidden_paths:\n - "path1"\n - "path2"
154
- const lines = configContent.split("\n");
155
- let inForbidden = false;
156
- const blockParsed = [];
157
- for (const line of lines) {
158
- if (line.trim().startsWith("forbidden_paths:")) {
159
- inForbidden = true;
160
- continue;
161
- }
162
- if (inForbidden) {
163
- const trimmed = line.trim();
164
- if (trimmed.startsWith("-")) {
165
- blockParsed.push(trimmed.slice(1).trim().replace(/^["']|["']$/g, ""));
166
- } else if (trimmed && !trimmed.startsWith("#")) {
167
- break;
168
- }
169
- }
170
- }
171
- if (blockParsed.length > 0) {
172
- return Array.from(new Set([...defaultForbidden, ...blockParsed]));
173
- }
174
-
175
- return defaultForbidden;
21
+ const defaults = ["scripts/jules-*", ".agent/jules.yml", ".github/**", "**/*.pem"];
22
+ if (!configContent) return defaults;
23
+ const parsed = parseYaml(configContent);
24
+ const userPaths = Array.isArray(parsed.forbidden_paths) ? parsed.forbidden_paths : [];
25
+ return Array.from(new Set([...defaults, ...userPaths]));
176
26
  }
177
27
 
178
28
  export function loadAllowedPatterns(configContent = "") {
179
29
  if (!configContent) return [];
180
- const flowMatch = configContent.match(/allow_paths:\s*\[([^\]]+)\]/);
181
- if (flowMatch) {
182
- return flowMatch[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
183
- }
184
- const lines = configContent.split("\n");
185
- let inAllow = false;
186
- const blockParsed = [];
187
- for (const line of lines) {
188
- if (line.trim().startsWith("allow_paths:")) {
189
- inAllow = true;
190
- continue;
191
- }
192
- if (inAllow) {
193
- const trimmed = line.trim();
194
- if (trimmed.startsWith("-")) {
195
- blockParsed.push(trimmed.slice(1).trim().replace(/^["']|["']$/g, ""));
196
- } else if (trimmed && !trimmed.startsWith("#")) {
197
- break;
30
+ const parsed = parseYaml(configContent);
31
+ return Array.isArray(parsed.allow_paths) ? parsed.allow_paths : [];
32
+ }
33
+
34
+ export function validateJulesConfig(_configContent = "", jsonGuardrailsContent = "") {
35
+ const errors = [];
36
+ if (jsonGuardrailsContent) {
37
+ try {
38
+ const parsed = JSON.parse(jsonGuardrailsContent);
39
+ if (Array.isArray(parsed.rules)) {
40
+ for (const rule of parsed.rules) {
41
+ try {
42
+ new RegExp(rule.trigger, "i");
43
+ } catch (err) {
44
+ errors.push(`Invalid RegExp trigger: ${err.message}`);
45
+ }
46
+ }
198
47
  }
48
+ } catch (err) {
49
+ errors.push(`JSON Parse Error: ${err.message}`);
199
50
  }
200
51
  }
201
- return blockParsed;
52
+ return { ok: errors.length === 0, errors };
202
53
  }
203
54
 
204
- export function parseAndCleanStderr(stderrStr) {
205
- if (!stderrStr) return "";
206
- const clean = stderrStr.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
207
- const lines = clean.split("\n").filter(Boolean);
208
- return lines.slice(-100).join("\n");
55
+ export function parseAndCleanStderr(str = "") {
56
+ if (typeof str !== "string") return "";
57
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\u001b\[[0-9;]*m/g, "");
209
58
  }
210
59
 
211
60
  export function getOodaStateFile(mergeBase = "main") {
212
- const oodaDir = path.resolve(process.cwd(), ".agent/state/ooda");
213
- if (!fs.existsSync(oodaDir)) fs.mkdirSync(oodaDir, { recursive: true });
214
-
215
- const keyInput = [
216
- process.env.GITHUB_REPOSITORY || "",
217
- process.env.GITHUB_REF || "",
218
- process.env.GITHUB_HEAD_REF || "",
219
- mergeBase,
220
- process.cwd()
221
- ].join("\u0000");
222
-
223
- const key = crypto.createHash("sha256").update(keyInput).digest("hex").slice(0, 16);
224
- return path.join(oodaDir, `${key}.json`);
225
- }
226
-
227
- export function logAuditMetrics(metrics) {
228
- const historyDir = path.resolve(process.cwd(), ".agent/history");
229
- if (!fs.existsSync(historyDir)) {
230
- fs.mkdirSync(historyDir, { recursive: true });
231
- }
232
- const metricsFile = path.join(historyDir, "metrics.jsonl");
233
- const logLine = JSON.stringify({
234
- timestamp: new Date().toISOString(),
235
- ...metrics,
236
- }) + "\n";
237
- fs.appendFileSync(metricsFile, logLine, "utf-8");
61
+ return `.agent/state/ooda-${mergeBase}.json`;
238
62
  }
239
63
 
240
- export function runSelfAudit() {
241
- log.header("Running Jules PR Self-Audit Gatekeeper...");
242
-
243
- const targetBranch = process.env.BASE_BRANCH || "main";
244
- const SAFE_BRANCH = /^[a-zA-Z0-9._\/-]+$/;
245
- if (!SAFE_BRANCH.test(targetBranch)) {
246
- const err = new Error(`FATAL: Invalid BASE_BRANCH "${targetBranch}". Must match ^[a-zA-Z0-9._\\/-]+$`);
247
- err.code = 2;
248
- throw err;
249
- }
250
- log.info(`Target Branch: ${targetBranch}`);
251
-
252
- try {
253
- execFileSync("git", ["--version"], { stdio: "ignore" });
254
- } catch {
255
- const err = new Error("FATAL: git is not installed or not in PATH.");
256
- err.code = 2;
257
- throw err;
258
- }
259
-
260
- if (process.env.CI) {
261
- log.info("☁️ CI environment detected. Fetching merge-base history...");
262
- runGitCommand(["fetch", "origin", targetBranch, "--depth=100"], true);
263
- const tmpRef = runGitCommand(["rev-parse", "--verify", `origin/${targetBranch}`], true) ? `origin/${targetBranch}` : targetBranch;
264
- const tmpMerge = runGitCommand(["merge-base", "HEAD", tmpRef], true);
265
- if (!tmpMerge) {
266
- log.warn("Shallow fetch failed to find merge-base, executing full unshallow...");
267
- runGitCommand(["fetch", "origin", targetBranch, "--unshallow"], true);
268
- }
269
- }
270
-
271
- const mainRef = runGitCommand(["rev-parse", "--verify", `origin/${targetBranch}`], true) ? `origin/${targetBranch}` : targetBranch;
272
- const mergeBase = runGitCommand(["merge-base", "HEAD", mainRef], true);
273
-
274
- if (!mergeBase) {
275
- const err = new Error(`FATAL: Could not compute merge-base with ${mainRef}. Make sure git history is unshallowed.`);
276
- err.code = 2;
277
- throw err;
278
- }
279
-
280
- log.info(`Merge-Base Hash: ${mergeBase}`);
281
-
282
- const rawDiffFiles = runGitCommand(["diff", "-z", "--name-only", `${mergeBase}...HEAD`])
283
- .split("\0")
284
- .map((f) => f.trim().replace(/^["']|["']$/g, ""))
285
- .filter(Boolean);
286
-
287
- const ALL_COMMAND_FILES = [...COMMAND_DEFINING_FILES, ...EXECUTION_CONFIG_FILES];
288
- const commandFileChanges = rawDiffFiles.filter((file) =>
289
- ALL_COMMAND_FILES.some((cmdFile) => file === cmdFile || file.endsWith(`/${cmdFile}`) || matchGlob(file, cmdFile))
290
- );
291
-
292
- if (commandFileChanges.length > 0 && process.env.JULES_ALLOW_COMMAND_FILE_CHANGES !== "true" && process.env.JULES_ALLOW_COMMAND_FILE_CHANGES !== "1") {
293
- const err = new Error(
294
- "COMMAND / BUILD CONFIG FILE CHANGE DETECTED: [" + commandFileChanges.join(", ") + "]. " +
295
- "Refusing to execute verification scripts from untrusted branch. " +
296
- "To override for legitimate dependency updates, set JULES_ALLOW_COMMAND_FILE_CHANGES=true."
297
- );
298
- err.code = 3;
299
- throw err;
300
- }
301
-
302
- const agentRuleChanges = rawDiffFiles.filter((file) =>
303
- RESTRICTED_AGENT_FILES.some((pattern) => matchGlob(file, pattern))
304
- );
305
-
306
- if (agentRuleChanges.length > 0 && process.env.JULES_ALLOW_AGENT_RULE_CHANGES !== "true" && process.env.JULES_ALLOW_AGENT_RULE_CHANGES !== "1") {
307
- const err = new Error(
308
- "AGENT RULE FILE CHANGE DETECTED: [" + agentRuleChanges.join(", ") + "]. " +
309
- "Refusing to load agent rules from untrusted branch. " +
310
- "To override for legitimate rule updates, set JULES_ALLOW_AGENT_RULE_CHANGES=true."
311
- );
312
- err.code = 3;
313
- throw err;
314
- }
315
-
316
- const isBloatFile = (file) => /(\.lock|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|\.png|\.jpg|\.jpeg|\.pdf|\.min\.js|\.map)$/i.test(file);
317
- const changedCodeFiles = rawDiffFiles.filter((f) => !isBloatFile(f));
318
-
319
- log.info(`Modified Code Files (${changedCodeFiles.length} of ${rawDiffFiles.length} total changes):`);
320
- changedCodeFiles.forEach((file) => log.dim(` - ${file}`));
321
-
322
- // Fail closed: Load security config exclusively from base branch (mainRef).
323
- // Never fall back to working-tree config which an untrusted PR could craft.
324
- const trustedConfig = runGitCommand(["show", `${mainRef}:.agent/jules.yml`], true);
325
- const forbiddenPatterns = loadForbiddenPatterns(trustedConfig);
326
- const allowedPatterns = loadAllowedPatterns(trustedConfig);
327
-
328
- const violations = rawDiffFiles.filter((file) => {
329
- const isForbidden = forbiddenPatterns.some((pattern) => matchGlob(file, pattern));
330
- if (isForbidden) return true; // Immutable forbidden path — CANNOT be overridden by allow_paths
331
- if (allowedPatterns.length > 0) {
332
- const isAllowed = allowedPatterns.some((pattern) => matchGlob(file, pattern));
333
- return !isAllowed;
334
- }
335
- return false;
336
- });
337
-
338
- if (violations.length > 0) {
339
- const err = new Error("RESTRICTED FILE VIOLATION DETECTED: " + violations.join(", "));
340
- err.code = 3;
341
- throw err;
342
- }
343
- log.success("Restricted File Boundary Check: PASSED");
344
-
345
- log.info("Analyzing Code Diff Payload Size & Content...");
346
- const codeDiffPayload = changedCodeFiles.length > 0
347
- ? runGitCommand(["diff", `${mergeBase}...HEAD`, "--", ...changedCodeFiles], true)
348
- : "";
349
-
350
- const diffBytes = Buffer.byteLength(codeDiffPayload, "utf8");
351
- const maxDiffBytes = 75 * 1024; // 75 KB
352
-
353
- if (diffBytes > maxDiffBytes) {
354
- const err = new Error(`DIFF PAYLOAD TOO LARGE: ${diffBytes} bytes exceeds ${maxDiffBytes} bytes limit for code files. Split the task.`);
355
- err.code = 5;
356
- throw err;
357
- }
358
-
359
- const fullDiffPayload = runGitCommand(["diff", `${mergeBase}...HEAD`], true);
360
- const addedLines = fullDiffPayload
361
- .split("\n")
362
- .filter((line) => line.startsWith("+") && !line.startsWith("+++"))
363
- .map((line) => line.slice(1))
364
- .join("\n");
365
-
366
- if (hasHighConfidenceSecret(addedLines)) {
367
- const err = new Error("SECRET LEAK PREVENTED: High-confidence secret token or private key detected in added diff lines. Aborting.");
368
- err.code = 6;
369
- throw err;
370
- }
371
-
372
- if (hasLowConfidenceSecret(addedLines)) {
373
- log.warn("SECURITY WARNING: Low-confidence secret or test key pattern detected in added diff lines.");
374
- }
375
- log.success(`Diff Inspection Check: PASSED (${diffBytes} bytes code diff, No Active High-Confidence Secrets)`);
376
-
377
- log.info("Resolving Dynamic Verification Suite from Trusted Base Branch...");
378
-
379
- const runId = crypto.randomBytes(4).toString("hex");
380
- const trustedDir = path.join(os.tmpdir(), `jules-trusted-tree-${runId}`);
381
- const tempArchive = path.join(os.tmpdir(), `jules-trusted-archive-${runId}.tar`);
382
-
383
- let resolutionRoot;
384
- try {
385
- fs.mkdirSync(trustedDir, { recursive: true });
386
- execFileSync("git", ["archive", mainRef, "-o", tempArchive]);
387
- execFileSync("tar", ["-xf", tempArchive, "-C", trustedDir]);
388
- resolutionRoot = trustedDir;
389
- } catch (err) {
390
- const error = new Error("FATAL: Failed to extract trusted base branch for command resolution. Refusing to fall back to working tree.\n" + err.message);
391
- error.code = 2;
392
- throw error;
393
- }
394
-
395
- let resolvedCmds;
396
- try {
397
- resolvedCmds = resolveWorkspaceExecutionBoundary(changedCodeFiles, resolutionRoot);
398
- } finally {
399
- try {
400
- if (trustedDir && fs.existsSync(trustedDir)) {
401
- fs.rmSync(trustedDir, { recursive: true, force: true });
402
- }
403
- if (tempArchive && fs.existsSync(tempArchive)) {
404
- fs.rmSync(tempArchive, { force: true });
405
- }
406
- } catch (e) {}
407
- }
408
-
409
- log.info(`Discovered Execution Scope: ${resolvedCmds.source}`);
410
-
411
- const startTime = Date.now();
412
- let testPassed = true;
413
- let failureLog = "";
414
-
415
- const runVerification = (cmd) => {
416
- try {
417
- execSync(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
418
- return true;
419
- } catch (err) {
420
- failureLog = parseAndCleanStderr((err.stderr || "") + "\n" + (err.stdout || "") + "\n" + (err.message || ""));
421
- return false;
422
- }
423
- };
64
+ export function logAuditMetrics() {}
424
65
 
425
- if (resolvedCmds.testCmd) {
426
- log.step("▶", `Running Test Verification: ${resolvedCmds.testCmd}`);
427
- if (!runVerification(resolvedCmds.testCmd)) {
428
- testPassed = false;
429
- log.error(`Test verification failed: ${resolvedCmds.testCmd}`);
430
- }
431
- }
66
+ export async function runSelfAudit(opts = {}) {
67
+ const root = opts.root || process.env.JULES_PROJECT_ROOT || process.cwd();
68
+ const config = loadConfig(root);
69
+ const base = opts.base || process.env.BASE_BRANCH || config.baseBranch || "main";
432
70
 
433
- if (testPassed && resolvedCmds.buildCmd) {
434
- log.step("▶", `Running Build Verification: ${resolvedCmds.buildCmd}`);
435
- if (!runVerification(resolvedCmds.buildCmd)) {
436
- testPassed = false;
437
- log.error(`Build verification failed: ${resolvedCmds.buildCmd}`);
438
- }
439
- }
440
-
441
- if (!resolvedCmds.testCmd && !resolvedCmds.buildCmd) {
442
- log.info("No build or test scripts found. Running git status check.");
443
- execSync("git status", { stdio: "inherit" });
444
- }
445
-
446
- const durationMs = Date.now() - startTime;
447
- logAuditMetrics({
448
- targetBranch,
449
- changedFilesCount: changedCodeFiles.length,
450
- testPassed,
451
- durationMs,
452
- scope: resolvedCmds.source,
71
+ const res = await gate({
72
+ root,
73
+ config,
74
+ base,
75
+ fix: process.env.ALLOW_AUTO_REPAIR === "true",
76
+ allowProtected: process.env.JULES_ALLOW_COMMAND_FILE_CHANGES === "true",
453
77
  });
454
-
455
- if (!testPassed) {
456
- log.error("OODA SELF-HEALING FEEDBACK LOG:");
457
- log.dim(failureLog.slice(-1500));
458
-
459
- if (process.env.CI && !process.env.ALLOW_AUTO_REPAIR) {
460
- const err = new Error("CI environment detected and ALLOW_AUTO_REPAIR is not set. Failing fast to save CI minutes.");
461
- err.code = 4;
462
- throw err;
463
- }
464
-
465
- let oodaRetries = 0;
466
- const oodaStateFile = getOodaStateFile(mergeBase);
467
- try {
468
- if (fs.existsSync(oodaStateFile)) {
469
- const oodaData = JSON.parse(fs.readFileSync(oodaStateFile, "utf-8"));
470
- oodaRetries = oodaData.attempts || 0;
471
- } else {
472
- const history = execSync("git log -n 5 --format=%B", { encoding: "utf-8" });
473
- const matches = history.match(/OODA Auto-Repair/gi);
474
- if (matches) {
475
- oodaRetries = matches.length;
476
- }
477
- }
478
- } catch (e) {
479
- // Ignore errors here
480
- }
481
-
482
- if (oodaRetries < 3) {
483
- log.info(`🛠️ Initiating OODA Auto-Repair (Attempt ${oodaRetries + 1}/3)...`);
484
- try {
485
- fs.writeFileSync(oodaStateFile, JSON.stringify({ attempts: oodaRetries + 1, lastAttempt: new Date().toISOString() }), "utf-8");
486
- } catch (e) {}
487
- const prompt = `OODA Auto-Repair Attempt ${oodaRetries + 1}\n\nThe verification suite failed with the following errors after the previous patch:\n\n\`\`\`\n${failureLog.slice(-1500)}\n\`\`\`\n\nPlease fix the errors so the verification passes.`;
488
-
489
- const dispatchScript = path.resolve(process.cwd(), "scripts/jules-dispatch.mjs");
490
- try {
491
- const env = {
492
- ...process.env,
493
- BASE_BRANCH: process.env.GITHUB_HEAD_REF || targetBranch
494
- };
495
- execFileSync("node", [dispatchScript, "OODA Auto-Repair", prompt], {
496
- stdio: "inherit",
497
- env
498
- });
499
- } catch (dispatchErr) {
500
- log.error(`Failed to trigger OODA repair dispatch: ${dispatchErr.message}`);
501
- }
502
- } else {
503
- log.error("❌ OODA Auto-Repair exhausted maximum retries (3). Giving up.");
504
- }
505
- const err = new Error("OODA Auto-Repair failed or exhausted.");
506
- err.code = 4;
507
- throw err;
508
- }
509
-
510
- try {
511
- const oodaStateFile = getOodaStateFile(mergeBase);
512
- if (fs.existsSync(oodaStateFile)) {
513
- fs.rmSync(oodaStateFile, { force: true });
514
- }
515
- } catch (_) {}
516
-
517
- log.success("🎉 JULES PR SELF-AUDIT PASSED SUCCESSFULLY!");
78
+ return res;
518
79
  }
519
80
 
520
- export function runPreflightSandbox() {
521
- log.header("Initializing Pre-Flight Sandbox (Sterile Simulation)...");
522
-
523
- const runId = crypto.randomBytes(4).toString("hex");
524
- const sandboxDir = path.join(os.tmpdir(), `jules-preflight-${runId}`);
525
- const tempArchive = path.join(os.tmpdir(), `jules-archive-${runId}.tar`);
526
-
527
- try {
528
- log.step("[1/5]", `Creating isolated environment in ${sandboxDir}...`);
529
- fs.mkdirSync(sandboxDir, { recursive: true });
530
-
531
- execSync(`git archive HEAD -o "${tempArchive}"`);
532
- execSync(`tar -xf "${tempArchive}" -C "${sandboxDir}"`);
533
-
534
- log.step("[2/5]", "Sterilizing environment variables...");
535
- const sterileEnv = { ...process.env };
536
- const stripKeys = ["DATABASE_URL", "NPM_TOKEN", "GITHUB_TOKEN", "GH_TOKEN", "AWS_ACCESS_KEY_ID", "STRIPE_TEST_KEY"];
537
- for (const key of stripKeys) {
538
- if (sterileEnv[key]) {
539
- sterileEnv[key] = `mock-${key.toLowerCase()}`;
540
- }
541
- }
542
-
543
- log.step("[3/5]", "Resolving package manager and installing dependencies...");
544
- let installCmd = "";
545
- if (fs.existsSync(path.join(sandboxDir, "pnpm-lock.yaml"))) installCmd = "pnpm install --ignore-scripts";
546
- else if (fs.existsSync(path.join(sandboxDir, "bun.lockb"))) installCmd = "bun install --ignore-scripts";
547
- else if (fs.existsSync(path.join(sandboxDir, "yarn.lock"))) installCmd = "yarn install --ignore-scripts";
548
- else if (fs.existsSync(path.join(sandboxDir, "package-lock.json"))) installCmd = "npm ci --ignore-scripts";
549
-
550
- if (installCmd) {
551
- try {
552
- execSync(installCmd, { cwd: sandboxDir, stdio: "pipe", encoding: "utf-8" });
553
- } catch (e) {
554
- log.warn(`${installCmd} failed or wasn't needed. Output: ${e.stdout}\nError: ${e.stderr}\nProceeding...`);
555
- }
556
- } else {
557
- log.info("Skipping dependency installation (no lockfile detected).");
558
- }
559
-
560
- log.step("[4/5]", "Resolving execution boundary in sandbox...");
561
- const resolvedCmds = resolveWorkspaceExecutionBoundary([], sandboxDir);
562
-
563
- if (resolvedCmds.testCmd || resolvedCmds.buildCmd) {
564
- log.step("[5/5]", `Running verification: ${resolvedCmds.testCmd || resolvedCmds.buildCmd}`);
565
- execSync(resolvedCmds.testCmd || resolvedCmds.buildCmd, {
566
- cwd: sandboxDir,
567
- env: sterileEnv,
568
- stdio: "inherit"
569
- });
570
- log.success("Pre-Flight Passed. Zero epistemic drift detected.");
571
- } else {
572
- log.info("No test/build commands found for verification.");
573
- log.success("Pre-Flight Passed (No-Op).");
574
- }
575
- } catch (err) {
576
- log.error("\nPRE-FLIGHT FAILED!");
577
- log.error("The agent would likely crash (The 5-Minute Drop-off) in the Web UI due to missing dependencies.");
578
- const wrappedErr = new Error(err.message);
579
- wrappedErr.code = 1;
580
- throw wrappedErr;
581
- } finally {
582
- try {
583
- fs.rmSync(sandboxDir, { recursive: true, force: true });
584
- if (fs.existsSync(tempArchive)) {
585
- fs.rmSync(tempArchive, { force: true });
586
- }
587
- } catch (e) {
588
- // Ignore cleanup errors
589
- }
590
- }
81
+ export async function runPreflightSandbox() {
82
+ return { ok: true };
591
83
  }
592
84
 
593
- // Execute when invoked directly from CLI
594
- if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
595
- try {
596
- if (process.argv.includes("--preflight")) {
597
- runPreflightSandbox();
598
- } else {
599
- runSelfAudit();
600
- }
601
- } catch (err) {
602
- if (err.message) log.error(err.message);
603
- process.exit(err.code || 1);
604
- }
85
+ if (process.argv[1] && process.argv[1].endsWith("jules-self-audit.mjs")) {
86
+ runSelfAudit()
87
+ .then((res) => {
88
+ if (!res.ok) {
89
+ if (res.code === 3) {
90
+ console.error("❌ RESTRICTED FILE VIOLATION");
91
+ } else if (res.code === 6) {
92
+ console.error("❌ SECRET LEAK PREVENTED");
93
+ } else {
94
+ console.error(`❌ Self Audit Failed (Exit ${res.code})`);
95
+ }
96
+ process.exit(res.code);
97
+ }
98
+ console.log("Audit Complete: PASSED");
99
+ process.exit(0);
100
+ })
101
+ .catch((err) => {
102
+ console.error(`Audit Failure: ${err.message}`);
103
+ process.exit(err.code || 1);
104
+ });
605
105
  }
606
-
607
-