chati-dev 4.4.1 → 4.5.2

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.
Files changed (82) hide show
  1. package/README.md +29 -27
  2. package/bin/chati.js +235 -1
  3. package/framework/agents/plan/tasks.md +46 -1
  4. package/framework/config.yaml +2 -2
  5. package/framework/constitution.md +13 -16
  6. package/framework/context/governance.md +5 -5
  7. package/framework/context/root.md +5 -5
  8. package/framework/i18n/en.yaml +8 -2
  9. package/framework/i18n/es.yaml +8 -2
  10. package/framework/i18n/fr.yaml +8 -2
  11. package/framework/i18n/pt.yaml +8 -2
  12. package/framework/manifest.json +21 -21
  13. package/framework/manifest.sig +1 -1
  14. package/framework/orchestrator/chati.md +43 -12
  15. package/node_modules/@chati/browser-capability/README.md +10 -0
  16. package/node_modules/@chati/browser-capability/package.json +17 -0
  17. package/node_modules/@chati/browser-capability/src/index.js +165 -0
  18. package/node_modules/@chati/core/package.json +13 -0
  19. package/node_modules/@chati/core/src/index.js +111 -0
  20. package/node_modules/@chati/knowledge-context/package.json +17 -0
  21. package/node_modules/@chati/knowledge-context/src/index.js +202 -0
  22. package/node_modules/@chati/planning/package.json +17 -0
  23. package/node_modules/@chati/planning/src/index.js +367 -0
  24. package/node_modules/@chati/provider-registry/package.json +16 -0
  25. package/node_modules/@chati/provider-registry/src/index.js +148 -0
  26. package/node_modules/@chati/rail/README.md +24 -0
  27. package/node_modules/@chati/rail/package.json +19 -0
  28. package/node_modules/@chati/rail/src/index.js +437 -0
  29. package/node_modules/@chati/release-lane/README.md +24 -0
  30. package/node_modules/@chati/release-lane/package.json +17 -0
  31. package/node_modules/@chati/release-lane/src/index.js +172 -0
  32. package/node_modules/@chati/review-council/package.json +17 -0
  33. package/node_modules/@chati/review-council/src/index.js +264 -0
  34. package/node_modules/@chati/tracking-clickup/README.md +55 -0
  35. package/node_modules/@chati/tracking-clickup/package.json +17 -0
  36. package/node_modules/@chati/tracking-clickup/src/index.js +293 -0
  37. package/package.json +25 -3
  38. package/src/config/ide-configs.js +11 -0
  39. package/src/context/domain-loader.js +1 -1
  40. package/src/dashboard/data-reader.js +1 -1
  41. package/src/executors/runner.js +1 -1
  42. package/src/installer/core.js +14 -13
  43. package/src/installer/provider-overlay.js +8 -15
  44. package/src/installer/scaffold-applier.js +1 -1
  45. package/src/installer/templates.js +12 -25
  46. package/src/installer/validator.js +5 -10
  47. package/src/installer-v2/catalog-client.js +165 -0
  48. package/src/installer-v2/index.js +304 -0
  49. package/src/installer-v2/model-catalog-envelope.json +278 -0
  50. package/src/installer-v2/model-catalog.json +130 -0
  51. package/src/installer-v2/model-catalog.sig +1 -0
  52. package/src/installer-v2/wizard-installation.js +116 -0
  53. package/src/intelligence/registry-manager.js +1 -1
  54. package/src/license/client.js +27 -1
  55. package/src/memory/session-digest.js +1 -1
  56. package/src/merger/yaml-merger.js +1 -1
  57. package/src/orchestrator/browser-runtime.js +25 -0
  58. package/src/orchestrator/cli.js +93 -8
  59. package/src/orchestrator/clickup-projection.js +84 -0
  60. package/src/orchestrator/clickup-runtime.js +13 -0
  61. package/src/orchestrator/knowledge-runtime.js +64 -0
  62. package/src/orchestrator/planning-runtime.js +127 -0
  63. package/src/orchestrator/rail-runtime.js +421 -0
  64. package/src/orchestrator/release-runtime.js +14 -0
  65. package/src/orchestrator/review-runtime.js +74 -0
  66. package/src/orchestrator/runtime-installation-v2.js +57 -0
  67. package/src/orchestrator/session-manager.js +1 -1
  68. package/src/telemetry/config.js +1 -1
  69. package/src/terminal/adapters/grok-adapter.js +16 -0
  70. package/src/terminal/adapters/index.js +1 -0
  71. package/src/terminal/cli-registry.js +20 -5
  72. package/src/terminal/prompt-builder.js +4 -2
  73. package/src/terminal/run-agent.js +3 -0
  74. package/src/terminal/run-parallel.js +21 -10
  75. package/src/terminal/spawner.js +7 -1
  76. package/src/terminal/team-task-list.js +1 -1
  77. package/src/upgrade/checker.js +1 -1
  78. package/src/upgrade/migrator.js +1 -1
  79. package/src/utils/config-parser.js +3 -8
  80. package/src/wizard/i18n.js +10 -4
  81. package/src/wizard/index.js +49 -17
  82. package/src/wizard/questions.js +50 -28
