multi-agents-cli 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -18
- package/core/workflow/agent.js +6 -285
- package/core/workflow/complete.js +5 -141
- package/core/workflow/reset.js +21 -28
- package/core/workflow/run.js +0 -3
- package/init.js +2 -2
- package/lib/questions-flow.js +2 -14
- package/lib/steps.js +1 -13
- package/lib/ui.js +1 -9
- package/package.json +1 -1
- package/core/templates/.agents/backend/API.md +0 -270
- package/core/templates/.agents/backend/AUTH.md +0 -257
- package/core/templates/.agents/backend/DB.md +0 -268
- package/core/templates/.agents/backend/EVENTS.md +0 -264
- package/core/templates/.agents/backend/INIT.md +0 -250
- package/core/templates/.agents/backend/JOBS.md +0 -267
- package/core/templates/.agents/backend/LOGIC.md +0 -302
- package/core/templates/.agents/backend/TESTING.md +0 -277
- package/core/templates/.agents/client/ACCESSIBILITY.md +0 -277
- package/core/templates/.agents/client/FORMS.md +0 -245
- package/core/templates/.agents/client/LOGIC.md +0 -288
- package/core/templates/.agents/client/ROUTING.md +0 -246
- package/core/templates/.agents/client/TESTING.md +0 -252
- package/core/templates/.agents/client/UI.md +0 -237
- package/core/templates/.agents/shared/CLOUD.md +0 -229
- package/core/templates/.agents/shared/CLOUD_TEARDOWN.md +0 -158
- package/core/templates/.agents/shared/SECURITY.md +0 -297
- package/core/templates/.frameworks/backend/django.md +0 -55
- package/core/templates/.frameworks/backend/express.md +0 -74
- package/core/templates/.frameworks/backend/fastapi.md +0 -107
- package/core/templates/.frameworks/backend/fastify.md +0 -74
- package/core/templates/.frameworks/backend/laravel.md +0 -79
- package/core/templates/.frameworks/backend/nestjs.md +0 -75
- package/core/templates/.frameworks/backend/rails.md +0 -84
- package/core/templates/.frameworks/client/angular.md +0 -80
- package/core/templates/.frameworks/client/nextjs.md +0 -47
- package/core/templates/.frameworks/client/nuxt.md +0 -45
- package/core/templates/.frameworks/client/remix.md +0 -44
- package/core/templates/.frameworks/client/sveltekit.md +0 -44
- package/core/templates/.frameworks/client/vite-react.md +0 -45
- package/core/templates/CLAUDE.md +0 -562
- package/core/templates/CONTRACTS.md +0 -16
- package/core/templates/backend/CLAUDE.md +0 -207
- package/core/templates/client/CLAUDE.md +0 -213
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ You and agents co-build - each owning a defined part of the codebase. Agent task
|
|
|
93
93
|
Next.js - Angular - Vue/Nuxt - SvelteKit - Vite+React - Remix
|
|
94
94
|
|
|
95
95
|
### Backend (separate)
|
|
96
|
-
Express - NestJS - Fastify - FastAPI - Django
|
|
96
|
+
Express - NestJS - Fastify - FastAPI - Django
|
|
97
97
|
|
|
98
98
|
Each framework has a dedicated scaffold instruction file in `.frameworks/client/` and `.frameworks/backend/` - agents read these before scaffolding to ensure files land in the correct location.
|
|
99
99
|
|
|
@@ -281,22 +281,6 @@ On completion, scope is validated and work merges into `main`. The final `main`
|
|
|
281
281
|
}
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
-
**Status values:** `null` (never launched) - `ACTIVE` (running) - `
|
|
284
|
+
**Status values:** `null` (never launched) - `ACTIVE` (running) - `MISSING` (worktree deleted without completing)
|
|
285
285
|
|
|
286
286
|
Managed entirely by `agent.js` and `complete.js`. Never edit manually.
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## What's new in v1.1.0
|
|
291
|
-
|
|
292
|
-
**Init flow**
|
|
293
|
-
- Step counter (`Step N of 12`) shown throughout the configuration flow
|
|
294
|
-
- Back-navigation fully wired — go back to any previous step and resume without re-answering everything
|
|
295
|
-
|
|
296
|
-
**After merge**
|
|
297
|
-
- `npm run complete` now surfaces the next recommended agent explicitly based on tracking state
|
|
298
|
-
- `start:client` script written into `package.json` after `client/UI` merges
|
|
299
|
-
- `start:backend` script written into `package.json` after `backend/INIT` merges
|
|
300
|
-
|
|
301
|
-
**Agent templates**
|
|
302
|
-
- Every agent now includes a `Session Close` block — explicit next-step instructions after Definition of Done
|
package/core/workflow/agent.js
CHANGED
|
@@ -836,52 +836,6 @@ 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
|
-
|
|
885
839
|
const clientCompleted = buildEntries.filter(e => e.scope === 'client' && e.status === 'COMPLETED');
|
|
886
840
|
const clientLogicDone = clientCompleted.some(e => e.agent === 'LOGIC');
|
|
887
841
|
const missing = [];
|
|
@@ -909,42 +863,6 @@ const main = async () => {
|
|
|
909
863
|
}
|
|
910
864
|
}
|
|
911
865
|
|
|
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
|
-
|
|
948
866
|
// ── Select agent (with re-selection loop + back to scope) ────────────────────
|
|
949
867
|
|
|
950
868
|
const agentOptions = AGENTS[project] || [];
|
|
@@ -978,14 +896,12 @@ const main = async () => {
|
|
|
978
896
|
|
|
979
897
|
const agentChoices = [
|
|
980
898
|
...agentOptions.map(a => {
|
|
981
|
-
const desc
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
const tag = isActive ? yellow(' ← in progress') : isNext ? cyan(completedAgents.length === 0 ? ' ← start here' : ' ← next step') : '';
|
|
988
|
-
return { label: `${indicator} ${label}${tag}${desc}` };
|
|
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}` };
|
|
989
905
|
}),
|
|
990
906
|
{ label: dim('← back to scope selection') },
|
|
991
907
|
];
|
|
@@ -995,34 +911,6 @@ const main = async () => {
|
|
|
995
911
|
agent = agentOptions[agentIdx];
|
|
996
912
|
contractsNote = '';
|
|
997
913
|
|
|
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
|
-
|
|
1026
914
|
// Agent already active - decisional block
|
|
1027
915
|
const { active, slot: activeSlot } = guards.checkAgentActive(tracking, project, agent);
|
|
1028
916
|
if (active) {
|
|
@@ -1170,32 +1058,6 @@ const main = async () => {
|
|
|
1170
1058
|
} // end else (argAgent bypass)
|
|
1171
1059
|
} // end if (project !== 'cloud')
|
|
1172
1060
|
|
|
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
|
-
|
|
1199
1061
|
// ── Task description ──────────────────────────────────────────────────────────
|
|
1200
1062
|
|
|
1201
1063
|
separator();
|
|
@@ -1243,11 +1105,6 @@ const main = async () => {
|
|
|
1243
1105
|
let skipped = [];
|
|
1244
1106
|
contextSection = '';
|
|
1245
1107
|
|
|
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
|
-
|
|
1251
1108
|
if (AGENT_QUESTIONS[agent] && userSeedingContracts) {
|
|
1252
1109
|
let gathering = true;
|
|
1253
1110
|
while (gathering) {
|
|
@@ -1286,135 +1143,6 @@ const main = async () => {
|
|
|
1286
1143
|
}
|
|
1287
1144
|
}
|
|
1288
1145
|
|
|
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
|
-
|
|
1418
1146
|
separator();
|
|
1419
1147
|
|
|
1420
1148
|
// ── Confirm ───────────────────────────────────────────────────────────────────
|
|
@@ -1497,13 +1225,6 @@ Mark each step complete. Only proceed to the task below when all are checked.
|
|
|
1497
1225
|
});
|
|
1498
1226
|
console.log(` ${green('✓')} Worktree created: worktrees/${worktreeName}`);
|
|
1499
1227
|
} 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
|
-
}
|
|
1507
1228
|
console.log(` ${yellow('!')} Worktree may already exist - continuing.`);
|
|
1508
1229
|
}
|
|
1509
1230
|
|
|
@@ -391,59 +391,6 @@ 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
|
-
|
|
447
394
|
// ── Update scaffold flags ──────────────────────────────────────────────────
|
|
448
395
|
|
|
449
396
|
try {
|
|
@@ -495,94 +442,11 @@ const main = async () => {
|
|
|
495
442
|
if (_agent) console.log(` ${dim('Agent')} : ${green(_agent)} ${dim('(' + _scope + ')')}`);
|
|
496
443
|
if (_task) console.log(` ${dim('Task')} : ${dim(_task)}`);
|
|
497
444
|
console.log(` ${dim('Branch')} : ${green(branchName)} merged into ${green('main')}\n`);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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 */ }
|
|
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('');
|
|
586
450
|
|
|
587
451
|
rl.close();
|
|
588
452
|
};
|
package/core/workflow/reset.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
|
+
const readline = require('readline');
|
|
20
21
|
const { execSync } = require('child_process');
|
|
21
22
|
|
|
22
23
|
let prompts = null;
|
|
@@ -68,7 +69,6 @@ const main = async () => {
|
|
|
68
69
|
|
|
69
70
|
// ── Load config and tracking ─────────────────────────────────────────────────
|
|
70
71
|
|
|
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) {
|
|
83
83
|
activeAgents.push({ scope, agent, data });
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -89,12 +89,11 @@ 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'
|
|
92
|
+
remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8' }).trim();
|
|
93
93
|
} catch {}
|
|
94
94
|
|
|
95
95
|
// ── Display warning ───────────────────────────────────────────────────────────
|
|
96
96
|
|
|
97
|
-
if (!confirmed) {
|
|
98
97
|
separator();
|
|
99
98
|
console.log(`${red(bold(' ⚠ FULL PROJECT RESET'))}\n`);
|
|
100
99
|
console.log(` ${bold('This will permanently delete:')}\n`);
|
|
@@ -129,7 +128,8 @@ const main = async () => {
|
|
|
129
128
|
|
|
130
129
|
// ── Step 1: First confirmation ────────────────────────────────────────────────
|
|
131
130
|
|
|
132
|
-
|
|
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())));
|
|
133
133
|
|
|
134
134
|
if (prompts && process.stdin.isTTY) {
|
|
135
135
|
const step1 = await prompts({
|
|
@@ -146,6 +146,13 @@ 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
|
+
}
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
// ── Step 2: Type project name ─────────────────────────────────────────────────
|
|
@@ -154,20 +161,14 @@ const main = async () => {
|
|
|
154
161
|
console.log(` ${yellow('To confirm, type the project name exactly:')}`);
|
|
155
162
|
console.log(` ${cyan(bold(projectName))}\n`);
|
|
156
163
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
name: 'value',
|
|
160
|
-
message: 'Project name',
|
|
161
|
-
}, { onCancel: () => process.exit(0) });
|
|
162
|
-
const typed = (step2.value || '').trim();
|
|
164
|
+
const typed = await ask(` ${bold('Project name')}: `);
|
|
165
|
+
rl.close();
|
|
163
166
|
|
|
164
167
|
if (typed !== projectName) {
|
|
165
168
|
console.log(`\n${red(' ✗ Project name does not match. Reset cancelled.')}\n`);
|
|
166
169
|
process.exit(1);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
} // end if (!confirmed)
|
|
170
|
-
|
|
171
172
|
// ── Execute wipe ──────────────────────────────────────────────────────────────
|
|
172
173
|
|
|
173
174
|
separator();
|
|
@@ -211,7 +212,7 @@ const main = async () => {
|
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
// Wipe project files (keep .git
|
|
215
|
+
// Wipe project files (keep .git temporarily for branch ops above)
|
|
215
216
|
const keepList = ['.git'];
|
|
216
217
|
const entries = fs.readdirSync(ROOT);
|
|
217
218
|
for (const entry of entries) {
|
|
@@ -222,26 +223,18 @@ const main = async () => {
|
|
|
222
223
|
}
|
|
223
224
|
console.log(` ${green('✓')} Project files removed`);
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
// ── Re-plant package.json ────────────────────────────────────────────
|
|
227
|
-
|
|
226
|
+
// Remove .git
|
|
228
227
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 */ }
|
|
228
|
+
fs.rmSync(path.join(ROOT, '.git'), { recursive: true, force: true });
|
|
229
|
+
console.log(` ${green('✓')} Git history removed`);
|
|
230
|
+
} catch {}
|
|
237
231
|
|
|
238
232
|
// ── Post-wipe instructions ────────────────────────────────────────────────────
|
|
239
233
|
|
|
240
234
|
separator();
|
|
241
235
|
console.log(`${green(bold(' Project wiped successfully.'))}\n`);
|
|
242
|
-
console.log(`
|
|
243
|
-
console.log(` ${cyan(
|
|
244
|
-
`);
|
|
236
|
+
console.log(` To start fresh:\n`);
|
|
237
|
+
console.log(` ${cyan(`cd .. && multi-agents init ${projectName}`)}\n`);
|
|
245
238
|
separator();
|
|
246
239
|
|
|
247
240
|
process.exit(0);
|