chati-dev 2.1.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -24
- package/bin/chati.js +1 -1
- package/framework/agents/build/dev.md +1 -0
- package/framework/agents/deploy/devops.md +1 -0
- package/framework/agents/discover/brief.md +1 -0
- package/framework/agents/discover/brownfield-wu.md +1 -0
- package/framework/agents/discover/greenfield-wu.md +1 -0
- package/framework/agents/plan/architect.md +1 -0
- package/framework/agents/plan/detail.md +1 -0
- package/framework/agents/plan/phases.md +3 -2
- package/framework/agents/plan/tasks.md +1 -0
- package/framework/agents/plan/ux.md +1 -0
- package/framework/agents/quality/qa-implementation.md +1 -0
- package/framework/agents/quality/qa-planning.md +1 -0
- package/framework/config.yaml +21 -2
- package/framework/constitution.md +67 -2
- package/framework/context/governance.md +16 -3
- package/framework/context/root.md +3 -3
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/brownfield-wu.yaml +4 -0
- package/framework/domains/agents/detail.yaml +1 -1
- package/framework/domains/agents/dev.yaml +4 -0
- package/framework/domains/agents/orchestrator.yaml +8 -0
- package/framework/domains/agents/phases.yaml +2 -2
- package/framework/domains/agents/tasks.yaml +2 -2
- package/framework/domains/constitution.yaml +30 -2
- package/framework/domains/global.yaml +20 -0
- package/framework/hooks/model-governance.js +17 -15
- package/framework/hooks/read-protection.js +1 -0
- package/framework/i18n/en.yaml +11 -4
- package/framework/i18n/es.yaml +11 -4
- package/framework/i18n/fr.yaml +11 -4
- package/framework/i18n/pt.yaml +11 -4
- package/framework/intelligence/context-engine.md +31 -2
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/intelligence/memory-layer.md +17 -0
- package/framework/orchestrator/chati.md +94 -14
- package/framework/patterns/elicitation-library.yaml +2 -2
- package/framework/schemas/config.schema.json +44 -0
- package/framework/schemas/session.schema.json +27 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
- package/framework/tasks/brownfield-wu-migration-plan.md +4 -4
- package/framework/tasks/brownfield-wu-risk-assess.md +2 -2
- package/framework/tasks/detail-expand-prd.md +2 -2
- package/framework/tasks/greenfield-wu-report.md +1 -1
- package/framework/tasks/orchestrator-handoff.md +2 -2
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/qa-impl-performance-test.md +2 -2
- package/framework/tasks/qa-impl-sast-scan.md +6 -6
- package/framework/tasks/qa-planning-consolidate.md +1 -1
- package/framework/tasks/qa-planning-coverage-plan.md +9 -9
- package/framework/tasks/qa-planning-gate-define.md +6 -6
- package/framework/tasks/qa-planning-risk-matrix.md +1 -1
- package/framework/tasks/tasks-consolidate.md +5 -5
- package/package.json +5 -1
- package/scripts/changelog-generator.js +2 -2
- package/scripts/ide-sync.js +9 -4
- package/scripts/pr-review.js +5 -5
- package/scripts/semantic-lint.js +3 -3
- package/src/autonomy/build-loop.js +194 -0
- package/src/autonomy/build-state.js +269 -0
- package/src/autonomy/execution-profile.js +151 -0
- package/src/config/context-file-generator.js +203 -0
- package/src/config/ide-configs.js +6 -6
- package/src/gates/g2-qa-planning.js +4 -2
- package/src/gates/g4-qa-implementation.js +5 -2
- package/src/gates/gate-base.js +33 -1
- package/src/health/engine.js +245 -0
- package/src/installer/core.js +54 -16
- package/src/installer/templates.js +131 -2
- package/src/installer/validator.js +5 -3
- package/src/intelligence/file-tracker.js +117 -0
- package/src/intelligence/registry-manager.js +2 -2
- package/src/intelligence/timeline.js +144 -0
- package/src/memory/gotchas-auto-capture.js +253 -0
- package/src/preview/detector.js +1 -1
- package/src/terminal/adapters/claude-adapter.js +33 -0
- package/src/terminal/adapters/codex-adapter.js +31 -0
- package/src/terminal/adapters/copilot-adapter.js +28 -0
- package/src/terminal/adapters/gemini-adapter.js +28 -0
- package/src/terminal/adapters/index.js +8 -0
- package/src/terminal/cli-registry.js +190 -0
- package/src/terminal/prompt-builder.js +9 -20
- package/src/terminal/run-agent.js +2 -1
- package/src/terminal/spawner.js +44 -14
- package/src/terminal/wave-analyzer.js +143 -0
- package/src/utils/config-parser.js +97 -0
- package/src/wizard/feedback.js +1 -1
- package/src/wizard/i18n.js +11 -4
- package/src/wizard/index.js +22 -10
- package/src/wizard/questions.js +50 -2
- package/framework/manifest.json +0 -5
- package/framework/manifest.sig +0 -1
- /package/assets/{logo - co/314/201pia.png" → logo - c/303/263pia.png"} +0 -0
- /package/assets/{logo - co/314/201pia.svg" → logo - c/303/263pia.svg"} +0 -0
package/src/gates/gate-base.js
CHANGED
|
@@ -8,6 +8,33 @@
|
|
|
8
8
|
|
|
9
9
|
import { evaluateGate, getGateThreshold, resolveGateAction } from '../autonomy/autonomous-gate.js';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Gate verdict constants (v3.0.0).
|
|
13
|
+
* APPROVED: Score >= threshold — proceed to next pipeline stage.
|
|
14
|
+
* NEEDS_REVISION: Score within 5 points below threshold — return to agent for fixes.
|
|
15
|
+
* BLOCKED: Score significantly below threshold or critical blocker — halt pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export const GateVerdict = {
|
|
18
|
+
APPROVED: 'approved',
|
|
19
|
+
NEEDS_REVISION: 'needs_revision',
|
|
20
|
+
BLOCKED: 'blocked',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determine the gate verdict from a score and threshold.
|
|
25
|
+
*
|
|
26
|
+
* @param {number} score - Gate evaluation score (0-100)
|
|
27
|
+
* @param {number} threshold - Minimum score to pass
|
|
28
|
+
* @param {boolean} [hasCriticalBlocker=false] - Whether a critical blocker exists
|
|
29
|
+
* @returns {string} One of GateVerdict values
|
|
30
|
+
*/
|
|
31
|
+
export function determineVerdict(score, threshold, hasCriticalBlocker = false) {
|
|
32
|
+
if (hasCriticalBlocker) return GateVerdict.BLOCKED;
|
|
33
|
+
if (score >= threshold) return GateVerdict.APPROVED;
|
|
34
|
+
if (score >= threshold - 5) return GateVerdict.NEEDS_REVISION;
|
|
35
|
+
return GateVerdict.BLOCKED;
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
/**
|
|
12
39
|
* Abstract base class for quality gates.
|
|
13
40
|
*
|
|
@@ -70,14 +97,19 @@ export class GateBase {
|
|
|
70
97
|
|
|
71
98
|
const action = resolveGateAction(gateResult.result, mode);
|
|
72
99
|
|
|
100
|
+
const threshold = getGateThreshold(this.agent);
|
|
101
|
+
const hasCriticalBlocker = warnings.some(w => w.toLowerCase().includes('critical'));
|
|
102
|
+
const verdict = determineVerdict(gateResult.score, threshold, hasCriticalBlocker);
|
|
103
|
+
|
|
73
104
|
return {
|
|
74
105
|
gateId: this.id,
|
|
75
106
|
gateName: this.name,
|
|
76
107
|
result: gateResult.result,
|
|
108
|
+
verdict,
|
|
77
109
|
score: gateResult.score,
|
|
78
110
|
evidence,
|
|
79
111
|
recommendation: action.action,
|
|
80
|
-
canProceed:
|
|
112
|
+
canProceed: verdict === GateVerdict.APPROVED,
|
|
81
113
|
details: gateResult.details,
|
|
82
114
|
warnings,
|
|
83
115
|
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check engine for chati.dev.
|
|
3
|
+
*
|
|
4
|
+
* Validates framework integrity, CLI availability, session state,
|
|
5
|
+
* hook health, dependencies, and provider authentication.
|
|
6
|
+
* Constitution Article XIV — Framework Registry Governance.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { isCommandAvailable, parseProviderConfig } from '../utils/config-parser.js';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Check Result Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} CheckResult
|
|
19
|
+
* @property {string} name - Check name
|
|
20
|
+
* @property {'pass'|'warn'|'fail'} status - Check status
|
|
21
|
+
* @property {string} message - Human-readable result
|
|
22
|
+
* @property {number} duration - Execution time in ms
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} HealthReport
|
|
27
|
+
* @property {number} score - Overall score (0-100)
|
|
28
|
+
* @property {number} total - Total checks run
|
|
29
|
+
* @property {number} passed - Checks passed
|
|
30
|
+
* @property {number} warned - Checks with warnings
|
|
31
|
+
* @property {number} failed - Checks failed
|
|
32
|
+
* @property {CheckResult[]} checks - Individual check results
|
|
33
|
+
* @property {string} timestamp - ISO timestamp
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Individual Checks
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if configured CLI providers are available on the system.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} projectDir
|
|
44
|
+
* @returns {Promise<CheckResult>}
|
|
45
|
+
*/
|
|
46
|
+
export async function checkCliAvailability(projectDir) {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
const configPath = join(projectDir, 'chati.dev', 'config.yaml');
|
|
49
|
+
|
|
50
|
+
if (!existsSync(configPath)) {
|
|
51
|
+
return { name: 'cli-availability', status: 'warn', message: 'No config.yaml found', duration: Date.now() - start };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { enabled } = parseProviderConfig(projectDir);
|
|
55
|
+
const missing = [];
|
|
56
|
+
|
|
57
|
+
for (const provider of enabled) {
|
|
58
|
+
const available = await isCommandAvailable(provider);
|
|
59
|
+
if (!available) {
|
|
60
|
+
missing.push(provider);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (missing.length > 0) {
|
|
65
|
+
return { name: 'cli-availability', status: 'warn', message: `Missing CLIs: ${missing.join(', ')}`, duration: Date.now() - start };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { name: 'cli-availability', status: 'pass', message: 'All enabled CLIs available', duration: Date.now() - start };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate framework file integrity against entity registry.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} projectDir
|
|
75
|
+
* @returns {Promise<CheckResult>}
|
|
76
|
+
*/
|
|
77
|
+
export async function checkFrameworkIntegrity(projectDir) {
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
const registryPath = join(projectDir, 'chati.dev', 'data', 'entity-registry.yaml');
|
|
80
|
+
|
|
81
|
+
if (!existsSync(registryPath)) {
|
|
82
|
+
return { name: 'framework-integrity', status: 'warn', message: 'No entity registry found', duration: Date.now() - start };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const raw = readFileSync(registryPath, 'utf-8');
|
|
86
|
+
const pathMatches = raw.match(/path:\s*["']?([^\s"']+)["']?/gm) || [];
|
|
87
|
+
let missing = 0;
|
|
88
|
+
|
|
89
|
+
for (const match of pathMatches) {
|
|
90
|
+
const filePath = match.replace(/path:\s*["']?/, '').replace(/["']?$/, '');
|
|
91
|
+
if (!existsSync(join(projectDir, filePath))) {
|
|
92
|
+
missing++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (missing > 0) {
|
|
97
|
+
return { name: 'framework-integrity', status: 'warn', message: `${missing} registered entities missing from filesystem`, duration: Date.now() - start };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { name: 'framework-integrity', status: 'pass', message: 'All registered entities exist', duration: Date.now() - start };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate session.yaml structure.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} projectDir
|
|
107
|
+
* @returns {Promise<CheckResult>}
|
|
108
|
+
*/
|
|
109
|
+
export async function checkSessionState(projectDir) {
|
|
110
|
+
const start = Date.now();
|
|
111
|
+
const sessionPath = join(projectDir, '.chati', 'session.yaml');
|
|
112
|
+
|
|
113
|
+
if (!existsSync(sessionPath)) {
|
|
114
|
+
return { name: 'session-state', status: 'pass', message: 'No active session (expected for new projects)', duration: Date.now() - start };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const raw = readFileSync(sessionPath, 'utf-8');
|
|
118
|
+
const requiredFields = ['project', 'language'];
|
|
119
|
+
const missingFields = requiredFields.filter((f) => !raw.includes(`${f}:`));
|
|
120
|
+
|
|
121
|
+
if (missingFields.length > 0) {
|
|
122
|
+
return { name: 'session-state', status: 'warn', message: `Session missing fields: ${missingFields.join(', ')}`, duration: Date.now() - start };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { name: 'session-state', status: 'pass', message: 'Session state valid', duration: Date.now() - start };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Verify that hooks are functional.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} projectDir
|
|
132
|
+
* @returns {Promise<CheckResult>}
|
|
133
|
+
*/
|
|
134
|
+
export async function checkHooksHealth(projectDir) {
|
|
135
|
+
const start = Date.now();
|
|
136
|
+
const hooksDir = join(projectDir, 'chati.dev', 'hooks');
|
|
137
|
+
|
|
138
|
+
if (!existsSync(hooksDir)) {
|
|
139
|
+
return { name: 'hooks-health', status: 'fail', message: 'Hooks directory not found', duration: Date.now() - start };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const expectedHooks = ['prism-engine.js', 'model-governance.js', 'mode-governance.js', 'constitution-guard.js', 'read-protection.js', 'session-digest.js'];
|
|
143
|
+
const missing = expectedHooks.filter((h) => !existsSync(join(hooksDir, h)));
|
|
144
|
+
|
|
145
|
+
if (missing.length > 0) {
|
|
146
|
+
return { name: 'hooks-health', status: 'fail', message: `Missing hooks: ${missing.join(', ')}`, duration: Date.now() - start };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { name: 'hooks-health', status: 'pass', message: 'All 6 hooks present', duration: Date.now() - start };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Verify system dependencies.
|
|
154
|
+
*
|
|
155
|
+
* @returns {Promise<CheckResult>}
|
|
156
|
+
*/
|
|
157
|
+
export async function checkDependencies() {
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
const issues = [];
|
|
160
|
+
|
|
161
|
+
const { execSync } = await import('child_process');
|
|
162
|
+
|
|
163
|
+
// Check Node.js version
|
|
164
|
+
try {
|
|
165
|
+
const nodeVersion = execSync('node --version', { encoding: 'utf-8' }).trim();
|
|
166
|
+
const major = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
|
|
167
|
+
if (major < 20) {
|
|
168
|
+
issues.push(`Node.js ${nodeVersion} < required v20`);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
issues.push('Node.js not found');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check git
|
|
175
|
+
try {
|
|
176
|
+
execSync('git --version', { stdio: 'ignore' });
|
|
177
|
+
} catch {
|
|
178
|
+
issues.push('git not found');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (issues.length > 0) {
|
|
182
|
+
return { name: 'dependencies', status: 'fail', message: issues.join('; '), duration: Date.now() - start };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { name: 'dependencies', status: 'pass', message: 'All dependencies satisfied', duration: Date.now() - start };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check git repository status.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} projectDir
|
|
192
|
+
* @returns {Promise<CheckResult>}
|
|
193
|
+
*/
|
|
194
|
+
export async function checkGitStatus(projectDir) {
|
|
195
|
+
const start = Date.now();
|
|
196
|
+
|
|
197
|
+
if (!existsSync(join(projectDir, '.git'))) {
|
|
198
|
+
return { name: 'git-status', status: 'warn', message: 'Not a git repository', duration: Date.now() - start };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { name: 'git-status', status: 'pass', message: 'Git repository detected', duration: Date.now() - start };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Health Engine
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Run all health checks and produce a report.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} projectDir - Project root directory
|
|
212
|
+
* @returns {Promise<HealthReport>}
|
|
213
|
+
*/
|
|
214
|
+
export async function runHealthChecks(projectDir) {
|
|
215
|
+
const checks = [
|
|
216
|
+
checkCliAvailability(projectDir),
|
|
217
|
+
checkFrameworkIntegrity(projectDir),
|
|
218
|
+
checkSessionState(projectDir),
|
|
219
|
+
checkHooksHealth(projectDir),
|
|
220
|
+
checkDependencies(),
|
|
221
|
+
checkGitStatus(projectDir),
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const results = await Promise.allSettled(checks);
|
|
225
|
+
const checkResults = results.map((r) => {
|
|
226
|
+
if (r.status === 'fulfilled') return r.value;
|
|
227
|
+
return { name: 'unknown', status: 'fail', message: r.reason?.message || 'Check failed', duration: 0 };
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const passed = checkResults.filter((c) => c.status === 'pass').length;
|
|
231
|
+
const warned = checkResults.filter((c) => c.status === 'warn').length;
|
|
232
|
+
const failed = checkResults.filter((c) => c.status === 'fail').length;
|
|
233
|
+
const total = checkResults.length;
|
|
234
|
+
const score = Math.round((passed / total) * 100);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
score,
|
|
238
|
+
total,
|
|
239
|
+
passed,
|
|
240
|
+
warned,
|
|
241
|
+
failed,
|
|
242
|
+
checks: checkResults,
|
|
243
|
+
timestamp: new Date().toISOString(),
|
|
244
|
+
};
|
|
245
|
+
}
|
package/src/installer/core.js
CHANGED
|
@@ -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, 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
|
|
|
@@ -101,13 +102,13 @@ export async function installFramework(config) {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// 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
105
|
const contextFiles = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
|
|
106
|
+
const claudeRulesDir = join(targetDir, '.claude', 'rules', 'chati');
|
|
107
|
+
createDir(claudeRulesDir);
|
|
107
108
|
for (const file of contextFiles) {
|
|
108
109
|
const src = join(FRAMEWORK_SOURCE, 'context', file);
|
|
109
110
|
if (existsSync(src)) {
|
|
110
|
-
copyFileSync(src, join(
|
|
111
|
+
copyFileSync(src, join(claudeRulesDir, file));
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -124,6 +125,14 @@ export async function installFramework(config) {
|
|
|
124
125
|
generateClaudeLocalMd(),
|
|
125
126
|
'utf-8'
|
|
126
127
|
);
|
|
128
|
+
|
|
129
|
+
// 7. Generate context files for non-Claude providers (GEMINI.md, AGENTS.md)
|
|
130
|
+
// Copilot CLI natively reads AGENTS.md + CLAUDE.md + GEMINI.md — no separate COPILOT.md needed.
|
|
131
|
+
const hasNonClaudeProvider = (llmProvider && llmProvider !== 'claude') ||
|
|
132
|
+
selectedIDEs.some(ide => ['gemini-cli', 'github-copilot'].includes(ide));
|
|
133
|
+
if (hasNonClaudeProvider) {
|
|
134
|
+
generateContextFiles(targetDir);
|
|
135
|
+
}
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
/**
|
|
@@ -157,12 +166,14 @@ function copyFrameworkFiles(destDir) {
|
|
|
157
166
|
'templates/fullstack-architecture-tmpl.yaml',
|
|
158
167
|
'templates/task-tmpl.yaml',
|
|
159
168
|
'templates/qa-gate-tmpl.yaml',
|
|
169
|
+
'templates/quick-brief-tmpl.yaml',
|
|
160
170
|
// Workflows
|
|
161
171
|
'workflows/greenfield-fullstack.yaml',
|
|
162
172
|
'workflows/brownfield-fullstack.yaml',
|
|
163
173
|
'workflows/brownfield-discovery.yaml',
|
|
164
174
|
'workflows/brownfield-service.yaml',
|
|
165
175
|
'workflows/brownfield-ui.yaml',
|
|
176
|
+
'workflows/quick-flow.yaml',
|
|
166
177
|
// Quality gates
|
|
167
178
|
'quality-gates/planning-gate.md',
|
|
168
179
|
'quality-gates/implementation-gate.md',
|
|
@@ -191,6 +202,7 @@ function copyFrameworkFiles(destDir) {
|
|
|
191
202
|
'hooks/session-digest.js',
|
|
192
203
|
'hooks/model-governance.js',
|
|
193
204
|
'hooks/settings.json',
|
|
205
|
+
'hooks/read-protection.js',
|
|
194
206
|
// Domains (PRISM Context Engine)
|
|
195
207
|
'domains/constitution.yaml',
|
|
196
208
|
'domains/global.yaml',
|
|
@@ -212,6 +224,7 @@ function copyFrameworkFiles(destDir) {
|
|
|
212
224
|
'domains/workflows/brownfield-discovery.yaml',
|
|
213
225
|
'domains/workflows/brownfield-service.yaml',
|
|
214
226
|
'domains/workflows/brownfield-ui.yaml',
|
|
227
|
+
'domains/workflows/quick-flow.yaml',
|
|
215
228
|
// i18n
|
|
216
229
|
'i18n/en.yaml',
|
|
217
230
|
'i18n/pt.yaml',
|
|
@@ -294,10 +307,32 @@ Pass through all context: session state, handoffs, artifacts, and user input.
|
|
|
294
307
|
'utf-8'
|
|
295
308
|
);
|
|
296
309
|
}
|
|
310
|
+
} else if (ideKey === 'gemini-cli') {
|
|
311
|
+
// Gemini CLI: TOML command file (native format for /chati command)
|
|
312
|
+
writeFileSync(join(targetDir, '.gemini', 'commands', 'chati.toml'), generateGeminiRouter(), 'utf-8');
|
|
313
|
+
} else if (ideKey === 'github-copilot') {
|
|
314
|
+
// GitHub Copilot: agent file (.github/agents/chati.md) for @chati invocation
|
|
315
|
+
writeFileSync(join(targetDir, '.github', 'agents', 'chati.md'), generateCopilotAgent(), 'utf-8');
|
|
316
|
+
|
|
317
|
+
// Copilot instructions file (auto-loaded by Copilot CLI)
|
|
318
|
+
createDir(dirname(join(targetDir, config.rulesFile)));
|
|
319
|
+
writeFileSync(join(targetDir, config.rulesFile), generateProviderInstructions(config.name), 'utf-8');
|
|
297
320
|
} else {
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
321
|
+
// VS Code, Cursor, AntiGravity — generic rules file
|
|
322
|
+
if (config.rulesFile) {
|
|
323
|
+
createDir(dirname(join(targetDir, config.rulesFile)));
|
|
324
|
+
writeFileSync(join(targetDir, config.rulesFile), generateProviderInstructions(config.name), 'utf-8');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Generate provider-agnostic instructions file content.
|
|
331
|
+
* Used for non-Claude IDEs (.github/copilot-instructions.md, .vscode/chati/rules.md, etc.)
|
|
332
|
+
*/
|
|
333
|
+
function generateProviderInstructions(providerName) {
|
|
334
|
+
return `# Chati.dev System Rules
|
|
335
|
+
# This file configures ${providerName} to work with Chati.dev
|
|
301
336
|
|
|
302
337
|
## System Location
|
|
303
338
|
All system content is in the \`chati.dev/\` directory.
|
|
@@ -310,7 +345,15 @@ The orchestrator is at \`chati.dev/orchestrator/chati.md\`.
|
|
|
310
345
|
Read it to understand routing, session management, and agent activation.
|
|
311
346
|
|
|
312
347
|
## Constitution
|
|
313
|
-
Governance rules are in \`chati.dev/constitution.md\` (
|
|
348
|
+
Governance rules are in \`chati.dev/constitution.md\` (19 Articles).
|
|
349
|
+
|
|
350
|
+
## Pipeline
|
|
351
|
+
\`\`\`
|
|
352
|
+
DISCOVER: WU -> Brief
|
|
353
|
+
PLAN: Detail -> Architect -> UX -> Phases -> Tasks -> QA-Planning
|
|
354
|
+
BUILD: Dev -> QA-Implementation
|
|
355
|
+
DEPLOY: DevOps
|
|
356
|
+
\`\`\`
|
|
314
357
|
|
|
315
358
|
## Agents
|
|
316
359
|
- DISCOVER: chati.dev/agents/discover/ (3 agents)
|
|
@@ -319,11 +362,6 @@ Governance rules are in \`chati.dev/constitution.md\` (17 Articles).
|
|
|
319
362
|
- BUILD: chati.dev/agents/build/ (1 agent)
|
|
320
363
|
- DEPLOY: chati.dev/agents/deploy/ (1 agent)
|
|
321
364
|
`;
|
|
322
|
-
if (config.rulesFile) {
|
|
323
|
-
createDir(dirname(join(targetDir, config.rulesFile)));
|
|
324
|
-
writeFileSync(join(targetDir, config.rulesFile), rulesContent, 'utf-8');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
365
|
}
|
|
328
366
|
|
|
329
367
|
/**
|
|
@@ -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', 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,70 @@ Type \`/chati\` to start.
|
|
|
81
146
|
`;
|
|
82
147
|
}
|
|
83
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Generate Gemini CLI TOML command (.gemini/commands/chati.toml)
|
|
151
|
+
*
|
|
152
|
+
* Gemini CLI custom commands use TOML format with `description` and `prompt` fields.
|
|
153
|
+
* Supports {{args}} for user arguments and @{file} for file injection.
|
|
154
|
+
*/
|
|
155
|
+
export function generateGeminiRouter() {
|
|
156
|
+
return `description = "Activate Chati.dev orchestrator"
|
|
157
|
+
prompt = """
|
|
158
|
+
CRITICAL — Language Override:
|
|
159
|
+
Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
|
|
160
|
+
ALL responses MUST be in this language (en, pt, es, fr).
|
|
161
|
+
If session.yaml does not exist or has no language field, default to English.
|
|
162
|
+
|
|
163
|
+
Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
|
|
164
|
+
|
|
165
|
+
Context to load:
|
|
166
|
+
- \`.chati/session.yaml\` (session state — includes language)
|
|
167
|
+
- \`chati.dev/artifacts/handoffs/\` (latest handoff)
|
|
168
|
+
- \`chati.dev/config.yaml\` (version info)
|
|
169
|
+
|
|
170
|
+
User input: {{args}}
|
|
171
|
+
"""
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate GitHub Copilot agent file (.github/agents/chati.md)
|
|
177
|
+
*
|
|
178
|
+
* Copilot CLI uses .github/agents/*.md for custom agents.
|
|
179
|
+
* The user invokes with @chati in the Copilot CLI.
|
|
180
|
+
*/
|
|
181
|
+
export function generateCopilotAgent() {
|
|
182
|
+
return `# Chati.dev Orchestrator
|
|
183
|
+
|
|
184
|
+
You are the Chati.dev orchestrator agent for GitHub Copilot CLI.
|
|
185
|
+
|
|
186
|
+
## CRITICAL — Language Override
|
|
187
|
+
|
|
188
|
+
Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
|
|
189
|
+
ALL responses MUST be in this language. This overrides any global setting.
|
|
190
|
+
|
|
191
|
+
| Value | Language |
|
|
192
|
+
|-------|----------|
|
|
193
|
+
| \`en\` | English |
|
|
194
|
+
| \`pt\` | Portugues |
|
|
195
|
+
| \`es\` | Espanol |
|
|
196
|
+
| \`fr\` | Francais |
|
|
197
|
+
|
|
198
|
+
If session.yaml does not exist or has no language field, default to English.
|
|
199
|
+
|
|
200
|
+
## Load
|
|
201
|
+
|
|
202
|
+
Read and execute the full orchestrator at \`chati.dev/orchestrator/chati.md\`.
|
|
203
|
+
|
|
204
|
+
Pass through all context: session state, handoffs, artifacts, and user input.
|
|
205
|
+
|
|
206
|
+
**Context to pass:**
|
|
207
|
+
- \`.chati/session.yaml\` (session state — includes language)
|
|
208
|
+
- \`chati.dev/artifacts/handoffs/\` (latest handoff)
|
|
209
|
+
- \`chati.dev/config.yaml\` (version info)
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
84
213
|
/**
|
|
85
214
|
* Generate CLAUDE.local.md content (runtime state — auto-gitignored, never committed)
|
|
86
215
|
*/
|
|
@@ -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 >=
|
|
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 ===
|
|
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 ===
|
|
121
|
+
results.templates.pass = templateCount === 6;
|
|
120
122
|
results.total += 1;
|
|
121
123
|
if (results.templates.pass) results.passed += 1;
|
|
122
124
|
|