chati-dev 3.0.0 → 3.0.1
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 +95 -24
- package/bin/chati.js +1 -1
- package/framework/agents/plan/phases.md +2 -2
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +4 -4
- package/framework/context/governance.md +16 -3
- package/framework/context/root.md +3 -3
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/detail.yaml +1 -1
- package/framework/domains/agents/phases.yaml +2 -2
- package/framework/domains/agents/tasks.yaml +2 -2
- package/framework/domains/constitution.yaml +2 -2
- package/framework/hooks/read-protection.js +1 -0
- package/framework/i18n/en.yaml +11 -4
- package/framework/i18n/es.yaml +11 -4
- package/framework/i18n/fr.yaml +11 -4
- package/framework/i18n/pt.yaml +11 -4
- package/framework/intelligence/context-engine.md +2 -2
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/patterns/elicitation-library.yaml +2 -2
- package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
- package/framework/tasks/brownfield-wu-migration-plan.md +4 -4
- package/framework/tasks/brownfield-wu-risk-assess.md +2 -2
- package/framework/tasks/detail-expand-prd.md +2 -2
- package/framework/tasks/greenfield-wu-report.md +1 -1
- package/framework/tasks/orchestrator-handoff.md +2 -2
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/qa-impl-performance-test.md +2 -2
- package/framework/tasks/qa-impl-sast-scan.md +6 -6
- package/framework/tasks/qa-planning-consolidate.md +1 -1
- package/framework/tasks/qa-planning-coverage-plan.md +9 -9
- package/framework/tasks/qa-planning-gate-define.md +6 -6
- package/framework/tasks/qa-planning-risk-matrix.md +1 -1
- package/framework/tasks/tasks-consolidate.md +5 -5
- package/package.json +1 -1
- package/scripts/changelog-generator.js +2 -2
- package/scripts/ide-sync.js +9 -4
- package/scripts/pr-review.js +5 -5
- package/scripts/semantic-lint.js +3 -3
- package/src/autonomy/build-state.js +1 -1
- package/src/autonomy/execution-profile.js +1 -1
- package/src/config/context-file-generator.js +20 -26
- package/src/config/ide-configs.js +6 -6
- package/src/gates/g2-qa-planning.js +1 -1
- package/src/gates/g4-qa-implementation.js +1 -1
- package/src/health/engine.js +7 -12
- package/src/installer/core.js +54 -16
- package/src/installer/templates.js +131 -2
- package/src/installer/validator.js +5 -3
- package/src/intelligence/registry-manager.js +2 -2
- package/src/preview/detector.js +1 -1
- package/src/terminal/adapters/claude-adapter.js +3 -13
- package/src/terminal/adapters/codex-adapter.js +3 -13
- package/src/terminal/adapters/copilot-adapter.js +3 -13
- package/src/terminal/adapters/gemini-adapter.js +3 -17
- package/src/terminal/cli-registry.js +20 -48
- package/src/terminal/prompt-builder.js +4 -18
- package/src/terminal/run-agent.js +2 -1
- package/src/terminal/spawner.js +27 -7
- package/src/terminal/wave-analyzer.js +1 -1
- package/src/utils/config-parser.js +97 -0
- package/src/wizard/feedback.js +1 -1
- package/src/wizard/i18n.js +11 -4
- package/src/wizard/index.js +22 -10
- package/src/wizard/questions.js +50 -2
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* truth for multi-CLI governance (Constitution Article XIX).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, readFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
9
|
import * as adapters from './adapters/index.js';
|
|
10
|
+
import { parseProviderConfig, parseAgentOverride } from '../utils/config-parser.js';
|
|
12
11
|
|
|
13
12
|
// ---------------------------------------------------------------------------
|
|
14
13
|
// Provider Definitions
|
|
@@ -49,10 +48,10 @@ const PROVIDERS = {
|
|
|
49
48
|
gemini: {
|
|
50
49
|
name: 'gemini',
|
|
51
50
|
command: 'gemini',
|
|
52
|
-
baseArgs: [
|
|
51
|
+
baseArgs: [],
|
|
53
52
|
modelFlag: '--model',
|
|
54
53
|
stdinSupport: true,
|
|
55
|
-
hooksSupport:
|
|
54
|
+
hooksSupport: false,
|
|
56
55
|
mcpSupport: true,
|
|
57
56
|
contextFile: 'GEMINI.md',
|
|
58
57
|
modelMap: {
|
|
@@ -72,7 +71,7 @@ const PROVIDERS = {
|
|
|
72
71
|
contextFile: 'AGENTS.md',
|
|
73
72
|
modelMap: {
|
|
74
73
|
codex: 'gpt-5.3-codex',
|
|
75
|
-
mini: '
|
|
74
|
+
mini: 'codex-mini-latest',
|
|
76
75
|
},
|
|
77
76
|
adapter: adapters.codex,
|
|
78
77
|
},
|
|
@@ -82,12 +81,12 @@ const PROVIDERS = {
|
|
|
82
81
|
baseArgs: ['-p'],
|
|
83
82
|
modelFlag: '--model',
|
|
84
83
|
stdinSupport: true,
|
|
85
|
-
hooksSupport:
|
|
84
|
+
hooksSupport: false,
|
|
86
85
|
mcpSupport: true,
|
|
87
86
|
contextFile: null,
|
|
88
87
|
modelMap: {
|
|
89
88
|
'claude-sonnet': 'claude-sonnet-4.5',
|
|
90
|
-
'gpt-5': 'gpt-5
|
|
89
|
+
'gpt-5': 'gpt-5',
|
|
91
90
|
},
|
|
92
91
|
adapter: adapters.copilot,
|
|
93
92
|
},
|
|
@@ -128,33 +127,7 @@ export function getAllProviders() {
|
|
|
128
127
|
* @returns {{ primary: string, enabled: string[] }}
|
|
129
128
|
*/
|
|
130
129
|
export function loadEnabledProviders(projectDir) {
|
|
131
|
-
const
|
|
132
|
-
if (!existsSync(configPath)) {
|
|
133
|
-
return { primary: 'claude', enabled: ['claude'] };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const raw = readFileSync(configPath, 'utf-8');
|
|
137
|
-
|
|
138
|
-
// Lightweight YAML extraction (avoid dependency in spawning path)
|
|
139
|
-
const enabled = [];
|
|
140
|
-
let primary = 'claude';
|
|
141
|
-
|
|
142
|
-
for (const name of Object.keys(PROVIDERS)) {
|
|
143
|
-
const enabledMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?enabled:\\s*(true|false)`, 'm'));
|
|
144
|
-
if (enabledMatch && enabledMatch[1] === 'true') {
|
|
145
|
-
enabled.push(name);
|
|
146
|
-
}
|
|
147
|
-
const primaryMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?primary:\\s*(true|false)`, 'm'));
|
|
148
|
-
if (primaryMatch && primaryMatch[1] === 'true') {
|
|
149
|
-
primary = name;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Claude is always enabled as fallback
|
|
154
|
-
if (!enabled.includes('claude')) {
|
|
155
|
-
enabled.unshift('claude');
|
|
156
|
-
}
|
|
157
|
-
|
|
130
|
+
const { primary, enabled } = parseProviderConfig(projectDir);
|
|
158
131
|
return { primary, enabled };
|
|
159
132
|
}
|
|
160
133
|
|
|
@@ -170,16 +143,12 @@ export function loadEnabledProviders(projectDir) {
|
|
|
170
143
|
export function resolveProviderForAgent(agent, projectDir, agentModels) {
|
|
171
144
|
const { primary, enabled } = loadEnabledProviders(projectDir);
|
|
172
145
|
|
|
173
|
-
// Check agent_overrides in config.yaml
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const overrideProvider = overrideMatch[1];
|
|
180
|
-
if (enabled.includes(overrideProvider)) {
|
|
181
|
-
return { provider: overrideProvider, model: overrideMatch[2] };
|
|
182
|
-
}
|
|
146
|
+
// Check agent_overrides in config.yaml (block-style YAML)
|
|
147
|
+
const { raw } = parseProviderConfig(projectDir);
|
|
148
|
+
if (raw) {
|
|
149
|
+
const override = parseAgentOverride(raw, agent);
|
|
150
|
+
if (override && enabled.includes(override.provider)) {
|
|
151
|
+
return override;
|
|
183
152
|
}
|
|
184
153
|
}
|
|
185
154
|
|
|
@@ -192,8 +161,10 @@ export function resolveProviderForAgent(agent, projectDir, agentModels) {
|
|
|
192
161
|
}
|
|
193
162
|
}
|
|
194
163
|
|
|
195
|
-
// Fallback to primary
|
|
196
|
-
|
|
164
|
+
// Fallback to primary provider with its default light-tier model
|
|
165
|
+
const provider = PROVIDERS[primary];
|
|
166
|
+
const defaultModel = provider ? Object.keys(provider.modelMap)[0] : 'sonnet';
|
|
167
|
+
return { provider: primary, model: defaultModel };
|
|
197
168
|
}
|
|
198
169
|
|
|
199
170
|
/**
|
|
@@ -206,9 +177,10 @@ export async function isProviderAvailable(name) {
|
|
|
206
177
|
const provider = PROVIDERS[name];
|
|
207
178
|
if (!provider) return false;
|
|
208
179
|
|
|
209
|
-
const {
|
|
180
|
+
const { execFileSync } = await import('child_process');
|
|
210
181
|
try {
|
|
211
|
-
|
|
182
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
183
|
+
execFileSync(cmd, [provider.command], { stdio: 'ignore' });
|
|
212
184
|
return true;
|
|
213
185
|
} catch {
|
|
214
186
|
return false;
|
|
@@ -14,24 +14,10 @@ import { runPrism } from '../context/engine.js';
|
|
|
14
14
|
import { loadHandoff, formatHandoff } from '../tasks/handoff.js';
|
|
15
15
|
import { getWriteScope } from './isolation.js';
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
const AGENT_MODELS =
|
|
21
|
-
orchestrator: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
22
|
-
'greenfield-wu': { provider: 'claude', model: 'haiku', tier: 'haiku' },
|
|
23
|
-
'brownfield-wu': { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
24
|
-
brief: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
25
|
-
detail: { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
26
|
-
architect: { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
27
|
-
ux: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
28
|
-
phases: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
29
|
-
tasks: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
30
|
-
'qa-planning': { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
31
|
-
'qa-implementation': { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
32
|
-
dev: { provider: 'claude', model: 'opus', tier: 'opus' },
|
|
33
|
-
devops: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
|
|
34
|
-
};
|
|
17
|
+
// Import DEFAULT_AGENT_MODELS from model-governance (safe — named export,
|
|
18
|
+
// does not trigger main() which is guarded by fileURLToPath check).
|
|
19
|
+
import { DEFAULT_AGENT_MODELS } from '../../framework/hooks/model-governance.js';
|
|
20
|
+
const AGENT_MODELS = DEFAULT_AGENT_MODELS;
|
|
35
21
|
|
|
36
22
|
/**
|
|
37
23
|
* Map of agent names to their definition file paths (relative to project root).
|
package/src/terminal/spawner.js
CHANGED
|
@@ -43,7 +43,8 @@ export function _resetCounter() {
|
|
|
43
43
|
* @typedef {object} SpawnConfig
|
|
44
44
|
* @property {string} agent - Agent name (e.g. "architect")
|
|
45
45
|
* @property {string} taskId - Task identifier
|
|
46
|
-
* @property {string} [model] - LLM model (
|
|
46
|
+
* @property {string} [model] - LLM model tier name (e.g. opus, pro, codex, claude-sonnet)
|
|
47
|
+
* @property {string} [provider] - CLI provider name (claude, gemini, codex, copilot)
|
|
47
48
|
* @property {string} [prompt] - Full prompt string (from prompt-builder, piped via stdin)
|
|
48
49
|
* @property {object} [contextPayload] - Context to inject via env var
|
|
49
50
|
* @property {string[]} [writeScope] - Override write scope
|
|
@@ -114,12 +115,15 @@ export function buildSpawnCommand(config) {
|
|
|
114
115
|
command = adapterResult.command;
|
|
115
116
|
args = adapterResult.args;
|
|
116
117
|
prompt = adapterResult.stdinPrompt;
|
|
117
|
-
} catch {
|
|
118
|
+
} catch (err) {
|
|
118
119
|
// Fallback to claude if provider resolution fails (backwards compatibility)
|
|
120
|
+
console.error(`[chati] Provider "${providerName}" resolution failed: ${err.message}. Falling back to claude.`);
|
|
119
121
|
command = 'claude';
|
|
120
122
|
args = ['--print', '--dangerously-skip-permissions'];
|
|
121
123
|
if (config.model) {
|
|
122
|
-
|
|
124
|
+
const claudeProvider = getProvider('claude');
|
|
125
|
+
const resolvedModel = claudeProvider.modelMap[config.model] || config.model;
|
|
126
|
+
args.push('--model', resolvedModel);
|
|
123
127
|
}
|
|
124
128
|
prompt = config.prompt || null;
|
|
125
129
|
}
|
|
@@ -157,7 +161,7 @@ export function spawnTerminal(config) {
|
|
|
157
161
|
process: child,
|
|
158
162
|
agent: config.agent,
|
|
159
163
|
taskId: config.taskId,
|
|
160
|
-
model: config.model || '
|
|
164
|
+
model: config.model || 'unknown',
|
|
161
165
|
startedAt: new Date().toISOString(),
|
|
162
166
|
status: 'running',
|
|
163
167
|
exitCode: null,
|
|
@@ -166,15 +170,20 @@ export function spawnTerminal(config) {
|
|
|
166
170
|
timeout,
|
|
167
171
|
};
|
|
168
172
|
|
|
169
|
-
// Capture output
|
|
173
|
+
// Capture output (capped at ~10MB to prevent unbounded memory growth)
|
|
174
|
+
const MAX_BUFFER_CHUNKS = 10_000;
|
|
170
175
|
if (child.stdout) {
|
|
171
176
|
child.stdout.on('data', (chunk) => {
|
|
172
|
-
handle.stdout.
|
|
177
|
+
if (handle.stdout.length < MAX_BUFFER_CHUNKS) {
|
|
178
|
+
handle.stdout.push(chunk.toString());
|
|
179
|
+
}
|
|
173
180
|
});
|
|
174
181
|
}
|
|
175
182
|
if (child.stderr) {
|
|
176
183
|
child.stderr.on('data', (chunk) => {
|
|
177
|
-
handle.stderr.
|
|
184
|
+
if (handle.stderr.length < MAX_BUFFER_CHUNKS) {
|
|
185
|
+
handle.stderr.push(chunk.toString());
|
|
186
|
+
}
|
|
178
187
|
});
|
|
179
188
|
}
|
|
180
189
|
|
|
@@ -189,6 +198,17 @@ export function spawnTerminal(config) {
|
|
|
189
198
|
handle.stderr.push(`spawn error: ${err.message}`);
|
|
190
199
|
});
|
|
191
200
|
|
|
201
|
+
// Enforce timeout — kill process if it exceeds max execution time
|
|
202
|
+
const timeoutTimer = setTimeout(() => {
|
|
203
|
+
if (handle.status === 'running') {
|
|
204
|
+
handle.stderr.push(`timeout: process exceeded ${timeout}ms limit`);
|
|
205
|
+
killTerminal(handle);
|
|
206
|
+
}
|
|
207
|
+
}, timeout);
|
|
208
|
+
|
|
209
|
+
// Clear timer when process exits normally
|
|
210
|
+
child.on('exit', () => clearTimeout(timeoutTimer));
|
|
211
|
+
|
|
192
212
|
return handle;
|
|
193
213
|
}
|
|
194
214
|
|
|
@@ -59,7 +59,7 @@ export function analyzeWaves(tasks) {
|
|
|
59
59
|
|
|
60
60
|
// Kahn's algorithm with wave grouping
|
|
61
61
|
const waves = [];
|
|
62
|
-
|
|
62
|
+
const remaining = new Set(tasks.map((t) => t.id));
|
|
63
63
|
|
|
64
64
|
while (remaining.size > 0) {
|
|
65
65
|
// Find all tasks with no pending dependencies (in-degree 0)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared YAML config parser for provider configuration.
|
|
3
|
+
*
|
|
4
|
+
* Extracts provider enabled/primary status and agent overrides from
|
|
5
|
+
* config.yaml using lightweight regex-based parsing. Used by
|
|
6
|
+
* cli-registry.js, context-file-generator.js, and health/engine.js.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse provider configuration from config.yaml.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} projectDir - Project root directory
|
|
16
|
+
* @returns {{ primary: string, enabled: string[], raw: string|null }}
|
|
17
|
+
*/
|
|
18
|
+
export function parseProviderConfig(projectDir) {
|
|
19
|
+
const configPath = join(projectDir, 'chati.dev', 'config.yaml');
|
|
20
|
+
if (!existsSync(configPath)) {
|
|
21
|
+
return { primary: 'claude', enabled: ['claude'], raw: null };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
25
|
+
const providers = ['claude', 'gemini', 'codex', 'copilot'];
|
|
26
|
+
const enabled = [];
|
|
27
|
+
let primary = 'claude';
|
|
28
|
+
|
|
29
|
+
for (const name of providers) {
|
|
30
|
+
const enabledMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?enabled:\\s*(true|false)`, 'm'));
|
|
31
|
+
if (enabledMatch && enabledMatch[1] === 'true') {
|
|
32
|
+
enabled.push(name);
|
|
33
|
+
}
|
|
34
|
+
const primaryMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?primary:\\s*(true|false)`, 'm'));
|
|
35
|
+
if (primaryMatch && primaryMatch[1] === 'true') {
|
|
36
|
+
primary = name;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Claude is always enabled as fallback
|
|
41
|
+
if (!enabled.includes('claude')) {
|
|
42
|
+
enabled.unshift('claude');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { primary, enabled, raw };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse agent override block from config.yaml.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} raw - Raw config.yaml content
|
|
52
|
+
* @param {string} agent - Agent name
|
|
53
|
+
* @returns {{ provider: string, model: string }|null}
|
|
54
|
+
*/
|
|
55
|
+
export function parseAgentOverride(raw, agent) {
|
|
56
|
+
const escaped = agent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
57
|
+
const overrideBlock = raw.match(new RegExp(`^\\s+${escaped}:\\s*\\n((?:\\s+\\w+:.*\\n?)*)`, 'm'));
|
|
58
|
+
if (!overrideBlock) return null;
|
|
59
|
+
|
|
60
|
+
const block = overrideBlock[1];
|
|
61
|
+
const providerMatch = block.match(/provider:\s*(\S+)/);
|
|
62
|
+
const modelMatch = block.match(/model:\s*(\S+)/);
|
|
63
|
+
|
|
64
|
+
if (providerMatch && modelMatch) {
|
|
65
|
+
return { provider: providerMatch[1], model: modelMatch[1] };
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get list of enabled non-Claude providers.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} projectDir - Project root directory
|
|
74
|
+
* @returns {string[]} List of enabled provider names (excludes claude)
|
|
75
|
+
*/
|
|
76
|
+
export function getEnabledNonClaudeProviders(projectDir) {
|
|
77
|
+
const { enabled } = parseProviderConfig(projectDir);
|
|
78
|
+
return enabled.filter(p => p !== 'claude');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a CLI command is available on the system.
|
|
83
|
+
* Cross-platform: uses 'which' on Unix, 'where' on Windows.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} command - CLI command name
|
|
86
|
+
* @returns {Promise<boolean>}
|
|
87
|
+
*/
|
|
88
|
+
export async function isCommandAvailable(command) {
|
|
89
|
+
const { execFileSync } = await import('child_process');
|
|
90
|
+
try {
|
|
91
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
92
|
+
execFileSync(cmd, [command], { stdio: 'ignore' });
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/wizard/feedback.js
CHANGED
|
@@ -60,7 +60,7 @@ export function showQuickStart(title, steps) {
|
|
|
60
60
|
export function showSummary(data) {
|
|
61
61
|
console.log();
|
|
62
62
|
for (const [label, value] of Object.entries(data)) {
|
|
63
|
-
const paddedLabel = label.padEnd(
|
|
63
|
+
const paddedLabel = label.padEnd(16);
|
|
64
64
|
console.log(` ${dim(paddedLabel)} ${value}`);
|
|
65
65
|
}
|
|
66
66
|
}
|
package/src/wizard/i18n.js
CHANGED
|
@@ -20,19 +20,26 @@ const FALLBACK_EN = {
|
|
|
20
20
|
language_label: 'Language',
|
|
21
21
|
ides_label: 'IDEs',
|
|
22
22
|
mcps_label: 'MCPs',
|
|
23
|
+
llm_provider_title: 'Select your default AI provider:',
|
|
24
|
+
llm_provider_claude: 'Claude (Anthropic) — Best for structured reasoning',
|
|
25
|
+
llm_provider_gemini: 'Gemini (Google) — 1M token context window',
|
|
26
|
+
llm_provider_codex: 'Codex (OpenAI) — Optimized for code generation',
|
|
27
|
+
llm_provider_copilot: 'Copilot (GitHub) — Multi-model, GitHub integrated',
|
|
28
|
+
llm_provider_not_installed: '(CLI not detected)',
|
|
29
|
+
llm_provider_label: 'AI Provider',
|
|
23
30
|
will_install: 'Will install:',
|
|
24
31
|
agents_count: '13 agent definitions (DISCOVER, PLAN, BUILD, DEPLOY phases)',
|
|
25
32
|
workflows_count: '6 workflow blueprints',
|
|
26
33
|
templates_count: '6 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate, Quick Brief)',
|
|
27
|
-
constitution: 'Constitution (
|
|
34
|
+
constitution: 'Constitution (19 Articles + Preamble)',
|
|
28
35
|
session_mgmt: 'Session management system',
|
|
29
|
-
quality_gates: 'Quality gates (
|
|
36
|
+
quality_gates: 'Quality gates (3-tier verdicts)',
|
|
30
37
|
proceed: 'Proceed with installation?',
|
|
31
38
|
installing: 'Installing Chati.dev...',
|
|
32
39
|
created_chati: 'Created .chati/ session directory',
|
|
33
40
|
created_framework: 'Created chati.dev/ system directory (agents, templates, workflows)',
|
|
34
41
|
created_commands: 'Created .claude/commands/ (thin router)',
|
|
35
|
-
installed_constitution: 'Installed Constitution (Articles I-
|
|
42
|
+
installed_constitution: 'Installed Constitution (Articles I-XIX)',
|
|
36
43
|
created_session: 'Created session.yaml schema',
|
|
37
44
|
created_claude_md: 'Created CLAUDE.md',
|
|
38
45
|
configured_mcps: 'Configured MCPs:',
|
|
@@ -40,7 +47,7 @@ const FALLBACK_EN = {
|
|
|
40
47
|
agents_valid: 'All 13 agents implement 8 protocols',
|
|
41
48
|
handoff_ok: 'Handoff protocol: OK',
|
|
42
49
|
validation_ok: 'Self-validation criteria: OK',
|
|
43
|
-
constitution_ok: 'Constitution:
|
|
50
|
+
constitution_ok: 'Constitution: 19 articles verified',
|
|
44
51
|
created_memories: 'Created .chati/memories/ (Memory Layer)',
|
|
45
52
|
installed_intelligence: 'Installed Intelligence Layer (Context Engine, Memory, Registry)',
|
|
46
53
|
intelligence_valid: 'Intelligence: 6 spec files verified',
|
package/src/wizard/index.js
CHANGED
|
@@ -3,7 +3,7 @@ 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, stepConfirmation } from './questions.js';
|
|
6
|
+
import { stepLanguage, stepProjectType, stepLlmProvider, 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';
|
|
@@ -35,16 +35,20 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
35
35
|
// Step 2: Project Type
|
|
36
36
|
const projectType = options.projectType || await stepProjectType(targetDir);
|
|
37
37
|
|
|
38
|
+
// Step 3: LLM Provider
|
|
39
|
+
const llmProvider = options.llmProvider || await stepLlmProvider();
|
|
40
|
+
|
|
38
41
|
// Auto-configured: Claude Code + default MCPs
|
|
39
42
|
const selectedIDEs = options.ides || ['claude-code'];
|
|
40
43
|
const selectedMCPs = options.mcps || DEFAULT_MCPS;
|
|
41
44
|
|
|
42
|
-
// Step
|
|
45
|
+
// Step 4: Confirmation
|
|
43
46
|
const projectName = basename(targetDir);
|
|
44
47
|
const config = {
|
|
45
48
|
projectName,
|
|
46
49
|
projectType,
|
|
47
50
|
language,
|
|
51
|
+
llmProvider,
|
|
48
52
|
selectedIDEs,
|
|
49
53
|
selectedMCPs,
|
|
50
54
|
targetDir,
|
|
@@ -53,7 +57,7 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
53
57
|
|
|
54
58
|
await stepConfirmation(config);
|
|
55
59
|
|
|
56
|
-
// Step
|
|
60
|
+
// Step 5: Installation + Validation
|
|
57
61
|
console.log();
|
|
58
62
|
const installSpinner = createSpinner(t('installer.installing'));
|
|
59
63
|
installSpinner.start();
|
|
@@ -80,14 +84,21 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
80
84
|
const validation = await validateInstallation(targetDir);
|
|
81
85
|
validateSpinner.stop();
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
// Show validation results based on actual checks
|
|
88
|
+
if (validation.agents?.pass) showValidation(t('installer.agents_valid'));
|
|
89
|
+
if (validation.constitution?.pass) showValidation(t('installer.constitution_ok'));
|
|
90
|
+
if (validation.intelligence?.pass) showValidation(t('installer.intelligence_valid'));
|
|
91
|
+
if (validation.registry?.pass) showValidation(t('installer.registry_valid'));
|
|
92
|
+
if (validation.memories?.pass) showValidation(t('installer.memories_valid'));
|
|
93
|
+
if (validation.session?.pass) showValidation(t('installer.session_ok'));
|
|
84
94
|
showValidation(t('installer.handoff_ok'));
|
|
85
95
|
showValidation(t('installer.validation_ok'));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
|
|
97
|
+
// Warn if any checks failed
|
|
98
|
+
if (validation.passed < validation.total) {
|
|
99
|
+
const failed = validation.total - validation.passed;
|
|
100
|
+
p.log.warn(`${failed} validation check(s) did not pass. Run 'npx chati-dev health' for details.`);
|
|
101
|
+
}
|
|
91
102
|
|
|
92
103
|
console.log();
|
|
93
104
|
p.outro(t('installer.success'));
|
|
@@ -101,7 +112,8 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
101
112
|
return { success: true, config, validation };
|
|
102
113
|
} catch (err) {
|
|
103
114
|
installSpinner.stop();
|
|
104
|
-
|
|
115
|
+
const phase = err.message?.includes('validat') ? 'Validation' : 'Installation';
|
|
116
|
+
p.cancel(`${phase} failed: ${err.message}`);
|
|
105
117
|
return { success: false, error: err.message };
|
|
106
118
|
}
|
|
107
119
|
}
|
package/src/wizard/questions.js
CHANGED
|
@@ -3,6 +3,7 @@ import { t, SUPPORTED_LANGUAGES, loadLanguage } from './i18n.js';
|
|
|
3
3
|
import { detectProjectType } from '../utils/detector.js';
|
|
4
4
|
import { showSummary, showChecklist } from './feedback.js';
|
|
5
5
|
import { brand, dim, success } from '../utils/colors.js';
|
|
6
|
+
import { isProviderAvailable } from '../terminal/cli-registry.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Step 1: Language Selection (always in English)
|
|
@@ -58,12 +59,58 @@ export async function stepProjectType(targetDir) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Step 3:
|
|
62
|
+
* Step 3: LLM Provider Selection
|
|
63
|
+
*/
|
|
64
|
+
export async function stepLlmProvider() {
|
|
65
|
+
const providers = [
|
|
66
|
+
{ name: 'claude', label: t('installer.llm_provider_claude') },
|
|
67
|
+
{ name: 'gemini', label: t('installer.llm_provider_gemini') },
|
|
68
|
+
{ name: 'codex', label: t('installer.llm_provider_codex') },
|
|
69
|
+
{ name: 'copilot', label: t('installer.llm_provider_copilot') },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// Check availability of each provider
|
|
73
|
+
const availability = {};
|
|
74
|
+
for (const prov of providers) {
|
|
75
|
+
try {
|
|
76
|
+
availability[prov.name] = await isProviderAvailable(prov.name);
|
|
77
|
+
} catch {
|
|
78
|
+
availability[prov.name] = prov.name === 'claude'; // claude always available
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const options = providers.map(prov => {
|
|
83
|
+
const installed = availability[prov.name];
|
|
84
|
+
const suffix = installed ? '' : ` ${dim(t('installer.llm_provider_not_installed'))}`;
|
|
85
|
+
return {
|
|
86
|
+
value: prov.name,
|
|
87
|
+
label: `${prov.label}${suffix}`,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const provider = await p.select({
|
|
92
|
+
message: t('installer.llm_provider_title'),
|
|
93
|
+
options,
|
|
94
|
+
initialValue: 'claude',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (p.isCancel(provider)) {
|
|
98
|
+
p.cancel('Installation cancelled.');
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return provider;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Step 4: Confirmation
|
|
62
107
|
*/
|
|
63
108
|
export async function stepConfirmation(config) {
|
|
64
|
-
const { projectName, projectType, language, selectedMCPs } = config;
|
|
109
|
+
const { projectName, projectType, language, llmProvider, selectedMCPs } = config;
|
|
65
110
|
|
|
66
111
|
const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
|
|
112
|
+
const providerNames = { claude: 'Claude (Anthropic)', gemini: 'Gemini (Google)', codex: 'Codex (OpenAI)', copilot: 'Copilot (GitHub)' };
|
|
113
|
+
const providerDisplay = providerNames[llmProvider] || llmProvider;
|
|
67
114
|
const ideNames = 'Claude Code (auto-configured)';
|
|
68
115
|
const mcpNames = selectedMCPs.length > 0
|
|
69
116
|
? `${selectedMCPs.join(', ')} (auto-installed)`
|
|
@@ -74,6 +121,7 @@ export async function stepConfirmation(config) {
|
|
|
74
121
|
showSummary({
|
|
75
122
|
[t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
|
|
76
123
|
[t('installer.language_label')]: langName,
|
|
124
|
+
[t('installer.llm_provider_label')]: providerDisplay,
|
|
77
125
|
[t('installer.ides_label')]: ideNames,
|
|
78
126
|
[t('installer.mcps_label')]: mcpNames,
|
|
79
127
|
});
|