chati-dev 3.0.0 → 3.0.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 (70) hide show
  1. package/README.md +96 -24
  2. package/assets/logo2.png +0 -0
  3. package/assets/social-preview.png +0 -0
  4. package/bin/chati.js +1 -1
  5. package/framework/agents/plan/phases.md +2 -2
  6. package/framework/config.yaml +2 -2
  7. package/framework/constitution.md +4 -4
  8. package/framework/context/governance.md +16 -3
  9. package/framework/context/root.md +3 -3
  10. package/framework/data/entity-registry.yaml +1 -1
  11. package/framework/domains/agents/detail.yaml +1 -1
  12. package/framework/domains/agents/phases.yaml +2 -2
  13. package/framework/domains/agents/tasks.yaml +2 -2
  14. package/framework/domains/constitution.yaml +2 -2
  15. package/framework/hooks/read-protection.js +1 -0
  16. package/framework/i18n/en.yaml +11 -4
  17. package/framework/i18n/es.yaml +11 -4
  18. package/framework/i18n/fr.yaml +11 -4
  19. package/framework/i18n/pt.yaml +11 -4
  20. package/framework/intelligence/context-engine.md +4 -4
  21. package/framework/intelligence/decision-engine.md +1 -1
  22. package/framework/patterns/elicitation-library.yaml +2 -2
  23. package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
  24. package/framework/tasks/brownfield-wu-migration-plan.md +4 -4
  25. package/framework/tasks/brownfield-wu-risk-assess.md +2 -2
  26. package/framework/tasks/detail-expand-prd.md +2 -2
  27. package/framework/tasks/greenfield-wu-report.md +1 -1
  28. package/framework/tasks/orchestrator-handoff.md +2 -2
  29. package/framework/tasks/orchestrator-resume.md +1 -1
  30. package/framework/tasks/qa-impl-performance-test.md +2 -2
  31. package/framework/tasks/qa-impl-sast-scan.md +6 -6
  32. package/framework/tasks/qa-planning-consolidate.md +1 -1
  33. package/framework/tasks/qa-planning-coverage-plan.md +9 -9
  34. package/framework/tasks/qa-planning-gate-define.md +6 -6
  35. package/framework/tasks/qa-planning-risk-matrix.md +1 -1
  36. package/framework/tasks/tasks-consolidate.md +5 -5
  37. package/package.json +1 -1
  38. package/scripts/changelog-generator.js +2 -2
  39. package/scripts/ide-sync.js +9 -4
  40. package/scripts/pr-review.js +5 -5
  41. package/scripts/semantic-lint.js +3 -3
  42. package/src/autonomy/build-state.js +1 -1
  43. package/src/autonomy/execution-profile.js +1 -1
  44. package/src/config/context-file-generator.js +31 -34
  45. package/src/config/ide-configs.js +15 -6
  46. package/src/dashboard/layout.js +3 -1
  47. package/src/gates/g2-qa-planning.js +1 -1
  48. package/src/gates/g4-qa-implementation.js +1 -1
  49. package/src/health/engine.js +7 -12
  50. package/src/installer/core.js +69 -34
  51. package/src/installer/templates.js +168 -2
  52. package/src/installer/validator.js +5 -3
  53. package/src/intelligence/registry-manager.js +2 -2
  54. package/src/preview/detector.js +1 -1
  55. package/src/terminal/adapters/claude-adapter.js +3 -13
  56. package/src/terminal/adapters/codex-adapter.js +3 -13
  57. package/src/terminal/adapters/copilot-adapter.js +3 -13
  58. package/src/terminal/adapters/gemini-adapter.js +3 -17
  59. package/src/terminal/cli-registry.js +20 -48
  60. package/src/terminal/index.js +1 -0
  61. package/src/terminal/prompt-builder.js +3 -18
  62. package/src/terminal/run-agent.js +2 -1
  63. package/src/terminal/spawner.js +70 -8
  64. package/src/terminal/wave-analyzer.js +1 -1
  65. package/src/utils/config-parser.js +97 -0
  66. package/src/wizard/feedback.js +1 -1
  67. package/src/wizard/i18n.js +11 -4
  68. package/src/wizard/index.js +45 -15
  69. package/src/wizard/questions.js +54 -3
  70. package/assets/logo - c/303/263pia.svg" +0 -42
