agentxchain 0.8.2 → 0.8.4
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 +60 -13
- package/bin/agentxchain.js +35 -0
- package/package.json +2 -1
- package/scripts/agentxchain-autonudge.applescript +122 -0
- package/scripts/build-binary.sh +42 -0
- package/scripts/publish-npm.sh +95 -0
- package/scripts/run-autonudge.sh +111 -0
- package/scripts/stop-autonudge.sh +13 -0
- package/src/adapters/cursor-local.js +65 -6
- package/src/commands/claim.js +17 -1
- package/src/commands/doctor.js +146 -0
- package/src/commands/init.js +16 -3
- package/src/commands/kickoff.js +119 -0
- package/src/commands/start.js +87 -6
- package/src/commands/supervise.js +100 -0
- package/src/commands/validate.js +49 -0
- package/src/commands/watch.js +50 -0
- package/src/lib/seed-prompt-polling.js +12 -0
- package/src/lib/validation.js +179 -0
|
@@ -9,8 +9,13 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
9
9
|
const agents = filterAgents(config, opts.agent);
|
|
10
10
|
const agentEntries = Object.entries(agents);
|
|
11
11
|
const total = agentEntries.length;
|
|
12
|
+
const selectedAgent = opts.agent ? agents[opts.agent] : null;
|
|
13
|
+
const isPmKickoff = !!selectedAgent && isPmLike(opts.agent, selectedAgent) && !opts.remaining;
|
|
12
14
|
|
|
13
15
|
console.log(chalk.bold(` Setting up ${total} agents: ${Object.keys(agents).join(', ')}`));
|
|
16
|
+
if (isPmKickoff) {
|
|
17
|
+
console.log(chalk.dim(' PM kickoff mode: start with human-PM planning before launching the full team.'));
|
|
18
|
+
}
|
|
14
19
|
console.log('');
|
|
15
20
|
|
|
16
21
|
const promptDir = join(root, '.agentxchain-prompts');
|
|
@@ -18,7 +23,9 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
18
23
|
|
|
19
24
|
// Save all prompts first
|
|
20
25
|
for (const [id, agent] of agentEntries) {
|
|
21
|
-
const prompt =
|
|
26
|
+
const prompt = isPmKickoff
|
|
27
|
+
? generateKickoffPrompt(id, agent, config)
|
|
28
|
+
: generatePollingPrompt(id, agent, config);
|
|
22
29
|
writeFileSync(join(promptDir, `${id}.prompt.md`), prompt);
|
|
23
30
|
}
|
|
24
31
|
|
|
@@ -28,7 +35,9 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
28
35
|
|
|
29
36
|
for (let i = 0; i < agentEntries.length; i++) {
|
|
30
37
|
const [id, agent] = agentEntries[i];
|
|
31
|
-
const prompt =
|
|
38
|
+
const prompt = isPmKickoff
|
|
39
|
+
? generateKickoffPrompt(id, agent, config)
|
|
40
|
+
: generatePollingPrompt(id, agent, config);
|
|
32
41
|
|
|
33
42
|
// Create symlink: .agentxchain-workspaces/<id> -> project root
|
|
34
43
|
const agentWorkspace = join(workspacesDir, id);
|
|
@@ -57,8 +66,13 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
57
66
|
console.log(` ${chalk.bold('In the new Cursor window:')}`);
|
|
58
67
|
console.log(` 1. Open chat (${chalk.bold('Cmd+L')})`);
|
|
59
68
|
console.log(` 2. Paste the prompt (${chalk.bold('Cmd+V')})`);
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
if (isPmKickoff) {
|
|
70
|
+
console.log(` 3. ${chalk.bold('Select Agent mode')} and discuss requirements with PM`);
|
|
71
|
+
console.log(` 4. Update planning docs together (PROJECT / REQUIREMENTS / ROADMAP)`);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(` 3. ${chalk.bold('Select Agent mode')} (not Ask/Edit)`);
|
|
74
|
+
console.log(` 4. Send it (${chalk.bold('Enter')})`);
|
|
75
|
+
}
|
|
62
76
|
|
|
63
77
|
if (i < agentEntries.length - 1) {
|
|
64
78
|
console.log('');
|
|
@@ -78,12 +92,20 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
78
92
|
console.log(chalk.green(` ✓ All ${total} agents launched in separate Cursor windows.`));
|
|
79
93
|
console.log('');
|
|
80
94
|
console.log(` ${chalk.cyan('Now run:')}`);
|
|
81
|
-
|
|
95
|
+
if (isPmKickoff) {
|
|
96
|
+
console.log(` ${chalk.bold('agentxchain start --remaining')} ${chalk.dim('# launch the rest of the team when PM plan is ready')}`);
|
|
97
|
+
console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# start watch + auto-nudge')}`);
|
|
98
|
+
console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock to begin team turns')}`);
|
|
99
|
+
} else {
|
|
100
|
+
console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# recommended: watch + auto-nudge in one command')}`);
|
|
101
|
+
console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock — agents start claiming turns')}`);
|
|
102
|
+
}
|
|
82
103
|
console.log('');
|
|
83
104
|
console.log(` ${chalk.dim('Other commands:')}`);
|
|
84
105
|
console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# check who holds the lock')}`);
|
|
85
106
|
console.log(` ${chalk.bold('agentxchain claim')} ${chalk.dim('# pause agents and take control')}`);
|
|
86
|
-
console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('#
|
|
107
|
+
console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# watcher / trigger writer')}`);
|
|
108
|
+
console.log(` ${chalk.bold('agentxchain doctor')} ${chalk.dim('# check local setup + trigger health')}`);
|
|
87
109
|
console.log('');
|
|
88
110
|
console.log(chalk.dim(' Agents self-coordinate via lock.json polling (sleep 60s between checks).'));
|
|
89
111
|
console.log(chalk.dim(' Re-paste a prompt: cat .agentxchain-prompts/<agent>.prompt.md | pbcopy'));
|
|
@@ -124,3 +146,40 @@ function openCursorWindow(folderPath) {
|
|
|
124
146
|
execSync(`cursor --new-window "${folderPath}"`, { stdio: 'ignore' });
|
|
125
147
|
} catch {}
|
|
126
148
|
}
|
|
149
|
+
|
|
150
|
+
function isPmLike(agentId, agentDef) {
|
|
151
|
+
if (agentId === 'pm') return true;
|
|
152
|
+
const name = String(agentDef?.name || '').toLowerCase();
|
|
153
|
+
return name.includes('product manager');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function generateKickoffPrompt(agentId, agentDef, config) {
|
|
157
|
+
return `You are "${agentId}" — ${agentDef.name}.
|
|
158
|
+
|
|
159
|
+
This is PM kickoff mode. Your job now is to collaborate with the human and finalize scope before autonomous turns begin.
|
|
160
|
+
|
|
161
|
+
Actions:
|
|
162
|
+
1) Read:
|
|
163
|
+
- .planning/PROJECT.md
|
|
164
|
+
- .planning/REQUIREMENTS.md
|
|
165
|
+
- .planning/ROADMAP.md
|
|
166
|
+
- state.md
|
|
167
|
+
- lock.json
|
|
168
|
+
2) Ask the human focused product questions until scope is clear:
|
|
169
|
+
- target user
|
|
170
|
+
- top pain point
|
|
171
|
+
- core workflow
|
|
172
|
+
- MVP boundary
|
|
173
|
+
- success metric
|
|
174
|
+
3) Update planning docs with concrete acceptance criteria and Get Shit Done structure:
|
|
175
|
+
- .planning/ROADMAP.md must define Waves and Phases.
|
|
176
|
+
- Create .planning/phases/phase-1/PLAN.md and TESTS.md.
|
|
177
|
+
4) Update .planning/PM_SIGNOFF.md:
|
|
178
|
+
- Set "Approved: YES" only when human agrees kickoff is complete.
|
|
179
|
+
5) Do NOT start round-robin agent handoffs yet.
|
|
180
|
+
|
|
181
|
+
Context:
|
|
182
|
+
- Project: ${config.project}
|
|
183
|
+
- Human currently holds lock.
|
|
184
|
+
- Once planning is approved, human will launch remaining agents and run release.`;
|
|
185
|
+
}
|
package/src/commands/claim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from 'fs';
|
|
1
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
|
|
@@ -36,6 +36,7 @@ export async function claimCommand(opts) {
|
|
|
36
36
|
claimed_at: new Date().toISOString()
|
|
37
37
|
};
|
|
38
38
|
writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n');
|
|
39
|
+
clearBlockedState(root);
|
|
39
40
|
|
|
40
41
|
console.log('');
|
|
41
42
|
console.log(chalk.green(` ✓ Lock claimed by ${chalk.bold('human')} (turn ${lock.turn_number})`));
|
|
@@ -74,9 +75,24 @@ export async function releaseCommand(opts) {
|
|
|
74
75
|
claimed_at: null
|
|
75
76
|
};
|
|
76
77
|
writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n');
|
|
78
|
+
if (who === 'human') {
|
|
79
|
+
clearBlockedState(root);
|
|
80
|
+
}
|
|
77
81
|
|
|
78
82
|
console.log('');
|
|
79
83
|
console.log(chalk.green(` ✓ Lock released by ${chalk.bold(who)} (turn ${newLock.turn_number})`));
|
|
80
84
|
console.log(chalk.dim(' The Stop hook will coordinate the next agent turn in VS Code.'));
|
|
81
85
|
console.log('');
|
|
82
86
|
}
|
|
87
|
+
|
|
88
|
+
function clearBlockedState(root) {
|
|
89
|
+
const statePath = join(root, 'state.json');
|
|
90
|
+
if (!existsSync(statePath)) return;
|
|
91
|
+
try {
|
|
92
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
93
|
+
if (state.blocked || state.blocked_on) {
|
|
94
|
+
const next = { ...state, blocked: false, blocked_on: null };
|
|
95
|
+
writeFileSync(statePath, JSON.stringify(next, null, 2) + '\n');
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig, loadLock } from '../lib/config.js';
|
|
6
|
+
import { validateProject } from '../lib/validation.js';
|
|
7
|
+
|
|
8
|
+
export async function doctorCommand() {
|
|
9
|
+
const result = loadConfig();
|
|
10
|
+
if (!result) {
|
|
11
|
+
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { root, config } = result;
|
|
16
|
+
const lock = loadLock(root);
|
|
17
|
+
const checks = [];
|
|
18
|
+
|
|
19
|
+
checks.push(checkFile('agentxchain.json', existsSync(join(root, 'agentxchain.json')), 'Project config exists'));
|
|
20
|
+
checks.push(checkFile('lock.json', !!lock, 'Lock file exists'));
|
|
21
|
+
checks.push(checkBinary('cursor', 'Cursor CLI available (optional for non-macOS launch)'));
|
|
22
|
+
checks.push(checkBinary('jq', 'jq installed (required for auto-nudge)'));
|
|
23
|
+
checks.push(checkBinary('osascript', 'osascript available (required for auto-nudge, macOS)'));
|
|
24
|
+
checks.push(checkPm(config));
|
|
25
|
+
checks.push(checkValidation(root, config));
|
|
26
|
+
checks.push(checkWatchProcess());
|
|
27
|
+
checks.push(checkTrigger(root));
|
|
28
|
+
checks.push(checkAccessibility());
|
|
29
|
+
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(chalk.bold(' AgentXchain Doctor'));
|
|
32
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
33
|
+
console.log(chalk.dim(` Project root: ${root}`));
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
for (const c of checks) {
|
|
37
|
+
const badge = c.level === 'pass'
|
|
38
|
+
? chalk.green('PASS')
|
|
39
|
+
: c.level === 'warn'
|
|
40
|
+
? chalk.yellow('WARN')
|
|
41
|
+
: chalk.red('FAIL');
|
|
42
|
+
console.log(` ${badge} ${c.name}`);
|
|
43
|
+
if (c.detail) console.log(` ${chalk.dim(c.detail)}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const failCount = checks.filter(c => c.level === 'fail').length;
|
|
47
|
+
const warnCount = checks.filter(c => c.level === 'warn').length;
|
|
48
|
+
|
|
49
|
+
console.log('');
|
|
50
|
+
if (failCount === 0 && warnCount === 0) {
|
|
51
|
+
console.log(chalk.green(' ✓ Environment looks ready.'));
|
|
52
|
+
} else if (failCount === 0) {
|
|
53
|
+
console.log(chalk.yellow(` Ready with warnings (${warnCount}).`));
|
|
54
|
+
} else {
|
|
55
|
+
console.log(chalk.red(` Not ready: ${failCount} blocking issue(s).`));
|
|
56
|
+
}
|
|
57
|
+
console.log('');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkFile(name, ok, detail) {
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
level: ok ? 'pass' : 'fail',
|
|
64
|
+
detail: ok ? detail : `${name} is missing`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function checkBinary(cmd, detail) {
|
|
69
|
+
try {
|
|
70
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
71
|
+
return { name: cmd, level: 'pass', detail };
|
|
72
|
+
} catch {
|
|
73
|
+
return { name: cmd, level: 'warn', detail: `${cmd} not found in PATH` };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function checkPm(config) {
|
|
78
|
+
if (config.agents?.pm) {
|
|
79
|
+
return { name: 'PM agent', level: 'pass', detail: 'pm exists in agentxchain.json' };
|
|
80
|
+
}
|
|
81
|
+
const hasPmLike = Object.values(config.agents || {}).some(a => String(a?.name || '').toLowerCase().includes('product manager'));
|
|
82
|
+
if (hasPmLike) {
|
|
83
|
+
return { name: 'PM agent', level: 'pass', detail: 'Product Manager role detected' };
|
|
84
|
+
}
|
|
85
|
+
return { name: 'PM agent', level: 'warn', detail: 'No explicit PM agent. PM-first onboarding will be less clear.' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function checkWatchProcess() {
|
|
89
|
+
try {
|
|
90
|
+
execSync('pgrep -f "agentxchain.*watch" >/dev/null', { stdio: 'ignore' });
|
|
91
|
+
return { name: 'watch process', level: 'pass', detail: 'watch appears to be running' };
|
|
92
|
+
} catch {
|
|
93
|
+
return { name: 'watch process', level: 'warn', detail: 'watch not running (start with `agentxchain watch` or `agentxchain supervise --autonudge`)' };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function checkTrigger(root) {
|
|
98
|
+
const triggerPath = join(root, '.agentxchain-trigger.json');
|
|
99
|
+
if (!existsSync(triggerPath)) {
|
|
100
|
+
return { name: 'trigger file', level: 'warn', detail: '.agentxchain-trigger.json not found yet' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const raw = JSON.parse(readFileSync(triggerPath, 'utf8'));
|
|
105
|
+
const triggeredAt = new Date(raw.triggered_at).getTime();
|
|
106
|
+
const ageMs = Date.now() - triggeredAt;
|
|
107
|
+
if (Number.isFinite(triggeredAt) && ageMs <= 120000) {
|
|
108
|
+
return { name: 'trigger file', level: 'pass', detail: `fresh trigger (${Math.round(ageMs / 1000)}s ago)` };
|
|
109
|
+
}
|
|
110
|
+
return { name: 'trigger file', level: 'warn', detail: 'trigger file is stale; lock may not be advancing' };
|
|
111
|
+
} catch {
|
|
112
|
+
return { name: 'trigger file', level: 'warn', detail: 'trigger file exists but is invalid JSON' };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function checkAccessibility() {
|
|
117
|
+
if (process.platform !== 'darwin') {
|
|
118
|
+
return { name: 'macOS Accessibility', level: 'warn', detail: 'only checked on macOS' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
execSync(
|
|
123
|
+
'osascript -e \'tell application "System Events" to get name of first process\'',
|
|
124
|
+
{ stdio: 'pipe' }
|
|
125
|
+
);
|
|
126
|
+
return { name: 'macOS Accessibility', level: 'pass', detail: 'System Events access available' };
|
|
127
|
+
} catch {
|
|
128
|
+
return {
|
|
129
|
+
name: 'macOS Accessibility',
|
|
130
|
+
level: 'warn',
|
|
131
|
+
detail: 'Grant Accessibility to Terminal and Cursor in System Settings.'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function checkValidation(root, config) {
|
|
137
|
+
const validation = validateProject(root, config, { mode: 'kickoff' });
|
|
138
|
+
if (validation.ok) {
|
|
139
|
+
return { name: 'kickoff validation', level: 'pass', detail: 'PM signoff + waves/phases look ready' };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
name: 'kickoff validation',
|
|
143
|
+
level: 'warn',
|
|
144
|
+
detail: `Run \`agentxchain validate --mode kickoff\` (${validation.errors.length} issue(s))`
|
|
145
|
+
};
|
|
146
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -229,6 +229,7 @@ export async function initCommand(opts) {
|
|
|
229
229
|
writeFileSync(join(dir, '.planning', 'REQUIREMENTS.md'), `# Requirements — ${project}\n\n## v1 (MVP)\n\n(PM fills this: numbered list of requirements. Each requirement has one-sentence acceptance criteria.)\n\n| # | Requirement | Acceptance criteria | Phase | Status |\n|---|-------------|-------------------|-------|--------|\n| 1 | | | | Pending |\n\n## v2 (Future)\n\n(Out of scope for MVP. Captured here so they don't creep in.)\n\n## Out of scope\n\n(Explicitly not building.)\n`);
|
|
230
230
|
|
|
231
231
|
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${project}\n\n## Phases\n\n| Phase | Description | Status | Requirements |\n|-------|-------------|--------|-------------|\n| 1 | Discovery + setup | In progress | — |\n\n(PM updates this as phases are planned and completed.)\n`);
|
|
232
|
+
writeFileSync(join(dir, '.planning', 'PM_SIGNOFF.md'), `# PM Signoff — ${project}\n\nApproved: NO\n\n## Discovery Checklist\n- [ ] Target user defined\n- [ ] Core pain point defined\n- [ ] Core workflow defined\n- [ ] MVP scope defined\n- [ ] Out-of-scope list defined\n- [ ] Success metric defined\n\n## Notes for team\n(PM and human add final kickoff notes here.)\n`);
|
|
232
233
|
|
|
233
234
|
// QA structure
|
|
234
235
|
writeFileSync(join(dir, '.planning', 'qa', 'TEST-COVERAGE.md'), `# Test Coverage — ${project}\n\n## Coverage Map\n\n| Feature / Area | Unit tests | Integration tests | E2E tests | Manual QA | UX audit | Status |\n|---------------|-----------|------------------|----------|----------|---------|--------|\n| (QA fills this as testing progresses) | | | | | | |\n\n## Coverage gaps\n\n(Areas with no tests or insufficient coverage.)\n`);
|
|
@@ -245,6 +246,7 @@ export async function initCommand(opts) {
|
|
|
245
246
|
const vsResult = generateVSCodeFiles(dir, config);
|
|
246
247
|
|
|
247
248
|
const agentCount = Object.keys(agents).length;
|
|
249
|
+
const kickoffId = pickPmKickoffId(agents);
|
|
248
250
|
console.log('');
|
|
249
251
|
console.log(chalk.green(` ✓ Created ${chalk.bold(folderName)}/`));
|
|
250
252
|
console.log('');
|
|
@@ -253,7 +255,7 @@ export async function initCommand(opts) {
|
|
|
253
255
|
console.log(` ${chalk.dim('├──')} state.json / state.md / history.jsonl`);
|
|
254
256
|
console.log(` ${chalk.dim('├──')} log.md / HUMAN_TASKS.md`);
|
|
255
257
|
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
256
|
-
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PROJECT.md / REQUIREMENTS.md / ROADMAP.md`);
|
|
258
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PROJECT.md / REQUIREMENTS.md / ROADMAP.md / PM_SIGNOFF.md`);
|
|
257
259
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} research/ / phases/`);
|
|
258
260
|
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} qa/ ${chalk.dim('TEST-COVERAGE / BUGS / UX-AUDIT / ACCEPTANCE-MATRIX')}`);
|
|
259
261
|
console.log(` ${chalk.dim('├──')} .github/agents/ ${chalk.dim(`(${agentCount} .agent.md files)`)}`);
|
|
@@ -264,7 +266,18 @@ export async function initCommand(opts) {
|
|
|
264
266
|
console.log('');
|
|
265
267
|
console.log(` ${chalk.cyan('Next:')}`);
|
|
266
268
|
console.log(` ${chalk.bold(`cd ${folderName}`)}`);
|
|
267
|
-
console.log(` ${chalk.bold(
|
|
268
|
-
console.log(` ${chalk.bold('agentxchain
|
|
269
|
+
console.log(` ${chalk.bold(`agentxchain start --agent ${kickoffId}`)} ${chalk.dim('# PM-first kickoff: align scope with human')}`);
|
|
270
|
+
console.log(` ${chalk.bold('agentxchain start --remaining')} ${chalk.dim('# launch rest of team after PM planning')}`);
|
|
271
|
+
console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# watch + AppleScript nudge loop')}`);
|
|
272
|
+
console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock when you are ready')}`);
|
|
269
273
|
console.log('');
|
|
270
274
|
}
|
|
275
|
+
|
|
276
|
+
function pickPmKickoffId(agents) {
|
|
277
|
+
if (agents.pm) return 'pm';
|
|
278
|
+
for (const [id, def] of Object.entries(agents || {})) {
|
|
279
|
+
const name = String(def?.name || '').toLowerCase();
|
|
280
|
+
if (name.includes('product manager')) return id;
|
|
281
|
+
}
|
|
282
|
+
return Object.keys(agents || {})[0] || 'pm';
|
|
283
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { loadConfig } from '../lib/config.js';
|
|
4
|
+
import { validateProject } from '../lib/validation.js';
|
|
5
|
+
import { startCommand } from './start.js';
|
|
6
|
+
import { releaseCommand } from './claim.js';
|
|
7
|
+
import { superviseCommand } from './supervise.js';
|
|
8
|
+
|
|
9
|
+
export async function kickoffCommand(opts) {
|
|
10
|
+
const result = loadConfig();
|
|
11
|
+
if (!result) {
|
|
12
|
+
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { root, config } = result;
|
|
17
|
+
const pmId = pickPmAgentId(config);
|
|
18
|
+
const ide = opts.ide || 'cursor';
|
|
19
|
+
|
|
20
|
+
if (!pmId) {
|
|
21
|
+
console.log(chalk.red('No agents configured.'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(chalk.bold(' AgentXchain Kickoff Wizard'));
|
|
27
|
+
console.log(chalk.dim(` Project: ${config.project}`));
|
|
28
|
+
console.log(chalk.dim(` PM agent: ${pmId}`));
|
|
29
|
+
console.log(chalk.dim(` IDE: ${ide}`));
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
await startCommand({ ide, agent: pmId, remaining: false, dryRun: false });
|
|
33
|
+
|
|
34
|
+
console.log(chalk.cyan(' PM kickoff launched.'));
|
|
35
|
+
console.log(chalk.dim(' Discuss product scope with PM, then mark .planning/PM_SIGNOFF.md as Approved: YES.'));
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
const { readyForValidation } = await inquirer.prompt([{
|
|
39
|
+
type: 'confirm',
|
|
40
|
+
name: 'readyForValidation',
|
|
41
|
+
message: 'Is PM kickoff complete and signoff updated?',
|
|
42
|
+
default: false
|
|
43
|
+
}]);
|
|
44
|
+
|
|
45
|
+
if (!readyForValidation) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk.yellow(' Kickoff paused.'));
|
|
48
|
+
console.log(chalk.dim(' Resume later with: agentxchain validate --mode kickoff && agentxchain start --remaining'));
|
|
49
|
+
console.log('');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const validation = validateProject(root, config, { mode: 'kickoff' });
|
|
54
|
+
if (!validation.ok) {
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log(chalk.red(' Kickoff validation failed.'));
|
|
57
|
+
for (const err of validation.errors) {
|
|
58
|
+
console.log(chalk.dim(` - ${err}`));
|
|
59
|
+
}
|
|
60
|
+
if (validation.warnings.length > 0) {
|
|
61
|
+
console.log(chalk.yellow(' Warnings:'));
|
|
62
|
+
for (const warn of validation.warnings) {
|
|
63
|
+
console.log(chalk.dim(` - ${warn}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(chalk.bold(' Run: agentxchain validate --mode kickoff'));
|
|
68
|
+
console.log('');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(chalk.green(' ✓ Kickoff validation passed.'));
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
await startCommand({ ide, remaining: true, agent: undefined, dryRun: false });
|
|
76
|
+
|
|
77
|
+
const { doRelease } = await inquirer.prompt([{
|
|
78
|
+
type: 'confirm',
|
|
79
|
+
name: 'doRelease',
|
|
80
|
+
message: 'Release human lock now so agents can start?',
|
|
81
|
+
default: true
|
|
82
|
+
}]);
|
|
83
|
+
|
|
84
|
+
if (doRelease) {
|
|
85
|
+
await releaseCommand({ force: false });
|
|
86
|
+
} else {
|
|
87
|
+
console.log(chalk.dim(' Lock remains with human. Run `agentxchain release` when ready.'));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (ide === 'cursor' && opts.autonudge !== false) {
|
|
91
|
+
const { startSupervisor } = await inquirer.prompt([{
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
name: 'startSupervisor',
|
|
94
|
+
message: 'Start supervisor with auto-nudge now?',
|
|
95
|
+
default: true
|
|
96
|
+
}]);
|
|
97
|
+
|
|
98
|
+
if (startSupervisor) {
|
|
99
|
+
await superviseCommand({
|
|
100
|
+
autonudge: true,
|
|
101
|
+
send: !!opts.send,
|
|
102
|
+
interval: opts.interval || '3'
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(chalk.dim(' Start later with: agentxchain supervise --autonudge'));
|
|
107
|
+
console.log('');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function pickPmAgentId(config) {
|
|
113
|
+
if (config.agents?.pm) return 'pm';
|
|
114
|
+
for (const [id, def] of Object.entries(config.agents || {})) {
|
|
115
|
+
const name = String(def?.name || '').toLowerCase();
|
|
116
|
+
if (name.includes('product manager')) return id;
|
|
117
|
+
}
|
|
118
|
+
return Object.keys(config.agents || {})[0] || null;
|
|
119
|
+
}
|
package/src/commands/start.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { loadConfig } from '../lib/config.js';
|
|
3
|
-
import {
|
|
3
|
+
import { validateProject } from '../lib/validation.js';
|
|
4
4
|
|
|
5
5
|
export async function startCommand(opts) {
|
|
6
6
|
const result = loadConfig();
|
|
@@ -26,15 +26,52 @@ export async function startCommand(opts) {
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
if (opts.agent && opts.remaining) {
|
|
30
|
+
console.log(chalk.red(' --agent and --remaining cannot be used together.'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (opts.remaining) {
|
|
35
|
+
const kickoffValidation = validateProject(root, config, { mode: 'kickoff' });
|
|
36
|
+
if (!kickoffValidation.ok) {
|
|
37
|
+
console.log(chalk.red(' PM kickoff is incomplete. Cannot run --remaining yet.'));
|
|
38
|
+
console.log(chalk.dim(' Fix these first:'));
|
|
39
|
+
for (const e of kickoffValidation.errors) {
|
|
40
|
+
console.log(chalk.dim(` - ${e}`));
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.dim(' Suggested next step: complete .planning/PM_SIGNOFF.md and roadmap waves/phases, then run:'));
|
|
44
|
+
console.log(chalk.bold(' agentxchain validate --mode kickoff'));
|
|
45
|
+
console.log('');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const launchConfig = buildLaunchConfig(config, opts);
|
|
51
|
+
const launchAgentIds = Object.keys(launchConfig.agents || {});
|
|
52
|
+
const launchCount = launchAgentIds.length;
|
|
53
|
+
|
|
54
|
+
if (launchCount === 0) {
|
|
55
|
+
console.log(chalk.red(' No agents selected to launch.'));
|
|
56
|
+
if (opts.remaining) {
|
|
57
|
+
console.log(chalk.dim(' Tip: this usually means only PM exists.'));
|
|
58
|
+
}
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
console.log('');
|
|
30
63
|
console.log(chalk.bold(` ${agentCount} agents configured for ${config.project}`));
|
|
64
|
+
if (opts.remaining) {
|
|
65
|
+
console.log(chalk.cyan(` Launch mode: remaining agents (${launchCount})`));
|
|
66
|
+
} else if (opts.agent) {
|
|
67
|
+
console.log(chalk.cyan(` Launch mode: single agent (${opts.agent})`));
|
|
68
|
+
}
|
|
31
69
|
console.log('');
|
|
32
70
|
|
|
33
71
|
if (opts.dryRun) {
|
|
34
72
|
console.log(chalk.yellow(' DRY RUN — showing agents:'));
|
|
35
73
|
console.log('');
|
|
36
|
-
for (const [id, agent] of Object.entries(
|
|
37
|
-
if (opts.agent && opts.agent !== id) continue;
|
|
74
|
+
for (const [id, agent] of Object.entries(launchConfig.agents)) {
|
|
38
75
|
console.log(` ${chalk.cyan(id)} — ${agent.name}`);
|
|
39
76
|
console.log(chalk.dim(` ${agent.mandate.slice(0, 80)}...`));
|
|
40
77
|
console.log('');
|
|
@@ -42,12 +79,21 @@ export async function startCommand(opts) {
|
|
|
42
79
|
return;
|
|
43
80
|
}
|
|
44
81
|
|
|
82
|
+
if (!opts.agent && !opts.remaining) {
|
|
83
|
+
const kickoffId = pickPmAgentId(config);
|
|
84
|
+
if (kickoffId) {
|
|
85
|
+
console.log(chalk.yellow(` Tip: for first run, start with ${chalk.bold(`agentxchain start --agent ${kickoffId}`)}.`));
|
|
86
|
+
console.log(chalk.dim(' Then use `agentxchain start --remaining` once PM planning is complete.'));
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
45
91
|
switch (ide) {
|
|
46
92
|
case 'vscode': {
|
|
47
93
|
console.log(chalk.green(' Agents are set up as VS Code custom agents.'));
|
|
48
94
|
console.log('');
|
|
49
95
|
console.log(chalk.dim(' Your agents in .github/agents/:'));
|
|
50
|
-
for (const [id, agent] of Object.entries(
|
|
96
|
+
for (const [id, agent] of Object.entries(launchConfig.agents)) {
|
|
51
97
|
console.log(` ${chalk.cyan(id)}.agent.md — ${agent.name}`);
|
|
52
98
|
}
|
|
53
99
|
console.log('');
|
|
@@ -65,12 +111,12 @@ export async function startCommand(opts) {
|
|
|
65
111
|
}
|
|
66
112
|
case 'cursor': {
|
|
67
113
|
const { launchCursorLocal } = await import('../adapters/cursor-local.js');
|
|
68
|
-
await launchCursorLocal(
|
|
114
|
+
await launchCursorLocal(launchConfig, root, opts);
|
|
69
115
|
break;
|
|
70
116
|
}
|
|
71
117
|
case 'claude-code': {
|
|
72
118
|
const { launchClaudeCodeAgents } = await import('../adapters/claude-code.js');
|
|
73
|
-
await launchClaudeCodeAgents(
|
|
119
|
+
await launchClaudeCodeAgents(launchConfig, root, opts);
|
|
74
120
|
break;
|
|
75
121
|
}
|
|
76
122
|
default:
|
|
@@ -78,3 +124,38 @@ export async function startCommand(opts) {
|
|
|
78
124
|
process.exit(1);
|
|
79
125
|
}
|
|
80
126
|
}
|
|
127
|
+
|
|
128
|
+
function buildLaunchConfig(config, opts) {
|
|
129
|
+
if (opts.agent) {
|
|
130
|
+
return {
|
|
131
|
+
...config,
|
|
132
|
+
agents: { [opts.agent]: config.agents[opts.agent] }
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (opts.remaining) {
|
|
137
|
+
const pmId = pickPmAgentId(config);
|
|
138
|
+
const agents = { ...config.agents };
|
|
139
|
+
|
|
140
|
+
if (pmId && agents[pmId]) {
|
|
141
|
+
delete agents[pmId];
|
|
142
|
+
} else {
|
|
143
|
+
const firstId = Object.keys(agents)[0];
|
|
144
|
+
if (firstId) delete agents[firstId];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { ...config, agents };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return config;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function pickPmAgentId(config) {
|
|
154
|
+
if (config.agents.pm) return 'pm';
|
|
155
|
+
|
|
156
|
+
for (const [id, def] of Object.entries(config.agents || {})) {
|
|
157
|
+
const name = String(def?.name || '').toLowerCase();
|
|
158
|
+
if (name.includes('product manager')) return id;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|