multi-agents-cli 1.0.52 → 1.0.54
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 +1413 -0
- package/core/workflow/complete.js +403 -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 +32 -15
- 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
|
+
};
|
package/init.js
CHANGED
|
@@ -1060,20 +1060,11 @@ const main = async () => {
|
|
|
1060
1060
|
separator();
|
|
1061
1061
|
console.log(`\n${bold('Setting up your project...')}\n`);
|
|
1062
1062
|
|
|
1063
|
-
// ──
|
|
1063
|
+
// ── Load bundled core ───────────────────────────────────────────────────────
|
|
1064
1064
|
|
|
1065
|
-
const
|
|
1066
|
-
const CORE_DIR = path.join(ROOT, '.agents-core');
|
|
1065
|
+
const CORE_DIR = path.join(__dirname, 'core');
|
|
1067
1066
|
|
|
1068
|
-
console.log(`
|
|
1069
|
-
try {
|
|
1070
|
-
execSync(`git clone "${CORE_REPO}" "${CORE_DIR}"`, { stdio: 'pipe' });
|
|
1071
|
-
console.log(` ${green('✓')} Templates fetched`);
|
|
1072
|
-
} catch (err) {
|
|
1073
|
-
console.log(` ${red('✗')} Failed to fetch templates. Check your internet connection.`);
|
|
1074
|
-
rl.close();
|
|
1075
|
-
process.exit(1);
|
|
1076
|
-
}
|
|
1067
|
+
console.log(` ${green('✓')} Templates ready`);
|
|
1077
1068
|
|
|
1078
1069
|
const TEMPLATES = path.join(CORE_DIR, 'templates');
|
|
1079
1070
|
|
|
@@ -1123,8 +1114,6 @@ const main = async () => {
|
|
|
1123
1114
|
fs.mkdirSync(WORKFLOW_DEST, { recursive: true });
|
|
1124
1115
|
copyDir(WORKFLOW_SRC, WORKFLOW_DEST);
|
|
1125
1116
|
console.log(` ${green('✓')} Workflow scripts copied (.workflow/)`);
|
|
1126
|
-
|
|
1127
|
-
execSync(`rm -rf "${CORE_DIR}"`);
|
|
1128
1117
|
console.log(` ${green('✓')} Temporary files cleaned up`);
|
|
1129
1118
|
|
|
1130
1119
|
// ── Write @config values ─────────────────────────────────────────────────────
|
|
@@ -1159,7 +1148,6 @@ const main = async () => {
|
|
|
1159
1148
|
}
|
|
1160
1149
|
|
|
1161
1150
|
ensureGitignore('worktrees/');
|
|
1162
|
-
ensureGitignore('.agents-core/');
|
|
1163
1151
|
ensureGitignore('.scaffold/');
|
|
1164
1152
|
ensureGitignore('.workflow/');
|
|
1165
1153
|
ensureGitignore('node_modules/');
|
|
@@ -1213,6 +1201,35 @@ const main = async () => {
|
|
|
1213
1201
|
);
|
|
1214
1202
|
console.log(` ${green('✓')} .scaffold/.config.json written`);
|
|
1215
1203
|
|
|
1204
|
+
// ── Write scope-policy.json ──────────────────────────────────────────────────
|
|
1205
|
+
|
|
1206
|
+
const scopePolicy = {
|
|
1207
|
+
client: {
|
|
1208
|
+
allowed: ['client/**'],
|
|
1209
|
+
blocked: ['backend/**', 'shared/**', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md'],
|
|
1210
|
+
},
|
|
1211
|
+
backend: {
|
|
1212
|
+
allowed: ['backend/**'],
|
|
1213
|
+
blocked: ['client/**', 'shared/**', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md'],
|
|
1214
|
+
agentOverrides: {
|
|
1215
|
+
INIT: {
|
|
1216
|
+
allowed: ['backend/**', 'shared/wiring.config.json', 'CONTRACTS.md'],
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
shared: {
|
|
1221
|
+
allowed: ['CLOUD_STATE.md', 'CLOUD.md', 'CLOUD_TEARDOWN.md', '.github/**', 'docker-compose*.yml', 'Dockerfile', '**/.env.example'],
|
|
1222
|
+
blocked: ['client/**', 'backend/**', 'CONTRACTS.md', 'BUILD_STATE.md', 'TASKS_HISTORY.md'],
|
|
1223
|
+
},
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
fs.writeFileSync(
|
|
1227
|
+
path.join(RUNTIME_DIR, 'scope-policy.json'),
|
|
1228
|
+
JSON.stringify(scopePolicy, null, 2),
|
|
1229
|
+
'utf8'
|
|
1230
|
+
);
|
|
1231
|
+
console.log(` ${green('✓')} .scaffold/scope-policy.json written`);
|
|
1232
|
+
|
|
1216
1233
|
// ── Generate BUILD_STATE.md ──────────────────────────────────────────────────
|
|
1217
1234
|
|
|
1218
1235
|
const backendDisplay = backendType === 'integrated'
|