@@ -64,7 +64,9 @@ export function buildProjectInfo(data) {
64
64
  const state = formatState(project.state);
65
65
  const mode = formatMode(session.execution_mode);
66
66
  const lang = session.language || 'en';
67
- const ide = (session.ides || [])[0] || 'unknown';
67
+ // Show the provider-specific IDE (last in the list) rather than always claude-code
68
+ const ides = session.ides || [];
69
+ const ide = ides[ides.length - 1] || 'unknown';
68
70
 
69
71
  return [
70
72
  brand('│') + ` ${dim('Project:')} ${name}` + ' '.repeat(Math.max(1, 30 - name.length)) + `${dim('Type:')} ${type}` + ' '.repeat(Math.max(1, 18 - type.length)) + brand('│'),
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { existsSync, readdirSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { GateBase, GateVerdict, determineVerdict } from './gate-base.js';
10
+ import { GateBase, determineVerdict } from './gate-base.js';
11
11
  import { loadSession } from '../orchestrator/session-manager.js';
12
12
  import { loadHandoff } from '../tasks/handoff.js';
13
13
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { existsSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
- import { GateBase, GateVerdict, determineVerdict } from './gate-base.js';
17
+ import { GateBase, determineVerdict } from './gate-base.js';
18
18
  import { loadSession } from '../orchestrator/session-manager.js';
19
19
  import { loadHandoff } from '../tasks/handoff.js';
20
20
 
@@ -6,8 +6,9 @@
6
6
  * Constitution Article XIV — Framework Registry Governance.
7
7
  */
8
8
 
9
- import { existsSync, readFileSync, readdirSync } from 'fs';
9
+ import { existsSync, readFileSync } from 'fs';
10
10
  import { join } from 'path';
11
+ import { isCommandAvailable, parseProviderConfig } from '../utils/config-parser.js';
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // Check Result Types
@@ -50,19 +51,13 @@ export async function checkCliAvailability(projectDir) {
50
51
  return { name: 'cli-availability', status: 'warn', message: 'No config.yaml found', duration: Date.now() - start };
51
52
  }
52
53
 
53
- const raw = readFileSync(configPath, 'utf-8');
54
- const providers = ['claude', 'gemini', 'codex', 'copilot'];
54
+ const { enabled } = parseProviderConfig(projectDir);
55
55
  const missing = [];
56
56
 
57
- for (const provider of providers) {
58
- const enabledMatch = raw.match(new RegExp(`${provider}:[\\s\\S]*?enabled:\\s*true`, 'm'));
59
- if (enabledMatch) {
60
- const { execSync } = await import('child_process');
61
- try {
62
- execSync(`which ${provider}`, { stdio: 'ignore' });
63
- } catch {
64
- missing.push(provider);
65
- }
57
+ for (const provider of enabled) {
58
+ const available = await isCommandAvailable(provider);
59
+ if (!available) {
60
+ missing.push(provider);
66
61
  }
67
62
  }
68
63
 
@@ -3,7 +3,8 @@ import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { IDE_CONFIGS } from '../config/ide-configs.js';
5
5
  import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
6
- import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd } from './templates.js';
6
+ import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexRouter, generateGeminiRouter, generateCopilotAgent } from './templates.js';
7
+ import { generateContextFiles } from '../config/context-file-generator.js';
7
8
  import { verifyManifest } from './manifest.js';
8
9
 
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -18,7 +19,7 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
18
19
  * Install Chati.dev framework into target directory
19
20
  */
20
21
  export async function installFramework(config) {
21
- const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, version } = config;
22
+ const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, version, llmProvider } = config;
22
23
 
23
24
  // 0. Verify framework signature (supply chain protection)
24
25
  const manifestPath = join(FRAMEWORK_SOURCE, 'manifest.json');
@@ -38,7 +39,7 @@ export async function installFramework(config) {
38
39
  createDir(join(targetDir, '.chati'));
39
40
  writeFileSync(
40
41
  join(targetDir, '.chati', 'session.yaml'),
41
- generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs }),
42
+ generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider }),
42
43
  'utf-8'
43
44
  );
44
45
 
