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.
@@ -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 = generatePollingPrompt(id, agent, config);
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 = generatePollingPrompt(id, agent, config);
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
- console.log(` 3. ${chalk.bold('Select Agent mode')} (not Ask/Edit)`);
61
- console.log(` 4. Send it (${chalk.bold('Enter')})`);
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
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock — agents start claiming turns')}`);
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('# optional: TTL safety net')}`);
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
+ }
@@ -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
+ }
@@ -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('agentxchain start')} ${chalk.dim('# opens Cursor windows + copies agent prompts')}`);
268
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock agents start working')}`);
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
+ }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig } from '../lib/config.js';
3
- import { generateSeedPrompt } from '../lib/seed-prompt.js';
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(config.agents)) {
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(config.agents)) {
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(config, root, opts);
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(config, root, opts);
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
+ }