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.
- package/.agent/jules-queue/README.md +4 -4
- package/.agent/prompts/Bolt.md +18 -0
- package/.agent/prompts/Overseer.md +18 -0
- package/.agent/prompts/Sentinel.md +18 -0
- package/.agent/prompts/Task_Template.md +26 -0
- package/JULES_RULES_TEMPLATE.md +6 -1
- package/README.md +54 -31
- package/bin/agentctl.mjs +281 -0
- package/bin/mcp-server.mjs +5 -0
- package/index.mjs +32 -5
- package/package.json +13 -2
- package/scripts/asset-integrity-check.mjs +35 -0
- package/scripts/command-resolver.mjs +37 -214
- package/scripts/jules-cleanup.mjs +23 -164
- package/scripts/jules-create.mjs +12 -56
- package/scripts/jules-dispatch.mjs +103 -449
- package/scripts/jules-merge-swarm.mjs +19 -173
- package/scripts/jules-nightly.mjs +7 -128
- package/scripts/jules-patch.mjs +15 -103
- package/scripts/jules-queue-runner.mjs +28 -221
- package/scripts/jules-scan-todos.mjs +31 -125
- package/scripts/jules-self-audit.mjs +69 -656
- package/scripts/jules-status.mjs +42 -70
- package/scripts/jules-swarm.mjs +23 -327
- package/scripts/jules-webhook-receiver.mjs +70 -0
- package/scripts/lock-manager.mjs +11 -177
- package/scripts/risk-tier.mjs +29 -0
- package/scripts/rules-lint.mjs +20 -0
- package/scripts/stale-base-check.mjs +26 -0
- package/scripts/utils.mjs +107 -369
- package/scripts/validate-envelope.mjs +44 -0
- package/src/asset_integrity.mjs +76 -0
- package/src/config.mjs +325 -0
- package/src/engine.mjs +217 -0
- package/src/envelope.mjs +105 -0
- package/src/git.mjs +127 -0
- package/src/mcp.mjs +211 -0
- package/src/provider.mjs +179 -0
- package/src/risk.mjs +120 -0
- package/src/rules_budget.mjs +77 -0
- package/src/security.mjs +212 -0
- package/src/state.mjs +217 -0
- package/src/webhook.mjs +138 -0
package/scripts/lock-manager.mjs
CHANGED
|
@@ -1,188 +1,22 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* lock-manager.mjs
|
|
5
|
-
*
|
|
6
|
-
* Manages multi-agent file locks to prevent concurrent modification collisions.
|
|
7
|
-
* Uses .agent/sync-manifest.json as the lock registry.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* node scripts/lock-manager.mjs acquire <agent_name> <task_id> <file_path1> <file_path2> [--unattended]
|
|
11
|
-
* node scripts/lock-manager.mjs release <task_id>
|
|
2
|
+
* Backward compatibility shim for lock-manager.mjs in v0.9.0.
|
|
3
|
+
* Delegates to src/state.mjs.
|
|
12
4
|
*/
|
|
13
5
|
|
|
14
|
-
import
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
|
|
18
|
-
const MANIFEST_PATH = path.join(process.cwd(), '.agent', 'file-locks.json');
|
|
19
|
-
const TTL_MS = 20 * 60 * 1000; // 20 minutes
|
|
20
|
-
const isMainModule = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
6
|
+
import { acquireLock, releaseLock, lockStatus } from "../src/state.mjs";
|
|
21
7
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const data = await fs.readFile(MANIFEST_PATH, 'utf-8');
|
|
25
|
-
return JSON.parse(data);
|
|
26
|
-
} catch (err) {
|
|
27
|
-
if (err.code === 'ENOENT') {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
8
|
+
export function acquire(agent, taskId, filePaths = [], opts = {}) {
|
|
9
|
+
return acquireLock(agent, taskId, filePaths, opts);
|
|
32
10
|
}
|
|
33
11
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const tmp = MANIFEST_PATH + `.tmp-${process.pid}`;
|
|
37
|
-
await fs.writeFile(tmp, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
38
|
-
await fs.rename(tmp, MANIFEST_PATH);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function withManifestLock(fn) {
|
|
42
|
-
const lockPath = MANIFEST_PATH + ".lock";
|
|
43
|
-
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
44
|
-
for (let i = 0; i < 50; i++) {
|
|
45
|
-
try {
|
|
46
|
-
const stats = await fs.stat(lockPath);
|
|
47
|
-
if (Date.now() - stats.mtimeMs > 5 * 60 * 1000) {
|
|
48
|
-
await fs.rm(lockPath, { force: true });
|
|
49
|
-
}
|
|
50
|
-
} catch (_) {}
|
|
51
|
-
|
|
52
|
-
let handle;
|
|
53
|
-
try {
|
|
54
|
-
handle = await fs.open(lockPath, "wx");
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err.code !== "EEXIST") throw err;
|
|
57
|
-
await new Promise(r => setTimeout(r, 50 + Math.random() * 50));
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
try { return await fn(); }
|
|
61
|
-
finally {
|
|
62
|
-
try { await handle.close(); } catch (_) {}
|
|
63
|
-
await fs.rm(lockPath, { force: true });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
throw new Error("Could not acquire manifest lock");
|
|
12
|
+
export function release(agent, taskId, opts = {}) {
|
|
13
|
+
return releaseLock(agent, taskId, opts);
|
|
67
14
|
}
|
|
68
15
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
let changed = false;
|
|
72
|
-
for (const [file, lock] of Object.entries(manifest)) {
|
|
73
|
-
if (lock.expiresAt < now) {
|
|
74
|
-
delete manifest[file];
|
|
75
|
-
changed = true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return changed;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function acquireLocks(agentName, taskId, files, isUnattended = false) {
|
|
82
|
-
await withManifestLock(async () => {
|
|
83
|
-
if (isUnattended) {
|
|
84
|
-
console.log(`[lock-manager] Operating in unattended mode for task ${taskId}`);
|
|
85
|
-
}
|
|
86
|
-
const manifest = await readManifest();
|
|
87
|
-
cleanExpiredLocks(manifest);
|
|
88
|
-
|
|
89
|
-
const now = Date.now();
|
|
90
|
-
const expiresAt = now + TTL_MS;
|
|
91
|
-
|
|
92
|
-
const conflicts = [];
|
|
93
|
-
for (const file of files) {
|
|
94
|
-
const normalizedFile = path.normalize(file);
|
|
95
|
-
if (manifest[normalizedFile] && manifest[normalizedFile].taskId !== taskId) {
|
|
96
|
-
conflicts.push({
|
|
97
|
-
file: normalizedFile,
|
|
98
|
-
lockedBy: manifest[normalizedFile].agentName,
|
|
99
|
-
task: manifest[normalizedFile].taskId
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (conflicts.length > 0) {
|
|
105
|
-
let msg = `Cannot acquire locks for task ${taskId}. Conflicts detected:\n`;
|
|
106
|
-
for (const conflict of conflicts) {
|
|
107
|
-
msg += ` - ${conflict.file} is locked by ${conflict.lockedBy} (Task: ${conflict.task})\n`;
|
|
108
|
-
}
|
|
109
|
-
throw new Error(msg.trim());
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const file of files) {
|
|
113
|
-
const normalizedFile = path.normalize(file);
|
|
114
|
-
manifest[normalizedFile] = {
|
|
115
|
-
agentName,
|
|
116
|
-
taskId,
|
|
117
|
-
acquiredAt: now,
|
|
118
|
-
expiresAt
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await writeManifest(manifest);
|
|
123
|
-
console.log(`SUCCESS: Acquired locks for task ${taskId} on ${files.length} files.`);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function releaseLocks(taskId) {
|
|
128
|
-
await withManifestLock(async () => {
|
|
129
|
-
const manifest = await readManifest();
|
|
130
|
-
let releasedCount = 0;
|
|
131
|
-
|
|
132
|
-
for (const [file, lock] of Object.entries(manifest)) {
|
|
133
|
-
if (lock.taskId === taskId) {
|
|
134
|
-
delete manifest[file];
|
|
135
|
-
releasedCount++;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (releasedCount > 0) {
|
|
140
|
-
await writeManifest(manifest);
|
|
141
|
-
console.log(`SUCCESS: Released ${releasedCount} locks for task ${taskId}.`);
|
|
142
|
-
} else {
|
|
143
|
-
console.log(`INFO: No locks found for task ${taskId}.`);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function main() {
|
|
149
|
-
const args = process.argv.slice(2);
|
|
150
|
-
const command = args[0];
|
|
151
|
-
|
|
152
|
-
if (command === 'acquire') {
|
|
153
|
-
const agentName = args[1];
|
|
154
|
-
const taskId = args[2];
|
|
155
|
-
const unattendedIndex = args.indexOf('--unattended');
|
|
156
|
-
const isUnattended = unattendedIndex !== -1;
|
|
157
|
-
let files = [];
|
|
158
|
-
if (isUnattended) {
|
|
159
|
-
files = args.slice(3, unattendedIndex).concat(args.slice(unattendedIndex + 1));
|
|
160
|
-
} else {
|
|
161
|
-
files = args.slice(3);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!agentName || !taskId || files.length === 0) {
|
|
165
|
-
console.error('Usage: acquire <agent_name> <task_id> <file_paths...> [--unattended]');
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
await acquireLocks(agentName, taskId, files, isUnattended);
|
|
170
|
-
} else if (command === 'release') {
|
|
171
|
-
const taskId = args[1];
|
|
172
|
-
if (!taskId) {
|
|
173
|
-
console.error('Usage: release <task_id>');
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
await releaseLocks(taskId);
|
|
177
|
-
} else {
|
|
178
|
-
console.error('Unknown command. Use "acquire" or "release".');
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
16
|
+
export function status(opts = {}) {
|
|
17
|
+
return lockStatus(opts);
|
|
181
18
|
}
|
|
182
19
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.error('Unhandled error:', err);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
});
|
|
20
|
+
export function cleanup() {
|
|
21
|
+
return { removed: 0 };
|
|
188
22
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { changedFiles } from "../src/git.mjs";
|
|
4
|
+
import { classifyRiskTier } from "../src/risk.mjs";
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const baseBranch = process.env.BASE_BRANCH || "main";
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const files = changedFiles(root, baseBranch);
|
|
11
|
+
const result = classifyRiskTier(files);
|
|
12
|
+
|
|
13
|
+
console.log(`[risk-tier] Classified PR Risk Tier: ${result.tier}`);
|
|
14
|
+
console.log(`[risk-tier] Reason: ${result.reason}`);
|
|
15
|
+
console.log(`[risk-tier] Auto-Merge Allowed: ${result.isAutoMergeAllowed}`);
|
|
16
|
+
console.log(`[risk-tier] Requires Human Review: ${result.requiresHumanReview}`);
|
|
17
|
+
|
|
18
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
19
|
+
const { appendFileSync } = await import("node:fs");
|
|
20
|
+
appendFileSync(process.env.GITHUB_OUTPUT, `risk_tier=${result.tier}\n`);
|
|
21
|
+
appendFileSync(process.env.GITHUB_OUTPUT, `auto_merge=${result.isAutoMergeAllowed}\n`);
|
|
22
|
+
appendFileSync(process.env.GITHUB_OUTPUT, `requires_human=${result.requiresHumanReview}\n`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(`❌ Risk tier classification failed: ${err.message}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { checkRulesBudget } from "../src/rules_budget.mjs";
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
const res = checkRulesBudget(root);
|
|
7
|
+
|
|
8
|
+
console.log("[rules-lint] Auditing character and line budgets for agent rule files...");
|
|
9
|
+
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
console.error("❌ RULES BUDGET VIOLATIONS DETECTED:");
|
|
12
|
+
for (const v of res.violations) {
|
|
13
|
+
console.error(` - ${v.path}: ${v.reason}`);
|
|
14
|
+
}
|
|
15
|
+
console.error("\nRemediation: Trim prose rules or convert textual learnings into AST lints / Vitest assertions.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log("✅ All agent rule files are within safe character (<10,000) and line (<250) budget limits.");
|
|
20
|
+
process.exit(0);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { git, resolveBase } from "../src/git.mjs";
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
const maxBehind = parseInt(process.env.STALE_BASE_MAX_COMMITS || "25", 10);
|
|
7
|
+
const baseBranch = process.env.BASE_BRANCH || "main";
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const resolvedBase = resolveBase(root, baseBranch);
|
|
11
|
+
const behindStr = git(["rev-list", "--count", `${resolvedBase}..HEAD`], { cwd: root, ignoreError: true });
|
|
12
|
+
const behindCount = parseInt(behindStr || "0", 10);
|
|
13
|
+
|
|
14
|
+
console.log(`[stale-base-check] Branch HEAD is ${behindCount} commits behind ${resolvedBase} (max allowed: ${maxBehind}).`);
|
|
15
|
+
|
|
16
|
+
if (!isNaN(behindCount) && behindCount > maxBehind) {
|
|
17
|
+
console.error(`❌ STALE BASE GATE FAIL: PR branch is ${behindCount} commits behind ${resolvedBase}. Rebase onto latest ${baseBranch} before merging.`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log("✅ Stale-base predicate check passed.");
|
|
22
|
+
process.exit(0);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.warn(`⚠️ Stale-base check skipped (git base ref not available or shallow clone): ${err.message}`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|