archondev 2.2.0 → 2.4.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.
@@ -4815,7 +4815,7 @@ function createPrompt() {
4815
4815
  }
4816
4816
  async function execute(atomId, options) {
4817
4817
  if (options.parallel && options.parallel.length > 0) {
4818
- const { parallelExecute } = await import("./parallel-IC6FLPSK.js");
4818
+ const { parallelExecute } = await import("./parallel-34TSORWU.js");
4819
4819
  const allAtomIds = [atomId, ...options.parallel];
4820
4820
  await parallelExecute(allAtomIds);
4821
4821
  return;
@@ -0,0 +1,673 @@
1
+ import {
2
+ listLocalAtoms,
3
+ loadAtom
4
+ } from "./chunk-5HVYNCLT.js";
5
+
6
+ // src/cli/parallel.ts
7
+ import chalk from "chalk";
8
+ import { spawn } from "child_process";
9
+ import { existsSync as existsSync2 } from "fs";
10
+ import { readFile, writeFile } from "fs/promises";
11
+ import { join as join2 } from "path";
12
+
13
+ // src/core/parallel/worktree.ts
14
+ import { execSync } from "child_process";
15
+ import { existsSync, mkdirSync } from "fs";
16
+ import { join } from "path";
17
+ var WorktreeManager = class {
18
+ cwd;
19
+ worktreesDir;
20
+ constructor(cwd) {
21
+ this.cwd = cwd;
22
+ this.worktreesDir = join(cwd, ".archon", "worktrees");
23
+ }
24
+ async createWorktree(atomId) {
25
+ const branchName = `archon/${atomId}`;
26
+ const worktreePath = join(this.worktreesDir, atomId);
27
+ if (!existsSync(this.worktreesDir)) {
28
+ mkdirSync(this.worktreesDir, { recursive: true });
29
+ }
30
+ execSync(`git worktree add -b ${branchName} "${worktreePath}"`, { cwd: this.cwd });
31
+ return worktreePath;
32
+ }
33
+ async removeWorktree(atomId) {
34
+ const worktreePath = join(this.worktreesDir, atomId);
35
+ execSync(`git worktree remove "${worktreePath}"`, { cwd: this.cwd });
36
+ execSync(`git branch -D archon/${atomId}`, { cwd: this.cwd });
37
+ }
38
+ async listWorktrees() {
39
+ const output = execSync("git worktree list --porcelain", { cwd: this.cwd, encoding: "utf-8" });
40
+ const worktrees = [];
41
+ const blocks = output.split("\n\n").filter(Boolean);
42
+ for (const block of blocks) {
43
+ const lines = block.split("\n");
44
+ let path = "";
45
+ let branch = "";
46
+ for (const line of lines) {
47
+ if (line.startsWith("worktree ")) {
48
+ path = line.substring(9);
49
+ } else if (line.startsWith("branch ")) {
50
+ branch = line.substring(7);
51
+ }
52
+ }
53
+ if (path.includes(".archon/worktrees/") && branch.startsWith("refs/heads/archon/")) {
54
+ const atomId = branch.replace("refs/heads/archon/", "");
55
+ worktrees.push({
56
+ atomId,
57
+ path,
58
+ branch: `archon/${atomId}`,
59
+ status: "pending"
60
+ });
61
+ }
62
+ }
63
+ return worktrees;
64
+ }
65
+ async mergeWorktree(atomId) {
66
+ const branchName = `archon/${atomId}`;
67
+ execSync(`git merge ${branchName}`, { cwd: this.cwd });
68
+ }
69
+ getWorktreePath(atomId) {
70
+ return join(this.worktreesDir, atomId);
71
+ }
72
+ worktreeExists(atomId) {
73
+ return existsSync(join(this.worktreesDir, atomId));
74
+ }
75
+ };
76
+
77
+ // src/core/parallel/scheduler.ts
78
+ var FILE_PATTERNS = {
79
+ setup: ["package.json", "tsconfig.json", ".eslintrc*", "README.md"],
80
+ database: ["prisma/*", "src/db/*", "supabase/*", "drizzle/*"],
81
+ auth: ["src/auth/*", "src/lib/auth*", "src/middleware/*"],
82
+ api: ["src/api/*", "src/routes/*", "src/app/api/*", "pages/api/*"],
83
+ ui: ["src/components/*", "src/app/*", "src/pages/*", "src/views/*"],
84
+ testing: ["src/**/*.test.*", "src/**/*.spec.*", "__tests__/*"]
85
+ };
86
+ var KEYWORD_PATTERNS = {
87
+ user: ["src/db/users*", "src/api/users*", "src/components/user*"],
88
+ payment: ["src/lib/stripe*", "src/api/payments*", "src/api/checkout*"],
89
+ dashboard: ["src/app/dashboard/*", "src/components/dashboard*"],
90
+ admin: ["src/app/admin/*", "src/api/admin/*"]
91
+ };
92
+ function predictFilesTouched(atom) {
93
+ const files = [];
94
+ const titleLower = atom.title.toLowerCase();
95
+ const idLower = atom.id.toLowerCase();
96
+ for (const [pattern, paths] of Object.entries(FILE_PATTERNS)) {
97
+ if (idLower.includes(pattern) || titleLower.includes(pattern)) {
98
+ files.push(...paths);
99
+ }
100
+ }
101
+ for (const [keyword, paths] of Object.entries(KEYWORD_PATTERNS)) {
102
+ if (titleLower.includes(keyword)) {
103
+ files.push(...paths);
104
+ }
105
+ }
106
+ if (files.length === 0) {
107
+ files.push("src/api/*", "src/components/*");
108
+ }
109
+ return [...new Set(files)];
110
+ }
111
+ function patternsOverlap(patternA, patternB) {
112
+ if (patternA === patternB) {
113
+ return true;
114
+ }
115
+ const cleanA = patternA.replace(/\*/g, "");
116
+ const cleanB = patternB.replace(/\*/g, "");
117
+ if (!patternA.includes("*") && !patternB.includes("*")) {
118
+ return patternA === patternB;
119
+ }
120
+ if (patternA.includes("*") && !patternB.includes("*")) {
121
+ const dirA2 = patternA.replace(/\/\*.*$/, "");
122
+ return patternB.startsWith(dirA2 + "/");
123
+ }
124
+ if (patternB.includes("*") && !patternA.includes("*")) {
125
+ const dirB2 = patternB.replace(/\/\*.*$/, "");
126
+ return patternA.startsWith(dirB2 + "/");
127
+ }
128
+ const dirA = patternA.replace(/\/\*.*$/, "");
129
+ const dirB = patternB.replace(/\/\*.*$/, "");
130
+ if (dirA === dirB) {
131
+ return true;
132
+ }
133
+ if (dirA.startsWith(dirB + "/") || dirB.startsWith(dirA + "/")) {
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+ function buildDependencyGraph(atoms, options = {}) {
139
+ const graph = { atoms, edges: [] };
140
+ const predictFn = options.predictFilesTouched ?? predictFilesTouched;
141
+ for (const atom of atoms) {
142
+ if (atom.dependencies) {
143
+ for (const depId of atom.dependencies) {
144
+ if (atoms.some((a) => a.id === depId)) {
145
+ graph.edges.push({
146
+ from: depId,
147
+ to: atom.id,
148
+ type: "explicit" /* EXPLICIT */,
149
+ reason: "Explicit dependency"
150
+ });
151
+ }
152
+ }
153
+ }
154
+ }
155
+ const atomFiles = /* @__PURE__ */ new Map();
156
+ for (const atom of atoms) {
157
+ const files = atom.filesTouched ?? predictFn(atom);
158
+ atomFiles.set(atom.id, files);
159
+ }
160
+ for (let i = 0; i < atoms.length; i++) {
161
+ for (let j = i + 1; j < atoms.length; j++) {
162
+ const atomA = atoms[i];
163
+ const atomB = atoms[j];
164
+ if (!atomA || !atomB) continue;
165
+ const filesA = atomFiles.get(atomA.id) ?? [];
166
+ const filesB = atomFiles.get(atomB.id) ?? [];
167
+ const conflicts = [];
168
+ for (const fileA of filesA) {
169
+ for (const fileB of filesB) {
170
+ if (patternsOverlap(fileA, fileB)) {
171
+ conflicts.push(`${fileA} <-> ${fileB}`);
172
+ }
173
+ }
174
+ }
175
+ if (conflicts.length > 0) {
176
+ const hasExplicitDep = graph.edges.some(
177
+ (e) => e.from === atomA.id && e.to === atomB.id || e.from === atomB.id && e.to === atomA.id
178
+ );
179
+ if (!hasExplicitDep) {
180
+ graph.edges.push({
181
+ from: atomA.id,
182
+ to: atomB.id,
183
+ type: "file_conflict" /* FILE_CONFLICT */,
184
+ reason: `File conflicts: ${conflicts.slice(0, 3).join(", ")}${conflicts.length > 3 ? "..." : ""}`
185
+ });
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return graph;
191
+ }
192
+ function topologicalSort(graph) {
193
+ const inDegree = /* @__PURE__ */ new Map();
194
+ const adjacency = /* @__PURE__ */ new Map();
195
+ for (const atom of graph.atoms) {
196
+ inDegree.set(atom.id, 0);
197
+ adjacency.set(atom.id, []);
198
+ }
199
+ for (const edge of graph.edges) {
200
+ const adj = adjacency.get(edge.from);
201
+ if (adj) {
202
+ adj.push(edge.to);
203
+ }
204
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
205
+ }
206
+ const queue = [];
207
+ for (const [id, degree] of inDegree) {
208
+ if (degree === 0) {
209
+ queue.push(id);
210
+ }
211
+ }
212
+ const sorted = [];
213
+ while (queue.length > 0) {
214
+ const nodeId = queue.shift();
215
+ if (!nodeId) continue;
216
+ const node = graph.atoms.find((a) => a.id === nodeId);
217
+ if (node) {
218
+ sorted.push(node);
219
+ }
220
+ const neighbors = adjacency.get(nodeId) ?? [];
221
+ for (const neighbor of neighbors) {
222
+ const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
223
+ inDegree.set(neighbor, newDegree);
224
+ if (newDegree === 0) {
225
+ queue.push(neighbor);
226
+ }
227
+ }
228
+ }
229
+ if (sorted.length !== graph.atoms.length) {
230
+ const remaining = graph.atoms.filter((a) => !sorted.find((s) => s.id === a.id));
231
+ console.warn(`Cycle detected involving: ${remaining.map((a) => a.id).join(", ")}`);
232
+ sorted.push(...remaining);
233
+ }
234
+ return sorted;
235
+ }
236
+ function findCriticalPath(graph) {
237
+ const distances = /* @__PURE__ */ new Map();
238
+ const predecessors = /* @__PURE__ */ new Map();
239
+ for (const atom of graph.atoms) {
240
+ distances.set(atom.id, 0);
241
+ predecessors.set(atom.id, null);
242
+ }
243
+ const sorted = topologicalSort(graph);
244
+ for (const atom of sorted) {
245
+ const outEdges = graph.edges.filter((e) => e.from === atom.id);
246
+ const currentDist = distances.get(atom.id) ?? 0;
247
+ for (const edge of outEdges) {
248
+ const targetDist = distances.get(edge.to) ?? 0;
249
+ if (currentDist + 1 > targetDist) {
250
+ distances.set(edge.to, currentDist + 1);
251
+ predecessors.set(edge.to, atom.id);
252
+ }
253
+ }
254
+ }
255
+ let maxDist = 0;
256
+ let maxNode = "";
257
+ for (const [id, dist] of distances) {
258
+ if (dist > maxDist) {
259
+ maxDist = dist;
260
+ maxNode = id;
261
+ }
262
+ }
263
+ const path = [];
264
+ let current = maxNode;
265
+ while (current) {
266
+ path.unshift(current);
267
+ current = predecessors.get(current) ?? null;
268
+ }
269
+ return path;
270
+ }
271
+ function scheduleExecution(graph, options = {}) {
272
+ const maxParallelism = options.maxParallelism ?? Infinity;
273
+ const waves = [];
274
+ const completed = /* @__PURE__ */ new Set();
275
+ const sorted = topologicalSort(graph);
276
+ let waveNumber = 1;
277
+ while (completed.size < graph.atoms.length) {
278
+ const waveAtoms = [];
279
+ for (const atom of sorted) {
280
+ if (completed.has(atom.id)) continue;
281
+ if (waveAtoms.length >= maxParallelism) break;
282
+ const deps = graph.edges.filter((e) => e.to === atom.id).map((e) => e.from);
283
+ const ready = deps.every((d) => completed.has(d));
284
+ if (ready) {
285
+ waveAtoms.push(atom);
286
+ }
287
+ }
288
+ if (waveAtoms.length === 0) {
289
+ const remaining = sorted.filter((a) => !completed.has(a.id));
290
+ if (remaining.length > 0 && remaining[0]) {
291
+ waveAtoms.push(remaining[0]);
292
+ } else {
293
+ break;
294
+ }
295
+ }
296
+ for (const atom of waveAtoms) {
297
+ completed.add(atom.id);
298
+ }
299
+ waves.push({
300
+ waveNumber,
301
+ atoms: waveAtoms
302
+ });
303
+ waveNumber++;
304
+ }
305
+ const parallelism = waves.map((w) => w.atoms.length);
306
+ const criticalPath = findCriticalPath(graph);
307
+ const serialTime = graph.atoms.length;
308
+ const parallelTime = waves.length;
309
+ const estimatedSpeedup = serialTime / parallelTime;
310
+ return {
311
+ waves,
312
+ totalAtoms: graph.atoms.length,
313
+ parallelism,
314
+ criticalPath,
315
+ estimatedSpeedup: Math.round(estimatedSpeedup * 100) / 100
316
+ };
317
+ }
318
+ function formatExecutionPlan(plan) {
319
+ const lines = [];
320
+ lines.push(`Execution Plan: ${plan.totalAtoms} atoms in ${plan.waves.length} waves`);
321
+ lines.push(`Estimated speedup: ${plan.estimatedSpeedup}\xD7 (vs serial execution)`);
322
+ lines.push("");
323
+ for (const wave of plan.waves) {
324
+ const atomLabels = wave.atoms.map((a) => `[${a.id}]`).join(" ");
325
+ const prefix = `Wave ${wave.waveNumber}:`.padEnd(10);
326
+ lines.push(`${prefix}${atomLabels}`);
327
+ }
328
+ if (plan.criticalPath.length > 1) {
329
+ lines.push("");
330
+ lines.push(`Critical path: ${plan.criticalPath.join(" \u2192 ")}`);
331
+ }
332
+ return lines.join("\n");
333
+ }
334
+ function formatAsMermaid(graph) {
335
+ const lines = ["graph TD"];
336
+ for (const atom of graph.atoms) {
337
+ const label = atom.title.length > 30 ? atom.title.substring(0, 27) + "..." : atom.title;
338
+ lines.push(` ${atom.id}["${label}"]`);
339
+ }
340
+ for (const edge of graph.edges) {
341
+ const style = edge.type === "explicit" /* EXPLICIT */ ? "-->" : "-.->";
342
+ const label = edge.type === "file_conflict" /* FILE_CONFLICT */ ? "|conflict|" : "";
343
+ lines.push(` ${edge.from} ${style}${label} ${edge.to}`);
344
+ }
345
+ return lines.join("\n");
346
+ }
347
+
348
+ // src/cli/parallel.ts
349
+ var PARALLEL_STATE_FILE = ".archon/parallel-state.json";
350
+ async function loadParallelState(cwd) {
351
+ const statePath = join2(cwd, PARALLEL_STATE_FILE);
352
+ if (!existsSync2(statePath)) {
353
+ return { executions: [] };
354
+ }
355
+ const content = await readFile(statePath, "utf-8");
356
+ return JSON.parse(content);
357
+ }
358
+ async function saveParallelState(cwd, state) {
359
+ const statePath = join2(cwd, PARALLEL_STATE_FILE);
360
+ await writeFile(statePath, JSON.stringify(state, null, 2));
361
+ }
362
+ async function parallelExecute(atomIds) {
363
+ const cwd = process.cwd();
364
+ const manager = new WorktreeManager(cwd);
365
+ console.log(chalk.blue(`
366
+ \u{1F680} Starting parallel execution of ${atomIds.length} atoms...
367
+ `));
368
+ const state = await loadParallelState(cwd);
369
+ for (const atomId of atomIds) {
370
+ const atom = await loadAtom(atomId);
371
+ if (!atom) {
372
+ console.error(chalk.red(`Atom ${atomId} not found. Skipping.`));
373
+ continue;
374
+ }
375
+ if (atom.status !== "READY") {
376
+ console.log(chalk.yellow(`Atom ${atomId} is not READY (status: ${atom.status}). Skipping.`));
377
+ continue;
378
+ }
379
+ try {
380
+ console.log(chalk.dim(`Creating worktree for ${atomId}...`));
381
+ const worktreePath = await manager.createWorktree(atomId);
382
+ state.executions.push({
383
+ atomId,
384
+ worktreePath,
385
+ status: "pending"
386
+ });
387
+ console.log(chalk.green(`\u2713 Created worktree: ${worktreePath}`));
388
+ } catch (error) {
389
+ console.error(chalk.red(`Failed to create worktree for ${atomId}:`));
390
+ console.error(chalk.dim(error instanceof Error ? error.message : "Unknown error"));
391
+ }
392
+ }
393
+ await saveParallelState(cwd, state);
394
+ console.log(chalk.dim("\nStarting parallel execution..."));
395
+ const promises = state.executions.filter((e) => e.status === "pending").map(async (execution) => {
396
+ execution.status = "running";
397
+ execution.startedAt = (/* @__PURE__ */ new Date()).toISOString();
398
+ await saveParallelState(cwd, state);
399
+ return new Promise((resolve) => {
400
+ const child = spawn("npx", ["archon", "execute", execution.atomId, "--skip-gates"], {
401
+ cwd: execution.worktreePath,
402
+ stdio: "pipe"
403
+ });
404
+ let output = "";
405
+ child.stdout?.on("data", (data) => {
406
+ output += data.toString();
407
+ });
408
+ child.stderr?.on("data", (data) => {
409
+ output += data.toString();
410
+ });
411
+ child.on("close", async (code) => {
412
+ execution.completedAt = (/* @__PURE__ */ new Date()).toISOString();
413
+ if (code === 0) {
414
+ execution.status = "completed";
415
+ console.log(chalk.green(`\u2713 ${execution.atomId} completed`));
416
+ } else {
417
+ execution.status = "failed";
418
+ execution.error = output.slice(-500);
419
+ console.log(chalk.red(`\u2717 ${execution.atomId} failed`));
420
+ }
421
+ await saveParallelState(cwd, state);
422
+ resolve();
423
+ });
424
+ });
425
+ });
426
+ await Promise.all(promises);
427
+ const completed = state.executions.filter((e) => e.status === "completed").length;
428
+ const failed = state.executions.filter((e) => e.status === "failed").length;
429
+ console.log(chalk.blue(`
430
+ Parallel execution complete:`));
431
+ console.log(chalk.green(` \u2713 Completed: ${completed}`));
432
+ if (failed > 0) {
433
+ console.log(chalk.red(` \u2717 Failed: ${failed}`));
434
+ }
435
+ console.log(chalk.dim(`
436
+ Run "archon parallel status" to see details.`));
437
+ console.log(chalk.dim(`Run "archon parallel merge" to merge completed worktrees.`));
438
+ }
439
+ async function parallelStatus() {
440
+ const cwd = process.cwd();
441
+ const state = await loadParallelState(cwd);
442
+ if (state.executions.length === 0) {
443
+ console.log(chalk.dim("No parallel executions found."));
444
+ return;
445
+ }
446
+ console.log(chalk.blue("\nParallel Execution Status\n"));
447
+ for (const execution of state.executions) {
448
+ const statusIcon = execution.status === "completed" ? chalk.green("\u2713") : execution.status === "failed" ? chalk.red("\u2717") : execution.status === "running" ? chalk.yellow("\u25CF") : chalk.dim("\u25CB");
449
+ console.log(`${statusIcon} ${execution.atomId}`);
450
+ console.log(chalk.dim(` Path: ${execution.worktreePath}`));
451
+ console.log(chalk.dim(` Status: ${execution.status}`));
452
+ if (execution.startedAt) {
453
+ console.log(chalk.dim(` Started: ${execution.startedAt}`));
454
+ }
455
+ if (execution.completedAt) {
456
+ console.log(chalk.dim(` Completed: ${execution.completedAt}`));
457
+ }
458
+ if (execution.error) {
459
+ console.log(chalk.red(` Error: ${execution.error.slice(0, 100)}...`));
460
+ }
461
+ console.log();
462
+ }
463
+ }
464
+ async function parallelMerge(atomId) {
465
+ const cwd = process.cwd();
466
+ const manager = new WorktreeManager(cwd);
467
+ const state = await loadParallelState(cwd);
468
+ const toMerge = atomId ? state.executions.filter((e) => e.atomId === atomId && e.status === "completed") : state.executions.filter((e) => e.status === "completed");
469
+ if (toMerge.length === 0) {
470
+ console.log(chalk.yellow("No completed executions to merge."));
471
+ return;
472
+ }
473
+ console.log(chalk.blue(`
474
+ \u{1F500} Merging ${toMerge.length} completed worktree(s)...
475
+ `));
476
+ for (const execution of toMerge) {
477
+ try {
478
+ console.log(chalk.dim(`Merging ${execution.atomId}...`));
479
+ await manager.mergeWorktree(execution.atomId);
480
+ console.log(chalk.green(`\u2713 Merged ${execution.atomId}`));
481
+ console.log(chalk.dim(`Cleaning up worktree...`));
482
+ await manager.removeWorktree(execution.atomId);
483
+ console.log(chalk.green(`\u2713 Removed worktree for ${execution.atomId}`));
484
+ state.executions = state.executions.filter((e) => e.atomId !== execution.atomId);
485
+ } catch (error) {
486
+ console.error(chalk.red(`Failed to merge ${execution.atomId}:`));
487
+ console.error(chalk.dim(error instanceof Error ? error.message : "Unknown error"));
488
+ console.log(chalk.yellow("You may need to resolve merge conflicts manually."));
489
+ }
490
+ }
491
+ await saveParallelState(cwd, state);
492
+ console.log(chalk.green("\n\u2705 Merge complete!"));
493
+ }
494
+ async function parallelClean() {
495
+ const cwd = process.cwd();
496
+ const manager = new WorktreeManager(cwd);
497
+ const state = await loadParallelState(cwd);
498
+ console.log(chalk.blue("\n\u{1F9F9} Cleaning up parallel execution state...\n"));
499
+ for (const execution of state.executions) {
500
+ try {
501
+ if (manager.worktreeExists(execution.atomId)) {
502
+ console.log(chalk.dim(`Removing worktree for ${execution.atomId}...`));
503
+ await manager.removeWorktree(execution.atomId);
504
+ console.log(chalk.green(`\u2713 Removed worktree for ${execution.atomId}`));
505
+ }
506
+ } catch (error) {
507
+ console.error(chalk.red(`Failed to remove worktree for ${execution.atomId}:`));
508
+ console.error(chalk.dim(error instanceof Error ? error.message : "Unknown error"));
509
+ }
510
+ }
511
+ await saveParallelState(cwd, { executions: [] });
512
+ console.log(chalk.green("\n\u2705 Cleanup complete!"));
513
+ }
514
+ function toSchedulableAtom(atom) {
515
+ return {
516
+ id: atom.id,
517
+ title: atom.title,
518
+ dependencies: atom.dependencies
519
+ };
520
+ }
521
+ async function parallelSchedule(options) {
522
+ const atoms = await listLocalAtoms();
523
+ if (atoms.length === 0) {
524
+ console.log(chalk.yellow('No atoms found. Run "archon plan" first.'));
525
+ return;
526
+ }
527
+ const targetAtoms = options.onlyReady ? atoms.filter((a) => a.status === "READY") : atoms;
528
+ if (targetAtoms.length === 0) {
529
+ console.log(chalk.yellow('No READY atoms found. Run "archon plan <atom-id>" first.'));
530
+ return;
531
+ }
532
+ console.log(chalk.blue(`
533
+ \u{1F4CA} Analyzing ${targetAtoms.length} atom(s)...
534
+ `));
535
+ const schedulableAtoms = targetAtoms.map(toSchedulableAtom);
536
+ const graph = buildDependencyGraph(schedulableAtoms);
537
+ const plan = scheduleExecution(graph, {
538
+ maxParallelism: options.maxParallelism
539
+ });
540
+ if (options.mermaid) {
541
+ console.log(chalk.dim("Mermaid Diagram:"));
542
+ console.log("```mermaid");
543
+ console.log(formatAsMermaid(graph));
544
+ console.log("```");
545
+ console.log();
546
+ }
547
+ console.log(formatExecutionPlan(plan));
548
+ console.log();
549
+ console.log(chalk.blue("Wave Details:\n"));
550
+ for (const wave of plan.waves) {
551
+ console.log(chalk.bold(`Wave ${wave.waveNumber}:`));
552
+ for (const atom of wave.atoms) {
553
+ const originalAtom = atoms.find((a) => a.id === atom.id);
554
+ const status = originalAtom?.status ?? "UNKNOWN";
555
+ const statusColor = status === "READY" ? chalk.green : status === "COMPLETED" ? chalk.blue : chalk.yellow;
556
+ console.log(` ${statusColor("\u25CF")} ${atom.id}: ${atom.title} ${chalk.dim(`(${status})`)}`);
557
+ }
558
+ console.log();
559
+ }
560
+ const conflictEdges = graph.edges.filter((e) => e.type === "file_conflict");
561
+ if (conflictEdges.length > 0) {
562
+ console.log(chalk.yellow("\u26A0\uFE0F File Conflict Dependencies:\n"));
563
+ for (const edge of conflictEdges) {
564
+ console.log(` ${edge.from} \u2192 ${edge.to}`);
565
+ if (edge.reason) {
566
+ console.log(chalk.dim(` ${edge.reason}`));
567
+ }
568
+ }
569
+ console.log();
570
+ }
571
+ console.log(chalk.dim("To execute atoms in parallel: archon parallel run <atom-ids...>"));
572
+ console.log(chalk.dim("To execute waves sequentially: archon parallel run-waves"));
573
+ }
574
+ async function parallelRunWaves(options) {
575
+ const atoms = await listLocalAtoms();
576
+ const readyAtoms = atoms.filter((a) => a.status === "READY");
577
+ if (readyAtoms.length === 0) {
578
+ console.log(chalk.yellow("No READY atoms found."));
579
+ return;
580
+ }
581
+ const schedulableAtoms = readyAtoms.map(toSchedulableAtom);
582
+ const graph = buildDependencyGraph(schedulableAtoms);
583
+ const plan = scheduleExecution(graph, { maxParallelism: options.maxParallelism });
584
+ console.log(chalk.blue(`
585
+ \u{1F680} Executing ${plan.totalAtoms} atoms in ${plan.waves.length} waves
586
+ `));
587
+ console.log(chalk.dim(`Estimated speedup: ${plan.estimatedSpeedup}\xD7 vs serial execution
588
+ `));
589
+ if (options.dryRun) {
590
+ console.log(chalk.yellow("DRY RUN - no atoms will be executed\n"));
591
+ console.log(formatExecutionPlan(plan));
592
+ return;
593
+ }
594
+ const cwd = process.cwd();
595
+ const manager = new WorktreeManager(cwd);
596
+ for (const wave of plan.waves) {
597
+ console.log(chalk.blue(`
598
+ \u2550\u2550\u2550 Wave ${wave.waveNumber} (${wave.atoms.length} atoms) \u2550\u2550\u2550
599
+ `));
600
+ const worktrees = [];
601
+ for (const atom of wave.atoms) {
602
+ try {
603
+ const worktreePath = await manager.createWorktree(atom.id);
604
+ worktrees.push({ atomId: atom.id, path: worktreePath });
605
+ console.log(chalk.dim(` Created worktree for ${atom.id}`));
606
+ } catch (error) {
607
+ console.error(chalk.red(` Failed to create worktree for ${atom.id}:`));
608
+ console.error(chalk.dim(error instanceof Error ? error.message : "Unknown error"));
609
+ }
610
+ }
611
+ const results = await Promise.all(
612
+ worktrees.map(({ atomId, path }) => executeAtomInWorktree(atomId, path))
613
+ );
614
+ const succeeded = results.filter((r) => r.success).length;
615
+ const failed = results.filter((r) => !r.success).length;
616
+ console.log();
617
+ console.log(chalk.green(` \u2713 ${succeeded} succeeded`));
618
+ if (failed > 0) {
619
+ console.log(chalk.red(` \u2717 ${failed} failed`));
620
+ }
621
+ for (const result of results) {
622
+ if (result.success) {
623
+ try {
624
+ await manager.mergeWorktree(result.atomId);
625
+ await manager.removeWorktree(result.atomId);
626
+ console.log(chalk.dim(` Merged and cleaned ${result.atomId}`));
627
+ } catch (error) {
628
+ console.error(chalk.yellow(` Failed to merge ${result.atomId} - manual merge may be required`));
629
+ }
630
+ }
631
+ }
632
+ if (failed > 0) {
633
+ console.log(chalk.red("\n\u26A0\uFE0F Wave had failures. Stopping execution."));
634
+ console.log(chalk.dim("Fix the issues and run again to continue."));
635
+ return;
636
+ }
637
+ }
638
+ console.log(chalk.green("\n\u2705 All waves completed successfully!"));
639
+ }
640
+ async function executeAtomInWorktree(atomId, worktreePath) {
641
+ return new Promise((resolve) => {
642
+ const child = spawn("npx", ["archon", "execute", atomId, "--skip-gates"], {
643
+ cwd: worktreePath,
644
+ stdio: "pipe"
645
+ });
646
+ let output = "";
647
+ child.stdout?.on("data", (data) => {
648
+ output += data.toString();
649
+ });
650
+ child.stderr?.on("data", (data) => {
651
+ output += data.toString();
652
+ });
653
+ child.on("close", (code) => {
654
+ if (code === 0) {
655
+ resolve({ atomId, success: true });
656
+ } else {
657
+ resolve({ atomId, success: false, error: output.slice(-500) });
658
+ }
659
+ });
660
+ child.on("error", (error) => {
661
+ resolve({ atomId, success: false, error: error.message });
662
+ });
663
+ });
664
+ }
665
+
666
+ export {
667
+ parallelExecute,
668
+ parallelStatus,
669
+ parallelMerge,
670
+ parallelClean,
671
+ parallelSchedule,
672
+ parallelRunWaves
673
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  execute
3
- } from "./chunk-HGO4UUAC.js";
3
+ } from "./chunk-6TCM6JYX.js";
4
4
  import "./chunk-M4LGRTLC.js";
5
5
  import "./chunk-5HVYNCLT.js";
6
6
  import "./chunk-5IQKC2TD.js";