multi-agents-cli 1.0.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -0
  3. package/init.js +1098 -0
  4. package/package.json +32 -0
package/init.js ADDED
@@ -0,0 +1,1098 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * multi-agents - Project Initializer
5
+ * Run with: npm run init (inside existing project)
6
+ * or: multi-agents init my-project (global CLI)
7
+ *
8
+ * Runs once. Locked after completion via .scaffold/.initialized
9
+ */
10
+
11
+ const readline = require('readline');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const { execSync, spawn } = require('child_process');
16
+
17
+ // ── Colors ────────────────────────────────────────────────────────────────────
18
+
19
+ const c = {
20
+ reset: '\x1b[0m',
21
+ bold: '\x1b[1m',
22
+ dim: '\x1b[2m',
23
+ green: '\x1b[32m',
24
+ blue: '\x1b[34m',
25
+ yellow: '\x1b[33m',
26
+ cyan: '\x1b[36m',
27
+ red: '\x1b[31m',
28
+ };
29
+
30
+ const bold = (s) => `${c.bold}${s}${c.reset}`;
31
+ const green = (s) => `${c.green}${s}${c.reset}`;
32
+ const yellow = (s) => `${c.yellow}${s}${c.reset}`;
33
+ const dim = (s) => `${c.dim}${s}${c.reset}`;
34
+ const cyan = (s) => `${c.cyan}${s}${c.reset}`;
35
+ const blue = (s) => `${c.blue}${s}${c.reset}`;
36
+ const red = (s) => `${c.red}${s}${c.reset}`;
37
+
38
+ // ── CLI argument handling ─────────────────────────────────────────────────────
39
+
40
+ const args = process.argv.slice(2);
41
+ const isGlobalCLI = args[0] === 'init' && args[1];
42
+ const projectArg = isGlobalCLI ? args[1] : null;
43
+
44
+ if (isGlobalCLI) {
45
+ const targetDir = path.resolve(process.cwd(), projectArg);
46
+
47
+ if (fs.existsSync(targetDir)) {
48
+ console.log(`\n${red(` ✗ Directory "${projectArg}" already exists.`)}`);
49
+ console.log(dim(' Choose a different project name.\n'));
50
+ process.exit(1);
51
+ }
52
+
53
+ fs.mkdirSync(targetDir, { recursive: true });
54
+ process.chdir(targetDir);
55
+
56
+ // Initialize git
57
+ try {
58
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
59
+ execSync('git commit --allow-empty -m "init: project created"', { cwd: targetDir, stdio: 'pipe' });
60
+ } catch { /* git may not have user config yet — continue */ }
61
+ }
62
+
63
+ // ── Lock check ────────────────────────────────────────────────────────────────
64
+
65
+ const ROOT = process.cwd();
66
+ const RUNTIME_DIR = path.join(ROOT, '.scaffold');
67
+ const LOCK_FILE = path.join(RUNTIME_DIR, '.initialized');
68
+
69
+ // ── Decision tree ─────────────────────────────────────────────────────────────
70
+
71
+ const CLIENT_FRAMEWORKS = [
72
+ { label: 'Next.js', value: 'Next.js', language: 'TypeScript', integratedBackend: true },
73
+ { label: 'Angular', value: 'Angular', language: 'TypeScript', integratedBackend: false },
74
+ { label: 'Vue / Nuxt', value: 'Nuxt', language: 'TypeScript', integratedBackend: true },
75
+ { label: 'SvelteKit', value: 'SvelteKit', language: 'TypeScript', integratedBackend: true },
76
+ { label: 'Remix', value: 'Remix', language: 'TypeScript', integratedBackend: true },
77
+ { label: 'Vite + React', value: 'Vite+React', language: 'TypeScript', integratedBackend: false },
78
+ ];
79
+
80
+ const BACKEND_FRAMEWORKS = [
81
+ { label: 'NestJS', value: 'NestJS', language: 'TypeScript' },
82
+ { label: 'Express', value: 'Express', language: 'TypeScript' },
83
+ { label: 'Fastify', value: 'Fastify', language: 'TypeScript' },
84
+ { label: 'Django', value: 'Django', language: 'Python' },
85
+ { label: 'Laravel', value: 'Laravel', language: 'PHP' },
86
+ { label: 'Rails', value: 'Rails', language: 'Ruby' },
87
+ ];
88
+
89
+ const STATE_OPTIONS = {
90
+ 'Next.js': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
91
+ 'Vite+React': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
92
+ 'Remix': ['Zustand', 'Redux Toolkit', 'Jotai', 'TanStack Query'],
93
+ 'Angular': ['NgRx', 'Signals (built-in)', 'Akita'],
94
+ 'Nuxt': ['Pinia', 'Vuex'],
95
+ 'SvelteKit': ['Svelte stores (built-in)', 'Zustand'],
96
+ };
97
+
98
+ const UI_OPTIONS = {
99
+ 'Next.js': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
100
+ 'Vite+React': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
101
+ 'Remix': ['shadcn/ui', 'Radix UI', 'MUI', 'Chakra UI', 'Ant Design'],
102
+ 'Angular': ['Angular Material', 'PrimeNG', 'Clarity'],
103
+ 'Nuxt': ['Vuetify', 'PrimeVue', 'Naive UI'],
104
+ 'SvelteKit': ['Skeleton', 'daisyUI', 'shadcn-svelte'],
105
+ };
106
+
107
+ const STYLING_OPTIONS = [
108
+ 'Tailwind CSS',
109
+ 'CSS Modules',
110
+ 'Styled Components',
111
+ 'SCSS / SASS',
112
+ 'UnoCSS',
113
+ ];
114
+
115
+ const ORM_OPTIONS = {
116
+ 'NestJS': ['TypeORM', 'Prisma', 'MikroORM', 'Drizzle'],
117
+ 'Express': ['Prisma', 'TypeORM', 'Drizzle', 'Sequelize'],
118
+ 'Fastify': ['Prisma', 'TypeORM', 'Drizzle'],
119
+ 'Django': ['Django ORM (built-in)', 'SQLAlchemy'],
120
+ 'Laravel': ['Eloquent (built-in)'],
121
+ 'Rails': ['Active Record (built-in)'],
122
+ };
123
+
124
+ const AUTH_OPTIONS = {
125
+ 'NestJS': ['Passport.js', 'JWT-only', 'OAuth2', 'Auth.js'],
126
+ 'Express': ['Passport.js', 'JWT-only', 'OAuth2'],
127
+ 'Fastify': ['fastify-jwt', 'Passport.js', 'OAuth2'],
128
+ 'Django': ['Django Auth (built-in)', 'DRF TokenAuth', 'OAuth2'],
129
+ 'Laravel': ['Laravel Sanctum', 'Laravel Passport', 'JWT'],
130
+ 'Rails': ['Devise', 'JWT', 'OAuth2'],
131
+ };
132
+
133
+ const IDE_CANDIDATES = [
134
+ {
135
+ cmd: 'code',
136
+ name: 'VS Code',
137
+ mac: { app: 'Visual Studio Code', args: ['--new-window'] },
138
+ win: { paths: ['{LOCALAPPDATA}\\Programs\\Microsoft VS Code\\Code.exe', '{ProgramFiles}\\Microsoft VS Code\\Code.exe'], args: ['--new-window'] },
139
+ linux: { paths: ['/snap/bin/code', '/usr/bin/code', '/usr/local/bin/code'], args: ['--new-window'] },
140
+ },
141
+ {
142
+ cmd: 'cursor',
143
+ name: 'Cursor',
144
+ mac: { app: 'Cursor', args: ['--new-window'] },
145
+ win: { paths: ['{LOCALAPPDATA}\\Programs\\cursor\\Cursor.exe'], args: ['--new-window'] },
146
+ linux: { paths: ['/usr/bin/cursor', '/opt/cursor/cursor'], args: ['--new-window'] },
147
+ },
148
+ {
149
+ cmd: 'webstorm',
150
+ name: 'WebStorm',
151
+ note: 'requires CLI launcher via Toolbox',
152
+ mac: { app: 'WebStorm', args: [] },
153
+ win: { paths: [], args: [] },
154
+ linux: { paths: ['/opt/webstorm/bin/webstorm.sh'], args: [] },
155
+ },
156
+ {
157
+ cmd: 'idea',
158
+ name: 'IntelliJ IDEA',
159
+ note: 'requires CLI launcher via Toolbox',
160
+ mac: { app: 'IntelliJ IDEA', args: [] },
161
+ win: { paths: [], args: [] },
162
+ linux: { paths: ['/opt/idea/bin/idea.sh'], args: [] },
163
+ },
164
+ {
165
+ cmd: 'zed',
166
+ name: 'Zed',
167
+ mac: { app: 'Zed', args: [] },
168
+ win: { paths: [], args: [] },
169
+ linux: { paths: ['/usr/bin/zed', `${os.homedir()}/.local/bin/zed`], args: [] },
170
+ },
171
+ {
172
+ cmd: null,
173
+ name: 'Other / Manual',
174
+ note: 'prints worktree path, open it yourself',
175
+ mac: null,
176
+ win: null,
177
+ linux:null,
178
+ },
179
+ ];
180
+
181
+ // Expands {LOCALAPPDATA} / {ProgramFiles} placeholders for Windows paths
182
+ const expandWinPath = (p) =>
183
+ p.replace('{LOCALAPPDATA}', process.env.LOCALAPPDATA || '')
184
+ .replace('{ProgramFiles}', process.env.ProgramFiles || 'C:\\Program Files');
185
+
186
+ const buildIDEOptions = () => {
187
+ const platform = process.platform;
188
+
189
+ return IDE_CANDIDATES.map(ide => {
190
+ if (!ide.cmd) {
191
+ const noteStr = ide.note ? dim(` (${ide.note})`) : '';
192
+ return { ...ide, detected: false, strategy: 'manual', label: `${ide.name} ${dim('→')}${noteStr}` };
193
+ }
194
+
195
+ let detected = false;
196
+ let strategy = 'cli';
197
+
198
+ if (platform === 'darwin' && ide.mac) {
199
+ // Mac — check .app bundle in /Applications or ~/Applications
200
+ const system = `/Applications/${ide.mac.app}.app`;
201
+ const user = path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`);
202
+ detected = fs.existsSync(system) || fs.existsSync(user);
203
+ if (detected) strategy = 'mac-app';
204
+
205
+ } else if (platform === 'win32' && ide.win) {
206
+ // Windows — CLI first, then known exe paths
207
+ try {
208
+ execSync(`where ${ide.cmd}`, { stdio: 'pipe' });
209
+ detected = true;
210
+ strategy = 'cli';
211
+ } catch {
212
+ const expanded = (ide.win.paths || []).map(expandWinPath);
213
+ detected = expanded.some(p => fs.existsSync(p));
214
+ if (detected) strategy = 'win-exe';
215
+ }
216
+
217
+ } else if (platform === 'linux' && ide.linux) {
218
+ // Linux — CLI first, then known install paths
219
+ try {
220
+ execSync(`which ${ide.cmd}`, { stdio: 'pipe' });
221
+ detected = true;
222
+ strategy = 'cli';
223
+ } catch {
224
+ detected = (ide.linux.paths || []).some(p => fs.existsSync(p));
225
+ if (detected) strategy = 'linux-path';
226
+ }
227
+ }
228
+
229
+ const statusStr = detected ? green('✓ detected') : dim('✗ not found');
230
+ const noteStr = ide.note ? dim(` (${ide.note})`) : '';
231
+ return {
232
+ ...ide,
233
+ detected,
234
+ strategy,
235
+ label: `${ide.name} ${statusStr}${noteStr}`,
236
+ };
237
+ });
238
+ };
239
+
240
+ const verifyIDE = (ide) => {
241
+ const platform = process.platform;
242
+
243
+ if (ide.strategy === 'mac-app' && ide.mac) {
244
+ // Mac — confirm .app exists and try to read version from plist
245
+ const appPath = `/Applications/${ide.mac.app}.app`;
246
+ if (!fs.existsSync(appPath) && !fs.existsSync(path.join(os.homedir(), 'Applications', `${ide.mac.app}.app`))) {
247
+ return { ok: false };
248
+ }
249
+ try {
250
+ const version = execSync(
251
+ `defaults read "/Applications/${ide.mac.app}.app/Contents/Info.plist" CFBundleShortVersionString`,
252
+ { stdio: 'pipe', encoding: 'utf8' }
253
+ ).trim();
254
+ return { ok: true, version };
255
+ } catch {
256
+ return { ok: true, version: null };
257
+ }
258
+ }
259
+
260
+ // Windows exe / Linux path / CLI — try --version
261
+ try {
262
+ const cmd = ide.strategy === 'win-exe'
263
+ ? `"${(ide.win?.paths || []).map(expandWinPath).find(p => fs.existsSync(p))}"`
264
+ : ide.strategy === 'linux-path'
265
+ ? `"${(ide.linux?.paths || []).find(p => fs.existsSync(p))}"`
266
+ : `"${ide.cmd}"`;
267
+ const result = execSync(`${cmd} --version`, { stdio: 'pipe', encoding: 'utf8' });
268
+ const version = result.split('\n')[0].trim();
269
+ return { ok: true, version };
270
+ } catch {
271
+ return { ok: false };
272
+ }
273
+ };
274
+
275
+ // ── Tracking structure ────────────────────────────────────────────────────────
276
+
277
+ const emptySlot = () => ({
278
+ branch: null,
279
+ timestamp: null,
280
+ launchedAt: null,
281
+ status: null,
282
+ missingCount: 0,
283
+ worktreePath: null,
284
+ });
285
+
286
+ const generateTrackingStructure = (config) => {
287
+ const bt = config.backend?.type;
288
+
289
+ const structure = {
290
+ client: {
291
+ UI: emptySlot(),
292
+ LOGIC: emptySlot(),
293
+ FORMS: emptySlot(),
294
+ ROUTING: emptySlot(),
295
+ TESTING: emptySlot(),
296
+ ACCESSIBILITY: emptySlot(),
297
+ },
298
+ shared: {
299
+ SECURITY: emptySlot(),
300
+ },
301
+ };
302
+
303
+ if (bt === 'separate') {
304
+ structure.backend = {
305
+ API: emptySlot(),
306
+ LOGIC: emptySlot(),
307
+ AUTH: emptySlot(),
308
+ DB: emptySlot(),
309
+ EVENTS: emptySlot(),
310
+ JOBS: emptySlot(),
311
+ TESTING: emptySlot(),
312
+ };
313
+ }
314
+
315
+ return structure;
316
+ };
317
+
318
+ // ── GitHub remote setup ───────────────────────────────────────────────────────
319
+
320
+ const detectGitHubUser = () => {
321
+ try {
322
+ return execSync('gh api user --jq .login',
323
+ { encoding: 'utf8', stdio: 'pipe' }).trim();
324
+ } catch {}
325
+ try {
326
+ return execSync('git config user.name',
327
+ { encoding: 'utf8', stdio: 'pipe' }).trim();
328
+ } catch {}
329
+ return null;
330
+ };
331
+
332
+ const setupUserRemote = (ROOT, projectName) => {
333
+ let currentOrigin = null;
334
+ try {
335
+ currentOrigin = execSync('git remote get-url origin',
336
+ { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim();
337
+ } catch {}
338
+
339
+ // Already has their own remote — nothing to do
340
+ if (currentOrigin && !currentOrigin.includes('multi-agents-template')) return;
341
+
342
+ // Demote template origin to upstream
343
+ if (currentOrigin?.includes('multi-agents-template')) {
344
+ try {
345
+ execSync('git remote remove origin', { cwd: ROOT, stdio: 'pipe' });
346
+ execSync(`git remote add upstream ${currentOrigin}`, { cwd: ROOT, stdio: 'pipe' });
347
+ console.log(dim(' ℹ Template remote moved to upstream'));
348
+ } catch {}
349
+ }
350
+
351
+ // Write flag — agent will handle remote setup on first session
352
+ const flagPath = path.join(ROOT, '.scaffold', '.remote-setup-needed');
353
+ fs.writeFileSync(flagPath, JSON.stringify({
354
+ projectName,
355
+ createdAt: new Date().toISOString(),
356
+ }), 'utf8');
357
+
358
+ console.log(`\n ${yellow('ℹ No remote configured.')} Your first agent session will set this up.`);
359
+ console.log(dim(' All work stays local until then.\n'));
360
+ };
361
+
362
+ const renderTrajectoryLines = (lines) => {
363
+ const HEADERS = ['Benefits', 'Best for', 'Use agents for', 'Handle manually'];
364
+ lines.forEach(l => {
365
+ if (!l) { console.log(''); return; }
366
+ if (l.startsWith('⚠')) console.log(` ${yellow(l)}`);
367
+ else if (HEADERS.includes(l)) console.log(`\n ${bold(l)}`);
368
+ else if (l.startsWith('·')) console.log(` ${l}`);
369
+ else console.log(` ${dim(l)}`);
370
+ });
371
+ };
372
+
373
+ const rl = readline.createInterface({
374
+ input: process.stdin,
375
+ output: process.stdout,
376
+ });
377
+
378
+ const ask = (question) =>
379
+ new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
380
+
381
+ // ── Selection helpers ─────────────────────────────────────────────────────────
382
+
383
+ const showList = (items, showSkip = false) => {
384
+ items.forEach((item, i) => {
385
+ const label = typeof item === 'string' ? item : item.label;
386
+ console.log(` ${dim(`${i + 1}.`)} ${label}`);
387
+ });
388
+ if (showSkip) console.log(` ${dim('0.')} Skip ${dim('(agent will propose when needed)')}`);
389
+ };
390
+
391
+ const selectRequired = async (prompt, items) => {
392
+ while (true) {
393
+ console.log(`\n${bold(prompt)}`);
394
+ showList(items);
395
+ const input = await ask(`\n ${bold('Select')} ${dim(`(1-${items.length})`)}: `);
396
+ const index = parseInt(input) - 1;
397
+ if (!isNaN(index) && index >= 0 && index < items.length) return items[index];
398
+ console.log(yellow(` Please enter a number between 1 and ${items.length}.`));
399
+ }
400
+ };
401
+
402
+ const selectOptional = async (prompt, items) => {
403
+ if (!items || items.length === 0) return null;
404
+ while (true) {
405
+ console.log(`\n${bold(prompt)}`);
406
+ showList(items, true);
407
+ const input = await ask(`\n ${bold('Select')} ${dim(`(0-${items.length})`)}: `);
408
+ if (input === '0' || input === '') return null;
409
+ const index = parseInt(input) - 1;
410
+ if (!isNaN(index) && index >= 0 && index < items.length) {
411
+ return typeof items[index] === 'string' ? items[index] : items[index].value;
412
+ }
413
+ console.log(yellow(` Invalid selection. Please enter a number between 0 and ${items.length}.`));
414
+ }
415
+ };
416
+
417
+ const separator = () => console.log(`\n${dim('─'.repeat(60))}`);
418
+
419
+ // ── Config writer ─────────────────────────────────────────────────────────────
420
+
421
+ const writeConfig = (filePath, configs) => {
422
+ if (!fs.existsSync(filePath)) return;
423
+ let content = fs.readFileSync(filePath, 'utf8');
424
+
425
+ for (const [key, value] of Object.entries(configs)) {
426
+ if (!value) continue;
427
+ const regex = new RegExp(`(# @config ${key}\\s*:)([^\\n]*)`, 'g');
428
+ content = content.replace(regex, `$1 ${value}`);
429
+ }
430
+
431
+ for (const [key, value] of Object.entries(configs)) {
432
+ if (!value) continue;
433
+ const token = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
434
+ content = content.replace(token, value);
435
+ }
436
+
437
+ fs.writeFileSync(filePath, content, 'utf8');
438
+ };
439
+
440
+ const ensureGitignore = (entry) => {
441
+ const p = path.join(ROOT, '.gitignore');
442
+ const content = fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
443
+ if (!content.includes(entry)) fs.appendFileSync(p, `\n${entry}\n`);
444
+ };
445
+
446
+ const summaryLine = (label, value) => {
447
+ const padded = label.padEnd(20);
448
+ if (!value) {
449
+ console.log(` ${dim(padded)}: ${yellow('(skipped - agent will propose when needed)')}`);
450
+ } else {
451
+ console.log(` ${dim(padded)}: ${green(value)}`);
452
+ }
453
+ };
454
+
455
+ // ── Copy directory ────────────────────────────────────────────────────────────
456
+
457
+ const copyDir = (src, dest) => {
458
+ if (!fs.existsSync(src)) return;
459
+ fs.mkdirSync(dest, { recursive: true });
460
+ fs.readdirSync(src).forEach(file => {
461
+ const srcFile = path.join(src, file);
462
+ const destFile = path.join(dest, file);
463
+ if (fs.statSync(srcFile).isDirectory()) {
464
+ copyDir(srcFile, destFile);
465
+ } else {
466
+ fs.copyFileSync(srcFile, destFile);
467
+ }
468
+ });
469
+ };
470
+
471
+ // ── Main ──────────────────────────────────────────────────────────────────────
472
+
473
+ const main = async () => {
474
+
475
+ // ── Lock check ───────────────────────────────────────────────────────────────
476
+
477
+ if (fs.existsSync(LOCK_FILE)) {
478
+ const ts = fs.readFileSync(LOCK_FILE, 'utf8').trim();
479
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
480
+ const ask2 = (q) => new Promise((resolve) => rl2.question(q, (a) => resolve(a.trim())));
481
+
482
+ console.log(`\n${yellow(' This project has already been initialized.')}`);
483
+ console.log(dim(` Initialized on: ${ts}\n`));
484
+ console.log(` ${dim('1.')} Continue — run ${cyan('npm run launch')}`);
485
+ console.log(` ${dim('2.')} Reset — delete config and re-run initialization`);
486
+ console.log(` ${dim('3.')} Exit\n`);
487
+
488
+ const choice = await ask2(` ${bold('Select')} ${dim('(1-3)')}: `);
489
+
490
+ if (choice === '1') {
491
+ console.log('');
492
+ rl2.close();
493
+ const child = spawn('node', [path.join(ROOT, '.workflow', 'launch.js')], {
494
+ stdio: 'inherit',
495
+ cwd: ROOT,
496
+ });
497
+ child.on('exit', (code) => process.exit(code));
498
+ return;
499
+ } else if (choice === '2') {
500
+ console.log(yellow('\n Resetting configuration...\n'));
501
+ fs.unlinkSync(LOCK_FILE);
502
+ const configPath = path.join(RUNTIME_DIR, '.config.json');
503
+ if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
504
+ rl2.close();
505
+ console.log(green(' Reset complete. Re-running initialization...\n'));
506
+ // Fall through to run init again
507
+ } else {
508
+ console.log(dim('\n Exited.\n'));
509
+ rl2.close();
510
+ return;
511
+ }
512
+ }
513
+
514
+ console.log('\n');
515
+ console.log(bold(cyan(' Multi-Agent Monorepo Template')));
516
+ console.log(dim(' Project Initializer\n'));
517
+ separator();
518
+
519
+ console.log(`\n${bold('Let\'s configure your project.')}`);
520
+ console.log(dim(' Required fields must be selected. Optional fields can be skipped (press 0 or Enter).\n'));
521
+ console.log(dim(' Skipped fields will be resolved by the agent when first needed.\n'));
522
+
523
+ // ── Project name ────────────────────────────────────────────────────────────
524
+
525
+ let projectName = '';
526
+ while (!projectName) {
527
+ projectName = await ask(`${bold('* Project name')}: `);
528
+ if (!projectName) console.log(yellow(' Project name is required. Please enter a name.'));
529
+ }
530
+
531
+ separator();
532
+
533
+ // ── Client ──────────────────────────────────────────────────────────────────
534
+
535
+ console.log(`\n${bold(blue('Client configuration'))}`);
536
+
537
+ const clientFw = await selectRequired('* Client framework (required):', CLIENT_FRAMEWORKS);
538
+ const clientLang = clientFw.language;
539
+ const clientState = await selectOptional('State management:', STATE_OPTIONS[clientFw.value] || []);
540
+ const clientUi = await selectOptional('UI library:', UI_OPTIONS[clientFw.value] || []);
541
+ const clientStyle = await selectOptional('Styling:', STYLING_OPTIONS);
542
+
543
+ separator();
544
+
545
+ // ── Backend ─────────────────────────────────────────────────────────────────
546
+
547
+ console.log(`\n${bold(blue('Backend configuration'))}`);
548
+
549
+ // Check if client framework has integrated backend support
550
+ let useIntegratedBackend = false;
551
+ let backendFw = null;
552
+ let backendLang = null;
553
+ let backendOrm = null;
554
+ let backendAuth = null;
555
+ let backendType = null;
556
+
557
+ if (clientFw.integratedBackend) {
558
+ console.log(dim(` ${clientFw.value} supports server-side rendering and API routes.\n`));
559
+ const integratedAnswer = await ask(` ${bold('Use integrated backend')} ${dim(`(${clientFw.value} API routes/SSR)`)} ${dim('instead of a separate backend? (y/n)')}: `);
560
+ useIntegratedBackend = integratedAnswer.toLowerCase() === 'y';
561
+
562
+ if (useIntegratedBackend) {
563
+ backendType = 'integrated';
564
+ console.log(dim(`\n Using ${clientFw.value} integrated backend. No separate backend needed.\n`));
565
+ }
566
+ }
567
+
568
+ if (!useIntegratedBackend) {
569
+ console.log(dim(' You can skip the backend framework and decide later.\n'));
570
+
571
+ let backendFwObj = undefined;
572
+ while (backendFwObj === undefined) {
573
+ console.log(`\n${bold('Backend framework:')}`);
574
+ showList(BACKEND_FRAMEWORKS, true);
575
+ const input = await ask(`\n ${bold('Select')} ${dim(`(0-${BACKEND_FRAMEWORKS.length})`)}: `);
576
+ if (input === '0' || input === '') {
577
+ backendFwObj = null;
578
+ } else {
579
+ const index = parseInt(input) - 1;
580
+ if (isNaN(index) || index < 0 || index >= BACKEND_FRAMEWORKS.length) {
581
+ console.log(yellow(` Please enter a number between 0 and ${BACKEND_FRAMEWORKS.length}.`));
582
+ } else {
583
+ backendFwObj = BACKEND_FRAMEWORKS[index];
584
+ }
585
+ }
586
+ }
587
+
588
+ backendFw = backendFwObj ? backendFwObj.value : null;
589
+ backendLang = backendFwObj ? backendFwObj.language : null;
590
+ backendOrm = backendFw ? await selectOptional('ORM / database layer:', ORM_OPTIONS[backendFw] || []) : null;
591
+ backendAuth = backendFw ? await selectOptional('Auth strategy:', AUTH_OPTIONS[backendFw] || []) : null;
592
+ backendType = backendFw ? 'separate' : null;
593
+ }
594
+
595
+ separator();
596
+
597
+ // ── Environment ─────────────────────────────────────────────────────────────
598
+
599
+ console.log(`\n${bold(blue('Environment'))}`);
600
+
601
+ const osName = { darwin: 'macOS', win32: 'Windows', linux: 'Linux' }[process.platform] || process.platform;
602
+ console.log(`\n ${dim('OS detected:')} ${bold(osName)}`);
603
+ console.log(dim(' Scanning for installed IDEs...\n'));
604
+
605
+ const ideOptions = buildIDEOptions();
606
+
607
+ const detectedIDEs = ideOptions.filter(o => o.detected);
608
+ const undetectedIDEs = ideOptions.filter(o => !o.detected && o.cmd);
609
+ const manualOption = ideOptions.filter(o => !o.cmd);
610
+
611
+ // Detected first → undetected → manual
612
+ const sortedIdeOptions = [...detectedIDEs, ...undetectedIDEs, ...manualOption];
613
+
614
+ if (detectedIDEs.length > 1) {
615
+ console.log(`\n ${yellow('Multiple IDEs found on this machine')} — select your preference:\n`);
616
+ } else if (detectedIDEs.length === 1) {
617
+ console.log(`\n ${green(`1 IDE found:`)} ${bold(detectedIDEs[0].name)}\n`);
618
+ } else {
619
+ console.log(`\n ${yellow('No IDEs detected on this machine.')}\n`);
620
+ }
621
+
622
+ let ideChoice;
623
+ while (true) {
624
+ ideChoice = await selectRequired('* IDE / editor (required):', sortedIdeOptions);
625
+
626
+ // ── Confirmation ──────────────────────────────────────────────────────────
627
+ console.log(`\n Selected: ${bold(ideChoice.name)}`);
628
+ if (ideChoice.cmd && !ideChoice.detected) {
629
+ console.log(yellow(` ⚠ ${ideChoice.name} was not detected on PATH. It may not open automatically.`));
630
+ }
631
+ const confirmIde = await ask(` ${bold('Confirm this selection?')} ${dim('(y/n)')}: `);
632
+ if (confirmIde.toLowerCase() !== 'y') {
633
+ console.log(dim(' Re-selecting...\n'));
634
+ continue;
635
+ }
636
+
637
+ // ── Double-check ──────────────────────────────────────────────────────────
638
+ if (!ideChoice.cmd) {
639
+ // Manual — no verification needed
640
+ console.log(dim(' Manual mode — worktree path will be printed at launch.'));
641
+ break;
642
+ }
643
+
644
+ console.log(dim(`\n Verifying ${ideChoice.name}...`));
645
+ const verified = verifyIDE(ideChoice);
646
+
647
+ if (verified.ok) {
648
+ const versionStr = verified.version ? dim(` (${verified.version})`) : '';
649
+ console.log(` ${green('✓')} ${ideChoice.name} confirmed${versionStr}`);
650
+ break;
651
+ }
652
+
653
+ console.log(` ${yellow('!')} Could not verify ${ideChoice.name}. The CLI may not be installed or accessible.`);
654
+ const proceedAnyway = await ask(` ${bold('Continue with this IDE anyway?')} ${dim('(y/n)')}: `);
655
+ if (proceedAnyway.toLowerCase() === 'y') break;
656
+ console.log(dim(' Re-selecting...\n'));
657
+ }
658
+
659
+ separator();
660
+
661
+ // ── Summary ─────────────────────────────────────────────────────────────────
662
+
663
+ console.log(`\n${bold('Review your configuration:')}\n`);
664
+ summaryLine('Project', projectName);
665
+ summaryLine('Client framework', clientFw.value);
666
+ summaryLine('Client language', clientLang);
667
+ summaryLine('State management', clientState);
668
+ summaryLine('UI library', clientUi);
669
+ summaryLine('Styling', clientStyle);
670
+ summaryLine('Backend type', backendType === 'integrated' ? `${clientFw.value} integrated` : backendFw || '(skipped)');
671
+ if (backendType !== 'integrated') {
672
+ summaryLine('Backend language', backendLang);
673
+ summaryLine('ORM', backendOrm);
674
+ summaryLine('Auth', backendAuth);
675
+ }
676
+ summaryLine('IDE / Editor', ideChoice.name);
677
+
678
+ console.log('');
679
+ console.log(dim(' y = confirm | n = abort | e = edit (start over)\n'));
680
+ const confirm = await ask(`${bold('Confirm and write to config files?')} ${dim('(y/n/e)')}: `);
681
+
682
+ if (confirm.toLowerCase() === 'e') {
683
+ console.log(yellow('\n Restarting configuration...\n'));
684
+ rl.close();
685
+ const { spawn } = require('child_process');
686
+ const child = spawn('node', [__filename], { stdio: 'inherit', cwd: ROOT });
687
+ child.on('exit', (code) => process.exit(code));
688
+ return;
689
+ }
690
+
691
+ if (confirm.toLowerCase() !== 'y') {
692
+ console.log(yellow('\n Aborted. No files were changed.\n'));
693
+ rl.close();
694
+ return;
695
+ }
696
+
697
+ // ── Write configs ────────────────────────────────────────────────────────────
698
+
699
+ separator();
700
+ console.log(`\n${bold('Setting up your project...')}\n`);
701
+
702
+ // ── Clone multi-agents-core ──────────────────────────────────────────────────
703
+
704
+ const CORE_REPO = 'https://github.com/JDev-il/multi-agents-core.git';
705
+ const CORE_DIR = path.join(ROOT, '.agents-core');
706
+
707
+ console.log(` Fetching templates...`);
708
+ try {
709
+ execSync(`git clone "${CORE_REPO}" "${CORE_DIR}"`, { stdio: 'pipe' });
710
+ console.log(` ${green('✓')} Templates fetched`);
711
+ } catch (err) {
712
+ console.log(` ${red('✗')} Failed to fetch templates. Check your internet connection.`);
713
+ rl.close();
714
+ process.exit(1);
715
+ }
716
+
717
+ const TEMPLATES = path.join(CORE_DIR, 'templates');
718
+
719
+ copyDir(path.join(TEMPLATES, 'client'), path.join(ROOT, 'client'));
720
+ copyDir(path.join(TEMPLATES, 'shared'), path.join(ROOT, 'shared'));
721
+ if (backendType === 'separate') {
722
+ copyDir(path.join(TEMPLATES, 'backend'), path.join(ROOT, 'backend'));
723
+ }
724
+ fs.copyFileSync(path.join(TEMPLATES, 'CLAUDE.md'), path.join(ROOT, 'CLAUDE.md'));
725
+ fs.copyFileSync(path.join(TEMPLATES, 'CONTRACTS.md'), path.join(ROOT, 'CONTRACTS.md'));
726
+ console.log(` ${green('✓')} Templates copied`);
727
+
728
+ // ── Copy workflow scripts ────────────────────────────────────────────────────
729
+
730
+ const WORKFLOW_SRC = path.join(CORE_DIR, 'workflow');
731
+ const WORKFLOW_DEST = path.join(ROOT, '.workflow');
732
+ fs.mkdirSync(WORKFLOW_DEST, { recursive: true });
733
+ copyDir(WORKFLOW_SRC, WORKFLOW_DEST);
734
+ console.log(` ${green('✓')} Workflow scripts copied (.workflow/)`);
735
+
736
+ execSync(`rm -rf "${CORE_DIR}"`);
737
+ console.log(` ${green('✓')} Temporary files cleaned up`);
738
+
739
+ // ── Write @config values ─────────────────────────────────────────────────────
740
+
741
+ writeConfig(path.join(ROOT, 'CLAUDE.md'), {
742
+ PROJECT_NAME: projectName,
743
+ PROJECT_ROOT: projectName,
744
+ });
745
+ console.log(` ${green('✓')} CLAUDE.md configured`);
746
+
747
+ writeConfig(path.join(ROOT, 'client', 'CLAUDE.md'), {
748
+ PROJECT_NAME: projectName,
749
+ FRAMEWORK: clientFw.value,
750
+ LANGUAGE: clientLang,
751
+ STATE: clientState,
752
+ UI_LIBRARY: clientUi,
753
+ STYLING: clientStyle,
754
+ });
755
+ console.log(` ${green('✓')} client/CLAUDE.md configured`);
756
+
757
+ if (backendType === 'separate') {
758
+ writeConfig(path.join(ROOT, 'backend', 'CLAUDE.md'), {
759
+ PROJECT_NAME: projectName,
760
+ FRAMEWORK: backendFw,
761
+ LANGUAGE: backendLang,
762
+ ORM: backendOrm,
763
+ AUTH: backendAuth,
764
+ });
765
+ console.log(` ${green('✓')} backend/CLAUDE.md configured`);
766
+ }
767
+
768
+ ensureGitignore('worktrees/');
769
+ ensureGitignore('.agents-core/');
770
+ ensureGitignore('.scaffold/');
771
+ ensureGitignore('.workflow/');
772
+
773
+ // Remove template-specific gitignore entries so generated files can be committed
774
+ const gitignorePath = path.join(ROOT, '.gitignore');
775
+ let gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
776
+ ['client/', 'backend/', 'shared/', 'CLAUDE.md', 'CONTRACTS.md', 'BUILD_STATE.md'].forEach(entry => {
777
+ gitignoreContent = gitignoreContent.replace(`\n${entry}`, '');
778
+ gitignoreContent = gitignoreContent.replace(`${entry}\n`, '');
779
+ gitignoreContent = gitignoreContent.replace(entry, '');
780
+ });
781
+ fs.writeFileSync(gitignorePath, gitignoreContent.trim() + '\n', 'utf8');
782
+ console.log(` ${green('✓')} .gitignore updated`);
783
+
784
+ // ── Write .config.json ───────────────────────────────────────────────────────
785
+
786
+ const config = {
787
+ projectName,
788
+ ide: {
789
+ name: ideChoice.name,
790
+ strategy: ideChoice.strategy,
791
+ cmd: ideChoice.cmd || null,
792
+ app: ideChoice.mac?.app || null,
793
+ openArgs: process.platform === 'darwin' ? (ideChoice.mac?.args || [])
794
+ : process.platform === 'win32' ? (ideChoice.win?.args || [])
795
+ : (ideChoice.linux?.args || []),
796
+ winPaths: (ideChoice.win?.paths || []).map(expandWinPath),
797
+ linuxPaths: ideChoice.linux?.paths || [],
798
+ },
799
+ client: {
800
+ framework: clientFw.value,
801
+ language: clientLang,
802
+ state: clientState,
803
+ uiLibrary: clientUi,
804
+ styling: clientStyle,
805
+ },
806
+ backend: {
807
+ type: backendType,
808
+ framework: backendFw,
809
+ language: backendLang,
810
+ orm: backendOrm,
811
+ auth: backendAuth,
812
+ },
813
+ };
814
+
815
+ fs.writeFileSync(
816
+ path.join(RUNTIME_DIR, '.config.json'),
817
+ JSON.stringify(config, null, 2),
818
+ 'utf8'
819
+ );
820
+ console.log(` ${green('✓')} .scaffold/.config.json written`);
821
+
822
+ // ── Generate BUILD_STATE.md ──────────────────────────────────────────────────
823
+
824
+ const backendDisplay = backendType === 'integrated'
825
+ ? `${clientFw.value} integrated (API routes/SSR)`
826
+ : backendFw || 'Not configured';
827
+
828
+ const clientStack = [clientFw.value, clientLang, clientStyle, clientUi, clientState]
829
+ .filter(Boolean).join(' + ');
830
+
831
+ const backendStack = backendType === 'separate'
832
+ ? [backendFw, backendLang, backendOrm, backendAuth].filter(Boolean).join(' + ')
833
+ : backendDisplay;
834
+
835
+ const buildState = `# BUILD_STATE.md
836
+ # Living project state. Read before every task. Update after completion.
837
+ # Every agent must read this file at session start.
838
+
839
+ ## Project
840
+ Name : ${projectName}
841
+ Initialized : ${new Date().toISOString()}
842
+
843
+ ## Stack
844
+ Client : ${clientStack}
845
+ Backend : ${backendStack}
846
+
847
+ ## Client State
848
+ - [ ] Scaffold - framework initialized
849
+ - [ ] UI - components and layout
850
+ - [ ] LOGIC - state management and API client
851
+ - [ ] FORMS - form architecture
852
+ - [ ] ROUTING - route definitions
853
+ - [ ] TESTING - test suite
854
+ - [ ] ACCESSIBILITY - a11y compliance
855
+
856
+ ## Backend State
857
+ ${backendType === 'integrated'
858
+ ? `Type: ${clientFw.value} integrated backend (API routes / SSR)
859
+ - [ ] API routes - server-side endpoints
860
+ - [ ] Auth - authentication strategy
861
+ - [ ] DB - data layer if needed`
862
+ : backendType === 'separate'
863
+ ? `Type: Separate backend (${backendFw})
864
+ - [ ] Scaffold - framework initialized
865
+ - [ ] DB - schema and entities
866
+ - [ ] API - endpoints and DTOs
867
+ - [ ] AUTH - authentication strategy
868
+ - [ ] LOGIC - business rules
869
+ - [ ] EVENTS - webhooks and queues
870
+ - [ ] JOBS - background tasks`
871
+ : 'Not configured - run node .workflow/launch.js and select backend when ready'}
872
+
873
+ ## Shared
874
+ - [ ] CONTRACTS.md - no shared types defined yet
875
+
876
+ ## Dependency Rules
877
+ Before starting any task, verify:
878
+ - Client LOGIC requires: Client scaffold done
879
+ - Client FORMS requires: Client scaffold done
880
+ - Client ROUTING requires: Client scaffold done
881
+ - API calls in client require: Backend API endpoints done OR mocked
882
+ - Backend API requires: DB schema done (if using DB)
883
+ - Backend AUTH requires: DB User entity done
884
+ - Any cross-boundary types: Must exist in CONTRACTS.md first
885
+
886
+ If a dependency is not met:
887
+ DEPENDENCY NOT MET - surface what is missing and propose options.
888
+ Never proceed silently on a missing dependency.
889
+
890
+ ## Agent Log
891
+ | Date | Agent | Scope | Task | Status | Branch |
892
+ |------|-------|-------|------|--------|--------|
893
+ `;
894
+
895
+ fs.writeFileSync(path.join(ROOT, 'BUILD_STATE.md'), buildState, 'utf8');
896
+ console.log(` ${green('✓')} BUILD_STATE.md generated`);
897
+
898
+ // ── Tracking ──────────────────────────────────────────────────────────────────
899
+
900
+ const trackingPath = path.join(RUNTIME_DIR, '.tracking.json');
901
+ if (!fs.existsSync(trackingPath)) {
902
+ const trackingStructure = generateTrackingStructure(config);
903
+ fs.writeFileSync(trackingPath, JSON.stringify(trackingStructure, null, 2), 'utf8');
904
+ console.log(` ${green('✓')} .tracking.json generated`);
905
+ } else {
906
+ console.log(dim(' ℹ .tracking.json already exists — preserved'));
907
+ }
908
+
909
+ // ── Lock ─────────────────────────────────────────────────────────────────────
910
+
911
+ fs.writeFileSync(LOCK_FILE, new Date().toISOString());
912
+ console.log(` ${green('✓')} Initialization locked`);
913
+
914
+ // ── Auto-commit ───────────────────────────────────────────────────────────────
915
+
916
+ try {
917
+ execSync('git add .', { cwd: ROOT, stdio: 'pipe' });
918
+ execSync('git commit -m "init: project configuration"', { cwd: ROOT, stdio: 'pipe' });
919
+ console.log(` ${green('✓')} Project configuration committed`);
920
+ } catch (err) {
921
+ console.log(` ${yellow('!')} Could not auto-commit. Run manually:`);
922
+ console.log(dim(' git add . && git commit -m "init: project configuration"'));
923
+ }
924
+
925
+ // ── Pre-commit hook — block direct commits to main ───────────────────────────
926
+
927
+ try {
928
+ const hooksDir = path.join(ROOT, '.git', 'hooks');
929
+ const hookPath = path.join(hooksDir, 'pre-commit');
930
+ const hookScript = `#!/bin/sh
931
+ branch=$(git symbolic-ref --short HEAD 2>/dev/null)
932
+ if [ "$branch" = "main" ]; then
933
+ echo ""
934
+ echo " ⚠ Direct commits to main are not allowed."
935
+ echo " Use npm run launch to start a task."
936
+ echo ""
937
+ exit 1
938
+ fi
939
+ `;
940
+ if (!fs.existsSync(hookPath)) {
941
+ fs.writeFileSync(hookPath, hookScript, { mode: 0o755 });
942
+ console.log(dim(' ℹ Pre-commit hook installed — direct main commits blocked'));
943
+ }
944
+ } catch { /* best-effort */ }
945
+
946
+ // ── Remote setup ─────────────────────────────────────────────────────────────
947
+
948
+ setupUserRemote(ROOT, projectName);
949
+
950
+ // ── Trajectory selection ─────────────────────────────────────────────────────
951
+
952
+ separator();
953
+ console.log(`\n${bold(green(' Project initialized successfully!'))}\n`);
954
+ console.log(` ${bold('How do you want to build?')}\n`);
955
+
956
+ console.log(` ${dim('1.')} ${bold('Multi-Agent Driven Orchestration')}`);
957
+ console.log(`${dim(' · Every task should start with npm run launch')}`);
958
+ console.log(`${dim(' · Each agent runs in its own git worktree — an isolated branch')}`);
959
+ console.log(`${dim(' and folder that merges back into main via npm run complete')}`);
960
+ console.log(`${dim(' · Faster builds and lower token spend than a single long session')}`);
961
+ console.log(`${yellow(' ⚠ If you commit directly to main yourself, you bypass the framework')}`);
962
+ console.log(`${yellow(' and break task tracking for any active agent branches')}\n`);
963
+
964
+ console.log(` ${dim('2.')} ${bold('Shared Orchestration')}`);
965
+ console.log(`${dim(' · You and agents co-build — each owning a defined part of the codebase')}`);
966
+ console.log(`${dim(' · Agent tasks run in git worktrees; your work happens directly in the project')}`);
967
+ console.log(`${dim(' · Agent tasks are token-efficient; your tasks cost only what you prompt')}`);
968
+ console.log(`${dim(' · Define boundaries before work begins — agents for well-scoped work,')}`);
969
+ console.log(`${dim(' you for areas where requirements are still evolving')}`);
970
+ console.log(`${yellow(' ⚠ If you and an agent touch the same file, expect merge conflicts')}\n`);
971
+
972
+ const TRAJECTORY_DETAILS = {
973
+ '1': {
974
+ label: 'Multi-Agent Driven Orchestration',
975
+ full: [
976
+ 'Every task must start with npm run launch.',
977
+ 'Agent sessions load only task-relevant context, enabling reliable',
978
+ 'chaining, predictable behavior, and efficient token usage.',
979
+ '',
980
+ '⚠ If you commit directly to main yourself, you bypass the framework',
981
+ ' and break task tracking for any active agent branches.',
982
+ '',
983
+ 'Benefits',
984
+ '· Scoped context per task',
985
+ '· Predictable token consumption',
986
+ '· Lower cost than maintaining large, persistent sessions',
987
+ '· Better isolation between parallel work streams',
988
+ ],
989
+ next: 'launch',
990
+ },
991
+ '2': {
992
+ label: 'Shared Orchestration',
993
+ full: [
994
+ 'You and agents work in the same codebase, each with clearly',
995
+ 'defined ownership. File boundaries must be established before',
996
+ 'work begins and remain fixed throughout the task.',
997
+ 'Agents excel when scope is well-defined;',
998
+ 'you excel when requirements are evolving.',
999
+ '',
1000
+ 'Use agents for',
1001
+ '· Multi-file features',
1002
+ '· Structured implementation work',
1003
+ '· Domain-specific tasks',
1004
+ '· Changes expected to exceed ~200 lines',
1005
+ '',
1006
+ 'Handle manually',
1007
+ '· Targeted bug fixes',
1008
+ '· Configuration changes',
1009
+ '· Small refactors',
1010
+ '· Single-file edits under ~50 lines',
1011
+ '',
1012
+ '⚠ Avoid overlapping file ownership. Working on the same files',
1013
+ ' as an active agent will create merge conflicts when merged.',
1014
+ '⚠ If you are spending time repeatedly clarifying scope, stop',
1015
+ ' and do the task yourself. The coordination cost often',
1016
+ ' exceeds the implementation cost.',
1017
+ '',
1018
+ 'Benefits',
1019
+ '· Maximum agent efficiency for well-defined work',
1020
+ '· Human flexibility where requirements change',
1021
+ '· Scales well across large projects',
1022
+ '· Most adaptable workflow — requires the most discipline',
1023
+ ],
1024
+ next: 'launch',
1025
+ },
1026
+ };
1027
+
1028
+ // Wrap in loop to support back navigation
1029
+ let trajectory = null;
1030
+ trajectoryLoop: while (true) {
1031
+ while (!trajectory) {
1032
+ const input = await ask(` ${bold('Select (1-2)')}: `);
1033
+ if (['1', '2'].includes(input)) trajectory = input;
1034
+ else console.log(yellow(' Please enter 1 or 2.'));
1035
+ }
1036
+
1037
+ const selected = TRAJECTORY_DETAILS[trajectory];
1038
+ separator();
1039
+ console.log(`\n ${green('✓')} ${bold(selected.label)}\n`);
1040
+ renderTrajectoryLines(selected.full);
1041
+ console.log('');
1042
+
1043
+ const confirm = await ask(` ${bold('Confirm?')} ${dim('(y / b = back)')}: `);
1044
+ if (confirm.toLowerCase() === 'y') break trajectoryLoop;
1045
+ trajectory = null; // reset and re-show menu
1046
+ separator();
1047
+ console.log(`\n ${bold('How do you want to build?')}\n`);
1048
+ console.log(` ${dim('1.')} ${bold('Multi-Agent Driven Orchestration')}`);
1049
+ console.log(`${dim(' · Every task should start with npm run launch')}`);
1050
+ console.log(`${dim(' · Each agent runs in its own git worktree — an isolated branch')}`);
1051
+ console.log(`${dim(' and folder that merges back into main via npm run complete')}`);
1052
+ console.log(`${dim(' · Faster builds and lower token spend than a single long session')}`);
1053
+ console.log(`${yellow(' ⚠ If you commit directly to main yourself, you bypass the framework')}`);
1054
+ console.log(`${yellow(' and break task tracking for any active agent branches')}\n`);
1055
+ console.log(` ${dim('2.')} ${bold('Shared Orchestration')}`);
1056
+ console.log(`${dim(' · You and agents co-build — each owning a defined part of the codebase')}`);
1057
+ console.log(`${dim(' · Agent tasks run in git worktrees; your work happens directly in the project')}`);
1058
+ console.log(`${dim(' · Agent tasks are token-efficient; your tasks cost only what you prompt')}`);
1059
+ console.log(`${dim(' · Define boundaries before work begins — agents for well-scoped work,')}`);
1060
+ console.log(`${dim(' you for areas where requirements are still evolving')}`);
1061
+ console.log(`${yellow(' ⚠ If you and an agent touch the same file, expect merge conflicts')}\n`);
1062
+ }
1063
+
1064
+ const selected = TRAJECTORY_DETAILS[trajectory];
1065
+
1066
+ // Store trajectory in config
1067
+ try {
1068
+ const cfg = JSON.parse(fs.readFileSync(path.join(RUNTIME_DIR, '.config.json'), 'utf8'));
1069
+ cfg.trajectory = selected.label.toLowerCase().replace(/ /g, '-');
1070
+ fs.writeFileSync(path.join(RUNTIME_DIR, '.config.json'), JSON.stringify(cfg, null, 2), 'utf8');
1071
+ } catch { /* best-effort */ }
1072
+
1073
+ if (selected.next === 'launch') {
1074
+ const launchInput = await ask(` ${bold('Ready to launch your first task?')} ${dim('(y/n)')}: `);
1075
+ if (launchInput.toLowerCase() === 'y') {
1076
+ rl.close();
1077
+ console.log('');
1078
+ const child = spawn('node', [path.join(ROOT, '.workflow', 'launch.js')], {
1079
+ stdio: 'inherit',
1080
+ cwd: ROOT,
1081
+ });
1082
+ child.on('exit', (code) => process.exit(code));
1083
+ return;
1084
+ }
1085
+ }
1086
+
1087
+ console.log('');
1088
+ console.log(` ${bold('When ready, run:')}`);
1089
+ console.log(` ${cyan('npm run launch')}\n`);
1090
+ separator();
1091
+ console.log('');
1092
+ rl.close();
1093
+ };
1094
+
1095
+ main().catch((err) => {
1096
+ console.error('\n Error:', err.message);
1097
+ process.exit(1);
1098
+ });