chati-dev 3.2.5 → 3.3.0
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/LICENSE +96 -0
- package/bin/chati.js +46 -0
- package/framework/agents/build/dev.md +122 -1
- package/framework/agents/deploy/devops.md +128 -3
- package/framework/agents/discover/brief.md +77 -15
- package/framework/agents/discover/brownfield-wu.md +2 -2
- package/framework/agents/discover/greenfield-wu.md +3 -3
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +3 -3
- package/framework/agents/plan/phases.md +127 -2
- package/framework/agents/plan/tasks.md +127 -2
- package/framework/agents/plan/ux.md +269 -22
- package/framework/agents/quality/qa-implementation.md +172 -8
- package/framework/agents/quality/qa-planning.md +147 -2
- package/framework/config.yaml +9 -5
- package/framework/constitution.md +7 -1
- package/framework/context/quality.md +1 -1
- package/framework/context/root.md +1 -1
- package/framework/hooks/constitution-guard.js +18 -2
- package/framework/hooks/mode-governance.js +3 -3
- package/framework/hooks/read-protection.js +10 -2
- package/framework/i18n/en.yaml +6 -0
- package/framework/i18n/es.yaml +6 -0
- package/framework/i18n/fr.yaml +6 -0
- package/framework/i18n/pt.yaml +6 -0
- package/framework/orchestrator/chati.md +102 -6
- package/framework/schemas/task.schema.json +1 -1
- package/framework/tasks/architect-dep-audit.md +128 -0
- package/framework/tasks/architect-stack-selection.md +28 -0
- package/framework/workflows/brownfield-fullstack.yaml +2 -2
- package/framework/workflows/brownfield-service.yaml +2 -2
- package/framework/workflows/brownfield-ui.yaml +2 -2
- package/framework/workflows/greenfield-fullstack.yaml +6 -2
- package/framework/workflows/quick-flow.yaml +7 -5
- package/framework/workflows/standard-flow.yaml +171 -0
- package/package.json +4 -2
- package/src/api/index.js +129 -0
- package/src/autonomy/build-loop.js +93 -6
- package/src/autonomy/build-state.js +20 -2
- package/src/autonomy/cause-analyzer.js +177 -0
- package/src/autonomy/escalation.js +214 -0
- package/src/autonomy/safety-net.js +23 -5
- package/src/autonomy/worktree-manager.js +245 -0
- package/src/config/agent-customizer.js +227 -0
- package/src/config/ide-configs.js +57 -27
- package/src/decision/analyzer.js +148 -0
- package/src/decision/registry-healer.js +38 -21
- package/src/extensions/loader.js +151 -0
- package/src/extensions/registry.js +134 -0
- package/src/gates/circuit-breaker.js +32 -0
- package/src/gates/g3-implementation.js +30 -4
- package/src/gates/g4-qa-implementation.js +34 -5
- package/src/gates/gate-base.js +9 -0
- package/src/health/auto-fix.js +216 -0
- package/src/installer/core.js +24 -11
- package/src/installer/provider-overlay.js +82 -0
- package/src/installer/templates.js +22 -10
- package/src/installer/transaction.js +3 -2
- package/src/installer/validator.js +74 -0
- package/src/intelligence/context-status.js +9 -5
- package/src/intelligence/document-sharder.js +221 -0
- package/src/intelligence/elicitation.js +265 -0
- package/src/intelligence/timeline.js +5 -0
- package/src/memory/gotchas.js +78 -2
- package/src/merger/semantic-merger.js +292 -0
- package/src/orchestrator/agent-selector.js +20 -0
- package/src/orchestrator/handoff-engine.js +77 -0
- package/src/orchestrator/index.js +0 -8
- package/src/orchestrator/intent-classifier.js +182 -0
- package/src/orchestrator/pipeline-manager.js +125 -1
- package/src/orchestrator/session-manager.js +164 -2
- package/src/quality/metrics-collector.js +283 -0
- package/src/quality/test-runner.js +368 -0
- package/src/telemetry/collector.js +83 -0
- package/src/telemetry/config.js +119 -0
- package/src/telemetry/index.js +11 -0
- package/src/telemetry/schema.js +104 -0
- package/src/telemetry/sender.js +60 -0
- package/src/terminal/cli-registry.js +7 -1
- package/src/terminal/cost-tracker.js +197 -0
- package/src/terminal/handoff-parser.js +61 -4
- package/src/terminal/prompt-builder.js +56 -18
- package/src/terminal/rate-limiter.js +172 -0
- package/src/terminal/run-agent.js +39 -0
- package/src/terminal/run-parallel.js +22 -1
- package/src/terminal/spawner.js +181 -3
- package/src/upgrade/migrator.js +2 -2
- package/src/utils/event-bus.js +126 -0
- package/src/utils/file-lock.js +291 -0
- package/src/utils/schema-validator.js +226 -0
- package/src/wizard/i18n.js +11 -0
- package/src/wizard/index.js +42 -20
- package/src/wizard/questions.js +200 -39
- package/src/autonomy/execution-profile.js +0 -151
- package/src/intelligence/file-tracker.js +0 -117
- package/src/memory/gotchas-auto-capture.js +0 -253
- package/src/orchestrator/pipeline-state.js +0 -223
- package/src/terminal/wave-analyzer.js +0 -143
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* WAIVED — Human explicitly overrode
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { GateBase, determineVerdict } from './gate-base.js';
|
|
18
18
|
import { loadSession } from '../orchestrator/session-manager.js';
|
|
@@ -69,26 +69,55 @@ export class QAImplementationGate extends GateBase {
|
|
|
69
69
|
evidence.noCriticalBugs = evidence.qaImplHandoff.blockers.length === 0;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Check for performance benchmark results
|
|
72
|
+
// Check for performance benchmark results — parse content for real findings
|
|
73
73
|
const perfPaths = [
|
|
74
74
|
join(projectDir, 'chati.dev', 'artifacts', 'performance-report.md'),
|
|
75
75
|
join(projectDir, '.performance-benchmark'),
|
|
76
76
|
];
|
|
77
77
|
for (const perfPath of perfPaths) {
|
|
78
78
|
if (existsSync(perfPath)) {
|
|
79
|
-
|
|
79
|
+
try {
|
|
80
|
+
const content = readFileSync(perfPath, 'utf-8');
|
|
81
|
+
const hasFailures = /fail|exceeded|timeout|regression|degradation|below\s*threshold/i.test(content);
|
|
82
|
+
const hasPassIndicator = /pass|met|within\s*budget|no\s*(regressions|issues)|acceptable/i.test(content);
|
|
83
|
+
if (hasFailures && !hasPassIndicator) {
|
|
84
|
+
evidence.performanceBenchmarksMet = false;
|
|
85
|
+
} else if (hasPassIndicator) {
|
|
86
|
+
evidence.performanceBenchmarksMet = true;
|
|
87
|
+
} else {
|
|
88
|
+
evidence.performanceBenchmarksMet = null; // Ambiguous
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
evidence.performanceBenchmarksMet = null;
|
|
92
|
+
}
|
|
80
93
|
break;
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
// Check for security scan
|
|
97
|
+
// Check for security scan — parse content for real findings
|
|
85
98
|
const secPaths = [
|
|
86
99
|
join(projectDir, 'chati.dev', 'artifacts', 'security-report.md'),
|
|
87
100
|
join(projectDir, '.security-scan'),
|
|
88
101
|
];
|
|
89
102
|
for (const secPath of secPaths) {
|
|
90
103
|
if (existsSync(secPath)) {
|
|
91
|
-
|
|
104
|
+
try {
|
|
105
|
+
const content = readFileSync(secPath, 'utf-8').toLowerCase();
|
|
106
|
+
// Look for indicators of security failures
|
|
107
|
+
const hasBlockers = /blocker|critical|high\s*severity|vulnerability found|failed/i.test(content);
|
|
108
|
+
const hasPassIndicator = /pass|clean|no\s*(issues|vulnerabilities|findings)|score:\s*(9[5-9]|100)/i.test(content);
|
|
109
|
+
if (hasBlockers) {
|
|
110
|
+
evidence.securityScanClean = false;
|
|
111
|
+
} else if (hasPassIndicator) {
|
|
112
|
+
evidence.securityScanClean = true;
|
|
113
|
+
} else {
|
|
114
|
+
// File exists but has no clear pass/fail indicators — ambiguous — not clean by default
|
|
115
|
+
evidence.securityScanClean = null;
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// File exists but cannot be read — treat as not available
|
|
119
|
+
evidence.securityScanClean = null;
|
|
120
|
+
}
|
|
92
121
|
break;
|
|
93
122
|
}
|
|
94
123
|
}
|
package/src/gates/gate-base.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { evaluateGate, getGateThreshold, resolveGateAction } from '../autonomy/autonomous-gate.js';
|
|
10
|
+
import { track as telemetryTrack } from '../telemetry/collector.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Gate verdict constants (v3.0.0).
|
|
@@ -101,6 +102,14 @@ export class GateBase {
|
|
|
101
102
|
const hasCriticalBlocker = warnings.some(w => w.toLowerCase().includes('critical'));
|
|
102
103
|
const verdict = determineVerdict(gateResult.score, threshold, hasCriticalBlocker);
|
|
103
104
|
|
|
105
|
+
// Track telemetry
|
|
106
|
+
telemetryTrack('gate_evaluated', {
|
|
107
|
+
gate: this.id,
|
|
108
|
+
result: verdict,
|
|
109
|
+
score: gateResult.score,
|
|
110
|
+
blockers: warnings.filter(w => w.toLowerCase().includes('critical')),
|
|
111
|
+
});
|
|
112
|
+
|
|
104
113
|
return {
|
|
105
114
|
gateId: this.id,
|
|
106
115
|
gateName: this.name,
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Auto-fix for common health check issues.
|
|
3
|
+
*
|
|
4
|
+
* Automatically repairs stale locks, oversized error logs,
|
|
5
|
+
* missing directories, and other recoverable problems.
|
|
6
|
+
*
|
|
7
|
+
* Constitution Article XIV — Framework Registry Governance.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, mkdirSync, rmSync, statSync, readdirSync, truncateSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Constants
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/** Maximum error log size before truncation (1 MB). */
|
|
18
|
+
const MAX_ERROR_LOG_SIZE = 1_024 * 1_024;
|
|
19
|
+
|
|
20
|
+
/** Stale lock file age threshold (30 seconds). */
|
|
21
|
+
const STALE_LOCK_AGE_MS = 30_000;
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Public API
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} FixAction
|
|
29
|
+
* @property {string} action - Description of what was fixed
|
|
30
|
+
* @property {boolean} success - Whether the fix was applied
|
|
31
|
+
* @property {string} [error] - Error message if fix failed
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {object} AutoFixResult
|
|
36
|
+
* @property {number} fixed - Number of issues fixed
|
|
37
|
+
* @property {number} failed - Number of fixes that failed
|
|
38
|
+
* @property {FixAction[]} actions - Detailed action log
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run auto-fix on a project directory.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} projectDir
|
|
45
|
+
* @param {object[]} [checkResults=[]] - Results from health checks (for targeted fixes)
|
|
46
|
+
* @returns {AutoFixResult}
|
|
47
|
+
*/
|
|
48
|
+
export function autoFix(projectDir, checkResults = []) {
|
|
49
|
+
const actions = [];
|
|
50
|
+
|
|
51
|
+
// Fix 1: Ensure required directories exist
|
|
52
|
+
actions.push(...fixMissingDirectories(projectDir));
|
|
53
|
+
|
|
54
|
+
// Fix 2: Remove stale .lock files
|
|
55
|
+
actions.push(...fixStaleLocks(projectDir));
|
|
56
|
+
|
|
57
|
+
// Fix 3: Truncate oversized error logs
|
|
58
|
+
actions.push(...fixOversizedLogs(projectDir));
|
|
59
|
+
|
|
60
|
+
// Fix 4: Clean up orphaned worktrees directory entries
|
|
61
|
+
actions.push(...fixOrphanedWorktrees(projectDir));
|
|
62
|
+
|
|
63
|
+
const fixed = actions.filter(a => a.success).length;
|
|
64
|
+
const failed = actions.filter(a => !a.success).length;
|
|
65
|
+
|
|
66
|
+
return { fixed, failed, actions };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Individual Fixers
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Ensure required .chati/ subdirectories exist.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} projectDir
|
|
77
|
+
* @returns {FixAction[]}
|
|
78
|
+
*/
|
|
79
|
+
function fixMissingDirectories(projectDir) {
|
|
80
|
+
const actions = [];
|
|
81
|
+
const requiredDirs = [
|
|
82
|
+
'.chati',
|
|
83
|
+
'.chati/memory',
|
|
84
|
+
'.chati/metrics',
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const dir of requiredDirs) {
|
|
88
|
+
const fullPath = join(projectDir, dir);
|
|
89
|
+
if (!existsSync(fullPath)) {
|
|
90
|
+
try {
|
|
91
|
+
mkdirSync(fullPath, { recursive: true });
|
|
92
|
+
actions.push({ action: `Created missing directory: ${dir}`, success: true });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
actions.push({ action: `Failed to create ${dir}`, success: false, error: err.message });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return actions;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Remove .lock files that are stale (old + no active PID).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} projectDir
|
|
106
|
+
* @returns {FixAction[]}
|
|
107
|
+
*/
|
|
108
|
+
function fixStaleLocks(projectDir) {
|
|
109
|
+
const actions = [];
|
|
110
|
+
const chatiDir = join(projectDir, '.chati');
|
|
111
|
+
|
|
112
|
+
if (!existsSync(chatiDir)) return actions;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const entries = readdirSync(chatiDir);
|
|
116
|
+
const lockFiles = entries.filter(e => e.endsWith('.lock'));
|
|
117
|
+
|
|
118
|
+
for (const lockFile of lockFiles) {
|
|
119
|
+
const lockPath = join(chatiDir, lockFile);
|
|
120
|
+
try {
|
|
121
|
+
const stat = statSync(lockPath);
|
|
122
|
+
const age = Date.now() - stat.mtimeMs;
|
|
123
|
+
|
|
124
|
+
if (age > STALE_LOCK_AGE_MS) {
|
|
125
|
+
rmSync(lockPath, { force: true });
|
|
126
|
+
actions.push({ action: `Removed stale lock: ${lockFile}`, success: true });
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
actions.push({ action: `Failed to check lock ${lockFile}`, success: false, error: err.message });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Directory read failed — skip
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return actions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Truncate oversized error/gotcha logs.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} projectDir
|
|
143
|
+
* @returns {FixAction[]}
|
|
144
|
+
*/
|
|
145
|
+
function fixOversizedLogs(projectDir) {
|
|
146
|
+
const actions = [];
|
|
147
|
+
const logFiles = [
|
|
148
|
+
join(projectDir, '.chati', 'memory', 'errors.json'),
|
|
149
|
+
join(projectDir, '.chati', 'memory', 'gotchas.json'),
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
for (const logPath of logFiles) {
|
|
153
|
+
if (!existsSync(logPath)) continue;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const stat = statSync(logPath);
|
|
157
|
+
if (stat.size > MAX_ERROR_LOG_SIZE) {
|
|
158
|
+
truncateSync(logPath, 0);
|
|
159
|
+
actions.push({
|
|
160
|
+
action: `Truncated oversized log: ${logPath.split('/').pop()} (${Math.round(stat.size / 1024)}KB)`,
|
|
161
|
+
success: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
actions.push({
|
|
166
|
+
action: `Failed to truncate ${logPath.split('/').pop()}`,
|
|
167
|
+
success: false,
|
|
168
|
+
error: err.message,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return actions;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Clean up orphaned worktree directory entries (empty dirs).
|
|
178
|
+
*
|
|
179
|
+
* @param {string} projectDir
|
|
180
|
+
* @returns {FixAction[]}
|
|
181
|
+
*/
|
|
182
|
+
function fixOrphanedWorktrees(projectDir) {
|
|
183
|
+
const actions = [];
|
|
184
|
+
const worktreeDir = join(projectDir, '.chati', 'worktrees');
|
|
185
|
+
|
|
186
|
+
if (!existsSync(worktreeDir)) return actions;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const entries = readdirSync(worktreeDir);
|
|
190
|
+
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
const entryPath = join(worktreeDir, entry);
|
|
193
|
+
try {
|
|
194
|
+
const stat = statSync(entryPath);
|
|
195
|
+
if (stat.isDirectory()) {
|
|
196
|
+
const contents = readdirSync(entryPath);
|
|
197
|
+
if (contents.length === 0) {
|
|
198
|
+
rmSync(entryPath, { recursive: true, force: true });
|
|
199
|
+
actions.push({ action: `Removed empty worktree dir: ${entry}`, success: true });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// Skip entries we can't read
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Directory read failed
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return actions;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Exported constants for testing.
|
|
215
|
+
*/
|
|
216
|
+
export { MAX_ERROR_LOG_SIZE, STALE_LOCK_AGE_MS };
|
package/src/installer/core.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { IDE_CONFIGS } from '../config/ide-configs.js';
|
|
4
|
+
import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
|
|
5
5
|
import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
|
|
6
6
|
import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
|
|
7
7
|
import { generateContextFiles } from '../config/context-file-generator.js';
|
|
8
8
|
import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
|
|
9
|
+
import { generateProviderOverlays } from './provider-overlay.js';
|
|
9
10
|
import { verifyManifest } from './manifest.js';
|
|
10
11
|
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -20,7 +21,7 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
|
|
|
20
21
|
* Install Chati.dev framework into target directory
|
|
21
22
|
*/
|
|
22
23
|
export async function installFramework(config) {
|
|
23
|
-
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, version, llmProvider } = config;
|
|
24
|
+
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, version, llmProvider, allProviders } = config;
|
|
24
25
|
|
|
25
26
|
// 0. Verify framework signature (supply chain protection)
|
|
26
27
|
const manifestPath = join(FRAMEWORK_SOURCE, 'manifest.json');
|
|
@@ -40,7 +41,7 @@ export async function installFramework(config) {
|
|
|
40
41
|
createDir(join(targetDir, '.chati'));
|
|
41
42
|
writeFileSync(
|
|
42
43
|
join(targetDir, '.chati', 'session.yaml'),
|
|
43
|
-
generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider }),
|
|
44
|
+
generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders }),
|
|
44
45
|
'utf-8'
|
|
45
46
|
);
|
|
46
47
|
|
|
@@ -58,7 +59,7 @@ export async function installFramework(config) {
|
|
|
58
59
|
'i18n', 'migrations', 'data', 'context',
|
|
59
60
|
'artifacts/0-WU', 'artifacts/1-Brief', 'artifacts/2-PRD',
|
|
60
61
|
'artifacts/3-Architecture', 'artifacts/4-UX', 'artifacts/5-Phases',
|
|
61
|
-
'artifacts/6-Tasks', 'artifacts/7-QA-Planning', 'artifacts/
|
|
62
|
+
'artifacts/6-Tasks', 'artifacts/7-QA-Planning', 'artifacts/9-QA-Implementation', 'artifacts/10-Deploy',
|
|
62
63
|
'artifacts/handoffs', 'artifacts/decisions',
|
|
63
64
|
];
|
|
64
65
|
|
|
@@ -90,16 +91,27 @@ export async function installFramework(config) {
|
|
|
90
91
|
// Copy framework files from source (adapted for non-Claude providers)
|
|
91
92
|
copyFrameworkFiles(frameworkDir, llmProvider || 'claude');
|
|
92
93
|
|
|
94
|
+
// Generate provider overlays for secondary CLI providers
|
|
95
|
+
if (allProviders && allProviders.length > 1) {
|
|
96
|
+
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, llmProvider || 'claude', allProviders);
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
// Write config.yaml
|
|
94
100
|
writeFileSync(
|
|
95
101
|
join(frameworkDir, 'config.yaml'),
|
|
96
|
-
generateConfigYaml({ version, projectType, language, selectedIDEs, llmProvider }),
|
|
102
|
+
generateConfigYaml({ version, projectType, language, selectedIDEs, llmProvider, allProviders }),
|
|
97
103
|
'utf-8'
|
|
98
104
|
);
|
|
99
105
|
|
|
100
|
-
// 3. Configure IDEs
|
|
106
|
+
// 3. Configure IDEs (secondary providers get overlay orchestrator path)
|
|
107
|
+
const primary = llmProvider || 'claude';
|
|
101
108
|
for (const ideKey of selectedIDEs) {
|
|
102
|
-
|
|
109
|
+
const ideProvider = IDE_TO_PROVIDER[ideKey];
|
|
110
|
+
const isSecondary = ideProvider && ideProvider !== primary && allProviders && allProviders.length > 1;
|
|
111
|
+
const orchestratorPath = isSecondary
|
|
112
|
+
? `chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
|
|
113
|
+
: 'chati.dev/orchestrator/chati.md';
|
|
114
|
+
await configureIDE(targetDir, ideKey, selectedMCPs, { orchestratorPath, isSecondary, providerName: ideProvider });
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const hasClaude = selectedIDEs.includes('claude-code');
|
|
@@ -261,7 +273,8 @@ function copyFrameworkFiles(destDir, provider = 'claude') {
|
|
|
261
273
|
/**
|
|
262
274
|
* Configure a specific IDE
|
|
263
275
|
*/
|
|
264
|
-
async function configureIDE(targetDir, ideKey, selectedMCPs) {
|
|
276
|
+
async function configureIDE(targetDir, ideKey, selectedMCPs, options = {}) {
|
|
277
|
+
const { orchestratorPath, isSecondary } = options;
|
|
265
278
|
const config = IDE_CONFIGS[ideKey];
|
|
266
279
|
if (!config) return;
|
|
267
280
|
|
|
@@ -290,7 +303,7 @@ If session.yaml does not exist or has no language field, default to English.
|
|
|
290
303
|
|
|
291
304
|
## Load
|
|
292
305
|
|
|
293
|
-
Read and execute the full orchestrator at
|
|
306
|
+
Read and execute the full orchestrator at \`${orchestratorPath || 'chati.dev/orchestrator/chati.md'}\`.
|
|
294
307
|
|
|
295
308
|
Pass through all context: session state, handoffs, artifacts, and user input.
|
|
296
309
|
|
|
@@ -316,7 +329,7 @@ Pass through all context: session state, handoffs, artifacts, and user input.
|
|
|
316
329
|
} else if (ideKey === 'codex-cli') {
|
|
317
330
|
// Codex CLI: chati skill via .agents/skills/chati/SKILL.md (invoke with $chati)
|
|
318
331
|
createDir(join(targetDir, '.agents', 'skills', 'chati'));
|
|
319
|
-
writeFileSync(join(targetDir, '.agents', 'skills', 'chati', 'SKILL.md'), generateCodexSkill(), 'utf-8');
|
|
332
|
+
writeFileSync(join(targetDir, '.agents', 'skills', 'chati', 'SKILL.md'), generateCodexSkill({ orchestratorPath }), 'utf-8');
|
|
320
333
|
|
|
321
334
|
// Session lock override file (equivalent to CLAUDE.local.md)
|
|
322
335
|
writeFileSync(join(targetDir, 'AGENTS.override.md'), generateAgentsOverrideMd(), 'utf-8');
|
|
@@ -327,7 +340,7 @@ Pass through all context: session state, handoffs, artifacts, and user input.
|
|
|
327
340
|
writeFileSync(join(targetDir, '.codex', 'rules', 'read-protection.rules'), generateCodexReadProtectionRules(), 'utf-8');
|
|
328
341
|
} else if (ideKey === 'gemini-cli') {
|
|
329
342
|
// Gemini CLI: TOML command file (native format for /chati command)
|
|
330
|
-
writeFileSync(join(targetDir, '.gemini', 'commands', 'chati.toml'), generateGeminiRouter(), 'utf-8');
|
|
343
|
+
writeFileSync(join(targetDir, '.gemini', 'commands', 'chati.toml'), generateGeminiRouter({ orchestratorPath }), 'utf-8');
|
|
331
344
|
|
|
332
345
|
// Context files via @import (equivalent to .claude/rules/chati/)
|
|
333
346
|
const geminiContextDir = join(targetDir, '.gemini', 'context');
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provider Overlay Generator for multi-CLI continuity.
|
|
3
|
+
*
|
|
4
|
+
* When multiple CLI providers are selected (e.g. Claude Code + Gemini CLI),
|
|
5
|
+
* the primary provider's adapted files go in chati.dev/ (main). For each
|
|
6
|
+
* secondary provider, this module generates an overlay directory at
|
|
7
|
+
* chati.dev/.adapted/<provider>/ with the ADAPTABLE_FILES adapted for
|
|
8
|
+
* that provider. Each CLI reads only its own files — zero runtime translation.
|
|
9
|
+
*
|
|
10
|
+
* Constitution Article XIX — multi-CLI governance.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate adapted framework overlays for secondary providers.
|
|
19
|
+
*
|
|
20
|
+
* For each non-primary CLI provider, creates chati.dev/.adapted/<provider>/
|
|
21
|
+
* with provider-adapted copies of all ADAPTABLE_FILES.
|
|
22
|
+
*
|
|
23
|
+
* Reads CANONICAL (Claude) content from frameworkSource, then adapts for
|
|
24
|
+
* each secondary provider. This avoids double-adaptation issues when the
|
|
25
|
+
* primary provider is not Claude.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} targetDir - Project root directory
|
|
28
|
+
* @param {string} frameworkSource - Source directory with canonical framework files
|
|
29
|
+
* @param {string} primaryProvider - Primary provider name ('claude', 'gemini', 'codex')
|
|
30
|
+
* @param {string[]} allProviders - All CLI provider names
|
|
31
|
+
* @returns {{ generated: string[], skipped: string[] }}
|
|
32
|
+
*/
|
|
33
|
+
export function generateProviderOverlays(targetDir, frameworkSource, primaryProvider, allProviders) {
|
|
34
|
+
const result = { generated: [], skipped: [] };
|
|
35
|
+
const frameworkDir = join(targetDir, 'chati.dev');
|
|
36
|
+
|
|
37
|
+
for (const provider of allProviders) {
|
|
38
|
+
if (provider === primaryProvider) {
|
|
39
|
+
result.skipped.push(provider);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const overlayDir = join(frameworkDir, '.adapted', provider);
|
|
44
|
+
|
|
45
|
+
for (const file of ADAPTABLE_FILES) {
|
|
46
|
+
const srcPath = join(frameworkSource, file);
|
|
47
|
+
if (!existsSync(srcPath)) continue;
|
|
48
|
+
|
|
49
|
+
// Read CANONICAL (Claude) content from source, then adapt for target provider
|
|
50
|
+
const canonicalContent = readFileSync(srcPath, 'utf-8');
|
|
51
|
+
const adapted = adaptFrameworkFile(canonicalContent, file, provider);
|
|
52
|
+
|
|
53
|
+
const destPath = join(overlayDir, file);
|
|
54
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
55
|
+
writeFileSync(destPath, adapted, 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
result.generated.push(provider);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the correct framework file path for a given provider.
|
|
66
|
+
*
|
|
67
|
+
* Returns the overlay path for secondary providers (if it exists),
|
|
68
|
+
* otherwise falls back to the main chati.dev/ path.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} projectDir - Project root directory
|
|
71
|
+
* @param {string} relativePath - Path relative to chati.dev/ (e.g. 'agents/build/dev.md')
|
|
72
|
+
* @param {string} provider - Target provider name
|
|
73
|
+
* @returns {string} Absolute path to the adapted file
|
|
74
|
+
*/
|
|
75
|
+
export function resolveOverlayPath(projectDir, relativePath, provider) {
|
|
76
|
+
// Check overlay first
|
|
77
|
+
const overlayPath = join(projectDir, 'chati.dev', '.adapted', provider, relativePath);
|
|
78
|
+
if (existsSync(overlayPath)) return overlayPath;
|
|
79
|
+
|
|
80
|
+
// Fallback to main chati.dev/
|
|
81
|
+
return join(projectDir, 'chati.dev', relativePath);
|
|
82
|
+
}
|
|
@@ -4,9 +4,10 @@ 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, llmProvider } = config;
|
|
7
|
+
const { projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders } = config;
|
|
8
8
|
|
|
9
9
|
const session = {
|
|
10
|
+
schema_version: '1.0',
|
|
10
11
|
project: {
|
|
11
12
|
name: projectName,
|
|
12
13
|
type: projectType,
|
|
@@ -18,7 +19,9 @@ export function generateSessionYaml(config) {
|
|
|
18
19
|
language: language,
|
|
19
20
|
ides: selectedIDEs,
|
|
20
21
|
mcps: selectedMCPs,
|
|
21
|
-
providers_enabled: [llmProvider || 'claude'],
|
|
22
|
+
providers_enabled: allProviders && allProviders.length > 0 ? allProviders : [llmProvider || 'claude'],
|
|
23
|
+
primary_provider: llmProvider || 'claude',
|
|
24
|
+
active_provider: llmProvider || 'claude',
|
|
22
25
|
user_level: 'auto',
|
|
23
26
|
user_level_confidence: 0.0,
|
|
24
27
|
agents: {},
|
|
@@ -86,8 +89,9 @@ export const PROVIDER_MODEL_MAPS = {
|
|
|
86
89
|
* Generate config.yaml content
|
|
87
90
|
*/
|
|
88
91
|
export function generateConfigYaml(config) {
|
|
89
|
-
const { version, projectType, language, selectedIDEs, llmProvider } = config;
|
|
92
|
+
const { version, projectType, language, selectedIDEs, llmProvider, allProviders } = config;
|
|
90
93
|
const provider = llmProvider || 'claude';
|
|
94
|
+
const providers = allProviders && allProviders.length > 0 ? allProviders : [provider];
|
|
91
95
|
|
|
92
96
|
const configData = {
|
|
93
97
|
version: version,
|
|
@@ -98,12 +102,18 @@ export function generateConfigYaml(config) {
|
|
|
98
102
|
language: language,
|
|
99
103
|
ides: selectedIDEs,
|
|
100
104
|
providers: {
|
|
101
|
-
claude: { enabled:
|
|
102
|
-
gemini: { enabled:
|
|
103
|
-
codex: { enabled:
|
|
105
|
+
claude: { enabled: providers.includes('claude') || provider === 'claude', primary: provider === 'claude' },
|
|
106
|
+
gemini: { enabled: providers.includes('gemini') || selectedIDEs.includes('gemini-cli'), model_default: 'pro', primary: provider === 'gemini' },
|
|
107
|
+
codex: { enabled: providers.includes('codex') || selectedIDEs.includes('codex-cli'), model_default: 'codex', primary: provider === 'codex' },
|
|
104
108
|
},
|
|
105
109
|
};
|
|
106
110
|
|
|
111
|
+
// Add telemetry config
|
|
112
|
+
configData.telemetry = {
|
|
113
|
+
enabled: config.telemetryEnabled || false,
|
|
114
|
+
anonymous_id: null,
|
|
115
|
+
};
|
|
116
|
+
|
|
107
117
|
// Add agent_overrides with optimal model map for the selected provider
|
|
108
118
|
if (provider !== 'claude') {
|
|
109
119
|
const modelMap = PROVIDER_MODEL_MAPS[provider];
|
|
@@ -144,7 +154,8 @@ Type \`/chati\` to start.
|
|
|
144
154
|
* The user invokes with $chati or Codex auto-detects based on description.
|
|
145
155
|
* Also reads AGENTS.md for project context (auto-generated by context-file-generator).
|
|
146
156
|
*/
|
|
147
|
-
export function generateCodexSkill() {
|
|
157
|
+
export function generateCodexSkill(options = {}) {
|
|
158
|
+
const orchPath = options.orchestratorPath || 'chati.dev/orchestrator/chati.md';
|
|
148
159
|
return `---
|
|
149
160
|
name: chati
|
|
150
161
|
description: >
|
|
@@ -175,7 +186,7 @@ If session.yaml does not exist or has no language field, default to English.
|
|
|
175
186
|
The orchestrator and all agent files are **pre-configured for Codex CLI**.
|
|
176
187
|
No translation needed — follow all instructions as written.
|
|
177
188
|
|
|
178
|
-
Read and execute the full orchestrator at
|
|
189
|
+
Read and execute the full orchestrator at \`${orchPath}\`.
|
|
179
190
|
|
|
180
191
|
**NEVER create or reference CLAUDE.md, CLAUDE.local.md, or .claude/ directories.**
|
|
181
192
|
|
|
@@ -195,7 +206,8 @@ Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
|
|
|
195
206
|
* Gemini CLI custom commands use TOML format with `description` and `prompt` fields.
|
|
196
207
|
* Supports {{args}} for user arguments and @{file} for file injection.
|
|
197
208
|
*/
|
|
198
|
-
export function generateGeminiRouter() {
|
|
209
|
+
export function generateGeminiRouter(options = {}) {
|
|
210
|
+
const orchPath = options.orchestratorPath || 'chati.dev/orchestrator/chati.md';
|
|
199
211
|
return `description = "Activate Chati.dev orchestrator"
|
|
200
212
|
prompt = """
|
|
201
213
|
CRITICAL — Language Override:
|
|
@@ -206,7 +218,7 @@ If session.yaml does not exist or has no language field, default to English.
|
|
|
206
218
|
The orchestrator and all agent files are pre-configured for Gemini CLI.
|
|
207
219
|
No translation needed — follow all instructions as written.
|
|
208
220
|
|
|
209
|
-
Read and execute the full orchestrator at
|
|
221
|
+
Read and execute the full orchestrator at ${orchPath}.
|
|
210
222
|
NEVER create or reference CLAUDE.md, CLAUDE.local.md, or .claude/ directories.
|
|
211
223
|
|
|
212
224
|
Context to load:
|
|
@@ -210,8 +210,9 @@ export class InstallTransaction {
|
|
|
210
210
|
break;
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
} catch {
|
|
214
|
-
// Best-effort rollback:
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// Best-effort rollback: log and continue
|
|
215
|
+
console.error('[chati] rollback warning:', err?.message);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
|