@@ -75,6 +75,20 @@ const PROVIDERS = {
75
75
  },
76
76
  adapter: adapters.codex,
77
77
  },
78
+ grok: {
79
+ name: 'grok',
80
+ command: 'grok',
81
+ baseArgs: [],
82
+ modelFlag: '-m',
83
+ stdinSupport: false,
84
+ hooksSupport: true,
85
+ mcpSupport: true,
86
+ contextFile: 'AGENTS.md',
87
+ modelMap: {
88
+ grok: 'grok-4.6',
89
+ },
90
+ adapter: adapters.grok,
91
+ },
78
92
  };
79
93
 
80
94
  // ---------------------------------------------------------------------------
@@ -117,14 +131,14 @@ export function loadEnabledProviders(projectDir) {
117
131
 
118
132
  // Filter out invalid provider names (typos in config.yaml)
119
133
  const validEnabled = enabled.filter(name => validNames.includes(name));
120
- const validPrimary = validNames.includes(primary) ? primary : 'claude';
134
+ const validPrimary = validNames.includes(primary) ? primary : validEnabled[0] || null;
121
135
 
122
136
  return { primary: validPrimary, enabled: validEnabled };
123
137
  }
124
138
 
125
139
  /**
126
140
  * Resolve which provider should be used for a given agent.
127
- * Priority: agent_overrides > agent default > primary provider.
141
+ * Priority: agent_overrides > agent default > first enabled legacy fallback.
128
142
  *
129
143
  * @param {string} agent - Agent name
130
144
  * @param {string} projectDir - Project root directory
@@ -152,10 +166,11 @@ export function resolveProviderForAgent(agent, projectDir, agentModels) {
152
166
  }
153
167
  }
154
168
 
155
- // Fallback to primary provider with its default light-tier model
156
- const provider = PROVIDERS[primary];
169
+ // Legacy fallback only. Installation v2 routes from the signed task catalog.
170
+ const fallbackProvider = primary || enabled[0];
171
+ const provider = PROVIDERS[fallbackProvider];
157
172
  const defaultModel = provider ? Object.keys(provider.modelMap)[0] : 'sonnet';
158
- return { provider: primary, model: defaultModel };
173
+ return { provider: fallbackProvider, model: defaultModel };
159
174
  }
160
175
 
161
176
  /**
@@ -135,6 +135,8 @@ export function buildToolMeshSection(agent) {
135
135
  * @property {string[]} [writeScope] - Override write scope
136
136
  * @property {string} [additionalContext] - Extra context (e.g. user answer to needs_input)
137
137
  * @property {string} [provider] - Active CLI provider (for overlay resolution)
138
+ * @property {string} [model] - Model selected by the installed provider profile
139
+ * @property {boolean} [strictProvider] - Runtime must not substitute a provider
138
140
  */
