jules-orchestrator-kit 0.72.2 → 0.73.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/.agent/prompts/{Overseer.md → Auditor.md} +3 -3
- package/.agent/prompts/{Alchemist.md → Database.md} +1 -1
- package/.agent/prompts/Debugger.md +25 -0
- package/.agent/prompts/{Scribe.md → Docs.md} +8 -5
- package/.agent/prompts/{Spectator.md → E2E.md} +9 -6
- package/.agent/prompts/{Janitor.md → Hygiene.md} +2 -2
- package/.agent/prompts/{Bolt.md → Performance.md} +1 -1
- package/.agent/prompts/Resilience.md +20 -0
- package/.agent/prompts/Security.md +21 -0
- package/.agent/prompts/Testing.md +30 -0
- package/.agent/prompts/Types.md +19 -0
- package/.agent/rules/jules-protocol.md +4 -3
- package/AGENTS.md +78 -96
- package/CHANGELOG.md +193 -0
- package/JULES_RULES_TEMPLATE.md +83 -96
- package/LICENSE +1 -1
- package/README.md +77 -439
- package/ROADMAP_V1.md +22 -132
- package/bin/agentctl.mjs +443 -144
- package/bin/init.js +6 -3
- package/index.mjs +9 -6
- package/package.json +1 -1
- package/scripts/asset-integrity-check.mjs +1 -1
- package/scripts/doc-sync-check.mjs +47 -0
- package/scripts/generate-command-reference.mjs +39 -0
- package/scripts/jules-dispatch.mjs +12 -113
- package/scripts/jules-merge-swarm.mjs +8 -196
- package/scripts/jules-patch.mjs +7 -8
- package/scripts/jules-queue-runner.mjs +6 -8
- package/scripts/jules-scan-todos.mjs +10 -38
- package/scripts/jules-self-audit.mjs +8 -139
- package/scripts/jules-status.mjs +32 -38
- package/scripts/jules-webhook-receiver.mjs +1 -1
- package/src/assertions.mjs +5 -50
- package/src/bidi-guard.mjs +36 -0
- package/src/budget.mjs +3 -14
- package/src/config.mjs +2 -6
- package/src/dashboard.mjs +7 -9
- package/src/dispatch.mjs +212 -0
- package/src/engine.mjs +40 -16
- package/src/evidence.mjs +10 -41
- package/src/execution-envelope.mjs +13 -1
- package/src/flaky-ledger.mjs +1 -1
- package/src/fs-atomic.mjs +72 -0
- package/src/git.mjs +298 -27
- package/src/mcp.mjs +296 -7
- package/src/memory.mjs +0 -0
- package/src/merge-swarm.mjs +202 -0
- package/src/ops/cli-intent.mjs +1 -0
- package/src/ops/command-registry.mjs +796 -70
- package/src/ops/doctor-registry.mjs +134 -47
- package/src/ops/handover.mjs +3 -27
- package/src/ops/pr-harvest.mjs +1 -1
- package/src/prompt-guard.mjs +33 -3
- package/src/provider.mjs +51 -7
- package/src/remediation.mjs +2 -2
- package/src/role-resolver.mjs +113 -3
- package/src/router.mjs +19 -11
- package/src/runtime-env.mjs +67 -0
- package/src/scaffold.mjs +3 -1
- package/src/scope-guard.mjs +249 -0
- package/src/secret-scanner.mjs +530 -0
- package/src/security.mjs +81 -2972
- package/src/self-audit.mjs +140 -0
- package/src/session-ops.mjs +29 -0
- package/src/stability.mjs +8 -1
- package/src/stack-detector.mjs +5 -2
- package/src/state.mjs +45 -0
- package/src/swarm.mjs +76 -0
- package/src/task-optimizer.mjs +34 -7
- package/src/telemetry.mjs +23 -0
- package/src/test-tamper-guard.mjs +2173 -0
- package/src/todo-scanner.mjs +129 -0
- package/src/web-templates.mjs +3 -3
- package/src/webhook.mjs +10 -3
- package/src/wizard-init.mjs +12 -20
- package/src/wizard-oracle.mjs +4 -3
- package/src/wizard-task.mjs +37 -9
- package/.agent/prompts/Sentinel.md +0 -18
- package/scripts/utils.mjs +0 -241
package/bin/init.js
CHANGED
|
@@ -260,9 +260,12 @@ const setupMdContent = `# Google Jules Encoded Workspace Manifest
|
|
|
260
260
|
> **Generated**: ${agentState.generatedAt}
|
|
261
261
|
> **Workspace Manifest Code**: \`${payloadToken}\`
|
|
262
262
|
|
|
263
|
-
##
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
## Encoded workspace preset
|
|
264
|
+
This kit-generated JULES1. token is an encoded workspace preset generated for the Jules web UI, not an official Google setup code or credential. It contains a SHA-256 digest and base64url-encoded Brotli-compressed JSON; encoding is not encryption. Review the decoded settings before use.
|
|
265
|
+
|
|
266
|
+
Jules web UI: https://jules.google (the preset format is maintained by this kit).
|
|
267
|
+
|
|
268
|
+
Workspace Manifest Code:
|
|
266
269
|
\`\`\`
|
|
267
270
|
${payloadToken}
|
|
268
271
|
\`\`\`
|
package/index.mjs
CHANGED
|
@@ -111,13 +111,16 @@ export { measureEventLoopDelay } from "./src/perf.mjs";
|
|
|
111
111
|
export { DagExecutor, DagCycleError } from "./src/dag-engine.mjs";
|
|
112
112
|
export { journalIntent, journalDone, reapOrphanedIntents, reapStaleMutexDirs } from "./src/journal.mjs";
|
|
113
113
|
|
|
114
|
-
//
|
|
114
|
+
// Command resolution entrypoint (package.json/CI required) and its boundary helpers.
|
|
115
115
|
export { resolveProjectCommands, resolveWorkspaceExecutionBoundary } from "./scripts/command-resolver.mjs";
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
export
|
|
119
|
-
export {
|
|
120
|
-
export {
|
|
116
|
+
// Self-audit, TODO scanner, dispatch helpers, and swarm sync manifest:
|
|
117
|
+
// implementation lives in src/ (P04 shim cleanup); the scripts/ files are
|
|
118
|
+
// minimal CLI entrypoints that re-export the same surface for compatibility.
|
|
119
|
+
export { runSelfAudit, runPreflightSandbox } from "./src/self-audit.mjs";
|
|
120
|
+
export { scanCodebaseForTodos, runScanner } from "./src/todo-scanner.mjs";
|
|
121
|
+
export { getDynamicGuardrails, dispatchTask } from "./src/dispatch.mjs";
|
|
122
|
+
export { classifyQueueFailure } from "./src/engine.mjs";
|
|
123
|
+
export { extractPrUrls, auditSessions, buildSyncManifest, pushReservationManifest } from "./src/swarm.mjs";
|
|
121
124
|
|
|
122
125
|
// Native TUI Primitives
|
|
123
126
|
export { isTTY, styleText, select, multiSelect, input, confirm, secretInput, spinner, ANSI } from "./src/tui.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.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",
|
|
@@ -8,7 +8,7 @@ const root = process.cwd();
|
|
|
8
8
|
const dirsToScan = ["public", "src/assets", "assets", "static"].map((d) => join(root, d)).filter(existsSync);
|
|
9
9
|
|
|
10
10
|
if (dirsToScan.length === 0) {
|
|
11
|
-
console.log("[asset-integrity] No asset directories (public/, src/assets/) found.
|
|
11
|
+
console.log("[asset-integrity] status: N/A — No asset directories (public/, src/assets/, assets/, static/) found. Nothing to check.");
|
|
12
12
|
process.exit(0);
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -20,6 +20,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { execSync } from "node:child_process";
|
|
22
22
|
import { resolveRoot } from "../src/config.mjs";
|
|
23
|
+
import { formatRegistryMarkdown } from "../src/ops/command-registry.mjs";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Runs the unit suite and extracts the authoritative counts.
|
|
@@ -317,6 +318,52 @@ export function checkDocSync(root = process.cwd(), opts = {}) {
|
|
|
317
318
|
}
|
|
318
319
|
}
|
|
319
320
|
|
|
321
|
+
// 6. SECURITY.md — supported version table must name the current version series.
|
|
322
|
+
const security = readIfExists(join(root, "SECURITY.md"));
|
|
323
|
+
if (security === null) {
|
|
324
|
+
add("SECURITY present", false, "SECURITY.md not found");
|
|
325
|
+
} else {
|
|
326
|
+
const sem = version.split(".");
|
|
327
|
+
const expectedSeries = `v${sem[0]}.${sem[1]}.x`;
|
|
328
|
+
const secMatch = security.match(/\|\s*`v(\d+\.\d+)\.x`\s*\(Latest\)\s*\|/);
|
|
329
|
+
add(
|
|
330
|
+
"SECURITY supported version",
|
|
331
|
+
secMatch ? `v${secMatch[1]}.x` === expectedSeries : false,
|
|
332
|
+
secMatch ? `found v${secMatch[1]}.x, expected ${expectedSeries}` : "no `vX.Y.x (Latest)` marker found"
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 7. docs/COMMAND_REFERENCE.md — regenerated from the command registry on
|
|
337
|
+
// every run and diffed against the file on disk. The registry feeds
|
|
338
|
+
// `agentctl --help` and `help <command>`, so a stale reference means the
|
|
339
|
+
// docs disagree with the CLI itself. Regenerate with:
|
|
340
|
+
// node scripts/generate-command-reference.mjs
|
|
341
|
+
const referencePath = join(root, "docs", "COMMAND_REFERENCE.md");
|
|
342
|
+
const referenceOnDisk = readIfExists(referencePath);
|
|
343
|
+
if (referenceOnDisk === null) {
|
|
344
|
+
add("COMMAND_REFERENCE present", false, "docs/COMMAND_REFERENCE.md not found");
|
|
345
|
+
} else {
|
|
346
|
+
let rendered = null;
|
|
347
|
+
try {
|
|
348
|
+
rendered = `${formatRegistryMarkdown()}`.replace(/\s+$/, "") + "\n";
|
|
349
|
+
} catch {
|
|
350
|
+
rendered = null;
|
|
351
|
+
}
|
|
352
|
+
if (rendered === null) {
|
|
353
|
+
add("COMMAND_REFERENCE in sync", false, "could not render registry (import failed)");
|
|
354
|
+
} else {
|
|
355
|
+
const diskNorm = referenceOnDisk.replace(/\r\n/g, "\n");
|
|
356
|
+
const renderedNorm = rendered.replace(/\r\n/g, "\n");
|
|
357
|
+
add(
|
|
358
|
+
"COMMAND_REFERENCE in sync",
|
|
359
|
+
diskNorm === renderedNorm,
|
|
360
|
+
diskNorm === renderedNorm
|
|
361
|
+
? "matches src/ops/command-registry.mjs"
|
|
362
|
+
: "stale — run: node scripts/generate-command-reference.mjs"
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
320
367
|
return { ok: checks.every((c) => c.ok), version, checks };
|
|
321
368
|
}
|
|
322
369
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Regenerate docs/COMMAND_REFERENCE.md from the command registry.
|
|
5
|
+
*
|
|
6
|
+
* The registry (src/ops/command-registry.mjs) is the single source of truth
|
|
7
|
+
* for command flags, usage and examples; this script renders it to Markdown
|
|
8
|
+
* so the checked-in reference cannot drift from `agentctl --help` output.
|
|
9
|
+
* The doc-sync gate (scripts/doc-sync-check.mjs) diffs the rendered output
|
|
10
|
+
* against the file on disk and fails on any difference.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node scripts/generate-command-reference.mjs # write the file
|
|
14
|
+
* node scripts/generate-command-reference.mjs --check # exit 1 if stale
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
18
|
+
import { join, dirname } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { formatRegistryMarkdown } from "../src/ops/command-registry.mjs";
|
|
21
|
+
|
|
22
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
const TARGET = join(ROOT, "docs", "COMMAND_REFERENCE.md");
|
|
24
|
+
|
|
25
|
+
const rendered = formatRegistryMarkdown().replace(/\s+$/, "") + "\n";
|
|
26
|
+
|
|
27
|
+
if (process.argv.includes("--check")) {
|
|
28
|
+
const onDisk = existsSync(TARGET) ? readFileSync(TARGET, "utf-8").replace(/\r\n/g, "\n") : null;
|
|
29
|
+
const expected = rendered.replace(/\r\n/g, "\n");
|
|
30
|
+
if (onDisk === expected) {
|
|
31
|
+
console.log("✅ docs/COMMAND_REFERENCE.md is in sync with the command registry.");
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
console.error("❌ docs/COMMAND_REFERENCE.md is stale — run: node scripts/generate-command-reference.mjs");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
writeFileSync(TARGET, rendered);
|
|
39
|
+
console.log(`✅ Wrote ${TARGET}`);
|
|
@@ -1,128 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Task dispatch entrypoint:
|
|
4
|
+
* Task dispatch entrypoint: deprecated CLI shim that delegates to the
|
|
5
|
+
* dispatch pipeline. All logic (guardrails, attachments, argv parsing,
|
|
6
|
+
* dispatchTask) lives in src/dispatch.mjs (P04 shim cleanup).
|
|
7
|
+
*
|
|
8
|
+
* The re-export below preserves the historical
|
|
9
|
+
* `scripts/jules-dispatch.mjs` import surface for external consumers,
|
|
10
|
+
* and — deliberately — keeps argv parsing OUT of module scope: importing
|
|
11
|
+
* this file must never consume the importer's process.argv (D10).
|
|
5
12
|
*/
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import { join } from "node:path";
|
|
14
|
+
export * from "../src/dispatch.mjs";
|
|
15
|
+
|
|
16
|
+
import { dispatchTask, parseDispatchArgs } from "../src/dispatch.mjs";
|
|
11
17
|
|
|
12
18
|
if (process.argv[1] && (process.argv[1].endsWith("jules-dispatch.mjs") || process.argv[1].endsWith("jules-dispatch"))) {
|
|
13
19
|
process.emitWarning(
|
|
14
20
|
"scripts/jules-dispatch.mjs is deprecated and will be removed in v1.0.0. Use 'agentctl dispatch' or the programmatic SDK.",
|
|
15
21
|
"DeprecationWarning"
|
|
16
22
|
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const args = process.argv.slice(2);
|
|
20
|
-
let title = "Task Dispatch";
|
|
21
|
-
let prompt = "";
|
|
22
|
-
let dryRun = process.env.JULES_DRY_RUN === "true" || process.env.JULES_DRY_RUN === "1" || args.includes("--dry-run");
|
|
23
|
-
let repoless = args.includes("--repoless");
|
|
24
|
-
|
|
25
|
-
for (let i = 0; i < args.length; i++) {
|
|
26
|
-
if (args[i] === "--title" && args[i + 1]) title = args[i + 1];
|
|
27
|
-
else if (args[i] === "--prompt" && args[i + 1]) prompt = args[i + 1];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!prompt && args[0] && !args[0].startsWith("-")) {
|
|
31
|
-
title = args[0];
|
|
32
|
-
prompt = args[1] || args[0];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!prompt) {
|
|
36
|
-
prompt = "Execute task";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getDynamicGuardrails(promptText = "") {
|
|
40
|
-
const p = (promptText || "").toLowerCase();
|
|
41
|
-
const rules = [];
|
|
42
|
-
if (p.includes("auth") || p.includes("sec") || p.includes("key") || p.includes("token")) {
|
|
43
|
-
rules.push("Sentinel: SECRET REDACTION GUARDRAILS");
|
|
44
|
-
}
|
|
45
|
-
if (p.includes("perf") || p.includes("optimiz") || p.includes("fast")) {
|
|
46
|
-
rules.push("Performance Guidance (Bolt)");
|
|
47
|
-
}
|
|
48
|
-
if (p.includes("clean") || p.includes("refactor") || p.includes("lint")) {
|
|
49
|
-
rules.push("Clean Code Guidance (Janitor)");
|
|
50
|
-
}
|
|
51
|
-
if (p.includes("db") || p.includes("sql") || p.includes("database") || p.includes("schema")) {
|
|
52
|
-
rules.push("Alchemist: DATABASE GUARDRAILS");
|
|
53
|
-
}
|
|
54
|
-
if (p.includes("css") || p.includes("tailwind") || p.includes("theme")) {
|
|
55
|
-
rules.push("CSS & DESIGN GUARDRAILS");
|
|
56
|
-
}
|
|
57
|
-
return rules.join("\n");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function extractImageAttachments(promptText = "", projectRoot = process.cwd()) {
|
|
61
|
-
if (!promptText || typeof promptText !== "string") return [];
|
|
62
|
-
const regex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
63
|
-
const matches = [];
|
|
64
|
-
let match;
|
|
65
|
-
while ((match = regex.exec(promptText)) !== null) {
|
|
66
|
-
const relPath = match[2].trim();
|
|
67
|
-
if (relPath.includes("..")) continue;
|
|
68
|
-
const absPath = join(projectRoot, relPath);
|
|
69
|
-
if (existsSync(absPath)) {
|
|
70
|
-
let mime = "image/png";
|
|
71
|
-
if (relPath.endsWith(".jpg") || relPath.endsWith(".jpeg")) mime = "image/jpeg";
|
|
72
|
-
if (relPath.endsWith(".gif")) mime = "image/gif";
|
|
73
|
-
if (relPath.endsWith(".webp")) mime = "image/webp";
|
|
74
|
-
const size = statSync(absPath).size;
|
|
75
|
-
matches.push({ relPath, absPath, mime, size });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return matches;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function getMultimodalAttachmentDirective(attachments = []) {
|
|
82
|
-
if (!attachments || attachments.length === 0) return "";
|
|
83
|
-
return `### Multimodal Task Attachments (${attachments.length})\n` + attachments.map((a) => `- ${a.relPath}`).join("\n");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function runPreflightStaticCheck(_projectRoot = process.cwd()) {
|
|
87
|
-
return "PASSED";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getAlphaRange(slotIndex = 0, totalSlots = 1) {
|
|
91
|
-
const total = Number(totalSlots);
|
|
92
|
-
const idx = Number(slotIndex);
|
|
93
|
-
if (total <= 1) return "A-Z";
|
|
94
|
-
if (total === 2) {
|
|
95
|
-
return idx === 0 ? "A-M" : "N-Z";
|
|
96
|
-
}
|
|
97
|
-
return `Slot ${idx + 1}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function getSlotPartitionDirective(slotIndex = 0, totalSlots = 1) {
|
|
101
|
-
const total = Number(totalSlots);
|
|
102
|
-
const idx = Number(slotIndex);
|
|
103
|
-
if (total <= 1) return "";
|
|
104
|
-
return `[PARALLEL SWARM SLOT ${idx} of ${total}] Range: ${getAlphaRange(idx, total)} (Partition Focus)`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export async function dispatchTask(opts = {}) {
|
|
108
|
-
const root = process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
109
|
-
const config = loadConfig(root);
|
|
110
|
-
const isDry = opts.dryRun || dryRun;
|
|
111
|
-
const taskTitle = opts.title || title;
|
|
112
|
-
const taskPrompt = opts.prompt || prompt;
|
|
113
|
-
|
|
114
|
-
if (isDry) {
|
|
115
|
-
console.log(`[DRY RUN] Dispatching task: ${taskTitle}`);
|
|
116
|
-
console.log(`[DRY RUN] Dispatch payload prepared successfully`);
|
|
117
|
-
if (repoless || opts.repoless) {
|
|
118
|
-
console.log(`[DRY RUN] Target: (repoless / serverless)`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return dispatch({ title: taskTitle, prompt: taskPrompt }, { config, dryRun: isDry });
|
|
122
|
-
}
|
|
123
23
|
|
|
124
|
-
|
|
125
|
-
dispatchTask()
|
|
24
|
+
dispatchTask(parseDispatchArgs(process.argv.slice(2)))
|
|
126
25
|
.then((session) => {
|
|
127
26
|
console.log(`[Shim] Task dispatched session: ${session.id}`);
|
|
128
27
|
process.exit(0);
|
|
@@ -1,204 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Swarm
|
|
5
|
-
*
|
|
4
|
+
* Swarm merge CLI entrypoint.
|
|
5
|
+
*
|
|
6
|
+
* The merge engine (3-way structural merge, git merge-file isolation,
|
|
7
|
+
* safety gate) lives in src/merge-swarm.mjs (P04 shim cleanup). The
|
|
8
|
+
* re-export below preserves the historical `scripts/jules-merge-swarm.mjs`
|
|
9
|
+
* import surface for external consumers.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
12
|
-
import { classifyRiskTier, RISK_TIERS } from "../src/risk.mjs";
|
|
13
|
-
import { git, resolveBase } from "../src/git.mjs";
|
|
14
|
-
import { normalizePath, loadConfig } from "../src/config.mjs";
|
|
12
|
+
export * from "../src/merge-swarm.mjs";
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
SUCCESS: 0,
|
|
18
|
-
R3_RESTRICTED: 3,
|
|
19
|
-
MERGE_CONFLICT: 4,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
23
|
-
|
|
24
|
-
function isPlainObject(v) {
|
|
25
|
-
return (
|
|
26
|
-
v !== null &&
|
|
27
|
-
typeof v === "object" &&
|
|
28
|
-
!Array.isArray(v) &&
|
|
29
|
-
(Object.getPrototypeOf(v) === null || Object.getPrototypeOf(v) === Object.prototype)
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function deepClone(v) {
|
|
34
|
-
if (v === undefined) return undefined;
|
|
35
|
-
return JSON.parse(JSON.stringify(v));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function stableStringify(v) {
|
|
39
|
-
if (v === null || typeof v !== "object") return JSON.stringify(v);
|
|
40
|
-
if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
|
|
41
|
-
const keys = Object.keys(v).filter((k) => !BLOCKED_KEYS.has(k)).sort();
|
|
42
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(",")}}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function deepEqual(a, b) {
|
|
46
|
-
return stableStringify(a) === stableStringify(b);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Recursive 3-way structural object & array merge.
|
|
51
|
-
*/
|
|
52
|
-
export function deepMerge3Way(base, ours, theirs, path = "$") {
|
|
53
|
-
const conflicts = [];
|
|
54
|
-
if (deepEqual(ours, theirs)) return { merged: deepClone(ours), conflicts };
|
|
55
|
-
if (deepEqual(ours, base)) return { merged: deepClone(theirs), conflicts };
|
|
56
|
-
if (deepEqual(theirs, base)) return { merged: deepClone(ours), conflicts };
|
|
57
|
-
|
|
58
|
-
if (isPlainObject(base) && isPlainObject(ours) && isPlainObject(theirs)) {
|
|
59
|
-
const merged = {};
|
|
60
|
-
const safeBase = base || {};
|
|
61
|
-
const safeOurs = ours || {};
|
|
62
|
-
const safeTheirs = theirs || {};
|
|
63
|
-
const keys = new Set([
|
|
64
|
-
...Object.keys(safeBase),
|
|
65
|
-
...Object.keys(safeOurs),
|
|
66
|
-
...Object.keys(safeTheirs),
|
|
67
|
-
]);
|
|
68
|
-
for (const k of keys) {
|
|
69
|
-
if (BLOCKED_KEYS.has(k)) continue;
|
|
70
|
-
const childPath = `${path}.${k}`;
|
|
71
|
-
const b = safeBase[k], o = safeOurs[k], t = safeTheirs[k];
|
|
72
|
-
if (o === undefined && t === undefined) continue;
|
|
73
|
-
if (o === undefined) { merged[k] = deepClone(t); continue; }
|
|
74
|
-
if (t === undefined) { merged[k] = deepClone(o); continue; }
|
|
75
|
-
const sub = deepMerge3Way(b, o, t, childPath);
|
|
76
|
-
merged[k] = sub.merged;
|
|
77
|
-
conflicts.push(...sub.conflicts);
|
|
78
|
-
}
|
|
79
|
-
return { merged, conflicts };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (Array.isArray(ours) && Array.isArray(theirs)) {
|
|
83
|
-
const baseArr = Array.isArray(base) ? base : [];
|
|
84
|
-
const minLen = Math.min(ours.length, theirs.length);
|
|
85
|
-
const merged = [];
|
|
86
|
-
for (let i = 0; i < minLen; i++) {
|
|
87
|
-
const sub = deepMerge3Way(baseArr[i], ours[i], theirs[i], `${path}[${i}]`);
|
|
88
|
-
merged.push(sub.merged);
|
|
89
|
-
conflicts.push(...sub.conflicts);
|
|
90
|
-
}
|
|
91
|
-
if (ours.length > minLen && theirs.length <= minLen) {
|
|
92
|
-
merged.push(...ours.slice(minLen).map(deepClone));
|
|
93
|
-
} else if (theirs.length > minLen && ours.length <= minLen) {
|
|
94
|
-
merged.push(...theirs.slice(minLen).map(deepClone));
|
|
95
|
-
} else if (ours.length > minLen && theirs.length > minLen) {
|
|
96
|
-
conflicts.push({
|
|
97
|
-
path: `${path}[tail]`,
|
|
98
|
-
base: baseArr.slice(minLen),
|
|
99
|
-
ours: ours.slice(minLen),
|
|
100
|
-
theirs: theirs.slice(minLen),
|
|
101
|
-
});
|
|
102
|
-
merged.push(...ours.slice(minLen).map(deepClone));
|
|
103
|
-
}
|
|
104
|
-
return { merged, conflicts };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
conflicts.push({ path, base: deepClone(base), ours: deepClone(ours), theirs: deepClone(theirs) });
|
|
108
|
-
return { merged: deepClone(ours), conflicts };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Executes a function inside a temporary directory in os.tmpdir() with guaranteed cleanup.
|
|
113
|
-
*/
|
|
114
|
-
export function withTempDir(prefix, fn) {
|
|
115
|
-
const dir = mkdtempSync(join(tmpdir(), prefix || "jules-merge-"));
|
|
116
|
-
try {
|
|
117
|
-
return fn(dir);
|
|
118
|
-
} finally {
|
|
119
|
-
try {
|
|
120
|
-
rmSync(dir, { recursive: true, force: true });
|
|
121
|
-
} catch (_) {}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Attempts a 3-way text file merge using git merge-file inside os.tmpdir().
|
|
127
|
-
*/
|
|
128
|
-
export function attemptCodeMergeFile(repoRoot, relPath, oursContent, baseContent, theirsContent) {
|
|
129
|
-
return withTempDir("jules-merge-", (tmp) => {
|
|
130
|
-
const baseFile = join(tmp, "base");
|
|
131
|
-
const oursFile = join(tmp, "ours");
|
|
132
|
-
const theirsFile = join(tmp, "theirs");
|
|
133
|
-
writeFileSync(baseFile, baseContent ?? "", "utf-8");
|
|
134
|
-
writeFileSync(oursFile, oursContent ?? "", "utf-8");
|
|
135
|
-
writeFileSync(theirsFile, theirsContent ?? "", "utf-8");
|
|
136
|
-
|
|
137
|
-
const res = spawnSync("git", ["merge-file", "-p", "--diff3", oursFile, baseFile, theirsFile], {
|
|
138
|
-
encoding: "utf-8",
|
|
139
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
if (res.error) {
|
|
143
|
-
return { ok: false, conflicts: 0, error: res.error.message };
|
|
144
|
-
}
|
|
145
|
-
const conflictCount = res.status && res.status > 0 ? res.status : 0;
|
|
146
|
-
const merged = res.stdout ?? "";
|
|
147
|
-
return {
|
|
148
|
-
ok: conflictCount === 0,
|
|
149
|
-
merged,
|
|
150
|
-
conflicts: conflictCount,
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Checks safety gate against active worker locks and risk tiers.
|
|
157
|
-
*/
|
|
158
|
-
export function checkSafetyGate(branchName = "", projectRoot = process.cwd(), opts = {}) {
|
|
159
|
-
const locksDir = join(projectRoot, ".agent/state/locks");
|
|
160
|
-
if (existsSync(locksDir)) {
|
|
161
|
-
try {
|
|
162
|
-
const files = readdirSync(locksDir);
|
|
163
|
-
for (const f of files) {
|
|
164
|
-
if (!f.endsWith(".json")) continue;
|
|
165
|
-
const content = readFileSync(join(locksDir, f), "utf-8");
|
|
166
|
-
const parsed = JSON.parse(content);
|
|
167
|
-
if (parsed.branch === branchName) {
|
|
168
|
-
return { safe: false, reason: `Active lock held by worker ${parsed.agent || "unknown"}` };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} catch (_) {}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check R3 risk tier if files modified on target branch
|
|
175
|
-
if (branchName) {
|
|
176
|
-
try {
|
|
177
|
-
const baseBranch = process.env.BASE_BRANCH || "main";
|
|
178
|
-
const resolvedBase = resolveBase(projectRoot, baseBranch);
|
|
179
|
-
const raw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", `${resolvedBase}...${branchName}`], { cwd: projectRoot, raw: true, ignoreError: true }) || "";
|
|
180
|
-
const files = raw.split("\0").map(normalizePath).filter(Boolean);
|
|
181
|
-
if (files.length > 0) {
|
|
182
|
-
// The repository's own risk paths must apply here too, or the gate and
|
|
183
|
-
// the harvester classify the same branch differently.
|
|
184
|
-
let config = opts.config;
|
|
185
|
-
if (!config) {
|
|
186
|
-
try {
|
|
187
|
-
config = loadConfig(projectRoot);
|
|
188
|
-
} catch (_) {
|
|
189
|
-
config = {};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const tier = classifyRiskTier(files, { config });
|
|
193
|
-
if (tier.tier === RISK_TIERS.R3) {
|
|
194
|
-
return { safe: false, reason: `R3 Restricted Path violation: ${tier.reason}` };
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} catch (_) {}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return { safe: true };
|
|
201
|
-
}
|
|
14
|
+
import { EXIT, checkSafetyGate } from "../src/merge-swarm.mjs";
|
|
202
15
|
|
|
203
16
|
if (process.argv[1] && process.argv[1].endsWith("jules-merge-swarm.mjs")) {
|
|
204
17
|
const branchName = process.argv[2] || "";
|
|
@@ -211,4 +24,3 @@ if (process.argv[1] && process.argv[1].endsWith("jules-merge-swarm.mjs")) {
|
|
|
211
24
|
console.log("No open Jules PRs found.");
|
|
212
25
|
process.exit(EXIT.SUCCESS);
|
|
213
26
|
}
|
|
214
|
-
|
package/scripts/jules-patch.mjs
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Session patch fetching
|
|
4
|
+
* Session patch fetching CLI entrypoint.
|
|
5
|
+
*
|
|
6
|
+
* fetchSessionPatch (argument validation around src/session-ops.mjs's
|
|
7
|
+
* extractSessionPatch) moved to session-ops itself (P04 shim cleanup); it is
|
|
8
|
+
* re-exported here for historical importers.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
export { fetchSessionPatch } from "../src/session-ops.mjs";
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
if (!sessionId || typeof sessionId !== "string") {
|
|
11
|
-
throw new Error("Session ID is required.");
|
|
12
|
-
}
|
|
13
|
-
return await extractSessionPatch(sessionId, options);
|
|
14
|
-
}
|
|
13
|
+
import { fetchSessionPatch } from "../src/session-ops.mjs";
|
|
15
14
|
|
|
16
15
|
if (process.argv[1] && process.argv[1].endsWith("jules-patch.mjs")) {
|
|
17
16
|
const sessionId = process.argv[2];
|
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Queue processing runner: processes envelopes in .agent/jules-queue/.
|
|
5
|
+
*
|
|
6
|
+
* Spawned by the webhook receiver as `node scripts/jules-queue-runner.mjs`.
|
|
7
|
+
* Failure classification lives in src/engine.mjs (P04 shim cleanup); the
|
|
8
|
+
* re-export keeps the historical import surface for external consumers.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
export { classifyQueueFailure } from "../src/engine.mjs";
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const msg = String(err?.message || err || "");
|
|
11
|
-
if (msg.includes("FAILED_PRECONDITION") || msg.includes("Active Session Limit") || msg.includes("concurrency")) {
|
|
12
|
-
return "concurrency_limit";
|
|
13
|
-
}
|
|
14
|
-
return "retriable";
|
|
15
|
-
}
|
|
13
|
+
import { run } from "../src/engine.mjs";
|
|
16
14
|
|
|
17
15
|
if (process.argv[1] && process.argv[1].endsWith("jules-queue-runner.mjs")) {
|
|
18
16
|
const root = process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
@@ -1,48 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* TODO/FIXME scanner CLI entrypoint.
|
|
5
|
+
*
|
|
6
|
+
* The scanner itself (extension allowlist, size/binary limits, .gitignore
|
|
7
|
+
* awareness) lives in src/todo-scanner.mjs (P04 shim cleanup). The re-export
|
|
8
|
+
* below preserves the historical `scripts/jules-scan-todos.mjs` import
|
|
9
|
+
* surface for external consumers.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
import { join } from "node:path";
|
|
12
|
+
export * from "../src/todo-scanner.mjs";
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
const todos = [];
|
|
12
|
-
function walk(current) {
|
|
13
|
-
let entries = [];
|
|
14
|
-
try { entries = readdirSync(current); } catch (_) { return; }
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
if (entry === "node_modules" || entry === ".git") continue;
|
|
17
|
-
const full = join(current, entry);
|
|
18
|
-
try {
|
|
19
|
-
const stat = statSync(full);
|
|
20
|
-
if (stat.isDirectory()) {
|
|
21
|
-
walk(full);
|
|
22
|
-
} else if (stat.isFile()) {
|
|
23
|
-
const content = readFileSync(full, "utf-8");
|
|
24
|
-
const lines = content.split("\n");
|
|
25
|
-
lines.forEach((line, idx) => {
|
|
26
|
-
if (line.includes("TODO:")) {
|
|
27
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "TODO", type: "TODO", priority: "MEDIUM" });
|
|
28
|
-
} else if (line.includes("FIXME:")) {
|
|
29
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "FIXME", type: "FIXME", priority: "HIGH" });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
} catch (_) {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
walk(dir);
|
|
37
|
-
return todos;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function runScanner(dir = process.cwd()) {
|
|
41
|
-
const todos = scanCodebaseForTodos(dir);
|
|
42
|
-
return { todos, count: todos.length };
|
|
43
|
-
}
|
|
14
|
+
import { runScanner } from "../src/todo-scanner.mjs";
|
|
44
15
|
|
|
45
16
|
if (process.argv[1] && process.argv[1].endsWith("jules-scan-todos.mjs")) {
|
|
46
|
-
|
|
17
|
+
const { count } = runScanner(process.env.JULES_PROJECT_ROOT || process.cwd());
|
|
18
|
+
console.log(`[Shim] TODO Scanner complete. Found ${count} annotation(s).`);
|
|
47
19
|
process.exit(0);
|
|
48
20
|
}
|