jules-orchestrator-kit 0.2.9 → 0.5.1

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.
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
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>
12
+ */
13
+
14
+ import fs from 'node:fs/promises';
15
+ import path from 'node:path';
16
+
17
+ const MANIFEST_PATH = path.join(process.cwd(), '.agent', 'sync-manifest.json');
18
+ const TTL_MS = 20 * 60 * 1000; // 20 minutes
19
+
20
+ async function readManifest() {
21
+ try {
22
+ const data = await fs.readFile(MANIFEST_PATH, 'utf-8');
23
+ return JSON.parse(data);
24
+ } catch (err) {
25
+ if (err.code === 'ENOENT') {
26
+ return {};
27
+ }
28
+ throw err;
29
+ }
30
+ }
31
+
32
+ async function writeManifest(manifest) {
33
+ await fs.mkdir(path.dirname(MANIFEST_PATH), { recursive: true });
34
+ await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf-8');
35
+ }
36
+
37
+ function cleanExpiredLocks(manifest) {
38
+ const now = Date.now();
39
+ let changed = false;
40
+ for (const [file, lock] of Object.entries(manifest)) {
41
+ if (lock.expiresAt < now) {
42
+ delete manifest[file];
43
+ changed = true;
44
+ }
45
+ }
46
+ return changed;
47
+ }
48
+
49
+ async function acquireLocks(agentName, taskId, files) {
50
+ const manifest = await readManifest();
51
+ cleanExpiredLocks(manifest);
52
+
53
+ const now = Date.now();
54
+ const expiresAt = now + TTL_MS;
55
+
56
+ const conflicts = [];
57
+ for (const file of files) {
58
+ const normalizedFile = path.normalize(file);
59
+ if (manifest[normalizedFile] && manifest[normalizedFile].taskId !== taskId) {
60
+ conflicts.push({
61
+ file: normalizedFile,
62
+ lockedBy: manifest[normalizedFile].agentName,
63
+ task: manifest[normalizedFile].taskId
64
+ });
65
+ }
66
+ }
67
+
68
+ if (conflicts.length > 0) {
69
+ console.error(`ERROR: Cannot acquire locks for task ${taskId}. Conflicts detected:`);
70
+ for (const conflict of conflicts) {
71
+ console.error(` - ${conflict.file} is locked by ${conflict.lockedBy} (Task: ${conflict.task})`);
72
+ }
73
+ process.exit(1);
74
+ }
75
+
76
+ for (const file of files) {
77
+ const normalizedFile = path.normalize(file);
78
+ manifest[normalizedFile] = {
79
+ agentName,
80
+ taskId,
81
+ acquiredAt: now,
82
+ expiresAt
83
+ };
84
+ }
85
+
86
+ await writeManifest(manifest);
87
+ console.log(`SUCCESS: Acquired locks for task ${taskId} on ${files.length} files.`);
88
+ }
89
+
90
+ async function releaseLocks(taskId) {
91
+ const manifest = await readManifest();
92
+ let releasedCount = 0;
93
+
94
+ for (const [file, lock] of Object.entries(manifest)) {
95
+ if (lock.taskId === taskId) {
96
+ delete manifest[file];
97
+ releasedCount++;
98
+ }
99
+ }
100
+
101
+ if (releasedCount > 0) {
102
+ await writeManifest(manifest);
103
+ console.log(`SUCCESS: Released ${releasedCount} locks for task ${taskId}.`);
104
+ } else {
105
+ console.log(`INFO: No locks found for task ${taskId}.`);
106
+ }
107
+ }
108
+
109
+ async function main() {
110
+ const args = process.argv.slice(2);
111
+ const command = args[0];
112
+
113
+ if (command === 'acquire') {
114
+ const agentName = args[1];
115
+ const taskId = args[2];
116
+ const unattendedIndex = args.indexOf('--unattended');
117
+
118
+ let files = [];
119
+ if (unattendedIndex !== -1) {
120
+ files = args.slice(3, unattendedIndex).concat(args.slice(unattendedIndex + 1));
121
+ } else {
122
+ files = args.slice(3);
123
+ }
124
+
125
+ if (!agentName || !taskId || files.length === 0) {
126
+ console.error('Usage: acquire <agent_name> <task_id> <file_paths...>');
127
+ process.exit(1);
128
+ }
129
+
130
+ await acquireLocks(agentName, taskId, files);
131
+ } else if (command === 'release') {
132
+ const taskId = args[1];
133
+ if (!taskId) {
134
+ console.error('Usage: release <task_id>');
135
+ process.exit(1);
136
+ }
137
+ await releaseLocks(taskId);
138
+ } else {
139
+ console.error('Unknown command. Use "acquire" or "release".');
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ main().catch(err => {
145
+ console.error('Unhandled error:', err);
146
+ process.exit(1);
147
+ });
@@ -0,0 +1,94 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+
6
+ // ANSI colors for standardizing CLI DX
7
+ const isCI = !!process.env.CI;
8
+ const noColor = (!process.stdout.isTTY && !isCI) || process.env.NO_COLOR;
9
+ const c = (color, text) => noColor ? text : `\x1b[${color}m${text}\x1b[0m`;
10
+
11
+ export const log = {
12
+ info: (msg) => console.log(c(36, `ℹ️ ${msg}`)),
13
+ success: (msg) => console.log(c(32, `✅ ${msg}`)),
14
+ warn: (msg) => console.warn(c(33, `⚠️ ${msg}`)),
15
+ error: (msg) => {
16
+ if (isCI) console.log(`::error::${msg}`);
17
+ console.error(c(31, `❌ ${msg}`));
18
+ },
19
+ step: (stepStr, msg) => console.log(`${c(90, stepStr)} ${msg}`),
20
+ dim: (msg) => console.log(c(90, msg)),
21
+ header: (msg) => {
22
+ if (isCI) console.log(`::group::${msg}`);
23
+ console.log(`\n${c("1;35", `=== ${msg} ===`)}\n`);
24
+ },
25
+ groupEnd: () => {
26
+ if (isCI) console.log("::endgroup::");
27
+ }
28
+ };
29
+
30
+ export const sleep = promisify(setTimeout);
31
+
32
+ export function ensureDir(dirPath) {
33
+ try {
34
+ if (!fs.existsSync(dirPath)) {
35
+ fs.mkdirSync(dirPath, { recursive: true });
36
+ }
37
+ } catch (error) {
38
+ log.error(`EACCES: Insufficient permissions or failed to create directory: ${dirPath}`);
39
+ log.error(error.message);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ export function logToHistory(filename, content, type = "audit") {
45
+ const dateStr = new Date().toISOString().split("T")[0];
46
+ const historyDir = path.resolve(process.env.JULES_PROJECT_ROOT || process.cwd(), ".agent/history");
47
+ ensureDir(historyDir);
48
+ const filePath = path.join(historyDir, `${dateStr}-${filename}`);
49
+ fs.writeFileSync(filePath, content, "utf-8");
50
+ return filePath;
51
+ }
52
+
53
+ export function resolveMarkdownConflict(content) {
54
+ if (!content || typeof content !== "string") return "";
55
+ if (!content.includes("<<<<<<<")) return content;
56
+
57
+ const lines = content.split("\n");
58
+ const result = [];
59
+ let inConflict = false;
60
+ let headBuffer = [];
61
+ let devBuffer = [];
62
+ let section = null;
63
+
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const line = lines[i];
66
+ if (line.startsWith("<<<<<<<")) {
67
+ inConflict = true;
68
+ section = "head";
69
+ continue;
70
+ }
71
+ if (line.startsWith("=======")) {
72
+ section = "dev";
73
+ continue;
74
+ }
75
+ if (line.startsWith(">>>>>>>")) {
76
+ result.push(...headBuffer);
77
+ result.push(...devBuffer);
78
+ headBuffer = [];
79
+ devBuffer = [];
80
+ inConflict = false;
81
+ section = null;
82
+ continue;
83
+ }
84
+
85
+ if (inConflict) {
86
+ if (section === "head") headBuffer.push(line);
87
+ else if (section === "dev") devBuffer.push(line);
88
+ } else {
89
+ result.push(line);
90
+ }
91
+ }
92
+
93
+ return result.join("\n");
94
+ }
@@ -1,9 +0,0 @@
1
- ---
2
- type: jules_dispatch
3
- title: "Test Swarm Task"
4
- timestamp: "2026-07-27T09:38:12.728Z"
5
- ---
6
- # Jules Task Dispatch: Test Swarm Task
7
-
8
- ## Prompt
9
- Test Prompt Description