chati-dev 4.4.1 → 4.5.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.
Files changed (75) 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/manifest.json +13 -13
  9. package/framework/manifest.sig +1 -1
  10. package/framework/orchestrator/chati.md +43 -12
  11. package/node_modules/@chati/browser-capability/README.md +10 -0
  12. package/node_modules/@chati/browser-capability/package.json +17 -0
  13. package/node_modules/@chati/browser-capability/src/index.js +165 -0
  14. package/node_modules/@chati/core/package.json +13 -0
  15. package/node_modules/@chati/core/src/index.js +111 -0
  16. package/node_modules/@chati/knowledge-context/package.json +17 -0
  17. package/node_modules/@chati/knowledge-context/src/index.js +202 -0
  18. package/node_modules/@chati/planning/package.json +17 -0
  19. package/node_modules/@chati/planning/src/index.js +367 -0
  20. package/node_modules/@chati/provider-registry/package.json +16 -0
  21. package/node_modules/@chati/provider-registry/src/index.js +132 -0
  22. package/node_modules/@chati/rail/README.md +24 -0
  23. package/node_modules/@chati/rail/package.json +19 -0
  24. package/node_modules/@chati/rail/src/index.js +437 -0
  25. package/node_modules/@chati/release-lane/README.md +24 -0
  26. package/node_modules/@chati/release-lane/package.json +17 -0
  27. package/node_modules/@chati/release-lane/src/index.js +172 -0
  28. package/node_modules/@chati/review-council/package.json +17 -0
  29. package/node_modules/@chati/review-council/src/index.js +264 -0
  30. package/node_modules/@chati/tracking-clickup/README.md +55 -0
  31. package/node_modules/@chati/tracking-clickup/package.json +17 -0
  32. package/node_modules/@chati/tracking-clickup/src/index.js +293 -0
  33. package/package.json +25 -3
  34. package/src/config/ide-configs.js +11 -0
  35. package/src/context/domain-loader.js +1 -1
  36. package/src/dashboard/data-reader.js +1 -1
  37. package/src/executors/runner.js +1 -1
  38. package/src/installer/scaffold-applier.js +1 -1
  39. package/src/installer/templates.js +3 -3
  40. package/src/installer/validator.js +1 -1
  41. package/src/installer-v2/catalog-client.js +141 -0
  42. package/src/installer-v2/index.js +301 -0
  43. package/src/installer-v2/model-catalog-envelope.json +59 -0
  44. package/src/installer-v2/model-catalog.json +33 -0
  45. package/src/installer-v2/model-catalog.sig +1 -0
  46. package/src/installer-v2/wizard-installation.js +115 -0
  47. package/src/intelligence/registry-manager.js +1 -1
  48. package/src/license/client.js +1 -1
  49. package/src/memory/session-digest.js +1 -1
  50. package/src/merger/yaml-merger.js +1 -1
  51. package/src/orchestrator/browser-runtime.js +25 -0
  52. package/src/orchestrator/cli.js +93 -8
  53. package/src/orchestrator/clickup-projection.js +84 -0
  54. package/src/orchestrator/clickup-runtime.js +13 -0
  55. package/src/orchestrator/knowledge-runtime.js +64 -0
  56. package/src/orchestrator/planning-runtime.js +127 -0
  57. package/src/orchestrator/rail-runtime.js +421 -0
  58. package/src/orchestrator/release-runtime.js +14 -0
  59. package/src/orchestrator/review-runtime.js +74 -0
  60. package/src/orchestrator/runtime-installation-v2.js +38 -0
  61. package/src/orchestrator/session-manager.js +1 -1
  62. package/src/telemetry/config.js +1 -1
  63. package/src/terminal/adapters/grok-adapter.js +16 -0
  64. package/src/terminal/adapters/index.js +1 -0
  65. package/src/terminal/cli-registry.js +14 -0
  66. package/src/terminal/prompt-builder.js +2 -0
  67. package/src/terminal/run-agent.js +3 -0
  68. package/src/terminal/run-parallel.js +21 -10
  69. package/src/terminal/spawner.js +7 -1
  70. package/src/terminal/team-task-list.js +1 -1
  71. package/src/upgrade/checker.js +1 -1
  72. package/src/upgrade/migrator.js +1 -1
  73. package/src/wizard/i18n.js +8 -2
  74. package/src/wizard/index.js +39 -4
  75. package/src/wizard/questions.js +59 -2
@@ -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
  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));
