chati-dev 4.5.8 → 4.5.9

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.
@@ -1,8 +1,8 @@
1
1
  # chati.dev Configuration
2
- version: "4.5.8"
2
+ version: "4.5.9"
3
3
  installed_at: "2026-02-07T10:00:00Z"
4
4
  updated_at: "2026-08-27T00:00:00Z"
5
- installer_version: "4.5.8"
5
+ installer_version: "4.5.9"
6
6
  project_type: greenfield
7
7
  language: en
8
8
  ides: []
@@ -1,7 +1,7 @@
1
1
  # Chati.dev System Context
2
2
 
3
3
  ## Framework
4
- - **Version**: 4.5.8
4
+ - **Version**: 4.5.9
5
5
  - **Agents**: Specialized discovery and planning agents feeding provider-neutral RAIL execution and a sealed release lane
6
6
  - **Constitution**: 25 Articles + Preamble
7
7
  - **Quality**: 5 pipeline gates + 3-tier verdicts + Fault Vector Protocol (Article XXII)
@@ -3,7 +3,7 @@
3
3
  # and by the Health Check for system integrity validation.
4
4
 
5
5
  metadata:
6
- version: "4.5.8"
6
+ version: "4.5.9"
7
7
  last_updated: "2026-08-27T00:00:00Z"
8
8
  entity_count: 109
9
9
  checksum_algorithm: sha256
@@ -1,5 +1,5 @@
1
1
  {
2
- "createdAt": "2026-08-27T12:02:42.275Z",
2
+ "createdAt": "2026-08-27T12:53:59.633Z",
3
3
  "files": {
4
4
  "agents/build/dev.md": {
5
5
  "hash": "13c2b3855c1f815154a2f4020b99fd8ee71e701b41783f5f26696222720a1466",
@@ -97,7 +97,7 @@
97
97
  "type": "md"
98
98
  },
99
99
  "config.yaml": {
100
- "hash": "00aa2be05f8cd997b3aae234f56969e1b3309af68c35ada1c439e66cefb37ae5",
100
+ "hash": "2a0de0eae5e361cc23b1777573b3dadf4676ce8c231a3022dd14ebac385eb89f",
101
101
  "size": 2669,
102
102
  "type": "yaml"
103
103
  },
@@ -122,12 +122,12 @@
122
122
  "type": "md"
123
123
  },
124
124
  "context/root.md": {
125
- "hash": "f04a8dc22486c1f0cc29ea2ec5ad30d4a1c343823f1dfc5674946acb854106bb",
125
+ "hash": "b76681d1bdd7c75e79733a520fc0c929858dc9399348dbb8ba30380e77c32849",
126
126
  "size": 1217,
127
127
  "type": "md"
128
128
  },
129
129
  "data/entity-registry.yaml": {
130
- "hash": "d11e07cb0ab2790938af077da954580f9bf0d7230bf4e79b59e6c45a00e2dcb7",
130
+ "hash": "c4b2005f61055a6f1d04c674ba369dbb997dda8d7f81991d6012f25b5a89210f",
131
131
  "size": 34605,
132
132
  "type": "yaml"
133
133
  },
@@ -1352,5 +1352,5 @@
1352
1352
  "type": "yaml"
1353
1353
  }
1354
1354
  },
1355
- "version": "4.5.8"
1355
+ "version": "4.5.9"
1356
1356
  }