@@ -91,7 +92,7 @@ export async function installFramework(config) {
91
92
  // Write config.yaml
92
93
  writeFileSync(
93
94
  join(frameworkDir, 'config.yaml'),
94
- generateConfigYaml({ version, projectType, language, selectedIDEs }),
95
+ generateConfigYaml({ version, projectType, language, selectedIDEs, llmProvider }),
95
96
  'utf-8'
96
97
  );
97
98
 
@@ -100,30 +101,32 @@ export async function installFramework(config) {
100
101
  await configureIDE(targetDir, ideKey, selectedMCPs);
101
102
  }
102
103
 
103
- // 4. Copy context files to .claude/rules/chati/ (auto-loaded by Claude Code)
104
- const rulesDir = join(targetDir, '.claude', 'rules', 'chati');
105
- createDir(rulesDir);
106
- const contextFiles = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
107
- for (const file of contextFiles) {
108
- const src = join(FRAMEWORK_SOURCE, 'context', file);
109
- if (existsSync(src)) {
110
- copyFileSync(src, join(rulesDir, file));
104
+ const hasClaude = selectedIDEs.includes('claude-code');
105
+ const hasNonClaude = selectedIDEs.some(ide => ide !== 'claude-code');
106
+
107
+ // 4. Generate base content (used as source for all provider context files)
108
+ const baseContent = generateClaudeMd({ projectName, projectType, language });
109
+
110
+ // 5. Claude Code specific: .claude/rules/chati/, CLAUDE.md, CLAUDE.local.md
111
+ if (hasClaude) {
112
+ const contextFiles = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
113
+ const claudeRulesDir = join(targetDir, '.claude', 'rules', 'chati');
114
+ createDir(claudeRulesDir);
115
+ for (const file of contextFiles) {
116
+ const src = join(FRAMEWORK_SOURCE, 'context', file);
117
+ if (existsSync(src)) {
118
+ copyFileSync(src, join(claudeRulesDir, file));
119
+ }
111
120
  }
112
- }
113
121
 
114
- // 5. Create CLAUDE.md (minimal — framework rules auto-loaded from .claude/rules/chati/)
115
- writeFileSync(
116
- join(targetDir, 'CLAUDE.md'),
117
- generateClaudeMd({ projectName, projectType, language }),
118
- 'utf-8'
119
- );
122
+ writeFileSync(join(targetDir, 'CLAUDE.md'), baseContent, 'utf-8');
123
+ writeFileSync(join(targetDir, 'CLAUDE.local.md'), generateClaudeLocalMd(), 'utf-8');
124
+ }
120
125
 
121
- // 6. Create CLAUDE.local.md (runtime state session lock, current agent)
122
- writeFileSync(
123
- join(targetDir, 'CLAUDE.local.md'),
124
- generateClaudeLocalMd(),
125
- 'utf-8'
126
- );
126
+ // 6. Non-Claude providers: generate context files (GEMINI.md, AGENTS.md) from base content
127
+ if (hasNonClaude) {
128
+ generateContextFiles(targetDir, baseContent);
129
+ }
127
130
  }
128
131
 
129
132
  /**
@@ -157,12 +160,14 @@ function copyFrameworkFiles(destDir) {
157
160
  'templates/fullstack-architecture-tmpl.yaml',
158
161
  'templates/task-tmpl.yaml',
159
162
  'templates/qa-gate-tmpl.yaml',
163
+ 'templates/quick-brief-tmpl.yaml',
160
164
  // Workflows
161
165
  'workflows/greenfield-fullstack.yaml',
162
166
  'workflows/brownfield-fullstack.yaml',
163
167
  'workflows/brownfield-discovery.yaml',
164
168
  'workflows/brownfield-service.yaml',
165
169
  'workflows/brownfield-ui.yaml',
170
+ 'workflows/quick-flow.yaml',
166
171
  // Quality gates
167
172
  'quality-gates/planning-gate.md',
168
173
  'quality-gates/implementation-gate.md',
@@ -191,6 +196,7 @@ function copyFrameworkFiles(destDir) {
191
196
  'hooks/session-digest.js',
192
197
  'hooks/model-governance.js',
193
198
  'hooks/settings.json',
199
+ 'hooks/read-protection.js',
194
200
  // Domains (PRISM Context Engine)
195
201
  'domains/constitution.yaml',
196
202
  'domains/global.yaml',
@@ -212,6 +218,7 @@ function copyFrameworkFiles(destDir) {
212
218
  'domains/workflows/brownfield-discovery.yaml',
213
219
  'domains/workflows/brownfield-service.yaml',
214
220
  'domains/workflows/brownfield-ui.yaml',
221
+ 'domains/workflows/quick-flow.yaml',
215
222
  // i18n
216
223
  'i18n/en.yaml',
217
224
  'i18n/pt.yaml',
@@ -294,10 +301,35 @@ Pass through all context: session state, handoffs, artifacts, and user input.
294
301
  'utf-8'
295
302
  );
296
303
  }
304
+ } else if (ideKey === 'codex-cli') {
305
+ // Codex CLI: /chati slash command via .codex/commands/
306
+ writeFileSync(join(targetDir, '.codex', 'commands', 'chati.md'), generateCodexRouter(), 'utf-8');
307
+ } else if (ideKey === 'gemini-cli') {
308
+ // Gemini CLI: TOML command file (native format for /chati command)
309
+ writeFileSync(join(targetDir, '.gemini', 'commands', 'chati.toml'), generateGeminiRouter(), 'utf-8');
310
+ } else if (ideKey === 'github-copilot') {
311
+ // GitHub Copilot: agent file (.github/agents/chati.md) for @chati invocation
312
+ writeFileSync(join(targetDir, '.github', 'agents', 'chati.md'), generateCopilotAgent(), 'utf-8');
313
+
314
+ // Copilot instructions file (auto-loaded by Copilot CLI)
315
+ createDir(dirname(join(targetDir, config.rulesFile)));
316
+ writeFileSync(join(targetDir, config.rulesFile), generateProviderInstructions(config.name), 'utf-8');
297
317
  } else {
298
- // For other IDEs, create a rules file pointing to chati.dev/
299
- const rulesContent = `# Chati.dev System Rules
300
- # This file configures ${config.name} to work with Chati.dev
318
+ // VS Code, Cursor, AntiGravity generic rules file
319
+ if (config.rulesFile) {
320
+ createDir(dirname(join(targetDir, config.rulesFile)));
321
+ writeFileSync(join(targetDir, config.rulesFile), generateProviderInstructions(config.name), 'utf-8');
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Generate provider-agnostic instructions file content.
328
+ * Used for non-Claude IDEs (.github/copilot-instructions.md, .vscode/chati/rules.md, etc.)
329
+ */
330
+ function generateProviderInstructions(providerName) {
331
+ return `# Chati.dev System Rules
332
+ # This file configures ${providerName} to work with Chati.dev
301
333
 
302
334
  ## System Location
303
335
  All system content is in the \`chati.dev/\` directory.
@@ -310,7 +342,15 @@ The orchestrator is at \`chati.dev/orchestrator/chati.md\`.
310
342
  Read it to understand routing, session management, and agent activation.
311
343
 
312
344
  ## Constitution
313
- Governance rules are in \`chati.dev/constitution.md\` (17 Articles).
345
+ Governance rules are in \`chati.dev/constitution.md\` (19 Articles).
346
+
347
+ ## Pipeline
348
+ \`\`\`
349
+ DISCOVER: WU -> Brief
350
+ PLAN: Detail -> Architect -> UX -> Phases -> Tasks -> QA-Planning
351
+ BUILD: Dev -> QA-Implementation
352
+ DEPLOY: DevOps
353
+ \`\`\`
314
354
 
315
355
  ## Agents
316
356
  - DISCOVER: chati.dev/agents/discover/ (3 agents)
@@ -319,11 +359,6 @@ Governance rules are in \`chati.dev/constitution.md\` (17 Articles).
319
359
  - BUILD: chati.dev/agents/build/ (1 agent)
320
360
  - DEPLOY: chati.dev/agents/deploy/ (1 agent)
321
361
  `;
322
- if (config.rulesFile) {
323
- createDir(dirname(join(targetDir, config.rulesFile)));
324
- writeFileSync(join(targetDir, config.rulesFile), rulesContent, 'utf-8');
325
- }
326
- }
327
362
  }
