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.
- package/framework/config.yaml +2 -2
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/manifest.json +5 -5
- package/framework/manifest.sig +1 -1
- package/package.json +1 -1
- package/src/config/context-file-generator.js +2 -2
- package/src/config/ide-configs.js +3 -3
- package/src/config/mcp-configs.js +80 -10
- package/src/installer/core.js +38 -38
package/framework/config.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Chati.dev System Context
|
|
2
2
|
|
|
3
3
|
## Framework
|
|
4
|
-
- **Version**: 4.5.
|
|
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)
|
package/framework/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"createdAt": "2026-08-27T12:
|
|
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": "
|
|
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": "
|
|
125
|
+
"hash": "b76681d1bdd7c75e79733a520fc0c929858dc9399348dbb8ba30380e77c32849",
|
|
126
126
|
"size": 1217,
|
|
127
127
|
"type": "md"
|
|
128
128
|
},
|
|
129
129
|
"data/entity-registry.yaml": {
|
|
130
|
-
"hash": "
|
|
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.
|
|
1355
|
+
"version": "4.5.9"
|
|
1356
1356
|
}
|
package/framework/manifest.sig
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Hb7c+de1wUKAcc4qkLRzsVYqMzP02CNhfXf+bcp5ezJLqsH+KyZ7sOvQUpRf89mWC57jrLu/dscvchEyVdxQBQ==
|
package/package.json
CHANGED
|
@@ -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
|
-
['.
|
|
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
|
-
['.
|
|
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: '.
|
|
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:
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
serverConfig: {
|
|
23
23
|
command: 'npx',
|
|
24
|
-
args: ['-y', '@context7
|
|
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
|
-
*
|
|
53
|
+
* Resolve the provider-neutral server definitions selected by the user.
|
|
51
54
|
*/
|
|
52
|
-
export function
|
|
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?.
|
|
59
|
+
if (config?.serverConfig) {
|
|
57
60
|
const entry = {
|
|
58
|
-
command: config.
|
|
59
|
-
args: config.
|
|
61
|
+
command: config.serverConfig.command,
|
|
62
|
+
args: [...(config.serverConfig.args || [])],
|
|
60
63
|
};
|
|
61
|
-
if (config.
|
|
62
|
-
entry.env = config.
|
|
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
|
+
}
|
package/src/installer/core.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
/**
|