@@ -1 +1 @@
1
- stchaq/GjnloDsRgwm8vCQpA4vFKsofjuyMg5b+4aU6pEblso6UgEAtPPlRkVVkxcmn8ctX9YxQmqe+MmaPsAA==
1
+ Hb7c+de1wUKAcc4qkLRzsVYqMzP02CNhfXf+bcp5ezJLqsH+KyZ7sOvQUpRf89mWC57jrLu/dscvchEyVdxQBQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.5.8",
3
+ "version": "4.5.9",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System - Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@ const GEMINI_REPLACEMENTS = [
32
32
  ['.claude/commands/', '.gemini/commands/'],
33
33
  ['.claude/rules/chati/', '.gemini/context/'],
34
34
  ['.claude/rules/', '.gemini/context/'],
35
- ['.claude/mcp.json', '.gemini/settings.json'],
35
+ ['.mcp.json', '.gemini/settings.json'],
36
36
  ['claude --print', 'gemini --prompt'],
37
37
  ['claude -p', 'gemini --prompt'],
38
38
  ['CLAUDE.local.md', '.gemini/session-lock.md'],
@@ -64,7 +64,7 @@ function buildCodexReplacements(frameworkDir = 'chati.dev') {
64
64
  ['.claude/commands/', `${frameworkDir}/orchestrator/`],
65
65
  ['.claude/rules/chati/', `${frameworkDir}/context/`],
66
66
  ['.claude/rules/', `${frameworkDir}/context/`],
67
- ['.claude/mcp.json', '.codex/config.toml'],
67
+ ['.mcp.json', '.codex/config.toml'],
68
68
  ['claude --print', 'codex exec'],
69
69
  ['claude -p', 'codex exec'],
70
70
  ['CLAUDE.local.md', 'AGENTS.override.md'],
@@ -10,7 +10,7 @@ export const IDE_CONFIGS = {
10
10
  recommended: true,
11
11
  configPath: '.claude/commands/',
12
12
  rulesFile: '.claude/CLAUDE.md',
13
- mcpConfigFile: '.claude/mcp.json',
13
+ mcpConfigFile: '.mcp.json',
14
14
  formatNotes: 'Native markdown format',
15
15
  },
16
16
  'gemini-cli': {
@@ -32,7 +32,7 @@ export const IDE_CONFIGS = {
32
32
  recommended: false,
33
33
  configPath: '.agents/skills/chati/',
34
34
  rulesFile: 'AGENTS.md',
35
- mcpConfigFile: null,
35
+ mcpConfigFile: '.codex/config.toml',
36
36
  formatNotes: 'Codex skill format (SKILL.md with YAML frontmatter)',
37
37
  rulesPath: '.codex/rules/',
38
38
  overrideFile: 'AGENTS.override.md',
@@ -44,7 +44,7 @@ export const IDE_CONFIGS = {
44
44
  recommended: false,
45
45
  configPath: '.grok/commands/',
46
46
  rulesFile: 'AGENTS.md',
47
- mcpConfigFile: '.grok/mcp.json',
47
+ mcpConfigFile: '.grok/config.toml',
48
48
  formatNotes: 'Native markdown command format',
49
49
  },
50
50
  'vscode': {
@@ -10,7 +10,7 @@ export const MCP_CONFIGS = {
10
10
  name: 'Browser (Playwright)',
11
11
  description: 'Web automation and testing',
12
12
  requiresEnv: [],
13
- claudeConfig: {
13
+ serverConfig: {
14
14
  command: 'npx',
15
15
  args: ['-y', '@playwright/mcp'],
16
16
  },
@@ -19,13 +19,16 @@ export const MCP_CONFIGS = {
19
19
  name: 'Context7',
20
20
  description: 'Library documentation search',
21
21
  requiresEnv: [],
22
- claudeConfig: {
22
+ serverConfig: {
23
23
  command: 'npx',
24
- args: ['-y', '@context7/mcp'],
24
+ args: ['-y', '@upstash/context7-mcp'],
25
25
  },
26
26
  },
27
27
  };
28
28
 
29
+ export const MCP_TOML_BEGIN = '# BEGIN CHATI MANAGED MCP SERVERS';
30
+ export const MCP_TOML_END = '# END CHATI MANAGED MCP SERVERS';
31
+
29
32
  /**
30
33
  * Agent-MCP dependency matrix
31
34
  */
@@ -47,22 +50,89 @@ export const AGENT_MCP_DEPS = {
47
50
  };
48
51
 
49
52
  /**
50
- * Generate MCP config for Claude Code (.claude/mcp.json)
53
+ * Resolve the provider-neutral server definitions selected by the user.
51
54
  */
52
- export function generateClaudeMCPConfig(selectedMCPs) {
55
+ export function resolveSelectedMCPServers(selectedMCPs) {
53
56
  const mcpServers = {};
54
57
  for (const mcpKey of selectedMCPs) {
55
58
  const config = MCP_CONFIGS[mcpKey];
56
- if (config?.claudeConfig) {
59
+ if (config?.serverConfig) {
57
60
  const entry = {
58
- command: config.claudeConfig.command,
59
- args: config.claudeConfig.args,
61
+ command: config.serverConfig.command,
62
+ args: [...(config.serverConfig.args || [])],
60
63
  };
61
- if (config.claudeConfig.env) {
62
- entry.env = config.claudeConfig.env;
64
+ if (config.serverConfig.env) {
65
+ entry.env = { ...config.serverConfig.env };
63
66
  }
64
67
  mcpServers[mcpKey] = entry;
65
68
  }
66
69
  }
70
+ return mcpServers;
71
+ }
72
+
73
+ /** Generate Claude Code's project-scoped root .mcp.json. */
74
+ export function generateClaudeMCPConfig(selectedMCPs) {
75
+ const mcpServers = resolveSelectedMCPServers(selectedMCPs);
67
76
  return { mcpServers };
68
77
  }
78
+
79
+ /**
80
+ * Merge CHATI-managed servers into an existing Claude project configuration.
81
+ * Unknown user-managed servers and top-level fields are preserved.
82
+ */
83
+ export function mergeClaudeMCPConfig(existingContent, selectedMCPs) {
84
+ let existing = {};
85
+ if (existingContent?.trim()) {
86
+ existing = JSON.parse(existingContent);
87
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
88
+ throw new Error('Existing .mcp.json must contain a JSON object');
89
+ }
90
+ }
91
+ if (
92
+ existing.mcpServers !== undefined
93
+ && (!existing.mcpServers || typeof existing.mcpServers !== 'object' || Array.isArray(existing.mcpServers))
94
+ ) {
95
+ throw new Error('Existing .mcp.json mcpServers must contain a JSON object');
96
+ }
97
+ const mcpServers = { ...(existing.mcpServers || {}) };
98
+ for (const key of Object.keys(MCP_CONFIGS)) delete mcpServers[key];
99
+ Object.assign(mcpServers, resolveSelectedMCPServers(selectedMCPs));
100
+ return { ...existing, mcpServers };
101
+ }
102
+
103
+ function tomlString(value) {
104
+ return JSON.stringify(String(value));
105
+ }
106
+
107
+ /** Generate the shared native TOML block used by Codex and Grok. */
108
+ export function generateManagedMcpToml(selectedMCPs) {
109
+ const servers = resolveSelectedMCPServers(selectedMCPs);
110
+ const lines = [MCP_TOML_BEGIN];
111
+ for (const [name, config] of Object.entries(servers)) {
112
+ lines.push(`[mcp_servers.${name}]`);
113
+ lines.push(`command = ${tomlString(config.command)}`);
114
+ if (config.args?.length) lines.push(`args = [${config.args.map(tomlString).join(', ')}]`);
115
+ if (config.env && Object.keys(config.env).length > 0) {
116
+ const values = Object.entries(config.env).map(([key, value]) => `${key} = ${tomlString(value)}`);
117
+ lines.push(`env = { ${values.join(', ')} }`);
118
+ }
119
+ lines.push('');
120
+ }
121
+ lines.push(MCP_TOML_END);
122
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n');
123
+ }
124
+
125
+ /** Replace only CHATI's marked TOML block and preserve all user configuration. */
126
+ export function upsertManagedMcpToml(existingContent, selectedMCPs) {
127
+ const managedPattern = new RegExp(`(?:^|\\n)${MCP_TOML_BEGIN}[\\s\\S]*?${MCP_TOML_END}\\n?`);
128
+ const withoutManaged = String(existingContent || '').replace(managedPattern, '\n').trimEnd();
129
+ const userManagedNames = new Set();
130
+ const tablePattern = /^\s*\[mcp_servers\.(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+))\]\s*(?:#.*)?$/gm;
131
+ for (const match of withoutManaged.matchAll(tablePattern)) {
132
+ userManagedNames.add(match[1] || match[2] || match[3]);
133
+ }
134
+ const managedSelections = (selectedMCPs || []).filter((name) => !userManagedNames.has(name));
135
+ if (!managedSelections.length) return withoutManaged ? `${withoutManaged}\n` : '';
136
+ const separator = withoutManaged ? '\n\n' : '';
137
+ return `${withoutManaged}${separator}${generateManagedMcpToml(managedSelections)}\n`;
138
+ }
@@ -4,7 +4,7 @@ import { join, dirname } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { createRequire } from 'module';
6
6
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
7
- import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
7
+ import { mergeClaudeMCPConfig, upsertManagedMcpToml } from '../config/mcp-configs.js';
8
8
  import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGrokRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
9
9
  import { generateContextFiles } from '../config/context-file-generator.js';
10
10
  import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
@@ -417,14 +417,13 @@ Parse the JSON output. This single command already handled: license validation,
417
417
  // (Pre-v4.2.2 bug: this file was never written, hooks never ran.)
418
418
  writeClaudeSettingsWithMerge(join(targetDir, '.claude', 'settings.json'));
419
419
 
420
- // MCP config
421
- if (selectedMCPs.length > 0) {
422
- const mcpConfig = generateClaudeMCPConfig(selectedMCPs);
423
- writeFileSync(
424
- join(targetDir, '.claude', 'mcp.json'),
425
- JSON.stringify(mcpConfig, null, 2) + '\n',
426
- 'utf-8'
427
- );
420
+ // Claude project-scope MCP config lives at the project root. Preserve
421
+ // unknown user-managed servers while replacing CHATI's known entries.
422
+ const mcpPath = join(targetDir, '.mcp.json');
423
+ if (selectedMCPs.length > 0 || existsSync(mcpPath)) {
424
+ const existingMcp = existsSync(mcpPath) ? readFileSync(mcpPath, 'utf-8') : '';
425
+ const mcpConfig = mergeClaudeMCPConfig(existingMcp, selectedMCPs);
426
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
428
427
  }
429
428
  } else if (ideKey === 'codex-cli') {
430
429
  // Codex CLI: chati skill via .agents/skills/chati/SKILL.md (invoke with $chati)
@@ -436,7 +435,7 @@ Parse the JSON output. This single command already handled: license validation,
436
435
 
437
436
  // Starlark execution policies (Codex sandboxed rule engine)
438
437
  createDir(join(targetDir, '.codex', 'rules'));
439
- writeCodexConfigWithHooks(join(targetDir, '.codex', 'config.toml'));
438
+ writeCodexConfigWithHooks(join(targetDir, '.codex', 'config.toml'), selectedMCPs);
440
439
  writeFileSync(join(targetDir, '.codex', 'rules', 'constitution-guard.rules'), generateCodexConstitutionGuardRules(), 'utf-8');
441
440
  writeFileSync(join(targetDir, '.codex', 'rules', 'read-protection.rules'), generateCodexReadProtectionRules(), 'utf-8');
442
441
 
@@ -495,6 +494,11 @@ Parse the JSON output. This single command already handled: license validation,
495
494
  } else if (ideKey === 'grok-cli') {
496
495
  writeFileSync(join(targetDir, '.grok', 'commands', 'chati.md'), generateGrokRouter({ orchestratorPath }), 'utf-8');
497
496
  writeFileSync(join(targetDir, '.grok', 'session-lock.md'), generateClaudeLocalMd(), 'utf-8');
497
+ const grokConfigPath = join(targetDir, '.grok', 'config.toml');
498
+ if (selectedMCPs.length > 0 || existsSync(grokConfigPath)) {
499
+ const existingConfig = existsSync(grokConfigPath) ? readFileSync(grokConfigPath, 'utf-8') : '';
500
+ writeFileSync(grokConfigPath, upsertManagedMcpToml(existingConfig, selectedMCPs), 'utf-8');
501
+ }
498
502
  } else {
499
503
  // VS Code, Cursor, AntiGravity — generic rules file
500
504
  if (config.rulesFile) {
@@ -504,36 +508,32 @@ Parse the JSON output. This single command already handled: license validation,
504
508
  }
505
509
  }
506
510
 
507
- export function writeCodexConfigWithHooks(configPath) {
511
+ export function writeCodexConfigWithHooks(configPath, selectedMCPs = []) {
508
512
  createDir(dirname(configPath));
509
- if (!existsSync(configPath)) {
510
- writeFileSync(configPath, '[features]\nhooks = true\n', 'utf-8');
511
- return;
512
- }
513
-
514
- const original = readFileSync(configPath, 'utf-8');
515
- if (/^\s*features\.hooks\s*=\s*true\s*(?:#.*)?$/m.test(original)) return;
516
- if (/^\s*features\.hooks\s*=/m.test(original)) {
517
- writeFileSync(configPath, original.replace(/^\s*features\.hooks\s*=.*$/m, 'features.hooks = true'), 'utf-8');
518
- return;
519
- }
520
-
521
- const lines = original.split('\n');
522
- const sectionStart = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
523
- if (sectionStart === -1) {
524
- const prefix = original.endsWith('\n') ? original : `${original}\n`;
525
- writeFileSync(configPath, `${prefix}\n[features]\nhooks = true\n`, 'utf-8');
526
- return;
513
+ const original = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
514
+ let hooksEnabled = original;
515
+ if (!original.trim()) {
516
+ hooksEnabled = '[features]\nhooks = true\n';
517
+ } else if (/^\s*features\.hooks\s*=/m.test(original)) {
518
+ hooksEnabled = original.replace(/^\s*features\.hooks\s*=.*$/m, 'features.hooks = true');
519
+ } else if (!/^\s*features\.hooks\s*=\s*true\s*(?:#.*)?$/m.test(original)) {
520
+ const lines = original.split('\n');
521
+ const sectionStart = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
522
+ if (sectionStart === -1) {
523
+ const prefix = original.endsWith('\n') ? original : `${original}\n`;
524
+ hooksEnabled = `${prefix}\n[features]\nhooks = true\n`;
525
+ } else {
526
+ let sectionEnd = lines.findIndex((line, index) => index > sectionStart && /^\s*\[.*\]\s*(?:#.*)?$/.test(line));
527
+ if (sectionEnd === -1) sectionEnd = lines.length;
528
+ const hookIndex = lines.findIndex((line, index) =>
529
+ index > sectionStart && index < sectionEnd && /^\s*hooks\s*=/.test(line)
530
+ );
531
+ if (hookIndex !== -1) lines[hookIndex] = 'hooks = true';
532
+ else lines.splice(sectionEnd, 0, 'hooks = true');
533
+ hooksEnabled = lines.join('\n');
534
+ }
527
535
  }
528
-
529
- let sectionEnd = lines.findIndex((line, index) => index > sectionStart && /^\s*\[.*\]\s*(?:#.*)?$/.test(line));
530
- if (sectionEnd === -1) sectionEnd = lines.length;
531
- const hookIndex = lines.findIndex((line, index) =>
532
- index > sectionStart && index < sectionEnd && /^\s*hooks\s*=/.test(line)
533
- );
534
- if (hookIndex !== -1) lines[hookIndex] = 'hooks = true';
535
- else lines.splice(sectionEnd, 0, 'hooks = true');
536
- writeFileSync(configPath, lines.join('\n'), 'utf-8');
536
+ writeFileSync(configPath, upsertManagedMcpToml(hooksEnabled, selectedMCPs), 'utf-8');
537
537
  }
538
538
 
539
539
  /**