jules-orchestrator-kit 0.8.5 → 0.9.4

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