multi-agents-cli 1.1.9 → 1.1.10

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 (44) hide show
  1. package/README.md +18 -2
  2. package/core/templates/.agents/backend/API.md +270 -0
  3. package/core/templates/.agents/backend/AUTH.md +257 -0
  4. package/core/templates/.agents/backend/DB.md +268 -0
  5. package/core/templates/.agents/backend/EVENTS.md +264 -0
  6. package/core/templates/.agents/backend/INIT.md +250 -0
  7. package/core/templates/.agents/backend/JOBS.md +267 -0
  8. package/core/templates/.agents/backend/LOGIC.md +302 -0
  9. package/core/templates/.agents/backend/TESTING.md +277 -0
  10. package/core/templates/.agents/client/ACCESSIBILITY.md +277 -0
  11. package/core/templates/.agents/client/FORMS.md +245 -0
  12. package/core/templates/.agents/client/LOGIC.md +288 -0
  13. package/core/templates/.agents/client/ROUTING.md +246 -0
  14. package/core/templates/.agents/client/TESTING.md +252 -0
  15. package/core/templates/.agents/client/UI.md +237 -0
  16. package/core/templates/.agents/shared/CLOUD.md +229 -0
  17. package/core/templates/.agents/shared/CLOUD_TEARDOWN.md +158 -0
  18. package/core/templates/.agents/shared/SECURITY.md +297 -0
  19. package/core/templates/.frameworks/backend/django.md +55 -0
  20. package/core/templates/.frameworks/backend/express.md +74 -0
  21. package/core/templates/.frameworks/backend/fastapi.md +107 -0
  22. package/core/templates/.frameworks/backend/fastify.md +74 -0
  23. package/core/templates/.frameworks/backend/laravel.md +79 -0
  24. package/core/templates/.frameworks/backend/nestjs.md +75 -0
  25. package/core/templates/.frameworks/backend/rails.md +84 -0
  26. package/core/templates/.frameworks/client/angular.md +80 -0
  27. package/core/templates/.frameworks/client/nextjs.md +47 -0
  28. package/core/templates/.frameworks/client/nuxt.md +45 -0
  29. package/core/templates/.frameworks/client/remix.md +44 -0
  30. package/core/templates/.frameworks/client/sveltekit.md +44 -0
  31. package/core/templates/.frameworks/client/vite-react.md +45 -0
  32. package/core/templates/CLAUDE.md +562 -0
  33. package/core/templates/CONTRACTS.md +16 -0
  34. package/core/templates/backend/CLAUDE.md +207 -0
  35. package/core/templates/client/CLAUDE.md +213 -0
  36. package/core/workflow/agent.js +285 -6
  37. package/core/workflow/complete.js +141 -5
  38. package/core/workflow/reset.js +28 -21
  39. package/core/workflow/run.js +3 -0
  40. package/init.js +2 -2
  41. package/lib/questions-flow.js +13 -2
  42. package/lib/steps.js +13 -1
  43. package/lib/ui.js +8 -0
  44. package/package.json +1 -1