328
363
 
329
364
  /**
@@ -4,7 +4,7 @@ import yaml from 'js-yaml';
4
4
  * Generate session.yaml content
5
5
  */
6
6
  export function generateSessionYaml(config) {
7
- const { projectName, projectType, language, selectedIDEs, selectedMCPs } = config;
7
+ const { projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider } = config;
8
8
 
9
9
  const session = {
10
10
  project: {
@@ -13,16 +13,19 @@ export function generateSessionYaml(config) {
13
13
  state: 'discover',
14
14
  },
15
15
  execution_mode: 'interactive',
16
+ execution_profile: 'guided',
16
17
  current_agent: '',
17
18
  language: language,
18
19
  ides: selectedIDEs,
19
20
  mcps: selectedMCPs,
21
+ providers_enabled: [llmProvider || 'claude'],
20
22
  user_level: 'auto',
21
23
  user_level_confidence: 0.0,
22
24
  agents: {},
23
25
  backlog: [],
24
26
  last_handoff: '',
25
27
  deviations: [],
28
+ profile_transitions: [],
26
29
  };
27
30
 
28
31
  // Initialize all 12 agent statuses
@@ -44,11 +47,56 @@ export function generateSessionYaml(config) {
44
47
  return yaml.dump(session, { lineWidth: -1, quotingType: '"', forceQuotes: false });
45
48
  }
46
49
 
50
+ /**
51
+ * Optimal model assignments per provider.
52
+ * Deep reasoning agents (architect, qa, dev, detail, brownfield-wu) get the top model.
53
+ * Lightweight agents (brief, phases, ux, greenfield-wu, devops, orchestrator) get the fast model.
54
+ */
55
+ const PROVIDER_MODEL_MAPS = {
56
+ claude: {
57
+ deep: 'opus', light: 'sonnet', minimal: 'haiku',
58
+ agents: {
59
+ orchestrator: 'sonnet', 'greenfield-wu': 'haiku', 'brownfield-wu': 'opus',
60
+ brief: 'sonnet', detail: 'opus', architect: 'opus', ux: 'sonnet',
61
+ phases: 'sonnet', tasks: 'sonnet', 'qa-planning': 'opus',
62
+ dev: 'opus', 'qa-implementation': 'opus', devops: 'sonnet',
63
+ },
64
+ },
65
+ gemini: {
66
+ deep: 'pro', light: 'flash', minimal: 'flash',
67
+ agents: {
68
+ orchestrator: 'flash', 'greenfield-wu': 'flash', 'brownfield-wu': 'pro',
69
+ brief: 'flash', detail: 'pro', architect: 'pro', ux: 'flash',
70
+ phases: 'flash', tasks: 'flash', 'qa-planning': 'pro',
71
+ dev: 'pro', 'qa-implementation': 'pro', devops: 'flash',
72
+ },
73
+ },
74
+ codex: {
75
+ deep: 'codex', light: 'mini', minimal: 'mini',
76
+ agents: {
77
+ orchestrator: 'mini', 'greenfield-wu': 'mini', 'brownfield-wu': 'codex',
78
+ brief: 'mini', detail: 'codex', architect: 'codex', ux: 'mini',
79
+ phases: 'mini', tasks: 'mini', 'qa-planning': 'codex',
80
+ dev: 'codex', 'qa-implementation': 'codex', devops: 'mini',
81
+ },
82
+ },
83
+ copilot: {
84
+ deep: 'claude-sonnet', light: 'claude-sonnet', minimal: 'gpt-5',
85
+ agents: {
86
+ orchestrator: 'claude-sonnet', 'greenfield-wu': 'gpt-5', 'brownfield-wu': 'claude-sonnet',
87
+ brief: 'claude-sonnet', detail: 'claude-sonnet', architect: 'claude-sonnet', ux: 'claude-sonnet',
88
+ phases: 'claude-sonnet', tasks: 'claude-sonnet', 'qa-planning': 'claude-sonnet',
89
+ dev: 'claude-sonnet', 'qa-implementation': 'claude-sonnet', devops: 'claude-sonnet',
90
+ },
91
+ },
92
+ };
93
+
47
94
  /**
48
95
  * Generate config.yaml content
49
96
  */
50
97
  export function generateConfigYaml(config) {
51
- const { version, projectType, language, selectedIDEs } = config;
98
+ const { version, projectType, language, selectedIDEs, llmProvider } = config;
99
+ const provider = llmProvider || 'claude';
52
100
 
53
101
  const configData = {
54
102
  version: version,
@@ -58,8 +106,25 @@ export function generateConfigYaml(config) {
58
106
  project_type: projectType,
59
107
  language: language,
60
108
  ides: selectedIDEs,
109
+ providers: {
110
+ claude: { enabled: true, primary: provider === 'claude' },
111
+ gemini: { enabled: provider === 'gemini' || selectedIDEs.includes('gemini-cli'), model_default: 'pro', primary: provider === 'gemini' },
112
+ codex: { enabled: provider === 'codex' || selectedIDEs.includes('codex-cli'), model_default: 'codex', primary: provider === 'codex' },
113
+ copilot: { enabled: provider === 'copilot' || selectedIDEs.includes('github-copilot'), primary: provider === 'copilot' },
114
+ },
61
115
  };
62
116
 
117
+ // Add agent_overrides with optimal model map for the selected provider
118
+ if (provider !== 'claude') {
119
+ const modelMap = PROVIDER_MODEL_MAPS[provider];
120
+ if (modelMap) {
121
+ configData.agent_overrides = {};
122
+ for (const [agent, model] of Object.entries(modelMap.agents)) {
123
+ configData.agent_overrides[agent] = { provider, model };
124
+ }
125
+ }
126
+ }
127
+
63
128
  return yaml.dump(configData, { lineWidth: -1, quotingType: '"', forceQuotes: false });
64
129
  }
65
130
 
@@ -81,6 +146,107 @@ Type \`/chati\` to start.
81
146
  `;
82
147
  }
83
148
 
149
+ /**
150
+ * Generate Codex CLI command (.codex/commands/chati.md)
151
+ *
152
+ * Codex CLI supports /chati slash command via .codex/commands/.
153
+ * Also reads AGENTS.md for project context (auto-generated by context-file-generator).
154
+ */
155
+ export function generateCodexRouter() {
156
+ return `# /chati — Thin Router
157
+
158
+ ## CRITICAL — Language Override
159
+
160
+ Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
161
+ ALL responses MUST be in this language. This overrides any global setting.
162
+
163
+ | Value | Language |
164
+ |-------|----------|
165
+ | \`en\` | English |
166
+ | \`pt\` | Portugues |
167
+ | \`es\` | Espanol |
168
+ | \`fr\` | Francais |
169
+
170
+ If session.yaml does not exist or has no language field, default to English.
171
+
172
+ ## Load
173
+
174
+ Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
175
+
176
+ Pass through all context: session state, handoffs, artifacts, and user input.
177
+
178
+ **Context to pass:**
179
+ - \`.chati/session.yaml\` (session state — includes language)
180
+ - \`AGENTS.md\` (project context — auto-generated)
181
+ - \`chati.dev/artifacts/handoffs/\` (latest handoff)
182
+ - \`chati.dev/config.yaml\` (version info)
183
+ `;
184
+ }
185
+
186
+ /**
187
+ * Generate Gemini CLI TOML command (.gemini/commands/chati.toml)
188
+ *
189
+ * Gemini CLI custom commands use TOML format with `description` and `prompt` fields.
190
+ * Supports {{args}} for user arguments and @{file} for file injection.
191
+ */
192
+ export function generateGeminiRouter() {
193
+ return `description = "Activate Chati.dev orchestrator"
194
+ prompt = """
195
+ CRITICAL — Language Override:
196
+ Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
197
+ ALL responses MUST be in this language (en, pt, es, fr).
198
+ If session.yaml does not exist or has no language field, default to English.
199
+
200
+ Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
201
+
202
+ Context to load:
203
+ - \`.chati/session.yaml\` (session state — includes language)
204
+ - \`chati.dev/artifacts/handoffs/\` (latest handoff)
205
+ - \`chati.dev/config.yaml\` (version info)
206
+
207
+ User input: {{args}}
208
+ """
209
+ `;
210
+ }
211
+
212
+ /**
213
+ * Generate GitHub Copilot agent file (.github/agents/chati.md)
214
+ *
215
+ * Copilot CLI uses .github/agents/*.md for custom agents.
216
+ * The user invokes with @chati in the Copilot CLI.
217
+ */
218
+ export function generateCopilotAgent() {
219
+ return `# Chati.dev Orchestrator
220
+
221
+ You are the Chati.dev orchestrator agent for GitHub Copilot CLI.
222
+
223
+ ## CRITICAL — Language Override
224
+
225
+ Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
226
+ ALL responses MUST be in this language. This overrides any global setting.
227
+
228
+ | Value | Language |
229
+ |-------|----------|
230
+ | \`en\` | English |
231
+ | \`pt\` | Portugues |
232
+ | \`es\` | Espanol |
233
+ | \`fr\` | Francais |
234
+
235
+ If session.yaml does not exist or has no language field, default to English.
236
+
237
+ ## Load
238
+
239
+ Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
240
+
241
+ Pass through all context: session state, handoffs, artifacts, and user input.
242
+
243
+ **Context to pass:**
244
+ - \`.chati/session.yaml\` (session state — includes language)
245
+ - \`chati.dev/artifacts/handoffs/\` (latest handoff)
246
+ - \`chati.dev/config.yaml\` (version info)
247
+ `;
248
+ }
249
+
84
250
  /**
85
251
  * Generate CLAUDE.local.md content (runtime state — auto-gitignored, never committed)
86
252
  */
@@ -63,7 +63,7 @@ export async function validateInstallation(targetDir) {
63
63
  if (existsSync(constitutionPath)) {
64
64
  const content = readFileSync(constitutionPath, 'utf-8');
65
65
  const articleCount = (content.match(/^## Article/gm) || []).length;
66
- results.constitution.pass = articleCount >= 17;
66
+ results.constitution.pass = articleCount >= 19;
67
67
  results.constitution.details.push({ articleCount });
68
68
  }
69
69
  results.total += 1;
@@ -94,6 +94,7 @@ export async function validateInstallation(targetDir) {
94
94
  const workflowFiles = [
95
95
  'greenfield-fullstack.yaml', 'brownfield-fullstack.yaml',
96
96
  'brownfield-discovery.yaml', 'brownfield-service.yaml', 'brownfield-ui.yaml',
97
+ 'quick-flow.yaml',
97
98
  ];
98
99
  let workflowCount = 0;
99
100
  for (const file of workflowFiles) {
@@ -101,7 +102,7 @@ export async function validateInstallation(targetDir) {
101
102
  workflowCount++;
102
103
  }
103
104
  }
104
- results.workflows.pass = workflowCount === 5;
105
+ results.workflows.pass = workflowCount === 6;
105
106
  results.total += 1;
106
107
  if (results.workflows.pass) results.passed += 1;
107
108
 
@@ -109,6 +110,7 @@ export async function validateInstallation(targetDir) {
109
110
  const templateFiles = [
110
111
  'prd-tmpl.yaml', 'brownfield-prd-tmpl.yaml',
111
112
  'fullstack-architecture-tmpl.yaml', 'task-tmpl.yaml', 'qa-gate-tmpl.yaml',
113
+ 'quick-brief-tmpl.yaml',
112
114
  ];
113
115
  let templateCount = 0;
114
116
  for (const file of templateFiles) {
@@ -116,7 +118,7 @@ export async function validateInstallation(targetDir) {
116
118
  templateCount++;
117
119
  }
118
120
  }
119
- results.templates.pass = templateCount === 5;
121
+ results.templates.pass = templateCount === 6;
120
122
  results.total += 1;
121
123
  if (results.templates.pass) results.passed += 1;
122
124
 
@@ -131,8 +131,8 @@ export function runHealthCheck(targetDir) {
131
131
  if (existsSync(constitutionPath)) {
132
132
  const content = readFileSync(constitutionPath, 'utf-8');
133
133
  const articleCount = (content.match(/^## Article/gm) || []).length;
134
- checks.constitution.pass = articleCount >= 17;
135
- checks.constitution.details = `${articleCount}/17 articles`;
134
+ checks.constitution.pass = articleCount >= 19;
135
+ checks.constitution.details = `${articleCount}/19 articles`;
136
136
  } else {
137
137
  checks.constitution.details = 'Not found';
138
138
  }
@@ -191,7 +191,7 @@ export function detectProjectKind(projectDir) {
191
191
  * @returns {{ command: string, args: string[], description: string, framework: string, defaultPort: number }}
192
192
  */
193
193
  function resolveFrameworkCommand(projectDir, detector) {
194
- const { framework, defaultPort, kind } = detector;
194
+ const { framework, defaultPort } = detector;
195
195
 
196
196
  // Django has its own command
197
197
  if (framework === 'django') {
@@ -18,26 +18,16 @@
18
18
  * @returns {{ command: string, args: string[], stdinPrompt: string|null }}
19
19
  */
20
20
  export function buildCommand(config, provider) {
21
- const args = ['--print', '--dangerously-skip-permissions'];
21
+ const args = [...provider.baseArgs];
22
22
 
23
23
  if (config.model) {
24
24
  const resolvedModel = provider.modelMap[config.model] || config.model;
25
- args.push('--model', resolvedModel);
25
+ args.push(provider.modelFlag, resolvedModel);
26
26
  }
27
27
 
28
28
  return {
29
- command: 'claude',
29
+ command: provider.command,
30
30
  args,
31
31
  stdinPrompt: config.prompt || null,
32
32
  };
33
33
  }
34
-
35
- /**
36
- * Build environment variables specific to Claude Code.
37
- *
38
- * @param {SpawnConfig} config
39
- * @returns {Record<string, string>}
40
- */
41
- export function buildEnv(config) {
42
- return {};
43
- }
@@ -13,29 +13,19 @@
13
13
  * @returns {{ command: string, args: string[], stdinPrompt: string|null }}
14
14
  */
15
15
  export function buildCommand(config, provider) {
16
- const args = ['exec'];
16
+ const args = [...provider.baseArgs];
17
17
 
18
18
  if (config.model) {
19
19
  const resolvedModel = provider.modelMap[config.model] || config.model;
20
- args.push('-m', resolvedModel);
20
+ args.push(provider.modelFlag, resolvedModel);
21
21
  }
22
22
 
23
23
  // Codex exec reads prompt from stdin when `-` is passed
24
24
  args.push('-');
25
25
 
26
26
  return {
27
- command: 'codex',
27
+ command: provider.command,
28
28
  args,
29
29
  stdinPrompt: config.prompt || null,
30
30
  };
31
31
  }
32
-
33
- /**
34
- * Build environment variables specific to Codex CLI.
35
- *
36
- * @param {import('../spawner.js').SpawnConfig} config
37
- * @returns {Record<string, string>}
38
- */
39
- export function buildEnv(config) {
40
- return {};
41
- }
@@ -13,26 +13,16 @@
13
13
  * @returns {{ command: string, args: string[], stdinPrompt: string|null }}
14
14
  */
15
15
  export function buildCommand(config, provider) {
16
- const args = ['-p'];
16
+ const args = [...provider.baseArgs];
17
17
 
18
18
  if (config.model) {
19
19
  const resolvedModel = provider.modelMap[config.model] || config.model;
20
- args.push('--model', resolvedModel);
20
+ args.push(provider.modelFlag, resolvedModel);
21
21
  }
22
22
 
23
23
  return {
24
- command: 'copilot',
24
+ command: provider.command,
25
25
  args,
26
26
  stdinPrompt: config.prompt || null,
27
27
  };
28
28
  }
29
-
30
- /**
31
- * Build environment variables specific to Copilot CLI.
32
- *
33
- * @param {import('../spawner.js').SpawnConfig} config
34
- * @returns {Record<string, string>}
35
- */
36
- export function buildEnv(config) {
37
- return {};
38
- }
@@ -13,30 +13,16 @@
13
13
  * @returns {{ command: string, args: string[], stdinPrompt: string|null }}
14
14
  */
15
15
  export function buildCommand(config, provider) {
16
- const args = [];
16
+ const args = [...provider.baseArgs];
17
17
 
18
18
  if (config.model) {
19
19
  const resolvedModel = provider.modelMap[config.model] || config.model;
20
- args.push('--model', resolvedModel);
20
+ args.push(provider.modelFlag, resolvedModel);
21
21
  }
22
22
 
23
- // Gemini CLI uses --prompt for non-interactive mode
24
- // When stdin is piped, Gemini reads from stdin automatically
25
- args.push('--prompt');
26
-
27
23
  return {
28
- command: 'gemini',
24
+ command: provider.command,
29
25
  args,
30
26
  stdinPrompt: config.prompt || null,
31
27
  };
32
28
  }
33
-
34
- /**
35
- * Build environment variables specific to Gemini CLI.
36
- *
37
- * @param {import('../spawner.js').SpawnConfig} config
38
- * @returns {Record<string, string>}
39
- */
40
- export function buildEnv(config) {
41
- return {};
42
- }