@@ -31,12 +31,18 @@ const FALLBACK_EN = {
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
33
  primary_provider_title: 'Which is your primary CLI provider?',
34
+ execution_profile_title: 'Select the execution profile',
35
+ execution_profile_standard: 'Standard',
36
+ execution_profile_standard_hint: 'Provider-neutral local project workflow',
37
+ execution_profile_internal: 'Focus AI internal',
38
+ execution_profile_internal_hint: 'Requires ClickUp tracking and completion metrics',
39
+ execution_profile_label: 'Execution profile',
34
40
  primary_provider_label: 'Primary Provider',
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,11 +1,14 @@
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, stepPrimaryProvider, stepModelSelection, stepExecutionProfile, 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';
@@ -69,11 +72,25 @@ export async function runWizard(targetDir, options = {}) {
69
72
  // Step 3b: Primary provider (only if multiple)
70
73
  const primaryProvider = options.llmProvider || await stepPrimaryProvider(selectedProviders);
71
74
 
72
- // Step 3c: Editor Selection (which editor IDEs get rules files)
75
+ // Step 3c: Models allowed for each selected provider. Never add a provider
76
+ // or model that the user did not explicitly bind to this installation.
77
+ if (options.capabilityCatalog) {
78
+ throw Object.assign(new Error('unsigned capabilityCatalog injection is forbidden; provide signedCapabilityCatalogEnvelope'), { code: 'UNSIGNED_CATALOG_FORBIDDEN' });
79
+ }
80
+ const catalogResolution = options.signedCapabilityCatalogEnvelope
81
+ ? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
82
+ : await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
83
+ const modelSelections = options.modelSelections || await stepModelSelection(selectedProviders, catalogResolution.catalog);
84
+
85
+ // Step 3d: Profile controls whether ClickUp and internal completion metrics
86
+ // are mandatory. It must never be silently forced on public installations.
87
+ const executionProfile = options.executionProfile || await stepExecutionProfile();
88
+
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,
@@ -94,9 +111,25 @@ export async function runWizard(targetDir, options = {}) {
94
111
  allProviders: cliProviders,
95
112
  selectedIDEs,
96
113
  selectedMCPs,
114
+ executionProfile,
97
115
  targetDir,
98
116
  version: VERSION,
99
117
  };
118
+ const v2InstallationInput = buildWizardV2InstallationInput({
119
+ projectName,
120
+ selectedProviders: cliProviders,
121
+ primaryProvider,
122
+ modelSelections,
123
+ capabilityCatalog: catalogResolution.catalog,
124
+ profile: executionProfile,
125
+ });
126
+ // Validate the complete v2 material and refuse a repeated init before the
127
+ // legacy installer writes anything. Reconfiguration uses its own backup and
128
+ // atomic replacement path below.
129
+ dryRunV2(v2InstallationInput, { projectDir: targetDir });
130
+ if (existsSync(join(targetDir, INSTALLATION_ARTIFACT_PATH)) && options.reconfigure !== true) {
131
+ throw Object.assign(new Error('a v2 installation already exists; use --reconfigure to replace it safely'), { code: 'INSTALLATION_ALREADY_EXISTS' });
132
+ }
100
133
 
101
134
  await stepConfirmation(config);
102
135
 
@@ -115,6 +148,8 @@ export async function runWizard(targetDir, options = {}) {
115
148
 
116
149
  try {
117
150
  await installFramework(config);
151
+ if (options.reconfigure === true) reconfigureV2(v2InstallationInput, { projectDir: targetDir });
152
+ else installV2(v2InstallationInput, { projectDir: targetDir });
118
153
  installSpinner.stop();
119
154
 
120
155
  showStep(t('installer.created_chati'));
@@ -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, wizardDefaultModels, 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) {
@@ -110,6 +112,26 @@ 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 stepExecutionProfile() {
120
+ const profile = await p.select({
121
+ message: t('installer.execution_profile_title'),
122
+ options: [
123
+ { value: 'other-supported-profile', label: t('installer.execution_profile_standard'), hint: t('installer.execution_profile_standard_hint') },
124
+ { value: 'focus-ai-internal', label: t('installer.execution_profile_internal'), hint: t('installer.execution_profile_internal_hint') },
125
+ ],
126
+ initialValue: 'other-supported-profile',
127
+ });
128
+ if (p.isCancel(profile)) {
129
+ p.cancel('Installation cancelled.');
130
+ process.exit(0);
131
+ }
132
+ return profile;
133
+ }
134
+
113
135
  /**
114
136
  * Step 3b: Editor Selection (multi-select, optional)
115
137
  *
@@ -228,11 +250,43 @@ export async function stepPrimaryProvider(cliProviders) {
228
250
  return primary;
229
251
  }
230
252
 
253
+ /**
254
+ * Records exactly which models the installation is allowed to orchestrate.
255
+ * A catalog refresh can replace the suggestion, but the user remains the
256
+ * authority for the final binding stored in the installation artifact.
257
+ */
258
+ export async function stepModelSelection(providers, capabilityCatalog) {
259
+ const selections = {};
260
+
261
+ for (const provider of providers) {
262
+ const suggestions = wizardModelSuggestions(provider, capabilityCatalog);
263
+ const suggested = suggestions[0] || wizardDefaultModels[provider];
264
+ const answer = await p.text({
265
+ message: `Models for ${PROVIDER_DISPLAY_NAMES[provider] || provider} (comma-separated):`,
266
+ placeholder: suggestions.join(', '),
267
+ initialValue: suggested,
268
+ validate: (value) => {
269
+ const models = String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
270
+ return models.length > 0 ? undefined : 'Select at least one model.';
271
+ },
272
+ });
273
+
274
+ if (p.isCancel(answer)) {
275
+ p.cancel('Installation cancelled.');
276
+ process.exit(0);
277
+ }
278
+
279
+ selections[provider] = String(answer).split(',').map((item) => item.trim()).filter(Boolean);
280
+ }
281
+
282
+ return selections;
283
+ }
284
+
231
285
  /**
232
286
  * Step 4: Confirmation
233
287
  */
234
288
  export async function stepConfirmation(config) {
235
- const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders } = config;
289
+ const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders, executionProfile } = config;
236
290
 
237
291
  const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
238
292
 
@@ -256,6 +310,9 @@ export async function stepConfirmation(config) {
256
310
  [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
257
311
  [t('installer.language_label')]: langName,
258
312
  [t('installer.providers_label')]: providersDisplay,
313
+ [t('installer.execution_profile_label')]: executionProfile === 'focus-ai-internal'
314
+ ? `${t('installer.execution_profile_internal')} (ClickUp required)`
315
+ : t('installer.execution_profile_standard'),
259
316
  };
260
317
 
261
318
  // Only show editor IDEs line if any were selected