@@ -836,6 +836,52 @@ const main = async () => {
836
836
 
837
837
  // Soft gate - backend selected but missing prerequisites
838
838
  if (project === 'backend') {
839
+ // Architectural change warning — no backend/ folder + integrated backend config
840
+ const backendFolderExists = fs.existsSync(path.join(ROOT, 'backend'));
841
+ const isIntegrated = config.backend?.type === 'integrated';
842
+
843
+ if (!backendFolderExists) {
844
+ separator();
845
+ console.log(`\n ${yellow('⚠ No backend/ folder detected')}
846
+ `);
847
+ if (isIntegrated) {
848
+ console.log(` This project currently uses ${bold('integrated backend')} (${dim(config.frontend?.type || 'client framework')}).`);
849
+ console.log(` Adding a separate backend will change your project architecture:
850
+ `);
851
+ } else {
852
+ console.log(` A ${bold('backend/')} directory does not exist yet.`);
853
+ console.log(` Proceeding will create it and establish a separate backend:
854
+ `);
855
+ }
856
+ console.log(` ${dim('→')} A new ${bold('backend/')} directory will be created`);
857
+ console.log(` ${dim('→')} Client API calls will need redirecting to the new backend`);
858
+ console.log(` ${dim('→')} CONTRACTS.md will need updating`);
859
+ if (isIntegrated) {
860
+ console.log(` ${dim('→')} Project config will update to ${bold('separate')} backend type
861
+ `);
862
+ }
863
+ console.log('');
864
+
865
+ const archIdx = await arrowSelect('How would you like to proceed?', [
866
+ { label: `${green('→')} Proceed — I want to add a separate backend` },
867
+ { label: `${yellow('←')} Go back — keep current setup ${dim('← recommended')}` },
868
+ ], rl);
869
+
870
+ if (archIdx === 1) continue flowLoop;
871
+
872
+ // Update config to separate if was integrated
873
+ if (isIntegrated) {
874
+ try {
875
+ const cfgPath = path.join(ROOT, '.scaffold', '.config.json');
876
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
877
+ cfg.backend.type = 'separate';
878
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
879
+ console.log(` ${green('✓')} Project config updated to separate backend
880
+ `);
881
+ } catch {}
882
+ }
883
+ }
884
+
839
885
  const clientCompleted = buildEntries.filter(e => e.scope === 'client' && e.status === 'COMPLETED');
840
886
  const clientLogicDone = clientCompleted.some(e => e.agent === 'LOGIC');
841
887
  const missing = [];
@@ -863,6 +909,42 @@ const main = async () => {
863
909
  }
864
910
  }
865
911
 
912
+
913
+ // Soft gate - shared selected but missing prerequisites
914
+ if (project === 'shared') {
915
+ const clientCompleted = buildEntries.filter(e => e.scope === 'client' && e.status === 'COMPLETED');
916
+ const backendCompleted = buildEntries.filter(e => e.scope === 'backend' && e.status === 'COMPLETED');
917
+ const clientUIДone = clientCompleted.some(e => e.agent === 'UI');
918
+ const backendINITDone = backendCompleted.some(e => e.agent === 'INIT');
919
+ const missing = [];
920
+
921
+ if (!clientUIДone) {
922
+ missing.push({ item: 'Client scaffold', detail: 'no completed client work found — SECURITY needs to know what auth patterns the client uses' });
923
+ }
924
+ if (!backendINITDone) {
925
+ missing.push({ item: 'Backend INIT', detail: 'backend not scaffolded yet — SECURITY needs to know the backend auth strategy and DB schema' });
926
+ }
927
+ if (!contracts.hasContent) {
928
+ missing.push({ item: 'CONTRACTS.md', detail: 'no shared types defined — auth utilities should align with established contracts' });
929
+ }
930
+
931
+ if (missing.length > 0) {
932
+ console.log(`\n${yellow(' ⚠ Shared prerequisites not fully met:')}
933
+ `);
934
+ missing.forEach(m => {
935
+ console.log(` ${dim('→')} ${bold(m.item)}`);
936
+ console.log(` ${m.detail}\n`);
937
+ });
938
+ console.log(` ${dim('The agent will proceed autonomously and make assumptions about auth patterns.')}`);
939
+ console.log(` ${dim('These may need revision once client and backend are fully established.\n')}`);
940
+ const proceedIdx = await arrowSelect('Shared prerequisites not met:', [
941
+ { label: `${green('→')} Proceed anyway` },
942
+ { label: `${yellow('←')} Go back — pick a different scope ${dim('← recommended')}` },
943
+ ], rl);
944
+ if (proceedIdx === 1) continue flowLoop;
945
+ }
946
+ }
947
+
866
948
  // ── Select agent (with re-selection loop + back to scope) ────────────────────
867
949
 
868
950
  const agentOptions = AGENTS[project] || [];
@@ -896,12 +978,14 @@ const main = async () => {
896
978
 
897
979
  const agentChoices = [
898
980
  ...agentOptions.map(a => {
899
- const desc = dim(` ${AGENT_DESCRIPTIONS[project]?.[a] || ''}`);
900
- const tag = a === recommendedAgent
901
- ? cyan(completedAgents.length === 0 ? ' start here' : ' ← next step')
902
- : '';
903
- const label = a === recommendedAgent ? bold(a) : a;
904
- return { label: `${label}${tag}${desc}` };
981
+ const desc = dim(` ${AGENT_DESCRIPTIONS[project]?.[a] || ''}`);
982
+ const completed = completedAgents.includes(a);
983
+ const isActive = tracking?.[project]?.[a]?.status === 'ACTIVE';
984
+ const isNext = a === recommendedAgent && !isActive;
985
+ const indicator = completed ? green('[✓]') : isActive ? yellow('[▶]') : isNext ? cyan(' → ') : dim('[ ]');
986
+ const label = completed ? dim(a) : isActive ? yellow(bold(a)) : isNext ? bold(a) : a;
987
+ const tag = isActive ? yellow(' ← in progress') : isNext ? cyan(completedAgents.length === 0 ? ' ← start here' : ' ← next step') : '';
988
+ return { label: `${indicator} ${label}${tag}${desc}` };
905
989
  }),
906
990
  { label: dim('← back to scope selection') },
907
991
  ];
@@ -911,6 +995,34 @@ const main = async () => {
911
995
  agent = agentOptions[agentIdx];
912
996
  contractsNote = '';
913
997
 
998
+ // ── Check for OTHER active agents in same scope ───────────────────────────
999
+ const otherActiveInScope = Object.entries(tracking?.[project] || {})
1000
+ .filter(([a, data]) => a !== agent && data?.status === 'ACTIVE');
1001
+
1002
+ if (otherActiveInScope.length > 0) {
1003
+ separator();
1004
+ console.log(`\n ${yellow('⚠ Active agent detected in ' + project + ' scope')}
1005
+ `);
1006
+ for (const [a, data] of otherActiveInScope) {
1007
+ console.log(` ${dim('→')} ${bold(a)} is currently ${yellow('ACTIVE')}`);
1008
+ console.log(` Branch : ${dim(data.branch)}`);
1009
+ console.log(` Launched: ${dim(data.launchedAt ? new Date(data.launchedAt).toLocaleString() : 'unknown')}
1010
+ `);
1011
+ }
1012
+ console.log(` ${yellow('Launching')} ${bold(agent)} ${yellow('alongside an active agent in the same scope means:')}`);
1013
+ console.log(` ${dim('→')} Both agents may write to overlapping files`);
1014
+ console.log(` ${dim('→')} Scope violations may occur at merge time`);
1015
+ console.log(` ${dim('→')} CONTRACTS.md may receive conflicting entries
1016
+ `);
1017
+
1018
+ const warningIdx = await arrowSelect('How would you like to proceed?', [
1019
+ { label: `${yellow('→')} Proceed anyway ${dim('— I understand the risks')}` },
1020
+ { label: `${green('←')} Go back — pick a different agent ${dim('← recommended')}` },
1021
+ ], rl);
1022
+
1023
+ if (warningIdx === 1) continue agentLoop;
1024
+ }
1025
+
914
1026
  // Agent already active - decisional block
915
1027
  const { active, slot: activeSlot } = guards.checkAgentActive(tracking, project, agent);
916
1028
  if (active) {
@@ -1058,6 +1170,32 @@ const main = async () => {
1058
1170
  } // end else (argAgent bypass)
1059
1171
  } // end if (project !== 'cloud')
1060
1172
 
1173
+ // ── First-run project context question (UI agent only, no completed agents) ──
1174
+ let projectContext = '';
1175
+ const isFirstRun = project === 'client' && agent === 'UI' && buildEntries.filter(e => e.status === 'COMPLETED').length === 0;
1176
+
1177
+ if (isFirstRun) {
1178
+ separator();
1179
+ console.log(`\n ${bold('Before we begin...')}
1180
+ `);
1181
+
1182
+ if (prompts && process.stdin.isTTY) {
1183
+ const ctxRes = await prompts({
1184
+ type: 'text',
1185
+ name: 'value',
1186
+ message: '* What are you building?',
1187
+ initial: 'e.g. A stock market prediction app with AI insights and portfolio tracking',
1188
+ }, { onCancel: () => process.exit(0) });
1189
+ const rawCtx = (ctxRes.value || '').trim();
1190
+ const isPlaceholderCtx = rawCtx.startsWith('e.g. ');
1191
+ projectContext = isPlaceholderCtx ? '' : rawCtx;
1192
+ } else {
1193
+ console.log(dim(' (optional — helps the agent make better decisions)'));
1194
+ const input = await ask(`\n${bold('* What are you building?')}: `);
1195
+ projectContext = input.trim();
1196
+ }
1197
+ }
1198
+
1061
1199
  // ── Task description ──────────────────────────────────────────────────────────
1062
1200
 
1063
1201
  separator();
@@ -1105,6 +1243,11 @@ const main = async () => {
1105
1243
  let skipped = [];
1106
1244
  contextSection = '';
1107
1245
 
1246
+ // Inject project context if provided on first UI run
1247
+ if (projectContext) {
1248
+ contextSection += `\n---\n\n## Project Context\n\n${projectContext}\n\nUse this as the primary directive for what you are building. All component names, page structure, and UI decisions should align with this project description.\n`;
1249
+ }
1250
+
1108
1251
  if (AGENT_QUESTIONS[agent] && userSeedingContracts) {
1109
1252
  let gathering = true;
1110
1253
  while (gathering) {
@@ -1143,6 +1286,135 @@ const main = async () => {
1143
1286
  }
1144
1287
  }
1145
1288
 
1289
+
1290
+ // ── Backend simultaneous flow (client/LOGIC only, separate backend, first run) ──
1291
+ let backendLaunchTiming = null;
1292
+ const isSimultaneousCandidate =
1293
+ project === 'client' &&
1294
+ agent === 'LOGIC' &&
1295
+ config.backend?.type === 'separate' &&
1296
+ !argAgent &&
1297
+ !buildEntries.some(e => e.scope === 'backend') &&
1298
+ !tracking?.backend?.INIT?.backendPromptShown;
1299
+
1300
+ if (isSimultaneousCandidate) {
1301
+ separator();
1302
+ console.log(`\n ${yellow('⚡ Backend not started yet')}\n`);
1303
+ console.log(` Your backend has not been scaffolded yet.`);
1304
+ console.log(` Since client/LOGIC establishes integration contracts, this is the ideal moment to plan backend scaffolding.\n`);
1305
+
1306
+ const intentIdx = await arrowSelect('Would you like to include backend/INIT?', [
1307
+ { label: `${green('→')} Yes — I want to scaffold the backend` },
1308
+ { label: `${dim('→')} No — continue with client/LOGIC only` },
1309
+ ], rl);
1310
+
1311
+ if (intentIdx === 0) {
1312
+ console.log('');
1313
+ const timingIdx = await arrowSelect('When would you like to scaffold backend/INIT?', [
1314
+ { label: `${green('→')} Now — open backend/INIT workspace alongside this LOGIC session\n ${dim('A second IDE window will open. Both agents run in parallel.')}` },
1315
+ { label: `${dim('→')} After — launch backend/INIT when LOGIC completes\n ${dim('complete.js will prompt you immediately after merge.')}` },
1316
+ { label: `${dim('→')} Skip — changed my mind, continue with client/LOGIC only` },
1317
+ ], rl);
1318
+
1319
+ if (timingIdx === 0) backendLaunchTiming = 'now';
1320
+ else if (timingIdx === 1) backendLaunchTiming = 'after';
1321
+ else backendLaunchTiming = 'skip';
1322
+ } else {
1323
+ backendLaunchTiming = 'skip';
1324
+ }
1325
+
1326
+ // Write flag to backend/INIT tracking slot — prevents prompt from showing again
1327
+ guards.updateTrackingSlot(tracking, 'backend', 'INIT', {
1328
+ backendPromptShown: true,
1329
+ backendLaunchTiming,
1330
+ }, ROOT);
1331
+
1332
+ // Inject backend coordination block into TASK.md contextSection
1333
+ if (backendLaunchTiming === 'now') {
1334
+ contextSection += `\n---\n\n## Backend Coordination\n\nBackend/INIT is running in parallel in a separate workspace.\nEstablish CONTRACTS.md entries early — the backend agent will consume them.\nDo not wait for backend confirmation. Work autonomously on client scope.\n`;
1335
+ } else if (backendLaunchTiming === 'after') {
1336
+ contextSection += `\n---\n\n## Backend Coordination\n\nBackend/INIT will launch after this LOGIC session completes.\nBe deliberate about API contracts — document all integration points in CONTRACTS.md.\nThe backend agent will use these as its starting reference.\n`;
1337
+ }
1338
+
1339
+ // If Now — create backend/INIT worktree immediately
1340
+ if (backendLaunchTiming === 'now') {
1341
+ const beTimestamp = Date.now();
1342
+ const beBranchName = `agent/backend/init/${beTimestamp}`;
1343
+ const beWorktreeName = `backend-${config.projectName.toLowerCase().replace(/\s+/g, '-')}-init-${beTimestamp}`;
1344
+ const beWorktreePath = path.join(ROOT, 'worktrees', beWorktreeName);
1345
+
1346
+ console.log(`\n ${dim('Setting up backend/INIT workspace...')}\n`);
1347
+
1348
+ try {
1349
+ execSync(`git worktree add "${beWorktreePath}" -b ${beBranchName}`, { cwd: ROOT, stdio: 'pipe' });
1350
+
1351
+ // Framework files
1352
+ const beScope = generateClaudeScope({ project: 'backend', agent: 'INIT', branchName: beBranchName, worktreePath: beWorktreePath });
1353
+ fs.writeFileSync(path.join(beWorktreePath, '.claude-scope'), beScope, 'utf8');
1354
+
1355
+ const beTask = AGENT_DESCRIPTIONS.backend?.INIT || 'scaffolds backend architecture, folder structure, DB setup, wiring config and contracts';
1356
+ const beTaskMd = generateTaskMd({ project: 'backend', agent: 'INIT', task: beTask, branchName: beBranchName, contextSection: '', remoteSetupSection: '' });
1357
+ fs.writeFileSync(path.join(beWorktreePath, 'TASK.md'), beTaskMd, 'utf8');
1358
+
1359
+ const bePkg = {
1360
+ name: `${config.projectName.toLowerCase().replace(/\s+/g, '-')}-worktree`,
1361
+ version: '1.0.0', private: true,
1362
+ scripts: {
1363
+ init: 'multi-agents init',
1364
+ agent: `node "${ROOT}/.workflow/run.js"`,
1365
+ restart: `node "${ROOT}/.workflow/restart.js"`,
1366
+ reset: `node "${ROOT}/.workflow/reset.js"`,
1367
+ complete: `node "${ROOT}/.workflow/complete.js"`,
1368
+ },
1369
+ };
1370
+ fs.writeFileSync(path.join(beWorktreePath, 'package.json'), JSON.stringify(bePkg, null, 2), 'utf8');
1371
+
1372
+ const beGitignore = ['# Framework files — never commit these to the agent branch', 'package.json', '.claude-scope', 'scope.json', 'TASK.md', '.vscode/', '.idea/', '.zed/', 'node_modules/'].join('\n') + '\n';
1373
+ fs.writeFileSync(path.join(beWorktreePath, '.gitignore'), beGitignore, 'utf8');
1374
+
1375
+ // Tracking
1376
+ guards.updateTrackingSlot(tracking, 'backend', 'INIT', {
1377
+ branch: beBranchName,
1378
+ timestamp: beTimestamp,
1379
+ launchedAt: new Date().toISOString(),
1380
+ status: 'ACTIVE',
1381
+ worktreePath: beWorktreePath,
1382
+ backendPromptShown: true,
1383
+ backendLaunchTiming: 'now',
1384
+ }, ROOT);
1385
+
1386
+ // TASKS_HISTORY
1387
+ tasksHistory.ensureHistoryFile(ROOT);
1388
+ tasksHistory.writeSessionEntry(ROOT, { scope: 'backend', agent: 'INIT', branch: beBranchName, task: beTask, launchedAt: new Date().toISOString() });
1389
+
1390
+ console.log(` ${green('✓')} Backend/INIT worktree created: worktrees/${beWorktreeName}`);
1391
+
1392
+ // BUILD_STATE.md entry
1393
+ const beBuildStatePath = path.join(ROOT, 'BUILD_STATE.md');
1394
+ if (fs.existsSync(beBuildStatePath)) {
1395
+ const beDate = new Date().toISOString().split('T')[0];
1396
+ const beLogEntry = `| ${beDate} | INIT | backend | ${beTask} | IN PROGRESS | ${beBranchName} |\n`;
1397
+ fs.appendFileSync(beBuildStatePath, beLogEntry, 'utf8');
1398
+ try {
1399
+ execSync('git add BUILD_STATE.md', { cwd: ROOT, stdio: 'pipe' });
1400
+ execSync(`git commit --no-verify -m "build: INIT task started on backend [${beBranchName}]"`, { cwd: ROOT, stdio: 'pipe' });
1401
+ console.log(` ${green('✓')} BUILD_STATE.md updated`);
1402
+ } catch {}
1403
+ }
1404
+
1405
+ // Open backend IDE window
1406
+ const beIdeOpened = openIDE(beWorktreePath);
1407
+ if (!beIdeOpened) console.log(` ${yellow('!')} Could not open IDE for backend — open manually at: ${dim(beWorktreePath)}`);
1408
+ console.log(` ${green('✓')} Backend/INIT workspace open — start a Claude Code session there when ready\n`);
1409
+
1410
+ } catch (e) {
1411
+ console.log(` ${yellow('⚠')} Could not create backend/INIT worktree: ${e.message}`);
1412
+ }
1413
+ }
1414
+
1415
+ separator();
1416
+ }
1417
+
1146
1418
  separator();
1147
1419
 
1148
1420
  // ── Confirm ───────────────────────────────────────────────────────────────────
@@ -1225,6 +1497,13 @@ Mark each step complete. Only proceed to the task below when all are checked.
1225
1497
  });
1226
1498
  console.log(` ${green('✓')} Worktree created: worktrees/${worktreeName}`);
1227
1499
  } catch (err) {
1500
+ const msg = err.stderr?.toString() || err.message || '';
1501
+ if (msg.includes('not a git repository')) {
1502
+ console.error(`
1503
+ ${red('✖')} Not a git repository. Run git init first, then npm run init.
1504
+ `);
1505
+ process.exit(1);
1506
+ }
1228
1507
  console.log(` ${yellow('!')} Worktree may already exist - continuing.`);
1229
1508
  }
1230
1509
 
@@ -391,6 +391,59 @@ const main = async () => {
391
391
  }
392
392
  } catch { /* best-effort */ }
393
393
 
394
+ // ── Write start:client / start:backend into root package.json ───────────────
395
+
396
+ try {
397
+ const CLIENT_START = {
398
+ 'Next.js': 'cd client && npm install && npm run dev',
399
+ 'Nuxt': 'cd client && npm install && npm run dev',
400
+ 'SvelteKit': 'cd client && npm install && npm run dev',
401
+ 'Remix': 'cd client && npm install && npm run dev',
402
+ 'Vite+React': 'cd client && npm install && npm run dev',
403
+ 'Angular': 'cd client && npm install && npx ng serve',
404
+ };
405
+ const BACKEND_START = {
406
+ 'NestJS': 'cd backend && npm install && npm run start:dev',
407
+ 'Express': 'cd backend && npm install && npm run dev',
408
+ 'Fastify': 'cd backend && npm install && npm run dev',
409
+ 'Django': 'cd backend && pip install -r requirements.txt && python manage.py runserver',
410
+ 'FastAPI': 'cd backend && pip install -r requirements.txt && uvicorn main:app --reload',
411
+ 'Laravel': 'cd backend && composer install && php artisan serve',
412
+ 'Rails': 'cd backend && bundle install && bin/rails server',
413
+ };
414
+
415
+ const pkgPath = path.join(ROOT, 'package.json');
416
+ if (fs.existsSync(pkgPath)) {
417
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
418
+ if (!pkg.scripts) pkg.scripts = {};
419
+
420
+ const parts = branchName.split('/');
421
+ const _t = guards.loadTracking(ROOT, config);
422
+ const uiDone = _t?.client?.UI?.status === 'COMPLETED';
423
+ const initDone = _t?.backend?.INIT?.status === 'COMPLETED';
424
+
425
+ if (uiDone) {
426
+ const fw = config.client && config.client.framework;
427
+ const cmd = CLIENT_START[fw];
428
+ if (cmd && !pkg.scripts['start:client']) {
429
+ pkg.scripts['start:client'] = cmd;
430
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
431
+ console.log(` ${green('✓')} start:client added to package.json (${cmd})`);
432
+ }
433
+ }
434
+
435
+ if (initDone) {
436
+ const fw = config.backend && config.backend.framework;
437
+ const cmd = BACKEND_START[fw];
438
+ if (cmd && !pkg.scripts['start:backend']) {
439
+ pkg.scripts['start:backend'] = cmd;
440
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
441
+ console.log(` ${green('✓')} start:backend added to package.json (${cmd})`);
442
+ }
443
+ }
444
+ }
445
+ } catch { /* best-effort */ }
446
+
394
447
  // ── Update scaffold flags ──────────────────────────────────────────────────
395
448
 
396
449
  try {
@@ -442,11 +495,94 @@ const main = async () => {
442
495
  if (_agent) console.log(` ${dim('Agent')} : ${green(_agent)} ${dim('(' + _scope + ')')}`);
443
496
  if (_task) console.log(` ${dim('Task')} : ${dim(_task)}`);
444
497
  console.log(` ${dim('Branch')} : ${green(branchName)} merged into ${green('main')}\n`);
445
- console.log(` ${bold('What to do next:')}\n`);
446
- console.log(` Start a new task:`);
447
- console.log(` ${cyan('npm run agent')}\n`);
448
- separator();
449
- console.log('');
498
+
499
+ // ── Derive next step from tracking ──────────────────────────────────────────
500
+ const AGENT_ORDER = {
501
+ client: ['UI', 'LOGIC', 'FORMS', 'ROUTING', 'TESTING', 'ACCESSIBILITY'],
502
+ backend: ['INIT', 'API', 'LOGIC', 'AUTH', 'DB', 'TESTING', 'EVENTS', 'JOBS'],
503
+ shared: ['SECURITY'],
504
+ };
505
+ const SCOPE_ORDER = ['client', 'backend', 'shared'];
506
+
507
+ try {
508
+ const _tracking = guards.loadTracking(ROOT, config);
509
+
510
+ const findNextInScope = (scope) => {
511
+ const order = AGENT_ORDER[scope] || [];
512
+ for (const a of order) {
513
+ const slot = (_tracking[scope] || {})[a];
514
+ if (!slot || (slot.status !== 'COMPLETED' && slot.status !== 'ACTIVE')) return a;
515
+ }
516
+ return null;
517
+ };
518
+
519
+ let nextScope = null;
520
+ let nextAgent = null;
521
+
522
+ const scopesToCheck = [_scope, ...SCOPE_ORDER.filter(s => s !== _scope)];
523
+ for (const s of scopesToCheck) {
524
+ const n = findNextInScope(s);
525
+ if (n) { nextScope = s; nextAgent = n; break; }
526
+ }
527
+
528
+ separator();
529
+ console.log(`\n ${bold('What to do next:')}`);
530
+
531
+ if (nextAgent) {
532
+ console.log(`\n ${green('→')} ${bold(nextAgent)} ${dim('(' + nextScope + ')')}`);
533
+ console.log(`\n ${cyan('npm run agent')}`);
534
+ console.log(` ${dim('Select: ' + nextScope + ' → ' + nextAgent)}`);
535
+ } else {
536
+ console.log(`\n ${cyan('npm run agent')}`);
537
+ }
538
+ } catch {
539
+ separator();
540
+ console.log(`\n ${bold('What to do next:')}\n`);
541
+ console.log(` ${cyan('npm run agent')}`);
542
+ }
543
+
544
+ console.log('\n');
545
+
546
+
547
+ // ── Backend/INIT launch prompt (if user chose 'after' during LOGIC session) ──
548
+ try {
549
+ const _tracking = guards.loadTracking(ROOT, config);
550
+ const beSlot = _tracking?.backend?.INIT;
551
+ if (
552
+ _scope === 'logic' &&
553
+ _parts[0] === 'agent' && _parts[1] === 'client' &&
554
+ beSlot?.backendLaunchTiming === 'after' &&
555
+ beSlot?.status !== 'ACTIVE' &&
556
+ beSlot?.status !== 'COMPLETED'
557
+ ) {
558
+ separator();
559
+ console.log(`\n ${yellow('⚡ Ready to scaffold your backend?')}\n`);
560
+ console.log(` client/LOGIC is merged. This is the ideal moment to launch backend/INIT.\n`);
561
+
562
+ const { spawn: _spawn } = require('child_process');
563
+ const launchIdx = await new Promise(resolve => {
564
+ rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
565
+ const choices = [
566
+ '1. Launch backend/INIT now ← recommended',
567
+ '2. Skip — I will handle it manually',
568
+ ];
569
+ choices.forEach(c => console.log(` ${c}`));
570
+ rl2.question('\n Select (1-2): ', ans => {
571
+ rl2.close();
572
+ resolve(parseInt(ans));
573
+ });
574
+ });
575
+
576
+ if (launchIdx === 1) {
577
+ console.log(`\n ${green('✓')} Launching backend/INIT...\n`);
578
+ rl.close();
579
+ _spawn('node', [path.join(ROOT, '.workflow', 'agent.js'), '--scope=backend', '--agent=INIT'], {
580
+ cwd: ROOT, stdio: 'inherit',
581
+ });
582
+ return;
583
+ }
584
+ }
585
+ } catch { /* best-effort */ }
450
586
 
451
587
  rl.close();
452
588
  };
@@ -17,7 +17,6 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const readline = require('readline');
21
20
  const { execSync } = require('child_process');
22
21
 
23
22
  let prompts = null;
@@ -69,6 +68,7 @@ const main = async () => {
69
68
 
70
69
  // ── Load config and tracking ─────────────────────────────────────────────────
71
70
 
71
+ const confirmed = process.argv.includes('--confirmed');
72
72
  const config = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) : {};
73
73
  const tracking = fs.existsSync(TRACKING_PATH) ? JSON.parse(fs.readFileSync(TRACKING_PATH, 'utf8')) : {};
74
74
  const projectName = config.projectName || path.basename(ROOT);
@@ -79,7 +79,7 @@ const main = async () => {
79
79
  for (const scope of ['client', 'backend', 'shared']) {
80
80
  const agents = tracking[scope] || {};
81
81
  for (const [agent, data] of Object.entries(agents)) {
82
- if (data && data.branch) {
82
+ if (data && data.branch && data.status !== 'COMPLETED') {
83
83
  activeAgents.push({ scope, agent, data });
84
84
  }
85
85
  }
@@ -89,11 +89,12 @@ const main = async () => {
89
89
 
90
90
  let remoteUrl = null;
91
91
  try {
92
- remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8' }).trim();
92
+ remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim();
93
93
  } catch {}
94
94
 
95
95
  // ── Display warning ───────────────────────────────────────────────────────────
96
96
 
97
+ if (!confirmed) {
97
98
  separator();
98
99
  console.log(`${red(bold(' ⚠ FULL PROJECT RESET'))}\n`);
99
100
  console.log(` ${bold('This will permanently delete:')}\n`);
@@ -128,8 +129,7 @@ const main = async () => {
128
129
 
129
130
  // ── Step 1: First confirmation ────────────────────────────────────────────────
130
131
 
131
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
132
- const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
132
+
133
133
 
134
134
  if (prompts && process.stdin.isTTY) {
135
135
  const step1 = await prompts({
@@ -146,13 +146,6 @@ const main = async () => {
146
146
  console.log(dim('\n Reset cancelled.\n'));
147
147
  process.exit(0);
148
148
  }
149
- } else {
150
- const ans = await ask(` ${bold('Are you sure?')} ${dim('(y/N)')}: `);
151
- if (ans.toLowerCase() !== 'y') {
152
- console.log(dim('\n Reset cancelled.\n'));
153
- rl.close();
154
- process.exit(0);
155
- }
156
149
  }
157
150
 
158
151
  // ── Step 2: Type project name ─────────────────────────────────────────────────
@@ -161,14 +154,20 @@ const main = async () => {
161
154
  console.log(` ${yellow('To confirm, type the project name exactly:')}`);
162
155
  console.log(` ${cyan(bold(projectName))}\n`);
163
156
 
164
- const typed = await ask(` ${bold('Project name')}: `);
165
- rl.close();
157
+ const step2 = await prompts({
158
+ type: 'text',
159
+ name: 'value',
160
+ message: 'Project name',
161
+ }, { onCancel: () => process.exit(0) });
162
+ const typed = (step2.value || '').trim();
166
163
 
167
164
  if (typed !== projectName) {
168
165
  console.log(`\n${red(' ✗ Project name does not match. Reset cancelled.')}\n`);
169
166
  process.exit(1);
170
167
  }
171
168
 
169
+ } // end if (!confirmed)
170
+
172
171
  // ── Execute wipe ──────────────────────────────────────────────────────────────
173
172
 
174
173
  separator();
@@ -212,7 +211,7 @@ const main = async () => {
212
211
  }
213
212
  }
214
213
 
215
- // Wipe project files (keep .git temporarily for branch ops above)
214
+ // Wipe project files (keep .git git repo is infrastructure, not framework state)
216
215
  const keepList = ['.git'];
217
216
  const entries = fs.readdirSync(ROOT);
218
217
  for (const entry of entries) {
@@ -223,18 +222,26 @@ const main = async () => {
223
222
  }
224
223
  console.log(` ${green('✓')} Project files removed`);
225
224
 
226
- // Remove .git
225
+
226
+ // ── Re-plant package.json ────────────────────────────────────────────
227
+
227
228
  try {
228
- fs.rmSync(path.join(ROOT, '.git'), { recursive: true, force: true });
229
- console.log(` ${green('')} Git history removed`);
230
- } catch {}
229
+ const freshPkg = {
230
+ name: projectName.toLowerCase().replace(/\s+/g, '-'),
231
+ version: '1.0.0',
232
+ scripts: { init: 'multi-agents init' },
233
+ };
234
+ fs.writeFileSync(path.join(ROOT, 'package.json'), JSON.stringify(freshPkg, null, 2) + '\n', 'utf8');
235
+ console.log(' ' + green('\u2713') + ' package.json re-planted');
236
+ } catch { /* best-effort */ }
231
237
 
232
238
  // ── Post-wipe instructions ────────────────────────────────────────────────────
233
239
 
234
240
  separator();
235
241
  console.log(`${green(bold(' Project wiped successfully.'))}\n`);
236
- console.log(` To start fresh:\n`);
237
- console.log(` ${cyan(`cd .. && multi-agents init ${projectName}`)}\n`);
242
+ console.log(` Run to start fresh:\n`);
243
+ console.log(` ${cyan('npm run init')}
244
+ `);
238
245
  separator();
239
246
 
240
247
  process.exit(0);
@@ -56,6 +56,9 @@ const copyWorkflow = (cliRoot) => {
56
56
  };
57
57
 
58
58
  const findCliRoot = () => {
59
+ // Local node_modules first (npx or local install)
60
+ const localPkg = path.join(__dirname, '..', 'node_modules', 'multi-agents-cli');
61
+ if (fs.existsSync(localPkg)) return localPkg;
59
62
  try {
60
63
  const globalPkg = execSync('npm root -g', { stdio: 'pipe', encoding: 'utf8' }).trim();
61
64
  const p = path.join(globalPkg, 'multi-agents-cli');
package/init.js CHANGED
@@ -78,7 +78,7 @@ const projectArg = isGlobalCLI ? args[1] : null;
78
78
 
79
79
  if (isReInit) {
80
80
  try {
81
- const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8' }).trim();
81
+ const gitCommonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8', stdio: 'pipe' }).trim();
82
82
  const repoRoot = path.resolve(gitCommonDir, '..');
83
83
  process.chdir(repoRoot);
84
84
  } catch { /* stay in current directory */ }
@@ -275,7 +275,7 @@ const main = async () => {
275
275
  ],
276
276
  }, { onCancel: () => process.exit(0) });
277
277
  if (confirm.value !== 'yes') { console.log(dim('\n Cancelled.\n')); process.exit(0); }
278
- const resetChild = spawn('node', [path.join(ROOT, '.workflow', 'reset.js')], { stdio: 'inherit', cwd: ROOT });
278
+ const resetChild = spawn('node', [path.join(ROOT, '.workflow', 'reset.js'), '--confirmed'], { stdio: 'inherit', cwd: ROOT });
279
279
  resetChild.on('exit', code => process.exit(code ?? 0));
280
280
  return;
281
281
  } else {