139
141
 
140
142
  /**
@@ -279,14 +281,14 @@ function buildPrismSection(config) {
279
281
 
280
282
  /**
281
283
  * Load the agent's full .md definition file.
282
- * For non-primary providers, resolves via overlay directory.
284
+ * For enabled providers, resolves via the provider-specific overlay directory.
283
285
  */
284
286
  function loadAgentDefinition(agent, projectDir, provider = null) {
285
287
  const relToFramework = AGENT_FILE_PATHS[agent];
286
288
  if (!relToFramework) return null;
287
289
 
288
290
  let fullPath;
289
- if (provider && provider !== 'claude') {
291
+ if (provider) {
290
292
  fullPath = resolveOverlayPath(projectDir, relToFramework, provider);
291
293
  } else {
292
294
  fullPath = join(projectDir, resolveFrameworkDir(projectDir), relToFramework);
@@ -94,6 +94,8 @@ async function main() {
94
94
  sessionState,
95
95
  additionalContext: args['additional-context'] || null,
96
96
  provider: args.provider || null,
97
+ model: args.model || null,
98
+ strictProvider: args['strict-provider'] === 'true',
97
99
  });
98
100
  } catch (err) {
99
101
  outputError(`Failed to build prompt: ${err.message}`);
@@ -127,6 +129,7 @@ async function main() {
127
129
  prompt: promptResult.prompt,
128
130
  workingDir: projectDir,
129
131
  timeout,
132
+ strictProvider: args['strict-provider'] === 'true',
130
133
  });
131
134
  } catch (err) {
132
135
  outputError(`Failed to spawn terminal: ${err.message}`);
@@ -45,6 +45,19 @@ function parseArgs(argv) {
45
45
  return args;
46
46
  }
47
47
 
48
+ function createSpawnConfig({ agent, taskId, promptResult, projectDir, timeout, strictProvider }) {
49
+ return {
50
+ agent,
51
+ taskId,
52
+ model: promptResult.model,
53
+ provider: promptResult.provider,
54
+ prompt: promptResult.prompt,
55
+ workingDir: projectDir,
56
+ timeout,
57
+ strictProvider,
58
+ };
59
+ }
60
+
48
61
  // ---------------------------------------------------------------------------
49
62
  // Main
50
63
  // ---------------------------------------------------------------------------
@@ -103,17 +116,14 @@ async function main() {
103
116
  previousAgent,
104
117
  sessionState,
105
118
  provider: args.provider || null,
119
+ model: args.model || null,
120
+ strictProvider: args['strict-provider'] === 'true',
106
121
  });
107
122
 
108
- configs.push({
109
- agent: agents[i],
110
- taskId: taskIds[i],
111
- model: promptResult.model,
112
- provider: promptResult.provider,
113
- prompt: promptResult.prompt,
114
- workingDir: projectDir,
115
- timeout,
116
- });
123
+ configs.push(createSpawnConfig({
124
+ agent: agents[i], taskId: taskIds[i], promptResult, projectDir, timeout,
125
+ strictProvider: args['strict-provider'] === 'true',
126
+ }));
117
127
  } catch (err) {
118
128
  outputError(`Failed to build prompt for ${agents[i]}: ${err.message}`);
119
129
  process.exit(1);
@@ -268,6 +278,7 @@ async function sequentialFallback(configs, timeout, _spawnFn = spawnTerminal) {
268
278
  prompt: cfg.prompt,
269
279
  workingDir: cfg.workingDir,
270
280
  timeout: cfg.timeout || timeout,
281
+ strictProvider: cfg.strictProvider,
271
282
  });
272
283
 
273
284
  // Wait for the REAL process exit before spawning the next agent. The old
@@ -328,4 +339,4 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
328
339
  });
329
340
  }
330
341
 
