chati-dev 4.5.9 → 4.5.11

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.
@@ -911,6 +911,8 @@ export function getPipelineProgress(pipelineState) {
911
911
  return {
912
912
  phase,
913
913
  progress: Math.round(progress),
914
+ completedCount: completed,
915
+ totalAgents,
914
916
  completedAgents,
915
917
  currentAgent,
916
918
  nextAgent,
@@ -10,6 +10,44 @@ const AGENT_ACTIONS = Object.freeze({
10
10
  devops: 'build',
11
11
  });
12
12
 
13
+ export const V2_PLANNING_AGENTS = Object.freeze([
14
+ 'greenfield-wu', 'brownfield-wu', 'brief',
15
+ 'detail', 'architect', 'ux', 'qa-planning', 'phases', 'tasks',
16
+ ]);
17
+
18
+ const RAIL_ACTIONS = new Set(['build', 'review']);
19
+
20
+ export function describeRuntimeInstallationV2(artifact) {
21
+ const railExecutionPool = artifact.installation.enabled_providers.map((binding) => {
22
+ const capabilities = binding.allowed_models.flatMap((modelId) => {
23
+ const model = artifact.capability_snapshot.models.find((candidate) =>
24
+ candidate.provider_id === binding.provider_id && candidate.model_id === modelId
25
+ );
26
+ if (!model) return [];
27
+ const actions = model.actions.filter((action) => RAIL_ACTIONS.has(action));
28
+ if (actions.length === 0) return [];
29
+ return [{ model_id: modelId, actions, tier: model.tier || null }];
30
+ });
31
+ return {
32
+ provider_id: binding.provider_id,
33
+ harness_id: binding.harness_id,
34
+ models: capabilities.map(({ model_id }) => model_id),
35
+ capabilities,
36
+ };
37
+ }).filter(({ models }) => models.length > 0);
38
+
39
+ return Object.freeze({
40
+ architecture: 'chati-v2-rail',
41
+ enabled_providers: artifact.installation.enabled_providers.map(({ provider_id, harness_id }) => ({ provider_id, harness_id })),
42
+ rail_execution_pool: railExecutionPool,
43
+ release_lane: {
44
+ authority: 'explicit-human-authorization',
45
+ execution_authority: 'rail',
46
+ legacy_devops_agent: false,
47
+ },
48
+ });
49
+ }
50
+
13
51
  export function loadRuntimeInstallationV2(projectDir, { clock = () => new Date() } = {}) {
14
52
  const path = join(projectDir, INSTALLATION_ARTIFACT_PATH);
15
53
  if (!existsSync(path)) return null;
@@ -973,7 +973,7 @@ export function validateSession(projectDir) {
973
973
  }
974
974
 
975
975
  // Validate mode
976
- const validModes = ['discover', 'plan', 'build', 'validate', 'deploy', 'completed'];
976
+ const validModes = ['discover', 'plan', 'rail', 'release', 'build', 'validate', 'deploy', 'completed'];
977
977
  if (!validModes.includes(session.mode)) {
978
978
  return {
979
979
  exists: true,
@@ -1,5 +1,6 @@
1
1
  import ora from 'ora';
2
2
  import { brand, success, dim } from '../utils/colors.js';
3
+ import { t } from './i18n.js';
3
4
 
4
5
  /**
5
6
  * Create a spinner with chati.dev branding
@@ -50,8 +51,8 @@ export function showQuickStart(title, steps) {
50
51
  console.log(` ${dim(`${i + 1}.`)} ${step}`);
51
52
  });
52
53
  console.log();
53
- console.log(`${dim('Documentation:')} .chati.dev/constitution.md`);
54
- console.log(`${dim('Session:')} .chati/session.yaml`);
54
+ console.log(`${dim(`${t('installer.documentation_label')}:`)} .chati.dev/constitution.md`);
55
+ console.log(`${dim(`${t('installer.session_label')}:`)} .chati/session.yaml`);
55
56
  }
56
57
 
57
58
  /**
@@ -30,6 +30,10 @@ const FALLBACK_EN = {
30
30
  ide_selection_title: 'Select your IDEs / CLIs (multiple allowed):',
31
31
  provider_selection_title: 'Which AI providers will you use?',
32
32
  editor_selection_title: 'Which editor IDEs should get rules files? (optional)',
33
+ provider_claude_hint: 'Anthropic official CLI',
34
+ provider_gemini_hint: 'Google AI terminal agent',
35
+ provider_codex_hint: 'OpenAI Codex terminal agent',
36
+ provider_grok_hint: 'xAI terminal agent',
33
37
  installation_mode_title: 'Select the installation mode',
34
38
  installation_mode_open: 'Open',
35
39
  installation_mode_open_hint: 'Public framework',
@@ -39,6 +43,19 @@ const FALLBACK_EN = {
39
43
  back: 'Back',
40
44
  back_hint: 'Return to the previous step',
41
45
  install: 'Install',
46
+ cancelled: 'Installation cancelled.',
47
+ multiselect_hint: '(space to select, enter to confirm)',
48
+ greenfield_label: 'Greenfield',
49
+ brownfield_label: 'Brownfield',
50
+ none: 'None',
51
+ auto_installed: 'auto-installed',
52
+ connected_via: 'Connected via {providers}',
53
+ target: 'Target',
54
+ yes: 'Yes',
55
+ no: 'No',
56
+ auto_detection_title: 'Auto-detection',
57
+ documentation_label: 'Documentation',
58
+ session_label: 'Session',
42
59
  model_routing_label: 'Model routing',
43
60
  model_routing_automatic: 'Automatic per task across all enabled models',
44
61
  providers_label: 'Providers',
@@ -64,6 +81,7 @@ const FALLBACK_EN = {
64
81
  agents_valid: 'All agents implement 8 protocols',
65
82
  handoff_ok: 'Handoff protocol: OK',
66
83
  validation_ok: 'Self-validation criteria: OK',
84
+ validation_failed_count: "{count} validation check(s) did not pass. Run 'npx chati-dev health' for details.",
67
85
  constitution_ok: 'Constitution: 25 articles verified',
68
86
  created_memories: 'Created .chati/memories/ (Memory Layer)',
69
87
  installed_intelligence: 'Installed Intelligence Layer (Context Engine, Memory, Registry)',
@@ -84,6 +102,29 @@ const FALLBACK_EN = {
84
102
  tos_declined: 'Terms of Use declined. Installation cancelled.',
85
103
  tos_telemetry_enabled: 'Telemetry: enabled (anonymous, opt-out — disable with: npx chati-dev telemetry disable)',
86
104
  tos_telemetry_disabled: 'Telemetry: disabled',
105
+ clickup_required: 'Internal mode requires an authorized ClickUp MCP connection in at least one selected CLI. Configure ClickUp in Claude, Codex or Grok, then retry.',
106
+ system_prerequisites_failed: 'System prerequisites not met:',
107
+ fix_prerequisites: 'Fix the issues above and try again.',
108
+ system_check: 'System check:',
109
+ created_provider_claude: 'Created .claude/commands/ (thin router)',
110
+ created_provider_gemini: 'Created .gemini/commands/ (TOML command)',
111
+ created_provider_codex: 'Created .agents/skills/chati/ (Codex skill)',
112
+ created_provider_grok: 'Created .grok/commands/ (native markdown command)',
113
+ validation_phase: 'Validation',
114
+ installation_phase: 'Installation',
115
+ phase_failed: '{phase} failed: {error}',
116
+ license_activation_title: 'License Activation',
117
+ license_activation_info: 'Your license key works across all projects on this machine.',
118
+ license_trial_info: 'Get a free 14-day trial at: https://chati.dev/pricing',
119
+ license_prompt: 'Enter your license key (press Enter to skip, activate later with: npx chati-dev activate):',
120
+ license_placeholder: 'CHATI-XXXX-XXXX-XXXX',
121
+ license_skipped: 'Skipped. Run: npx chati-dev activate --key=YOUR-KEY when ready.',
122
+ license_activating: 'Activating license...',
123
+ license_activated: 'License activated! Plan: {plan}, {days} day(s) remaining.',
124
+ license_activation_failed: 'Activation failed: {error}',
125
+ license_retry: 'Run: npx chati-dev activate --key=YOUR-KEY to try again.',
126
+ license_saved_invalid: 'Saved license is not valid on this machine: {reason}',
127
+ license_verify_failed: 'Could not verify the saved license: {error}',
87
128
  },
88
129
  agents: {
89
130
  starting: 'Starting agent: {agent}',
@@ -137,6 +178,7 @@ export function loadLanguage(lang, targetDir = null) {
137
178
  // Try to load from installed project i18n
138
179
  const paths = [
139
180
  targetDir && join(targetDir, resolveFrameworkDir(targetDir), 'i18n', `${lang}.yaml`),
181
+ join(__dirname, '..', '..', 'framework', 'i18n', `${lang}.yaml`),
140
182
  join(__dirname, '..', '..', 'assets', 'i18n', `${lang}.yaml`),
141
183
  ].filter(Boolean);
142
184
 
@@ -14,7 +14,7 @@ import { validateInstallation } from '../installer/validator.js';
14
14
  import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
15
15
  import { sendEvents } from '../telemetry/sender.js';
16
16
  import { getTelemetryConfig } from '../telemetry/config.js';
17
- import { t } from './i18n.js';
17
+ import { loadLanguage, t } from './i18n.js';
18
18
  import { getLicenseKey, activateLicense, ensureCurrentMachineLicense, requireLicenseEntitlement } from '../license/client.js';
19
19
  import { DEFAULT_MCPS } from '../config/mcp-configs.js';
20
20
  import { IDE_CONFIGS } from '../config/ide-configs.js';
@@ -40,6 +40,10 @@ export async function runWizard(targetDir, options = {}) {
40
40
 
41
41
  p.intro('Setting up chati.dev');
42
42
 
43
+ // Language is the first question and controls every subsequent wizard step.
44
+ let language = options.language || await stepLanguage();
45
+ loadLanguage(language);
46
+
43
47
  await runLicenseActivationStep();
44
48
 
45
49
  // Preflight: validate system prerequisites (Node >= 20, npm, Git, Playwright)
@@ -50,14 +54,11 @@ export async function runWizard(targetDir, options = {}) {
50
54
  return ` ${icon} ${c.message}`;
51
55
  }).join('\n');
52
56
  if (!preflight.passed) {
53
- p.log.error(`System prerequisites not met:\n${preflightFmt}`);
54
- p.cancel('Fix the issues above and try again.');
57
+ p.log.error(`${t('installer.system_prerequisites_failed')}\n${preflightFmt}`);
58
+ p.cancel(t('installer.fix_prerequisites'));
55
59
  process.exit(1);
56
60
  }
57
- p.log.success(`System check:\n${preflightFmt}`);
58
-
59
- // Step 2: Language Selection
60
- let language = options.language || await stepLanguage();
61
+ p.log.success(`${t('installer.system_check')}\n${preflightFmt}`);
61
62
 
62
63
  // Step 2: Terms of Use (must accept to continue)
63
64
  if (options.telemetry === undefined) {
@@ -156,7 +157,7 @@ export async function runWizard(targetDir, options = {}) {
156
157
  }
157
158
  clickupPreflight = options.clickupPreflight || checkClickUpMcp(selectedProviders);
158
159
  if (!clickupPreflight.passed) {
159
- p.log.error('Internal mode requires an authorized ClickUp MCP connection in at least one selected CLI. Configure ClickUp in Claude, Codex or Grok, then retry.');
160
+ p.log.error(t('installer.clickup_required'));
160
161
  continue;
161
162
  }
162
163
  }
@@ -229,10 +230,10 @@ export async function runWizard(targetDir, options = {}) {
229
230
 
230
231
  // Show provider-specific command creation
231
232
  const commandStepMap = {
232
- 'claude-code': 'Created .claude/commands/ (thin router)',
233
- 'gemini-cli': 'Created .gemini/commands/ (TOML command)',
234
- 'codex-cli': 'Created .agents/skills/chati/ (Codex skill)',
235
- 'grok-cli': 'Created .grok/commands/ (native markdown command)',
233
+ 'claude-code': t('installer.created_provider_claude'),
234
+ 'gemini-cli': t('installer.created_provider_gemini'),
235
+ 'codex-cli': t('installer.created_provider_codex'),
236
+ 'grok-cli': t('installer.created_provider_grok'),
236
237
  };
237
238
  for (const cliIDE of selectedCLIIDEs) {
238
239
  showStep(commandStepMap[cliIDE] || t('installer.created_commands'));
@@ -275,7 +276,7 @@ export async function runWizard(targetDir, options = {}) {
275
276
  // Warn if any checks failed
276
277
  if (validation.passed < validation.total) {
277
278
  const failed = validation.total - validation.passed;
278
- p.log.warn(`${failed} validation check(s) did not pass. Run 'npx chati-dev health' for details.`);
279
+ p.log.warn(t('installer.validation_failed_count', { count: failed }));
279
280
  }
280
281
 
281
282
  console.log();
@@ -316,8 +317,8 @@ export async function runWizard(targetDir, options = {}) {
316
317
  return { success: true, config, validation };
317
318
  } catch (err) {
318
319
  installSpinner.stop();
319
- const phase = err.message?.includes('validat') ? 'Validation' : 'Installation';
320
- p.cancel(`${phase} failed: ${err.message}`);
320
+ const phase = err.message?.includes('validat') ? t('installer.validation_phase') : t('installer.installation_phase');
321
+ p.cancel(t('installer.phase_failed', { phase, error: err.message }));
321
322
  return { success: false, error: err.message };
322
323
  }
323
324
  }
@@ -328,38 +329,38 @@ async function runLicenseActivationStep() {
328
329
  try {
329
330
  const status = await ensureCurrentMachineLicense({ key: existingKey });
330
331
  if (status.status === 'VALID') return;
331
- p.log.warn(`Saved license is not valid on this machine: ${status.reason || status.status}`);
332
+ p.log.warn(t('installer.license_saved_invalid', { reason: status.reason || status.status }));
332
333
  } catch (error) {
333
- p.log.warn(`Could not verify the saved license: ${error.message}`);
334
+ p.log.warn(t('installer.license_verify_failed', { error: error.message }));
334
335
  }
335
336
  }
336
337
 
337
- console.log('\n ┌─ License Activation ─────────────────────────────────────────┐');
338
- console.log(' │ Your license key works across ALL projects on this machine.');
339
- console.log(' │ Get a free 14-day trial at: https://chati.dev/pricing │');
340
- console.log(' └──────────────────────────────────────────────────────────────┘\n');
338
+ p.note(
339
+ `${t('installer.license_activation_info')}\n${t('installer.license_trial_info')}`,
340
+ t('installer.license_activation_title'),
341
+ );
341
342
 
342
343
  const keyInput = await p.text({
343
- message: 'Enter your license key (press Enter to skip — activate later with: npx chati-dev activate):',
344
- placeholder: 'CHATI-XXXX-XXXX-XXXX',
344
+ message: t('installer.license_prompt'),
345
+ placeholder: t('installer.license_placeholder'),
345
346
  });
346
347
 
347
348
  if (p.isCancel(keyInput) || !keyInput?.trim()) {
348
- console.log(' Skipped. Run: npx chati-dev activate --key=YOUR-KEY when ready.\n');
349
+ console.log(` ${t('installer.license_skipped')}\n`);
349
350
  return;
350
351
  }
351
352
 
352
- const activateSpinner = createSpinner('Activating license...');
353
+ const activateSpinner = createSpinner(t('installer.license_activating'));
353
354
  activateSpinner.start();
354
355
  try {
355
356
  const result = await activateLicense(null, keyInput.trim());
356
357
  activateSpinner.stop();
357
358
  const plan = result.plan ?? 'trial';
358
359
  const days = result.days_remaining ?? '?';
359
- console.log(` ✓ License activated! Plan: ${plan} ${days} day(s) remaining.\n`);
360
+ console.log(` ✓ ${t('installer.license_activated', { plan, days })}\n`);
360
361
  } catch (err) {
361
362
  activateSpinner.stop();
362
- console.log(` ✗ Activation failed: ${err.message}`);
363
- console.log(' Run: npx chati-dev activate --key=YOUR-KEY to try again.\n');
363
+ console.log(` ✗ ${t('installer.license_activation_failed', { error: err.message })}`);
364
+ console.log(` ${t('installer.license_retry')}\n`);
364
365
  }
365
366
  }
@@ -9,6 +9,11 @@ import { supportedV2WizardProviders, wizardModelSuggestions } from '../installer
9
9
 
10
10
  export const WIZARD_BACK = '__chati_wizard_back__';
11
11
 
12
+ function cancelInstallation() {
13
+ p.cancel(t('installer.cancelled'));
14
+ process.exit(0);
15
+ }
16
+
12
17
  function backOption() {
13
18
  return { value: WIZARD_BACK, label: `← ${t('installer.back')}`, hint: t('installer.back_hint') };
14
19
  }
@@ -26,8 +31,7 @@ export async function stepLanguage() {
26
31
  });
27
32
 
28
33
  if (p.isCancel(language)) {
29
- p.cancel('Installation cancelled.');
30
- process.exit(0);
34
+ cancelInstallation();
31
35
  }
32
36
 
33
37
  // Load i18n strings from this point forward
@@ -45,7 +49,7 @@ export async function stepProjectType(targetDir, { allowBack = false } = {}) {
45
49
  if (detection.suggestion === 'brownfield' && detection.confidence !== 'low') {
46
50
  p.note(
47
51
  `${success('✓')} ${t('installer.detected_brownfield')}\n${t('installer.suggestion_brownfield')}`,
48
- dim('Auto-detection')
52
+ dim(t('installer.auto_detection_title'))
49
53
  );
50
54
  }
51
55
 
@@ -60,8 +64,7 @@ export async function stepProjectType(targetDir, { allowBack = false } = {}) {
60
64
  });
61
65
 
62
66
  if (p.isCancel(projectType)) {
63
- p.cancel('Installation cancelled.');
64
- process.exit(0);
67
+ cancelInstallation();
65
68
  }
66
69
 
67
70
  return projectType;
@@ -101,19 +104,18 @@ export async function stepProviderSelection({ allowBack = false } = {}) {
101
104
  options.push({
102
105
  value: provider,
103
106
  label: `${providerName}${suffix}`,
104
- hint: config.description,
107
+ hint: t(`installer.provider_${provider}_hint`),
105
108
  });
106
109
  }
107
110
 
108
111
  const selected = await p.multiselect({
109
- message: `${t('installer.provider_selection_title')} ${dim('(space to select, enter to confirm)')}`,
112
+ message: `${t('installer.provider_selection_title')} ${dim(t('installer.multiselect_hint'))}`,
110
113
  options: allowBack ? [...options, backOption()] : options,
111
114
  required: true,
112
115
  });
113
116
 
114
117
  if (p.isCancel(selected)) {
115
- p.cancel('Installation cancelled.');
116
- process.exit(0);
118
+ cancelInstallation();
117
119
  }
118
120
 
119
121
  return selected.includes(WIZARD_BACK) ? WIZARD_BACK : selected;
@@ -134,8 +136,7 @@ export async function stepInstallationMode({ allowBack = false } = {}) {
134
136
  initialValue: 'open',
135
137
  });
136
138
  if (p.isCancel(mode)) {
137
- p.cancel('Installation cancelled.');
138
- process.exit(0);
139
+ cancelInstallation();
139
140
  }
140
141
  return mode;
141
142
  }
@@ -162,14 +163,13 @@ export async function stepEditorSelection({ allowBack = false } = {}) {
162
163
  }));
163
164
 
164
165
  const selected = await p.multiselect({
165
- message: `${t('installer.editor_selection_title')} ${dim('(space to select, enter to confirm)')}`,
166
+ message: `${t('installer.editor_selection_title')} ${dim(t('installer.multiselect_hint'))}`,
166
167
  options: allowBack ? [...options, backOption()] : options,
167
168
  required: false,
168
169
  });
169
170
 
170
171
  if (p.isCancel(selected)) {
171
- p.cancel('Installation cancelled.');
172
- process.exit(0);
172
+ cancelInstallation();
173
173
  }
174
174
 
175
175
  return selected.includes(WIZARD_BACK) ? WIZARD_BACK : selected;
@@ -223,14 +223,13 @@ export async function stepIDESelection() {
223
223
  }
224
224
 
225
225
  const selectedIDEs = await p.multiselect({
226
- message: `${t('installer.ide_selection_title')} ${dim('(space to select, enter to confirm)')}`,
226
+ message: `${t('installer.ide_selection_title')} ${dim(t('installer.multiselect_hint'))}`,
227
227
  options,
228
228
  required: true,
229
229
  });
230
230
 
231
231
  if (p.isCancel(selectedIDEs)) {
232
- p.cancel('Installation cancelled.');
233
- process.exit(0);
232
+ cancelInstallation();
234
233
  }
235
234
 
236
235
  // Filter out separator headers
@@ -269,13 +268,13 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
269
268
  const editorIDEs = (selectedIDEs || []).filter(id => IDE_CONFIGS[id]?.group === 'editor');
270
269
  const editorNames = editorIDEs.length > 0
271
270
  ? editorIDEs.map(id => IDE_CONFIGS[id]?.name || id).join(', ')
272
- : 'None';
271
+ : t('installer.none');
273
272
  const mcpNames = selectedMCPs.length > 0
274
- ? `${selectedMCPs.join(', ')} (auto-installed)`
275
- : 'None';
273
+ ? `${selectedMCPs.join(', ')} (${t('installer.auto_installed')})`
274
+ : t('installer.none');
276
275
 
277
276
  const summaryData = {
278
- [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
277
+ [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? t('installer.greenfield_label') : t('installer.brownfield_label')})`,
279
278
  [t('installer.language_label')]: langName,
280
279
  [t('installer.providers_label')]: providersDisplay,
281
280
  [t('installer.installation_mode_label')]: installationMode === 'internal'
@@ -289,7 +288,7 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
289
288
  }
290
289
 
291
290
  if (installationMode === 'internal') {
292
- summaryData.ClickUp = `Connected via ${(clickupPreflight?.connected_providers || []).join(', ')}`;
291
+ summaryData.ClickUp = t('installer.connected_via', { providers: (clickupPreflight?.connected_providers || []).join(', ') });
293
292
  }
294
293
 
295
294
  summaryData[t('installer.mcps_label')] = mcpNames;
@@ -299,7 +298,7 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
299
298
  showSummary(summaryData);
300
299
 
301
300
  console.log();
302
- console.log(` ${t('installer.will_install')}:`);
301
+ console.log(` ${t('installer.will_install')}`);
303
302
  showChecklist([
304
303
  t('installer.agents_count'),
305
304
  t('installer.workflows_count'),
@@ -310,7 +309,7 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
310
309
  ]);
311
310
 
312
311
  console.log();
313
- console.log(` ${dim('Target:')} ${config.targetDir}`);
312
+ console.log(` ${dim(`${t('installer.target')}:`)} ${config.targetDir}`);
314
313
 
315
314
  const proceed = allowBack
316
315
  ? await p.select({
@@ -324,13 +323,14 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
324
323
  : await p.confirm({
325
324
  message: t('installer.proceed'),
326
325
  initialValue: true,
326
+ active: t('installer.yes'),
327
+ inactive: t('installer.no'),
327
328
  });
328
329
 
329
330
  if (proceed === WIZARD_BACK) return WIZARD_BACK;
330
331
 
331
332
  if (p.isCancel(proceed) || !proceed) {
332
- p.cancel('Installation cancelled.');
333
- process.exit(0);
333
+ cancelInstallation();
334
334
  }
335
335
 
336
336
  return true;
@@ -354,6 +354,8 @@ export async function stepTermsOfUse() {
354
354
  const accepted = await p.confirm({
355
355
  message: t('installer.tos_accept'),
356
356
  initialValue: true,
357
+ active: t('installer.yes'),
358
+ inactive: t('installer.no'),
357
359
  });
358
360
 
359
361
  if (p.isCancel(accepted) || !accepted) {