multi-agents-cli 1.0.51 → 1.0.53
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/core/templates/.agents/backend/API.md +259 -0
- package/core/templates/.agents/backend/AUTH.md +246 -0
- package/core/templates/.agents/backend/DB.md +257 -0
- package/core/templates/.agents/backend/EVENTS.md +253 -0
- package/core/templates/.agents/backend/INIT.md +239 -0
- package/core/templates/.agents/backend/JOBS.md +256 -0
- package/core/templates/.agents/backend/LOGIC.md +291 -0
- package/core/templates/.agents/backend/TESTING.md +266 -0
- package/core/templates/.agents/client/ACCESSIBILITY.md +266 -0
- package/core/templates/.agents/client/FORMS.md +234 -0
- package/core/templates/.agents/client/LOGIC.md +277 -0
- package/core/templates/.agents/client/ROUTING.md +235 -0
- package/core/templates/.agents/client/TESTING.md +241 -0
- package/core/templates/.agents/client/UI.md +226 -0
- package/core/templates/.agents/shared/CLOUD.md +229 -0
- package/core/templates/.agents/shared/CLOUD_TEARDOWN.md +158 -0
- package/core/templates/.agents/shared/SECURITY.md +286 -0
- package/core/templates/.frameworks/backend/django.md +55 -0
- package/core/templates/.frameworks/backend/express.md +74 -0
- package/core/templates/.frameworks/backend/fastapi.md +107 -0
- package/core/templates/.frameworks/backend/fastify.md +74 -0
- package/core/templates/.frameworks/backend/nestjs.md +75 -0
- package/core/templates/.frameworks/client/angular.md +80 -0
- package/core/templates/.frameworks/client/nextjs.md +47 -0
- package/core/templates/.frameworks/client/nuxt.md +45 -0
- package/core/templates/.frameworks/client/remix.md +44 -0
- package/core/templates/.frameworks/client/sveltekit.md +44 -0
- package/core/templates/.frameworks/client/vite-react.md +45 -0
- package/core/templates/CLAUDE.md +531 -0
- package/core/templates/CLOUD_STATE.md +55 -0
- package/core/templates/CONTRACTS.md +16 -0
- package/core/templates/TASKS_HISTORY.md +6 -0
- package/core/templates/backend/CLAUDE.md +207 -0
- package/core/templates/client/CLAUDE.md +213 -0
- package/core/templates/shared/.gitkeep +0 -0
- package/core/templates/shared/wiring.config.json +14 -0
- package/core/workflow/agent.js +1404 -0
- package/core/workflow/complete.js +354 -0
- package/core/workflow/guards.js +643 -0
- package/core/workflow/reset.js +246 -0
- package/core/workflow/restart.js +243 -0
- package/core/workflow/tasks_history.js +120 -0
- package/init.js +35 -32
- package/package.json +2 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Multi-Agent Monorepo Template - Reset
|
|
5
|
+
* Full project wipe with 2-step confirmation.
|
|
6
|
+
*
|
|
7
|
+
* Deletes:
|
|
8
|
+
* - All worktrees
|
|
9
|
+
* - All agent branches (local + remote)
|
|
10
|
+
* - Remote GitHub repository
|
|
11
|
+
* - All project files
|
|
12
|
+
*
|
|
13
|
+
* Run with: npm run reset
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const readline = require('readline');
|
|
21
|
+
const { execSync } = require('child_process');
|
|
22
|
+
|
|
23
|
+
let prompts = null;
|
|
24
|
+
try { prompts = require('prompts'); } catch { prompts = null; }
|
|
25
|
+
|
|
26
|
+
// ── Self-relocate to repo root ────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const ROOT = path.join(__dirname, '..');
|
|
29
|
+
|
|
30
|
+
// ── Colors ────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const c = {
|
|
33
|
+
reset: '\x1b[0m',
|
|
34
|
+
bold: '\x1b[1m',
|
|
35
|
+
dim: '\x1b[2m',
|
|
36
|
+
green: '\x1b[32m',
|
|
37
|
+
yellow: '\x1b[33m',
|
|
38
|
+
red: '\x1b[31m',
|
|
39
|
+
cyan: '\x1b[36m',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
43
|
+
const dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
44
|
+
const green = (s) => `${c.green}${s}${c.reset}`;
|
|
45
|
+
const yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
46
|
+
const red = (s) => `${c.red}${s}${c.reset}`;
|
|
47
|
+
const cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
48
|
+
|
|
49
|
+
const separator = () => console.log(`\n${dim('─'.repeat(60))}\n`);
|
|
50
|
+
|
|
51
|
+
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const RUNTIME_DIR = path.join(ROOT, '.scaffold');
|
|
54
|
+
const CONFIG_PATH = path.join(RUNTIME_DIR, '.config.json');
|
|
55
|
+
const TRACKING_PATH = path.join(RUNTIME_DIR, '.tracking.json');
|
|
56
|
+
const LOCK_FILE = path.join(RUNTIME_DIR, '.initialized');
|
|
57
|
+
|
|
58
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const main = async () => {
|
|
61
|
+
|
|
62
|
+
// ── Guard: must be initialized ───────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
if (!fs.existsSync(LOCK_FILE)) {
|
|
65
|
+
console.log(`\n${red(' ✗ No initialized project found.')}`);
|
|
66
|
+
console.log(dim(' Run multi-agents init to create a project.\n'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Load config and tracking ─────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const config = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) : {};
|
|
73
|
+
const tracking = fs.existsSync(TRACKING_PATH) ? JSON.parse(fs.readFileSync(TRACKING_PATH, 'utf8')) : {};
|
|
74
|
+
const projectName = config.projectName || path.basename(ROOT);
|
|
75
|
+
|
|
76
|
+
// ── Collect active resources ──────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
const activeAgents = [];
|
|
79
|
+
for (const scope of ['client', 'backend', 'shared']) {
|
|
80
|
+
const agents = tracking[scope] || {};
|
|
81
|
+
for (const [agent, data] of Object.entries(agents)) {
|
|
82
|
+
if (data && data.branch) {
|
|
83
|
+
activeAgents.push({ scope, agent, data });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Get remote ────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
let remoteUrl = null;
|
|
91
|
+
try {
|
|
92
|
+
remoteUrl = execSync('git remote get-url origin', { cwd: ROOT, encoding: 'utf8' }).trim();
|
|
93
|
+
} catch {}
|
|
94
|
+
|
|
95
|
+
// ── Display warning ───────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
separator();
|
|
98
|
+
console.log(`${red(bold(' ⚠ FULL PROJECT RESET'))}\n`);
|
|
99
|
+
console.log(` ${bold('This will permanently delete:')}\n`);
|
|
100
|
+
|
|
101
|
+
// Project files
|
|
102
|
+
console.log(` ${red('Project files')}`);
|
|
103
|
+
console.log(` - All source code and configuration files`);
|
|
104
|
+
console.log(` - All scaffold and workflow files\n`);
|
|
105
|
+
|
|
106
|
+
// Active agents
|
|
107
|
+
if (activeAgents.length > 0) {
|
|
108
|
+
console.log(` ${red('Active agent workspaces')}`);
|
|
109
|
+
for (const { agent, scope, data } of activeAgents) {
|
|
110
|
+
console.log(`\n ${bold(`${agent} (${scope})`)}`);
|
|
111
|
+
console.log(` - Branch (${data.branch})`);
|
|
112
|
+
console.log(` - Remote branch (origin/${data.branch})`);
|
|
113
|
+
if (data.worktreePath) {
|
|
114
|
+
console.log(` - Worktree (${path.relative(ROOT, data.worktreePath)})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Remote repo
|
|
121
|
+
if (remoteUrl) {
|
|
122
|
+
console.log(` ${red('Remote repository')}`);
|
|
123
|
+
console.log(` - ${remoteUrl}\n`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(` ${red(bold('This cannot be undone.'))}\n`);
|
|
127
|
+
separator();
|
|
128
|
+
|
|
129
|
+
// ── Step 1: First confirmation ────────────────────────────────────────────────
|
|
130
|
+
|
|
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
|
+
|
|
134
|
+
if (prompts && process.stdin.isTTY) {
|
|
135
|
+
const step1 = await prompts({
|
|
136
|
+
type: 'select',
|
|
137
|
+
name: 'value',
|
|
138
|
+
message: 'Are you sure you want to permanently delete this project?',
|
|
139
|
+
choices: [
|
|
140
|
+
{ title: red('Yes - I understand this cannot be undone'), value: 'yes' },
|
|
141
|
+
{ title: 'No - Cancel', value: 'no' },
|
|
142
|
+
],
|
|
143
|
+
}, { onCancel: () => process.exit(0) });
|
|
144
|
+
|
|
145
|
+
if (step1.value !== 'yes') {
|
|
146
|
+
console.log(dim('\n Reset cancelled.\n'));
|
|
147
|
+
process.exit(0);
|
|
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
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Step 2: Type project name ─────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
separator();
|
|
161
|
+
console.log(` ${yellow('To confirm, type the project name exactly:')}`);
|
|
162
|
+
console.log(` ${cyan(bold(projectName))}\n`);
|
|
163
|
+
|
|
164
|
+
const typed = await ask(` ${bold('Project name')}: `);
|
|
165
|
+
rl.close();
|
|
166
|
+
|
|
167
|
+
if (typed !== projectName) {
|
|
168
|
+
console.log(`\n${red(' ✗ Project name does not match. Reset cancelled.')}\n`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Execute wipe ──────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
separator();
|
|
175
|
+
console.log(`${yellow(' Wiping project...')}\n`);
|
|
176
|
+
|
|
177
|
+
// Remove worktrees
|
|
178
|
+
for (const { agent, data } of activeAgents) {
|
|
179
|
+
if (data.worktreePath && fs.existsSync(data.worktreePath)) {
|
|
180
|
+
try {
|
|
181
|
+
execSync(`git worktree remove "${data.worktreePath}" --force`, { cwd: ROOT, stdio: 'pipe' });
|
|
182
|
+
console.log(` ${green('✓')} Worktree removed (${agent})`);
|
|
183
|
+
} catch {}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Delete local branches
|
|
188
|
+
for (const { agent, data } of activeAgents) {
|
|
189
|
+
try {
|
|
190
|
+
execSync(`git branch -D ${data.branch}`, { cwd: ROOT, stdio: 'pipe' });
|
|
191
|
+
console.log(` ${green('✓')} Local branch deleted (${agent})`);
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Delete remote branches
|
|
196
|
+
for (const { agent, data } of activeAgents) {
|
|
197
|
+
try {
|
|
198
|
+
execSync(`git push origin --delete ${data.branch}`, { cwd: ROOT, stdio: 'pipe' });
|
|
199
|
+
console.log(` ${green('✓')} Remote branch deleted (${agent})`);
|
|
200
|
+
} catch {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Delete remote repo via gh CLI if available
|
|
204
|
+
if (remoteUrl) {
|
|
205
|
+
try {
|
|
206
|
+
const repoPath = remoteUrl.replace('https://github.com/', '').replace('git@github.com:', '').replace('.git', '');
|
|
207
|
+
execSync(`gh repo delete ${repoPath} --yes`, { stdio: 'pipe' });
|
|
208
|
+
console.log(` ${green('✓')} Remote repository deleted`);
|
|
209
|
+
} catch {
|
|
210
|
+
console.log(` ${yellow('!')} Could not delete remote repository automatically.`);
|
|
211
|
+
console.log(dim(` Delete manually at: ${remoteUrl}`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Wipe project files (keep .git temporarily for branch ops above)
|
|
216
|
+
const keepList = ['.git'];
|
|
217
|
+
const entries = fs.readdirSync(ROOT);
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (keepList.includes(entry)) continue;
|
|
220
|
+
try {
|
|
221
|
+
fs.rmSync(path.join(ROOT, entry), { recursive: true, force: true });
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
224
|
+
console.log(` ${green('✓')} Project files removed`);
|
|
225
|
+
|
|
226
|
+
// Remove .git
|
|
227
|
+
try {
|
|
228
|
+
fs.rmSync(path.join(ROOT, '.git'), { recursive: true, force: true });
|
|
229
|
+
console.log(` ${green('✓')} Git history removed`);
|
|
230
|
+
} catch {}
|
|
231
|
+
|
|
232
|
+
// ── Post-wipe instructions ────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
separator();
|
|
235
|
+
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`);
|
|
238
|
+
separator();
|
|
239
|
+
|
|
240
|
+
process.exit(0);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
main().catch((err) => {
|
|
244
|
+
console.error(err);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Multi-Agent Monorepo Template - Agent Restarter
|
|
5
|
+
* Run with: npm run restart
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execSync, spawn } = require('child_process');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
let prompts;
|
|
14
|
+
try { prompts = require('prompts'); } catch { prompts = null; }
|
|
15
|
+
|
|
16
|
+
// ── Colour helpers ────────────────────────────────────────────────────────────
|
|
17
|
+
const rst = '\x1b[0m';
|
|
18
|
+
const dim = s => `\x1b[2m${s}${rst}`;
|
|
19
|
+
const bold = s => `\x1b[1m${s}${rst}`;
|
|
20
|
+
const green = s => `\x1b[32m${s}${rst}`;
|
|
21
|
+
const yellow = s => `\x1b[33m${s}${rst}`;
|
|
22
|
+
const red = s => `\x1b[31m${s}${rst}`;
|
|
23
|
+
const cyan = s => `\x1b[36m${s}${rst}`;
|
|
24
|
+
const sep = () => console.log(dim('─'.repeat(60)));
|
|
25
|
+
|
|
26
|
+
// ── ROOT resolution (works from any worktree) ─────────────────────────────────
|
|
27
|
+
const ROOT = (() => {
|
|
28
|
+
try {
|
|
29
|
+
const common = execSync('git rev-parse --git-common-dir', { stdio: 'pipe' }).toString().trim();
|
|
30
|
+
return common.endsWith('/.git') ? common.slice(0, -5) : path.resolve(common, '..');
|
|
31
|
+
} catch {
|
|
32
|
+
console.error(red(' Not inside a git repository.\n'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
const SCAFFOLD_DIR = path.join(ROOT, '.scaffold');
|
|
38
|
+
const TRACKING_PATH = path.join(SCAFFOLD_DIR, '.tracking.json');
|
|
39
|
+
const CONFIG_PATH = path.join(SCAFFOLD_DIR, '.config.json');
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
42
|
+
console.log(red('\n Missing .scaffold/.config.json.'));
|
|
43
|
+
console.log(dim(' Run npm run init first.\n'));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
48
|
+
const tracking = fs.existsSync(TRACKING_PATH)
|
|
49
|
+
? JSON.parse(fs.readFileSync(TRACKING_PATH, 'utf8'))
|
|
50
|
+
: {};
|
|
51
|
+
|
|
52
|
+
const ENTRY_CWD = process.cwd();
|
|
53
|
+
|
|
54
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
55
|
+
const INIT_AGENTS = { client: ['UI'], backend: ['INIT'] };
|
|
56
|
+
|
|
57
|
+
const DEPENDENCIES = {
|
|
58
|
+
client: { UI: ['LOGIC', 'FORMS', 'ROUTING', 'TESTING', 'ACCESSIBILITY'] },
|
|
59
|
+
backend: { INIT: ['API', 'LOGIC', 'AUTH', 'DB', 'EVENTS', 'JOBS', 'TESTING'] },
|
|
60
|
+
shared: {},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── Git worktrees ─────────────────────────────────────────────────────────────
|
|
64
|
+
const getWorktrees = () => {
|
|
65
|
+
try {
|
|
66
|
+
const out = execSync('git worktree list --porcelain', { cwd: ROOT, stdio: 'pipe' }).toString();
|
|
67
|
+
return out.trim().split('\n\n').reduce((acc, block) => {
|
|
68
|
+
const lines = block.split('\n');
|
|
69
|
+
const wtPath = lines.find(l => l.startsWith('worktree '))?.replace('worktree ', '').trim();
|
|
70
|
+
const branch = lines.find(l => l.startsWith('branch '))?.replace('branch refs/heads/', '').trim();
|
|
71
|
+
if (wtPath && branch && wtPath !== ROOT) acc.push({ path: wtPath, branch });
|
|
72
|
+
return acc;
|
|
73
|
+
}, []);
|
|
74
|
+
} catch { return []; }
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// ── Candidate list: tracked + untracked ──────────────────────────────────────
|
|
78
|
+
const buildCandidates = () => {
|
|
79
|
+
const worktrees = getWorktrees();
|
|
80
|
+
const candidates = [];
|
|
81
|
+
|
|
82
|
+
for (const scope of ['client', 'backend', 'shared']) {
|
|
83
|
+
for (const [agent, data] of Object.entries(tracking[scope] || {})) {
|
|
84
|
+
if (!data?.branch) continue;
|
|
85
|
+
const wt = worktrees.find(w => w.branch === data.branch);
|
|
86
|
+
candidates.push({
|
|
87
|
+
scope, agent,
|
|
88
|
+
branch: data.branch,
|
|
89
|
+
worktreePath: data.worktreePath || wt?.path || null,
|
|
90
|
+
status: data.status || 'ACTIVE',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const wt of worktrees) {
|
|
96
|
+
const m = wt.branch.match(/^agent\/(client|backend|shared)\/([A-Z]+)\//);
|
|
97
|
+
if (!m || candidates.find(c => c.branch === wt.branch)) continue;
|
|
98
|
+
candidates.push({ scope: m[1], agent: m[2], branch: wt.branch, worktreePath: wt.path, status: 'UNTRACKED' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return candidates;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const detectCurrentAgent = (candidates) =>
|
|
105
|
+
candidates.find(c => c.worktreePath && ENTRY_CWD.startsWith(c.worktreePath));
|
|
106
|
+
|
|
107
|
+
// ── Prompts ───────────────────────────────────────────────────────────────────
|
|
108
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
109
|
+
const ask = q => new Promise(r => rl.question(q, a => r(a.trim())));
|
|
110
|
+
|
|
111
|
+
const arrowSelect = async (message, choices) => {
|
|
112
|
+
if (prompts && process.stdin.isTTY) {
|
|
113
|
+
const res = await prompts({
|
|
114
|
+
type: 'select', name: 'value', message,
|
|
115
|
+
choices: choices.map((c, i) => ({ title: c.label, value: i })),
|
|
116
|
+
}, { onCancel: () => process.exit(0) });
|
|
117
|
+
return res.value;
|
|
118
|
+
}
|
|
119
|
+
choices.forEach((c, i) => console.log(` ${dim(`${i + 1}.`)} ${c.label}`));
|
|
120
|
+
return new Promise(resolve => {
|
|
121
|
+
rl.question(`\n Select (1-${choices.length}): `, ans => {
|
|
122
|
+
const n = parseInt(ans) - 1;
|
|
123
|
+
resolve(!isNaN(n) && n >= 0 && n < choices.length ? n : 0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ── Wipe a single agent slot ──────────────────────────────────────────────────
|
|
129
|
+
const wipeAgent = ({ scope, agent, branch, worktreePath }) => {
|
|
130
|
+
try { execSync(`git worktree remove "${worktreePath}" --force`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
131
|
+
try { execSync(`git branch -D ${branch}`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
132
|
+
try { execSync(`git push origin --delete ${branch}`, { cwd: ROOT, stdio: 'pipe' }); } catch {}
|
|
133
|
+
|
|
134
|
+
if (tracking[scope]?.[agent]) {
|
|
135
|
+
tracking[scope][agent] = { branch: null, timestamp: null, launchedAt: null, status: null, missingCount: 0, worktreePath: null };
|
|
136
|
+
fs.writeFileSync(TRACKING_PATH, JSON.stringify(tracking, null, 2), 'utf8');
|
|
137
|
+
}
|
|
138
|
+
console.log(` ${green('✓')} ${agent} wiped`);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
142
|
+
const main = async () => {
|
|
143
|
+
console.log('\n');
|
|
144
|
+
console.log(bold(cyan(' Multi-Agent Monorepo Template')));
|
|
145
|
+
console.log(dim(` Agent Restarter - ${config.projectName}\n`));
|
|
146
|
+
sep();
|
|
147
|
+
|
|
148
|
+
const candidates = buildCandidates();
|
|
149
|
+
|
|
150
|
+
if (candidates.length === 0) {
|
|
151
|
+
console.log(yellow('\n No active agents found. Nothing to restart.\n'));
|
|
152
|
+
console.log(dim(` Run ${cyan('npm run agent')} to start a new task.\n`));
|
|
153
|
+
rl.close(); process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Detect location ───────────────────────────────────────────────────────
|
|
157
|
+
let candidate = detectCurrentAgent(candidates);
|
|
158
|
+
|
|
159
|
+
if (!candidate) {
|
|
160
|
+
sep();
|
|
161
|
+
console.log(`\n${bold('* Select agent to restart:')}\n`);
|
|
162
|
+
const idx = await arrowSelect('Select agent', [
|
|
163
|
+
...candidates.map(c => ({
|
|
164
|
+
label: `${bold(c.agent)} ${dim(`(${c.scope})`)} ${dim(c.branch)} ${c.status === 'UNTRACKED' ? yellow('untracked') : dim(c.status)}`,
|
|
165
|
+
})),
|
|
166
|
+
{ label: dim('← cancel') },
|
|
167
|
+
]);
|
|
168
|
+
if (idx === candidates.length) {
|
|
169
|
+
console.log(dim('\n Cancelled.\n')); rl.close(); process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
candidate = candidates[idx];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { scope, agent, branch, worktreePath } = candidate;
|
|
175
|
+
const isInitAgent = (INIT_AGENTS[scope] || []).includes(agent);
|
|
176
|
+
const deps = (DEPENDENCIES[scope] || {})[agent] || [];
|
|
177
|
+
const activeDeps = deps.filter(dep => tracking[scope]?.[dep]?.branch);
|
|
178
|
+
|
|
179
|
+
// ── Show agent info ───────────────────────────────────────────────────────
|
|
180
|
+
sep();
|
|
181
|
+
console.log(`\n ${bold('Agent:')} ${cyan(agent)} ${dim(`(${scope})`)}`);
|
|
182
|
+
console.log(` ${bold('Branch:')} ${dim(branch)}`);
|
|
183
|
+
if (worktreePath) console.log(` ${bold('Path:')} ${dim(worktreePath)}\n`);
|
|
184
|
+
|
|
185
|
+
// ── Cascade warning ───────────────────────────────────────────────────────
|
|
186
|
+
if (isInitAgent) {
|
|
187
|
+
console.log(yellow(` ⚠ ${agent} is a scaffold agent. Restarting will also wipe:\n`));
|
|
188
|
+
deps.forEach(dep => console.log(` ${dim('→')} ${dep}`));
|
|
189
|
+
console.log(`\n ${red('All dependent work will be permanently lost.')}\n`);
|
|
190
|
+
} else if (activeDeps.length > 0) {
|
|
191
|
+
console.log(yellow(' ⚠ Active dependent agents that will also be wiped:\n'));
|
|
192
|
+
activeDeps.forEach(dep => console.log(` ${dim('→')} ${dep}`));
|
|
193
|
+
console.log('');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Decision block ────────────────────────────────────────────────────────
|
|
197
|
+
const actionIdx = await arrowSelect('What would you like to do?', [
|
|
198
|
+
{ label: `${bold('Init this agent')} - wipe and restart fresh` },
|
|
199
|
+
{ label: `${bold('Abort')} - go back or exit` },
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
if (actionIdx === 1) {
|
|
203
|
+
sep();
|
|
204
|
+
const abortIdx = await arrowSelect('What would you like to do?', [
|
|
205
|
+
{ label: `${bold('Take me back')} - return to where you were` },
|
|
206
|
+
{ label: `${bold('Exit')} - stay here and exit` },
|
|
207
|
+
]);
|
|
208
|
+
if (abortIdx === 0) {
|
|
209
|
+
console.log(`\n ${green('✓')} Run this to return:\n`);
|
|
210
|
+
console.log(` ${cyan(`cd "${ENTRY_CWD}"`)}\n`);
|
|
211
|
+
} else {
|
|
212
|
+
console.log(dim('\n Exited.\n'));
|
|
213
|
+
}
|
|
214
|
+
rl.close(); process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Wipe ──────────────────────────────────────────────────────────────────
|
|
218
|
+
sep();
|
|
219
|
+
console.log(`\n ${bold('Wiping')} ${cyan(agent)}...\n`);
|
|
220
|
+
|
|
221
|
+
const depsToWipe = isInitAgent ? deps : activeDeps;
|
|
222
|
+
for (const dep of depsToWipe) {
|
|
223
|
+
const d = tracking[scope]?.[dep];
|
|
224
|
+
const wt = d?.branch ? getWorktrees().find(w => w.branch === d.branch) : null;
|
|
225
|
+
if (d?.branch) wipeAgent({ scope, agent: dep, branch: d.branch, worktreePath: d.worktreePath || wt?.path });
|
|
226
|
+
}
|
|
227
|
+
wipeAgent(candidate);
|
|
228
|
+
|
|
229
|
+
// ── Chain into agent.js ───────────────────────────────────────────────────
|
|
230
|
+
sep();
|
|
231
|
+
console.log(`\n ${green('✓')} Restart complete. Launching agent selector...\n`);
|
|
232
|
+
rl.close();
|
|
233
|
+
|
|
234
|
+
spawn('node', [path.join(ROOT, '.workflow', 'agent.js'), `--scope=${scope}`, `--agent=${agent}`], {
|
|
235
|
+
cwd: ROOT, stdio: 'inherit',
|
|
236
|
+
}).on('exit', code => process.exit(code ?? 0));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
main().catch(err => {
|
|
240
|
+
console.error(red(`\n Error: ${err.message}\n`));
|
|
241
|
+
rl.close();
|
|
242
|
+
process.exit(1);
|
|
243
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Multi-Agent Monorepo Template - Tasks History
|
|
5
|
+
* Utility for reading and writing to TASKS_HISTORY.md
|
|
6
|
+
*
|
|
7
|
+
* Used by: agent.js (on launch), complete.js (on completion)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const HISTORY_FILE = 'TASKS_HISTORY.md';
|
|
16
|
+
|
|
17
|
+
// ── Write session entry on agent launch ───────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const writeSessionEntry = (ROOT, { scope, agent, branch, task, launchedAt }) => {
|
|
20
|
+
const historyPath = path.join(ROOT, HISTORY_FILE);
|
|
21
|
+
|
|
22
|
+
const entry = `
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ${new Date(launchedAt).toISOString().slice(0, 10)} | ${agent} | ${scope} | ${branch}
|
|
26
|
+
Launched : ${launchedAt}
|
|
27
|
+
Status : IN PROGRESS
|
|
28
|
+
Task : ${task}
|
|
29
|
+
|
|
30
|
+
### User Overrides
|
|
31
|
+
<!-- agent.js appends [USER OVERRIDE] entries here during session -->
|
|
32
|
+
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
fs.appendFileSync(historyPath, entry, 'utf8');
|
|
37
|
+
} catch { /* best-effort */ }
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ── Update session status on completion ───────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const updateSessionStatus = (ROOT, { branch, status, completedAt, notes }) => {
|
|
43
|
+
const historyPath = path.join(ROOT, HISTORY_FILE);
|
|
44
|
+
if (!fs.existsSync(historyPath)) return;
|
|
45
|
+
|
|
46
|
+
let content = fs.readFileSync(historyPath, 'utf8');
|
|
47
|
+
|
|
48
|
+
// Find the session block by branch name and update status
|
|
49
|
+
const sessionRegex = new RegExp(
|
|
50
|
+
`(## [^\\n]+ \\| ${branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n[\\s\\S]*?Status : )IN PROGRESS`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (sessionRegex.test(content)) {
|
|
54
|
+
content = content.replace(
|
|
55
|
+
sessionRegex,
|
|
56
|
+
`$1${status}`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Append completion info after status line
|
|
60
|
+
content = content.replace(
|
|
61
|
+
`Status : ${status}\nTask`,
|
|
62
|
+
`Status : ${status}\nCompleted: ${completedAt}${notes ? `\nNotes : ${notes}` : ''}\nTask`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(historyPath, content, 'utf8');
|
|
67
|
+
} catch { /* best-effort */ }
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ── Append user override entry ────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const appendUserOverride = (ROOT, { branch, timestamp, input, deviation, action }) => {
|
|
74
|
+
const historyPath = path.join(ROOT, HISTORY_FILE);
|
|
75
|
+
if (!fs.existsSync(historyPath)) return;
|
|
76
|
+
|
|
77
|
+
let content = fs.readFileSync(historyPath, 'utf8');
|
|
78
|
+
|
|
79
|
+
const overrideEntry = `- [USER OVERRIDE] ${timestamp}
|
|
80
|
+
Input : ${input}
|
|
81
|
+
Deviation: ${deviation}
|
|
82
|
+
Action : ${action}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// Insert override under the correct session's User Overrides section
|
|
86
|
+
const placeholder = `### User Overrides\n<!-- agent.js appends [USER OVERRIDE] entries here during session -->`;
|
|
87
|
+
const sessionBlock = `## [^\\n]+ \\| ${branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`;
|
|
88
|
+
const sessionRegex = new RegExp(`(${sessionBlock}[\\s\\S]*?${placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`);
|
|
89
|
+
|
|
90
|
+
if (sessionRegex.test(content)) {
|
|
91
|
+
content = content.replace(
|
|
92
|
+
sessionRegex,
|
|
93
|
+
`$1\n${overrideEntry}`
|
|
94
|
+
);
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(historyPath, content, 'utf8');
|
|
97
|
+
} catch { /* best-effort */ }
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ── Initialize TASKS_HISTORY.md if missing ────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const ensureHistoryFile = (ROOT) => {
|
|
104
|
+
const historyPath = path.join(ROOT, HISTORY_FILE);
|
|
105
|
+
if (!fs.existsSync(historyPath)) {
|
|
106
|
+
fs.writeFileSync(historyPath, `# TASKS_HISTORY.md
|
|
107
|
+
# Audit trail of all agent sessions and user overrides.
|
|
108
|
+
# Written by agent.js (on launch) and complete.js (on completion).
|
|
109
|
+
# Read by all agents at session start for full project history.
|
|
110
|
+
# Never edit manually.
|
|
111
|
+
`, 'utf8');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
writeSessionEntry,
|
|
117
|
+
updateSessionStatus,
|
|
118
|
+
appendUserOverride,
|
|
119
|
+
ensureHistoryFile,
|
|
120
|
+
};
|