chati-dev 2.0.4 → 2.0.6
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 +48 -36
- package/framework/agents/{clarity → planning}/architect.md +1 -1
- package/framework/agents/{clarity → planning}/brief.md +1 -1
- package/framework/agents/{clarity → planning}/brownfield-wu.md +1 -1
- package/framework/agents/{clarity → planning}/detail.md +1 -1
- package/framework/agents/{clarity → planning}/greenfield-wu.md +1 -1
- package/framework/agents/{clarity → planning}/phases.md +1 -1
- package/framework/agents/{clarity → planning}/tasks.md +1 -1
- package/framework/agents/{clarity → planning}/ux.md +1 -1
- package/framework/agents/quality/qa-planning.md +5 -5
- package/framework/constitution.md +6 -6
- package/framework/context/governance.md +3 -3
- package/framework/context/quality.md +1 -1
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +9 -9
- package/framework/domains/agents/qa-implementation.yaml +1 -1
- package/framework/domains/constitution.yaml +2 -2
- package/framework/domains/global.yaml +1 -1
- package/framework/hooks/mode-governance.js +5 -5
- package/framework/hooks/model-governance.js +38 -18
- package/framework/hooks/prism-engine.js +1 -1
- package/framework/hooks/session-digest.js +1 -1
- package/framework/i18n/en.yaml +1 -3
- package/framework/i18n/es.yaml +1 -3
- package/framework/i18n/fr.yaml +1 -3
- package/framework/i18n/pt.yaml +1 -3
- package/framework/intelligence/context-engine.md +3 -3
- package/framework/orchestrator/chati.md +148 -78
- package/framework/quality-gates/planning-gate.md +1 -1
- package/framework/schemas/context.schema.json +1 -1
- package/framework/schemas/session.schema.json +3 -3
- package/framework/tasks/architect-api-design.md +1 -1
- package/framework/tasks/architect-consolidate.md +1 -1
- package/framework/tasks/architect-db-design.md +1 -1
- package/framework/tasks/architect-design.md +1 -1
- package/framework/tasks/architect-security-review.md +1 -1
- package/framework/tasks/architect-stack-selection.md +1 -1
- package/framework/tasks/brief-consolidate.md +1 -1
- package/framework/tasks/brief-constraint-identify.md +1 -1
- package/framework/tasks/brief-extract-requirements.md +1 -1
- package/framework/tasks/brief-stakeholder-map.md +1 -1
- package/framework/tasks/brief-validate-completeness.md +1 -1
- package/framework/tasks/brownfield-wu-architecture-map.md +1 -1
- package/framework/tasks/brownfield-wu-deep-discovery.md +1 -1
- package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
- package/framework/tasks/brownfield-wu-migration-plan.md +1 -1
- package/framework/tasks/brownfield-wu-report.md +1 -1
- package/framework/tasks/brownfield-wu-risk-assess.md +1 -1
- package/framework/tasks/detail-acceptance-criteria.md +1 -1
- package/framework/tasks/detail-consolidate.md +1 -1
- package/framework/tasks/detail-edge-case-analysis.md +1 -1
- package/framework/tasks/detail-expand-prd.md +1 -1
- package/framework/tasks/detail-nfr-extraction.md +1 -1
- package/framework/tasks/greenfield-wu-analyze-empty.md +1 -1
- package/framework/tasks/greenfield-wu-report.md +1 -1
- package/framework/tasks/greenfield-wu-scaffold-detection.md +1 -1
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +1 -1
- package/framework/tasks/orchestrator-deviation.md +3 -3
- package/framework/tasks/orchestrator-escalate.md +2 -2
- package/framework/tasks/orchestrator-handoff.md +6 -6
- package/framework/tasks/orchestrator-health.md +1 -1
- package/framework/tasks/orchestrator-mode-switch.md +20 -20
- package/framework/tasks/orchestrator-resume.md +6 -6
- package/framework/tasks/orchestrator-route.md +7 -7
- package/framework/tasks/orchestrator-status.md +8 -8
- package/framework/tasks/orchestrator-suggest-mode.md +13 -13
- package/framework/tasks/phases-breakdown.md +1 -1
- package/framework/tasks/phases-dependency-mapping.md +1 -1
- package/framework/tasks/phases-mvp-scoping.md +1 -1
- package/framework/tasks/qa-impl-regression-check.md +2 -2
- package/framework/tasks/qa-planning-consolidate.md +3 -3
- package/framework/tasks/qa-planning-coverage-plan.md +1 -1
- package/framework/tasks/qa-planning-gate-define.md +7 -7
- package/framework/tasks/qa-planning-risk-matrix.md +1 -1
- package/framework/tasks/qa-planning-test-strategy.md +1 -1
- package/framework/tasks/tasks-acceptance-write.md +1 -1
- package/framework/tasks/tasks-consolidate.md +1 -1
- package/framework/tasks/tasks-decompose.md +1 -1
- package/framework/tasks/tasks-estimate.md +1 -1
- package/framework/tasks/ux-a11y-check.md +1 -1
- package/framework/tasks/ux-component-map.md +1 -1
- package/framework/tasks/ux-consolidate.md +1 -1
- package/framework/tasks/ux-user-flow.md +1 -1
- package/framework/tasks/ux-wireframe.md +1 -1
- package/framework/workflows/brownfield-discovery.yaml +7 -7
- package/framework/workflows/brownfield-fullstack.yaml +9 -9
- package/framework/workflows/brownfield-service.yaml +5 -5
- package/framework/workflows/brownfield-ui.yaml +5 -5
- package/framework/workflows/greenfield-fullstack.yaml +9 -9
- package/package.json +2 -2
- package/scripts/bundle-framework.js +1 -1
- package/scripts/health-check.js +8 -8
- package/scripts/populate-entity-registry.js +1 -1
- package/scripts/validate-agents.js +1 -1
- package/scripts/validate-tasks.js +1 -1
- package/src/autonomy/autonomous-gate.js +1 -1
- package/src/autonomy/progress-reporter.js +9 -9
- package/src/config/mcp-configs.js +16 -85
- package/src/context/engine.js +2 -2
- package/src/context/layers/l1-global.js +3 -3
- package/src/dashboard/layout.js +7 -7
- package/src/dashboard/renderer.js +2 -2
- package/src/gates/g1-planning-complete.js +9 -9
- package/src/installer/core.js +11 -11
- package/src/installer/templates.js +1 -1
- package/src/installer/validator.js +8 -8
- package/src/intelligence/registry-manager.js +4 -4
- package/src/memory/session-digest.js +1 -1
- package/src/orchestrator/agent-selector.js +10 -10
- package/src/orchestrator/intent-classifier.js +4 -4
- package/src/orchestrator/pipeline-manager.js +3 -3
- package/src/orchestrator/pipeline-state.js +1 -1
- package/src/orchestrator/session-manager.js +3 -3
- package/src/tasks/executor.js +1 -1
- package/src/tasks/loader.js +1 -1
- package/src/terminal/handoff-parser.js +127 -0
- package/src/terminal/index.js +9 -0
- package/src/terminal/prompt-builder.js +313 -0
- package/src/terminal/run-agent.js +248 -0
- package/src/terminal/run-parallel.js +238 -0
- package/src/terminal/spawner.js +29 -18
- package/src/wizard/i18n.js +1 -3
- package/src/wizard/index.js +10 -15
- package/src/wizard/questions.js +7 -70
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI runner for parallel agent terminal execution.
|
|
4
|
+
*
|
|
5
|
+
* Called by the orchestrator via the Bash tool to spawn multiple agents
|
|
6
|
+
* simultaneously in separate Claude Code processes.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node run-parallel.js --agents detail,architect,ux \
|
|
10
|
+
* --task-ids expand-prd,architect-design,ux-wireframe \
|
|
11
|
+
* --project-dir /path/to/project --previous-agent brief \
|
|
12
|
+
* --timeout 900000
|
|
13
|
+
*
|
|
14
|
+
* Outputs consolidated JSON to stdout for the orchestrator to parse.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { buildAgentPrompt } from './prompt-builder.js';
|
|
19
|
+
import { spawnParallelGroup } from './spawner.js';
|
|
20
|
+
import { TerminalMonitor } from './monitor.js';
|
|
21
|
+
import { collectResults, mergeHandoffs, buildConsolidatedHandoff } from './collector.js';
|
|
22
|
+
import { parseAgentOutput } from './handoff-parser.js';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// CLI argument parsing
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
const args = {};
|
|
30
|
+
for (let i = 2; i < argv.length; i++) {
|
|
31
|
+
const arg = argv[i];
|
|
32
|
+
if (arg.startsWith('--')) {
|
|
33
|
+
const key = arg.slice(2);
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
if (next && !next.startsWith('--')) {
|
|
36
|
+
args[key] = next;
|
|
37
|
+
i++;
|
|
38
|
+
} else {
|
|
39
|
+
args[key] = 'true';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return args;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Main
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
const args = parseArgs(process.argv);
|
|
52
|
+
|
|
53
|
+
if (!args.agents) {
|
|
54
|
+
outputError('Missing required argument: --agents (comma-separated)');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
if (!args['task-ids']) {
|
|
58
|
+
outputError('Missing required argument: --task-ids (comma-separated)');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const agents = args.agents.split(',').map(s => s.trim());
|
|
63
|
+
const taskIds = args['task-ids'].split(',').map(s => s.trim());
|
|
64
|
+
|
|
65
|
+
if (agents.length !== taskIds.length) {
|
|
66
|
+
outputError(`Agent count (${agents.length}) must match task-id count (${taskIds.length})`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const projectDir = args['project-dir'] || process.cwd();
|
|
71
|
+
const previousAgent = args['previous-agent'] || null;
|
|
72
|
+
const timeout = parseInt(args.timeout, 10) || 900_000; // default 15 minutes
|
|
73
|
+
|
|
74
|
+
// Load session state
|
|
75
|
+
let sessionState = {};
|
|
76
|
+
try {
|
|
77
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
78
|
+
const { join } = await import('path');
|
|
79
|
+
const sessionPath = join(projectDir, '.chati', 'session.yaml');
|
|
80
|
+
if (existsSync(sessionPath)) {
|
|
81
|
+
const raw = readFileSync(sessionPath, 'utf-8');
|
|
82
|
+
// Minimal parse
|
|
83
|
+
sessionState = {};
|
|
84
|
+
for (const line of raw.split('\n')) {
|
|
85
|
+
const m = line.trim().match(/^([\w_-]+):\s*(.+)/);
|
|
86
|
+
if (m) sessionState[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch { /* optional */ }
|
|
90
|
+
|
|
91
|
+
// Build prompts for all agents
|
|
92
|
+
const configs = [];
|
|
93
|
+
const startTime = Date.now();
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < agents.length; i++) {
|
|
96
|
+
try {
|
|
97
|
+
const promptResult = buildAgentPrompt({
|
|
98
|
+
agent: agents[i],
|
|
99
|
+
taskId: taskIds[i],
|
|
100
|
+
projectDir,
|
|
101
|
+
previousAgent,
|
|
102
|
+
sessionState,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
configs.push({
|
|
106
|
+
agent: agents[i],
|
|
107
|
+
taskId: taskIds[i],
|
|
108
|
+
model: promptResult.model,
|
|
109
|
+
prompt: promptResult.prompt,
|
|
110
|
+
workingDir: projectDir,
|
|
111
|
+
timeout,
|
|
112
|
+
});
|
|
113
|
+
} catch (err) {
|
|
114
|
+
outputError(`Failed to build prompt for ${agents[i]}: ${err.message}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Spawn all terminals in parallel
|
|
120
|
+
let group;
|
|
121
|
+
try {
|
|
122
|
+
group = spawnParallelGroup(configs);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
outputError(`Failed to spawn parallel group: ${err.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Monitor until completion
|
|
129
|
+
const monitor = new TerminalMonitor({ pollInterval: 2000, timeout });
|
|
130
|
+
|
|
131
|
+
for (const terminal of group.terminals) {
|
|
132
|
+
monitor.addTerminal(terminal);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await new Promise((resolve) => {
|
|
136
|
+
monitor.onComplete(() => {
|
|
137
|
+
monitor.stopMonitoring();
|
|
138
|
+
resolve();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Safety timeout
|
|
142
|
+
const safetyTimer = setTimeout(() => {
|
|
143
|
+
monitor.stopMonitoring();
|
|
144
|
+
resolve();
|
|
145
|
+
}, timeout + 10_000);
|
|
146
|
+
|
|
147
|
+
monitor.onComplete(() => clearTimeout(safetyTimer));
|
|
148
|
+
monitor.startMonitoring();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const elapsed = Date.now() - startTime;
|
|
152
|
+
|
|
153
|
+
// Collect and merge results
|
|
154
|
+
const rawResults = collectResults(group.groupId, group.terminals);
|
|
155
|
+
|
|
156
|
+
// Parse handoffs from each terminal's stdout
|
|
157
|
+
const agentResults = rawResults.results.map(r => {
|
|
158
|
+
const parsed = parseAgentOutput(r.stdout);
|
|
159
|
+
return {
|
|
160
|
+
...r,
|
|
161
|
+
handoff: parsed.found ? parsed.handoff : null,
|
|
162
|
+
handoffFound: parsed.found,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Merge handoffs
|
|
167
|
+
const mergeInput = agentResults.map(r => ({
|
|
168
|
+
agent: r.agent,
|
|
169
|
+
status: r.handoff?.status || (r.exitCode === 0 ? 'complete' : 'failed'),
|
|
170
|
+
outputs: r.handoff?.outputs || [],
|
|
171
|
+
decisions: r.handoff?.decisions || {},
|
|
172
|
+
blockers: r.handoff?.blockers || [],
|
|
173
|
+
summary: r.handoff?.summary || '',
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
const merged = mergeHandoffs(mergeInput);
|
|
177
|
+
const nextAgent = determineNextAgent(agents);
|
|
178
|
+
const consolidated = buildConsolidatedHandoff(merged, nextAgent);
|
|
179
|
+
|
|
180
|
+
// Output consolidated result
|
|
181
|
+
const output = {
|
|
182
|
+
status: rawResults.summary.failed === 0 ? 'complete' : 'partial',
|
|
183
|
+
groupId: group.groupId,
|
|
184
|
+
agents: agents.map((a, i) => ({
|
|
185
|
+
agent: a,
|
|
186
|
+
model: configs[i].model,
|
|
187
|
+
status: agentResults[i].handoff?.status || (agentResults[i].exitCode === 0 ? 'complete' : 'failed'),
|
|
188
|
+
score: agentResults[i].handoff?.score || null,
|
|
189
|
+
exitCode: agentResults[i].exitCode,
|
|
190
|
+
handoffFound: agentResults[i].handoffFound,
|
|
191
|
+
})),
|
|
192
|
+
mergedHandoff: consolidated,
|
|
193
|
+
summary: rawResults.summary,
|
|
194
|
+
elapsed,
|
|
195
|
+
performance: {
|
|
196
|
+
sequentialEstimate: elapsed * agents.length,
|
|
197
|
+
parallelActual: elapsed,
|
|
198
|
+
timeSaved: elapsed * (agents.length - 1),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
203
|
+
process.exit(rawResults.summary.failed > 0 ? 1 : 0);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Helpers
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Determine the next sequential agent after a parallel group.
|
|
212
|
+
* GROUP 1 (detail+architect+ux) → phases
|
|
213
|
+
* GROUP 2 (dev tasks) → qa-implementation
|
|
214
|
+
*/
|
|
215
|
+
function determineNextAgent(agents) {
|
|
216
|
+
const groupKey = agents.sort().join(',');
|
|
217
|
+
if (groupKey.includes('architect') && groupKey.includes('detail') && groupKey.includes('ux')) {
|
|
218
|
+
return 'phases';
|
|
219
|
+
}
|
|
220
|
+
if (agents.every(a => a === 'dev')) {
|
|
221
|
+
return 'qa-implementation';
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function outputError(message) {
|
|
227
|
+
process.stdout.write(JSON.stringify({ status: 'error', error: message }) + '\n');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Guard pattern
|
|
231
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
232
|
+
main().catch(err => {
|
|
233
|
+
outputError(err.message);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { parseArgs, determineNextAgent };
|
package/src/terminal/spawner.js
CHANGED
|
@@ -40,8 +40,10 @@ export function _resetCounter() {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* @typedef {object} SpawnConfig
|
|
43
|
-
* @property {string} agent
|
|
44
|
-
* @property {string} taskId
|
|
43
|
+
* @property {string} agent - Agent name (e.g. "architect")
|
|
44
|
+
* @property {string} taskId - Task identifier
|
|
45
|
+
* @property {string} [model] - LLM model (haiku/sonnet/opus)
|
|
46
|
+
* @property {string} [prompt] - Full prompt string (from prompt-builder, piped via stdin)
|
|
45
47
|
* @property {object} [contextPayload] - Context to inject via env var
|
|
46
48
|
* @property {string[]} [writeScope] - Override write scope
|
|
47
49
|
* @property {string} [workingDir] - Working directory for the process
|
|
@@ -54,6 +56,7 @@ export function _resetCounter() {
|
|
|
54
56
|
* @property {object|null} process - child_process.ChildProcess (null when dry)
|
|
55
57
|
* @property {string} agent - Agent name
|
|
56
58
|
* @property {string} taskId - Task identifier
|
|
59
|
+
* @property {string} model - Model used for this terminal
|
|
57
60
|
* @property {string} startedAt - ISO timestamp
|
|
58
61
|
* @property {string} status - "running" | "exited" | "killed"
|
|
59
62
|
* @property {number|null} exitCode - Process exit code (null while running)
|
|
@@ -68,7 +71,7 @@ export function _resetCounter() {
|
|
|
68
71
|
* perform any I/O and is therefore fully testable in isolation.
|
|
69
72
|
*
|
|
70
73
|
* @param {SpawnConfig} config
|
|
71
|
-
* @returns {{ command: string, args: string[], env: Record<string, string
|
|
74
|
+
* @returns {{ command: string, args: string[], env: Record<string, string>, terminalId: string, prompt: string|null }}
|
|
72
75
|
*/
|
|
73
76
|
export function buildSpawnCommand(config) {
|
|
74
77
|
if (!config || typeof config !== 'object') {
|
|
@@ -89,6 +92,7 @@ export function buildSpawnCommand(config) {
|
|
|
89
92
|
CHATI_TERMINAL_ID: terminalId,
|
|
90
93
|
CHATI_AGENT: config.agent,
|
|
91
94
|
CHATI_TASK_ID: config.taskId,
|
|
95
|
+
CHATI_SPAWNED: 'true',
|
|
92
96
|
};
|
|
93
97
|
|
|
94
98
|
if (config.contextPayload) {
|
|
@@ -99,19 +103,18 @@ export function buildSpawnCommand(config) {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
// Build
|
|
103
|
-
const prompt = `Execute task ${config.taskId} as agent ${config.agent}. ` +
|
|
104
|
-
`Write scope: ${isolationEnv.CHATI_WRITE_SCOPE || 'none'}. ` +
|
|
105
|
-
`Terminal ID: ${terminalId}.`;
|
|
106
|
-
|
|
106
|
+
// Build CLI args — prompt is piped via stdin, NOT as a CLI argument
|
|
107
107
|
const command = 'claude';
|
|
108
|
-
const args = [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
const args = ['--print', '--dangerously-skip-permissions'];
|
|
109
|
+
|
|
110
|
+
if (config.model) {
|
|
111
|
+
args.push('--model', config.model);
|
|
112
|
+
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
// Prompt is returned separately for stdin piping (avoids ARG_MAX limits)
|
|
115
|
+
const prompt = config.prompt || null;
|
|
116
|
+
|
|
117
|
+
return { command, args, env, terminalId, prompt };
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
/**
|
|
@@ -121,7 +124,7 @@ export function buildSpawnCommand(config) {
|
|
|
121
124
|
* @returns {TerminalHandle}
|
|
122
125
|
*/
|
|
123
126
|
export function spawnTerminal(config) {
|
|
124
|
-
const { command, args, env, terminalId } = buildSpawnCommand(config);
|
|
127
|
+
const { command, args, env, terminalId, prompt } = buildSpawnCommand(config);
|
|
125
128
|
|
|
126
129
|
const cwd = config.workingDir || process.cwd();
|
|
127
130
|
const timeout = config.timeout || 300_000; // default 5 minutes
|
|
@@ -129,15 +132,22 @@ export function spawnTerminal(config) {
|
|
|
129
132
|
const child = spawn(command, args, {
|
|
130
133
|
cwd,
|
|
131
134
|
env: { ...process.env, ...env },
|
|
132
|
-
stdio: ['
|
|
135
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
133
136
|
});
|
|
134
137
|
|
|
138
|
+
// Pipe prompt via stdin (avoids shell argument length limits)
|
|
139
|
+
if (prompt) {
|
|
140
|
+
child.stdin.write(prompt);
|
|
141
|
+
}
|
|
142
|
+
child.stdin.end();
|
|
143
|
+
|
|
135
144
|
/** @type {TerminalHandle} */
|
|
136
145
|
const handle = {
|
|
137
146
|
id: terminalId,
|
|
138
147
|
process: child,
|
|
139
148
|
agent: config.agent,
|
|
140
149
|
taskId: config.taskId,
|
|
150
|
+
model: config.model || 'sonnet',
|
|
141
151
|
startedAt: new Date().toISOString(),
|
|
142
152
|
status: 'running',
|
|
143
153
|
exitCode: null,
|
|
@@ -250,11 +260,11 @@ export function killTerminal(handle) {
|
|
|
250
260
|
* Return the current status snapshot of a terminal.
|
|
251
261
|
*
|
|
252
262
|
* @param {TerminalHandle} handle
|
|
253
|
-
* @returns {{ id: string, agent: string, status: string, elapsed: number, exitCode: number|null }}
|
|
263
|
+
* @returns {{ id: string, agent: string, model: string, status: string, elapsed: number, exitCode: number|null }}
|
|
254
264
|
*/
|
|
255
265
|
export function getTerminalStatus(handle) {
|
|
256
266
|
if (!handle) {
|
|
257
|
-
return { id: 'unknown', agent: 'unknown', status: 'unknown', elapsed: 0, exitCode: null };
|
|
267
|
+
return { id: 'unknown', agent: 'unknown', model: 'unknown', status: 'unknown', elapsed: 0, exitCode: null };
|
|
258
268
|
}
|
|
259
269
|
|
|
260
270
|
const elapsed = Date.now() - new Date(handle.startedAt).getTime();
|
|
@@ -262,6 +272,7 @@ export function getTerminalStatus(handle) {
|
|
|
262
272
|
return {
|
|
263
273
|
id: handle.id,
|
|
264
274
|
agent: handle.agent,
|
|
275
|
+
model: handle.model || 'unknown',
|
|
265
276
|
status: handle.status,
|
|
266
277
|
elapsed,
|
|
267
278
|
exitCode: handle.exitCode,
|
package/src/wizard/i18n.js
CHANGED
|
@@ -15,15 +15,13 @@ const FALLBACK_EN = {
|
|
|
15
15
|
brownfield: 'Brownfield (existing project integration)',
|
|
16
16
|
detected_brownfield: 'Detected package.json and src/ in current directory',
|
|
17
17
|
suggestion_brownfield: 'Suggestion: Brownfield',
|
|
18
|
-
select_ides: 'Select IDE(s) for Chati.dev configuration:',
|
|
19
|
-
select_mcps: 'Select MCPs to install (project-level):',
|
|
20
18
|
confirmation_title: 'Installation Summary',
|
|
21
19
|
project_label: 'Project',
|
|
22
20
|
language_label: 'Language',
|
|
23
21
|
ides_label: 'IDEs',
|
|
24
22
|
mcps_label: 'MCPs',
|
|
25
23
|
will_install: 'Will install:',
|
|
26
|
-
agents_count: '13 agent definitions (
|
|
24
|
+
agents_count: '13 agent definitions (PLANNING, BUILD, DEPLOY phases)',
|
|
27
25
|
workflows_count: '5 workflow blueprints',
|
|
28
26
|
templates_count: '5 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate)',
|
|
29
27
|
constitution: 'Constitution (17 Articles + Preamble)',
|
package/src/wizard/index.js
CHANGED
|
@@ -3,17 +3,18 @@ import { readFileSync } from 'fs';
|
|
|
3
3
|
import { join, dirname, basename } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { logBanner } from '../utils/logger.js';
|
|
6
|
-
import { stepLanguage, stepProjectType,
|
|
6
|
+
import { stepLanguage, stepProjectType, stepConfirmation } from './questions.js';
|
|
7
7
|
import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
8
8
|
import { installFramework } from '../installer/core.js';
|
|
9
9
|
import { validateInstallation } from '../installer/validator.js';
|
|
10
10
|
import { t } from './i18n.js';
|
|
11
|
+
import { DEFAULT_MCPS } from '../config/mcp-configs.js';
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* Run the
|
|
17
|
+
* Run the 4-step installer wizard
|
|
17
18
|
*/
|
|
18
19
|
export async function runWizard(targetDir, options = {}) {
|
|
19
20
|
// Load ASCII logo
|
|
@@ -34,13 +35,11 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
34
35
|
// Step 2: Project Type
|
|
35
36
|
const projectType = options.projectType || await stepProjectType(targetDir);
|
|
36
37
|
|
|
37
|
-
//
|
|
38
|
-
const selectedIDEs = options.ides ||
|
|
38
|
+
// Auto-configured: Claude Code + default MCPs
|
|
39
|
+
const selectedIDEs = options.ides || ['claude-code'];
|
|
40
|
+
const selectedMCPs = options.mcps || DEFAULT_MCPS;
|
|
39
41
|
|
|
40
|
-
// Step
|
|
41
|
-
const selectedMCPs = options.mcps || await stepMCPs(projectType);
|
|
42
|
-
|
|
43
|
-
// Step 5: Confirmation
|
|
42
|
+
// Step 3: Confirmation
|
|
44
43
|
const projectName = basename(targetDir);
|
|
45
44
|
const config = {
|
|
46
45
|
projectName,
|
|
@@ -54,7 +53,7 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
54
53
|
|
|
55
54
|
await stepConfirmation(config);
|
|
56
55
|
|
|
57
|
-
// Step
|
|
56
|
+
// Step 4: Installation + Validation
|
|
58
57
|
console.log();
|
|
59
58
|
const installSpinner = createSpinner(t('installer.installing'));
|
|
60
59
|
installSpinner.start();
|
|
@@ -71,10 +70,7 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
71
70
|
showStep(t('installer.created_claude_md'));
|
|
72
71
|
showStep(t('installer.created_memories'));
|
|
73
72
|
showStep(t('installer.installed_intelligence'));
|
|
74
|
-
|
|
75
|
-
if (selectedMCPs.length > 0) {
|
|
76
|
-
showStep(`${t('installer.configured_mcps')} ${selectedMCPs.join(', ')}`);
|
|
77
|
-
}
|
|
73
|
+
showStep(`${t('installer.configured_mcps')} ${selectedMCPs.join(', ')}`);
|
|
78
74
|
|
|
79
75
|
// Validation
|
|
80
76
|
console.log();
|
|
@@ -96,9 +92,8 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
96
92
|
console.log();
|
|
97
93
|
p.outro(t('installer.success'));
|
|
98
94
|
|
|
99
|
-
const primaryIDE = selectedIDEs[0] || 'your IDE';
|
|
100
95
|
showQuickStart(t('installer.quick_start_title'), [
|
|
101
|
-
`${t('installer.quick_start_1')} (
|
|
96
|
+
`${t('installer.quick_start_1')} (Claude Code)`,
|
|
102
97
|
t('installer.quick_start_2'),
|
|
103
98
|
t('installer.quick_start_3'),
|
|
104
99
|
]);
|
package/src/wizard/questions.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { t, SUPPORTED_LANGUAGES, loadLanguage } from './i18n.js';
|
|
3
|
-
import { getIDEChoices } from '../config/ide-configs.js';
|
|
4
|
-
import { getMCPChoices, getMCPWarnings } from '../config/mcp-configs.js';
|
|
5
3
|
import { detectProjectType } from '../utils/detector.js';
|
|
6
4
|
import { showSummary, showChecklist } from './feedback.js';
|
|
7
|
-
import { brand, dim, success
|
|
5
|
+
import { brand, dim, success } from '../utils/colors.js';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Step 1: Language Selection (always in English)
|
|
@@ -60,77 +58,16 @@ export async function stepProjectType(targetDir) {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/**
|
|
63
|
-
* Step 3:
|
|
64
|
-
*/
|
|
65
|
-
export async function stepIDEs() {
|
|
66
|
-
const ideChoices = getIDEChoices();
|
|
67
|
-
|
|
68
|
-
const selectedIDEs = await p.multiselect({
|
|
69
|
-
message: t('installer.select_ides'),
|
|
70
|
-
options: ideChoices.map(ide => ({
|
|
71
|
-
value: ide.value,
|
|
72
|
-
label: `${ide.label} ${dim('--')} ${dim(ide.hint)}`,
|
|
73
|
-
})),
|
|
74
|
-
initialValues: ['claude-code'],
|
|
75
|
-
required: true,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (p.isCancel(selectedIDEs)) {
|
|
79
|
-
p.cancel('Installation cancelled.');
|
|
80
|
-
process.exit(0);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return selectedIDEs;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Step 4: MCP Selection (Multi-select)
|
|
88
|
-
*/
|
|
89
|
-
export async function stepMCPs(projectType) {
|
|
90
|
-
const mcpChoices = getMCPChoices();
|
|
91
|
-
|
|
92
|
-
const selectedMCPs = await p.multiselect({
|
|
93
|
-
message: t('installer.select_mcps'),
|
|
94
|
-
options: mcpChoices.map(mcp => ({
|
|
95
|
-
value: mcp.value,
|
|
96
|
-
label: `${mcp.label} ${dim('--')} ${dim(mcp.hint)}`,
|
|
97
|
-
})),
|
|
98
|
-
initialValues: mcpChoices.filter(m => m.initialValue).map(m => m.value),
|
|
99
|
-
required: false,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (p.isCancel(selectedMCPs)) {
|
|
103
|
-
p.cancel('Installation cancelled.');
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Check for MCP warnings
|
|
108
|
-
const warnings = getMCPWarnings(projectType, selectedMCPs);
|
|
109
|
-
for (const warn of warnings) {
|
|
110
|
-
if (warn.level === 'critical') {
|
|
111
|
-
const continueAnyway = await p.confirm({
|
|
112
|
-
message: `${yellow('Warning:')} ${warn.message}\nContinue anyway?`,
|
|
113
|
-
initialValue: false,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
117
|
-
return stepMCPs(projectType); // Re-prompt
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return selectedMCPs;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Step 5: Confirmation
|
|
61
|
+
* Step 3: Confirmation
|
|
127
62
|
*/
|
|
128
63
|
export async function stepConfirmation(config) {
|
|
129
|
-
const { projectName, projectType, language,
|
|
64
|
+
const { projectName, projectType, language, selectedMCPs } = config;
|
|
130
65
|
|
|
131
66
|
const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
|
|
132
|
-
const ideNames =
|
|
133
|
-
const mcpNames = selectedMCPs.length > 0
|
|
67
|
+
const ideNames = 'Claude Code (auto-configured)';
|
|
68
|
+
const mcpNames = selectedMCPs.length > 0
|
|
69
|
+
? `${selectedMCPs.join(', ')} (auto-installed)`
|
|
70
|
+
: 'None';
|
|
134
71
|
|
|
135
72
|
console.log();
|
|
136
73
|
console.log(brand(t('installer.confirmation_title') + ':'));
|