331
- export { parseArgs, determineNextAgent, sequentialFallback };
342
+ export { parseArgs, createSpawnConfig, determineNextAgent, sequentialFallback };
@@ -107,7 +107,8 @@ export function cleanParentEnv(env) {
107
107
  * @property {string} agent - Agent name (e.g. "architect")
108
108
  * @property {string} taskId - Task identifier
109
109
  * @property {string} [model] - LLM model tier name (e.g. opus, pro, codex, claude-sonnet)
110
- * @property {string} [provider] - CLI provider name (claude, gemini, codex)
110
+ * @property {string} [provider] - CLI provider name (claude, gemini, codex, grok)
111
+ * @property {boolean} [strictProvider] - Reject a missing provider rather than using the legacy fallback
111
112
  * @property {string} [prompt] - Full prompt string (from prompt-builder, piped via stdin)
112
113
  * @property {object} [contextPayload] - Context to inject via env var
113
114
  * @property {string[]} [writeScope] - Override write scope
@@ -181,6 +182,11 @@ export function buildSpawnCommand(config) {
181
182
  args = adapterResult.args;
182
183
  prompt = adapterResult.stdinPrompt;
183
184
  } catch (err) {
185
+ if (config.strictProvider) {
186
+ const failure = new Error(`Selected provider "${providerName}" is unavailable: ${err.message}`);
187
+ failure.code = 'SELECTED_PROVIDER_UNAVAILABLE';
188
+ throw failure;
189
+ }
184
190
  // Fallback to claude if provider resolution fails (backwards compatibility)
185
191
  providerFallback = {
186
192
  requested: providerName,
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, realpathSync } from 'fs';
12
12
  import { join, dirname, resolve, relative } from 'path';
13
- import yaml from 'js-yaml';
13
+ import * as yaml from 'js-yaml';
14
14
  import { withLock } from '../utils/file-lock.js';
15
15
 
16
16
  // ---------------------------------------------------------------------------
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { resolveFrameworkDir } from '../utils/framework-dir.js';
4
- import yaml from 'js-yaml';
4
+ import * as yaml from 'js-yaml';
5
5
  import semver from 'semver';
6
6
 
7
7
  /**
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync
2
2
  import { dirname, join } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { resolveFrameworkDir } from '../utils/framework-dir.js';
5
- import yaml from 'js-yaml';
5
+ import * as yaml from 'js-yaml';
6
6
  import semver from 'semver';
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Shared YAML config parser for provider configuration.
3
3
  *
4
- * Extracts provider enabled/primary status and agent overrides from
4
+ * Extracts enabled providers and legacy primary status from
5
5
  * config.yaml using lightweight regex-based parsing. Used by
6
6
  * cli-registry.js, context-file-generator.js, and health/engine.js.
7
7
  */
@@ -23,9 +23,9 @@ export function parseProviderConfig(projectDir) {
23
23
  }
24
24
 
25
25
  const raw = readFileSync(configPath, 'utf-8');
26
- const providers = ['claude', 'gemini', 'codex'];
26
+ const providers = ['claude', 'gemini', 'codex', 'grok'];
27
27
  const enabled = [];
28
- let primary = 'claude';
28
+ let primary = null;
29
29
 
30
30
  for (const name of providers) {
31
31
  const enabledMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?enabled:\\s*(true|false)`, 'm'));
@@ -38,11 +38,6 @@ export function parseProviderConfig(projectDir) {
38
38
  }
39
39
  }
40
40
 
41
- // Claude is always enabled as fallback
42
- if (!enabled.includes('claude')) {
43
- enabled.unshift('claude');
44
- }
45
-
46
41
  return { primary, enabled, raw };
47
42
  }
48
43
 
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import yaml from 'js-yaml';
4
+ import * as yaml from 'js-yaml';
5
5
  import { resolveFrameworkDir } from '../utils/framework-dir.js';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -30,13 +30,19 @@ 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
- primary_provider_title: 'Which is your primary CLI provider?',
34
- primary_provider_label: 'Primary Provider',
33
+ installation_mode_title: 'Select the installation mode',
34
+ installation_mode_open: 'Open',
35
+ installation_mode_open_hint: 'Public framework without mandatory company integrations',
36
+ installation_mode_internal: 'Internal',
37
+ installation_mode_internal_hint: 'Requires an authorized license and enables ClickUp tracking plus internal policies',
38
+ installation_mode_label: 'Installation mode',
39
+ model_routing_label: 'Model routing',
40
+ model_routing_automatic: 'Automatic per task across all enabled models',
35
41
  providers_label: 'Providers',
36
42
  quick_start_switch_hint: 'Switch CLIs anytime — your session continues from where you left off',
37
43
  created_overlays: 'Created provider overlay directories',
38
44
  will_install: 'Will install:',
39
- agents_count: 'Specialized agent definitions across DISCOVER, PLAN, BUILD, DEPLOY phases',
45
+ agents_count: 'Specialized discovery and planning agents with RAIL execution and release lanes',
40
46
  workflows_count: '6 workflow blueprints',
41
47
  templates_count: '6 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate, Quick Brief)',
42
48
  constitution: 'Constitution (25 Articles + Preamble)',
@@ -1,19 +1,22 @@
1
1
  import * as p from '@clack/prompts';
2
- import { readFileSync } from 'fs';
2
+ import { existsSync, 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, stepProviderSelection, stepEditorSelection, stepPrimaryProvider, stepConfirmation, stepTermsOfUse } from './questions.js';
6
+ import { stepLanguage, stepProjectType, stepProviderSelection, stepEditorSelection, stepModelSelection, stepInstallationMode, stepConfirmation, stepTermsOfUse } from './questions.js';
7
7
  import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
8
8
  import { installFramework } from '../installer/core.js';
9
+ import { INSTALLATION_ARTIFACT_PATH, dryRunV2, installV2, reconfigureV2 } from '../installer-v2/index.js';
10
+ import { buildWizardV2InstallationInput } from '../installer-v2/wizard-installation.js';
11
+ import { resolveCapabilityCatalog, verifySignedCapabilityCatalog } from '../installer-v2/catalog-client.js';
9
12
  import { validateInstallation } from '../installer/validator.js';
10
13
  import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
11
14
  import { sendEvents } from '../telemetry/sender.js';
12
15
  import { getTelemetryConfig } from '../telemetry/config.js';
13
16
  import { t } from './i18n.js';
14
- import { getLicenseKey, activateLicense } from '../license/client.js';
17
+ import { getLicenseKey, activateLicense, requireLicenseEntitlement } from '../license/client.js';
15
18
  import { DEFAULT_MCPS } from '../config/mcp-configs.js';
16
- import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
19
+ import { IDE_CONFIGS } from '../config/ide-configs.js';
17
20
 
18
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
22
  const VERSION = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
@@ -66,14 +69,28 @@ export async function runWizard(targetDir, options = {}) {
66
69
  // Step 3a: Provider Selection (which AI providers to use)
67
70
  const selectedProviders = options.providers || await stepProviderSelection();
68
71
 
69
- // Step 3b: Primary provider (only if multiple)
70
- const primaryProvider = options.llmProvider || await stepPrimaryProvider(selectedProviders);
72
+ // Step 3b: Enable the full signed lineup for every selected provider. Runtime
73
+ // routing selects the right model per task instead of pinning one at install.
74
+ if (options.capabilityCatalog) {
75
+ throw Object.assign(new Error('unsigned capabilityCatalog injection is forbidden; provide signedCapabilityCatalogEnvelope'), { code: 'UNSIGNED_CATALOG_FORBIDDEN' });
76
+ }
77
+ const catalogResolution = options.signedCapabilityCatalogEnvelope
78
+ ? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
79
+ : await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
80
+ const modelSelections = options.modelSelections || await stepModelSelection(selectedProviders, catalogResolution.catalog);
81
+
82
+ // Step 3c: Internal mode is a server-authorized entitlement. It enables the
83
+ // internal policy bundle, including mandatory ClickUp tracking.
84
+ const installationMode = options.installationMode || await stepInstallationMode();
85
+ if (installationMode === 'internal') {
86
+ await requireLicenseEntitlement('internal', { validate: options.licenseValidator });
87
+ }
71
88
 
72
- // Step 3c: Editor Selection (which editor IDEs get rules files)
89
+ // Step 3e: Editor Selection (which editor IDEs get rules files)
73
90
  const selectedEditors = options.editors || await stepEditorSelection();
74
91
 
75
92
  // Combine providers + editors into selectedIDEs for backward compatibility
76
- const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli' };
93
+ const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli', grok: 'grok-cli' };
77
94
  const selectedIDEs = options.ides || [
78
95
  ...selectedProviders.map(p => providerToIDE[p]).filter(Boolean),
79
96
  ...selectedEditors,
@@ -90,13 +107,28 @@ export async function runWizard(targetDir, options = {}) {
90
107
  projectName,
91
108
  projectType,
92
109
  language,
93
- llmProvider: primaryProvider,
94
110
  allProviders: cliProviders,
95
111
  selectedIDEs,
96
112
  selectedMCPs,
113
+ installationMode,
114
+ modelSelections,
97
115
  targetDir,
98
116
  version: VERSION,
99
117
  };
118
+ const v2InstallationInput = buildWizardV2InstallationInput({
119
+ projectName,
120
+ selectedProviders: cliProviders,
121
+ modelSelections,
122
+ capabilityCatalog: catalogResolution.catalog,
123
+ installationMode,
124
+ });
125
+ // Validate the complete v2 material and refuse a repeated init before the
126
+ // legacy installer writes anything. Reconfiguration uses its own backup and
127
+ // atomic replacement path below.
128
+ dryRunV2(v2InstallationInput, { projectDir: targetDir });
129
+ if (existsSync(join(targetDir, INSTALLATION_ARTIFACT_PATH)) && options.reconfigure !== true) {
130
+ throw Object.assign(new Error('a v2 installation already exists; use --reconfigure to replace it safely'), { code: 'INSTALLATION_ALREADY_EXISTS' });
131
+ }
100
132
 
101
133
  await stepConfirmation(config);
102
134
 
@@ -104,8 +136,7 @@ export async function runWizard(targetDir, options = {}) {
104
136
  config.telemetryEnabled = options.telemetry !== undefined ? options.telemetry : true;
105
137
 
106
138
  // Installation + Validation
107
- const primaryIDE = selectedIDEs.find(ide => IDE_TO_PROVIDER[ide] === primaryProvider) || selectedIDEs[0];
108
- const primaryIDEName = IDE_CONFIGS[primaryIDE]?.name || primaryIDE;
139
+ const selectedCLIIDEs = selectedIDEs.filter(ide => IDE_CONFIGS[ide]?.group === 'cli');
109
140
 
110
141
  const installStart = Date.now();
111
142
 
@@ -115,6 +146,8 @@ export async function runWizard(targetDir, options = {}) {
115
146
 
116
147
  try {
117
148
  await installFramework(config);
149
+ if (options.reconfigure === true) reconfigureV2(v2InstallationInput, { projectDir: targetDir });
150
+ else installV2(v2InstallationInput, { projectDir: targetDir });
118
151
  installSpinner.stop();
119
152
 
120
153
  showStep(t('installer.created_chati'));
@@ -126,8 +159,9 @@ export async function runWizard(targetDir, options = {}) {
126
159
  'gemini-cli': 'Created .gemini/commands/ (TOML command)',
127
160
  'codex-cli': 'Created .agents/skills/chati/ (Codex skill)',
128
161
  };
129
- const commandStep = commandStepMap[primaryIDE] || t('installer.created_commands');
130
- showStep(commandStep);
162
+ for (const cliIDE of selectedCLIIDEs) {
163
+ showStep(commandStepMap[cliIDE] || t('installer.created_commands'));
164
+ }
131
165
 
132
166
  showStep(t('installer.installed_constitution'));
133
167
  showStep(t('installer.created_session'));
@@ -176,10 +210,9 @@ export async function runWizard(targetDir, options = {}) {
176
210
  const invokeCmdMap = {
177
211
  'codex-cli': '$chati',
178
212
  };
179
- const invokeCmd = invokeCmdMap[primaryIDE] || '/chati';
180
213
  const quickStartSteps = [
181
- `${t('installer.quick_start_1')} (${primaryIDEName})`,
182
- `Type: ${invokeCmd}`,
214
+ t('installer.quick_start_1'),
215
+ ...selectedCLIIDEs.map((cliIDE) => `${IDE_CONFIGS[cliIDE]?.name || cliIDE}: ${invokeCmdMap[cliIDE] || '/chati'}`),
183
216
  t('installer.quick_start_3'),
184
217
  ];
185
218
 
@@ -197,7 +230,6 @@ export async function runWizard(targetDir, options = {}) {
197
230
  editors: selectedEditors,
198
231
  projectType,
199
232
  language,
200
- primaryProvider,
201
233
  installDuration: Date.now() - installStart,
202
234
  });
203
235
  const installEvents = telemetryFlush();
@@ -5,6 +5,7 @@ import { showSummary, showChecklist } from './feedback.js';
5
5
  import { brand, dim, success } from '../utils/colors.js';
6
6
  import { isProviderAvailable } from '../terminal/cli-registry.js';
7
7
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
8
+ import { supportedV2WizardProviders, wizardModelSuggestions } from '../installer-v2/wizard-installation.js';
8
9
 
9
10
  /**
10
11
  * Step 1: Language Selection (always in English)
@@ -66,6 +67,7 @@ const PROVIDER_DISPLAY_NAMES = {
66
67
  claude: 'Claude (Anthropic)',
67
68
  gemini: 'Gemini (Google)',
68
69
  codex: 'Codex (OpenAI)',
70
+ grok: 'Grok (xAI)',
69
71
  };
70
72
 
71
73
  /**
@@ -75,7 +77,7 @@ const PROVIDER_DISPLAY_NAMES = {
75
77
  * Returns an array of provider names (e.g. ['claude', 'gemini']).
76
78
  */
77
79
  export async function stepProviderSelection() {
78
- const cliIDEs = Object.entries(IDE_CONFIGS).filter(([, c]) => c.group === 'cli');
80
+ const cliIDEs = Object.entries(IDE_CONFIGS).filter(([key, c]) => c.group === 'cli' && supportedV2WizardProviders.includes(IDE_TO_PROVIDER[key]));
79
81
  const options = [];
80
82
 
81
83
  for (const [key, config] of cliIDEs) {
@@ -91,7 +93,7 @@ export async function stepProviderSelection() {
91
93
  const providerName = PROVIDER_DISPLAY_NAMES[provider] || config.name;
92
94
  options.push({
93
95
  value: provider,
94
- label: `${providerName}${config.recommended ? ' (Recommended)' : ''}${suffix}`,
96
+ label: `${providerName}${suffix}`,
95
97
  hint: config.description,
96
98
  });
97
99
  }
@@ -110,6 +112,30 @@ export async function stepProviderSelection() {
110
112
  return selected;
111
113
  }
112
114
 
115
+ /**
116
+ * Selects the operational policy profile. Focus AI internal projects require
117
+ * ClickUp tracking; standard projects do not gain that external dependency.
118
+ */
119
+ export async function stepInstallationMode() {
120
+ const mode = await p.select({
121
+ message: t('installer.installation_mode_title'),
122
+ options: [
123
+ { value: 'open', label: t('installer.installation_mode_open'), hint: t('installer.installation_mode_open_hint') },
124
+ { value: 'internal', label: t('installer.installation_mode_internal'), hint: t('installer.installation_mode_internal_hint') },
125
+ ],
126
+ initialValue: 'open',
127
+ });
128
+ if (p.isCancel(mode)) {
129
+ p.cancel('Installation cancelled.');
130
+ process.exit(0);
131
+ }
132
+ return mode;
133
+ }
134
+
135
+ // Backward-compatible programmatic alias. The interactive wizard no longer
136
+ // exposes an abstract execution profile to users.
137
+ export const stepExecutionProfile = stepInstallationMode;
138
+
113
139
  /**
114
140
  * Step 3b: Editor Selection (multi-select, optional)
115
141
  *
@@ -204,44 +230,36 @@ export async function stepIDESelection() {
204
230
  }
205
231
 
206
232
  /**
207
- * Step 3b: Primary CLI Provider Selection
208
- *
209
- * Only shown when the user selected multiple CLI-based IDEs.
210
- * Determines which provider's adapted files go in chati.dev/ (main).
233
+ * Enables every catalog model for each selected provider. Runtime routing
234
+ * chooses a model per task, so installation never collapses a provider to one
235
+ * supposedly universal "recommended" model.
211
236
  */
212
- export async function stepPrimaryProvider(cliProviders) {
213
- if (cliProviders.length <= 1) return cliProviders[0] || 'claude';
214
-
215
- const primary = await p.select({
216
- message: t('installer.primary_provider_title'),
217
- options: cliProviders.map(prov => ({
218
- value: prov,
219
- label: PROVIDER_DISPLAY_NAMES[prov] || prov,
220
- })),
221
- });
222
-
223
- if (p.isCancel(primary)) {
224
- p.cancel('Installation cancelled.');
225
- process.exit(0);
237
+ export async function stepModelSelection(providers, capabilityCatalog) {
238
+ const selections = {};
239
+ const summary = [];
240
+
241
+ for (const provider of providers) {
242
+ const models = wizardModelSuggestions(provider, capabilityCatalog);
243
+ selections[provider] = [...models];
244
+ summary.push(`${PROVIDER_DISPLAY_NAMES[provider] || provider}: ${models.join(', ')}`);
226
245
  }
227
246
 
228
- return primary;
247
+ p.note(summary.join('\n'), t('installer.model_routing_label'));
248
+
249
+ return selections;
229
250
  }
230
251
 
231
252
  /**
232
253
  * Step 4: Confirmation
233
254
  */
234
255
  export async function stepConfirmation(config) {
235
- const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders } = config;
256
+ const { projectName, projectType, language, selectedMCPs, selectedIDEs, allProviders, installationMode } = config;
236
257
 
237
258
  const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
238
259
 
239
- // Show all providers with primary indicator
240
- const providers = allProviders || [llmProvider || 'claude'];
241
- const providersDisplay = providers.map(prov => {
242
- const name = PROVIDER_DISPLAY_NAMES[prov] || prov;
243
- return prov === llmProvider ? `${name} (Primary)` : name;
244
- }).join(', ');
260
+ // Show all enabled providers symmetrically.
261
+ const providers = allProviders || [];
262
+ const providersDisplay = providers.map(prov => PROVIDER_DISPLAY_NAMES[prov] || prov).join(', ');
245
263
 
246
264
  // Separate editors from CLI IDEs for display
247
265
  const editorIDEs = (selectedIDEs || []).filter(id => IDE_CONFIGS[id]?.group === 'editor');
@@ -256,6 +274,10 @@ export async function stepConfirmation(config) {
256
274
  [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
257
275
  [t('installer.language_label')]: langName,
258
276
  [t('installer.providers_label')]: providersDisplay,
277
+ [t('installer.model_routing_label')]: t('installer.model_routing_automatic'),
278
+ [t('installer.installation_mode_label')]: installationMode === 'internal'
279
+ ? t('installer.installation_mode_internal')
280
+ : t('installer.installation_mode_open'),
259
281
  };
260
282
 
261
283
  // Only show editor IDEs line if any were selected