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.
Files changed (95) hide show
  1. package/README.md +95 -24
  2. package/bin/chati.js +1 -1
  3. package/framework/agents/build/dev.md +1 -0
  4. package/framework/agents/deploy/devops.md +1 -0
  5. package/framework/agents/discover/brief.md +1 -0
  6. package/framework/agents/discover/brownfield-wu.md +1 -0
  7. package/framework/agents/discover/greenfield-wu.md +1 -0
  8. package/framework/agents/plan/architect.md +1 -0
  9. package/framework/agents/plan/detail.md +1 -0
  10. package/framework/agents/plan/phases.md +3 -2
  11. package/framework/agents/plan/tasks.md +1 -0
  12. package/framework/agents/plan/ux.md +1 -0
  13. package/framework/agents/quality/qa-implementation.md +1 -0
  14. package/framework/agents/quality/qa-planning.md +1 -0
  15. package/framework/config.yaml +21 -2
  16. package/framework/constitution.md +67 -2
  17. package/framework/context/governance.md +16 -3
  18. package/framework/context/root.md +3 -3
  19. package/framework/data/entity-registry.yaml +1 -1
  20. package/framework/domains/agents/brownfield-wu.yaml +4 -0
  21. package/framework/domains/agents/detail.yaml +1 -1
  22. package/framework/domains/agents/dev.yaml +4 -0
  23. package/framework/domains/agents/orchestrator.yaml +8 -0
  24. package/framework/domains/agents/phases.yaml +2 -2
  25. package/framework/domains/agents/tasks.yaml +2 -2
  26. package/framework/domains/constitution.yaml +30 -2
  27. package/framework/domains/global.yaml +20 -0
  28. package/framework/hooks/model-governance.js +17 -15
  29. package/framework/hooks/read-protection.js +1 -0
  30. package/framework/i18n/en.yaml +11 -4
  31. package/framework/i18n/es.yaml +11 -4
  32. package/framework/i18n/fr.yaml +11 -4
  33. package/framework/i18n/pt.yaml +11 -4
  34. package/framework/intelligence/context-engine.md +31 -2
  35. package/framework/intelligence/decision-engine.md +1 -1
  36. package/framework/intelligence/memory-layer.md +17 -0
  37. package/framework/orchestrator/chati.md +94 -14
  38. package/framework/patterns/elicitation-library.yaml +2 -2
  39. package/framework/schemas/config.schema.json +44 -0
  40. package/framework/schemas/session.schema.json +27 -0
  41. package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
  42. package/framework/tasks/brownfield-wu-migration-plan.md +4 -4
  43. package/framework/tasks/brownfield-wu-risk-assess.md +2 -2
  44. package/framework/tasks/detail-expand-prd.md +2 -2
  45. package/framework/tasks/greenfield-wu-report.md +1 -1
  46. package/framework/tasks/orchestrator-handoff.md +2 -2
  47. package/framework/tasks/orchestrator-resume.md +1 -1
  48. package/framework/tasks/qa-impl-performance-test.md +2 -2
  49. package/framework/tasks/qa-impl-sast-scan.md +6 -6
  50. package/framework/tasks/qa-planning-consolidate.md +1 -1
  51. package/framework/tasks/qa-planning-coverage-plan.md +9 -9
  52. package/framework/tasks/qa-planning-gate-define.md +6 -6
  53. package/framework/tasks/qa-planning-risk-matrix.md +1 -1
  54. package/framework/tasks/tasks-consolidate.md +5 -5
  55. package/package.json +5 -1
  56. package/scripts/changelog-generator.js +2 -2
  57. package/scripts/ide-sync.js +9 -4
  58. package/scripts/pr-review.js +5 -5
  59. package/scripts/semantic-lint.js +3 -3
  60. package/src/autonomy/build-loop.js +194 -0
  61. package/src/autonomy/build-state.js +269 -0
  62. package/src/autonomy/execution-profile.js +151 -0
  63. package/src/config/context-file-generator.js +203 -0
  64. package/src/config/ide-configs.js +6 -6
  65. package/src/gates/g2-qa-planning.js +4 -2
  66. package/src/gates/g4-qa-implementation.js +5 -2
  67. package/src/gates/gate-base.js +33 -1
  68. package/src/health/engine.js +245 -0
  69. package/src/installer/core.js +54 -16
  70. package/src/installer/templates.js +131 -2
  71. package/src/installer/validator.js +5 -3
  72. package/src/intelligence/file-tracker.js +117 -0
  73. package/src/intelligence/registry-manager.js +2 -2
  74. package/src/intelligence/timeline.js +144 -0
  75. package/src/memory/gotchas-auto-capture.js +253 -0
  76. package/src/preview/detector.js +1 -1
  77. package/src/terminal/adapters/claude-adapter.js +33 -0
  78. package/src/terminal/adapters/codex-adapter.js +31 -0
  79. package/src/terminal/adapters/copilot-adapter.js +28 -0
  80. package/src/terminal/adapters/gemini-adapter.js +28 -0
  81. package/src/terminal/adapters/index.js +8 -0
  82. package/src/terminal/cli-registry.js +190 -0
  83. package/src/terminal/prompt-builder.js +9 -20
  84. package/src/terminal/run-agent.js +2 -1
  85. package/src/terminal/spawner.js +44 -14
  86. package/src/terminal/wave-analyzer.js +143 -0
  87. package/src/utils/config-parser.js +97 -0
  88. package/src/wizard/feedback.js +1 -1
  89. package/src/wizard/i18n.js +11 -4
  90. package/src/wizard/index.js +22 -10
  91. package/src/wizard/questions.js +50 -2
  92. package/framework/manifest.json +0 -5
  93. package/framework/manifest.sig +0 -1
  94. /package/assets/{logo - co/314/201pia.png" → logo - c/303/263pia.png"} +0 -0
  95. /package/assets/{logo - co/314/201pia.svg" → logo - c/303/263pia.svg"} +0 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @fileoverview Build state manager with checkpoint-based persistence.
3
+ *
4
+ * Manages the state of autonomous build execution, including
5
+ * checkpoints saved after each task completion, abandoned build
6
+ * detection, and attempt logging.
7
+ *
8
+ * Constitution Article XVII — Execution Mode Governance.
9
+ */
10
+
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** Maximum iterations per individual task before giving up */
19
+ export const MAX_ITERATIONS_PER_TASK = 10;
20
+
21
+ /** Global timeout for an autonomous build session (30 minutes) */
22
+ export const GLOBAL_TIMEOUT_MS = 30 * 60 * 1000;
23
+
24
+ /** Time after which a build is considered abandoned (1 hour) */
25
+ export const ABANDONED_THRESHOLD_MS = 60 * 60 * 1000;
26
+
27
+ /**
28
+ * Build status enum.
29
+ * @enum {string}
30
+ */
31
+ export const BuildStatus = {
32
+ PENDING: 'pending',
33
+ IN_PROGRESS: 'in_progress',
34
+ PAUSED: 'paused',
35
+ ABANDONED: 'abandoned',
36
+ FAILED: 'failed',
37
+ COMPLETED: 'completed',
38
+ };
39
+
40
+ /**
41
+ * Task checkpoint status.
42
+ * @enum {string}
43
+ */
44
+ export const CheckpointStatus = {
45
+ PENDING: 'pending',
46
+ IN_PROGRESS: 'in_progress',
47
+ COMPLETED: 'completed',
48
+ FAILED: 'failed',
49
+ SKIPPED: 'skipped',
50
+ };
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // State Schema
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * @typedef {object} TaskCheckpoint
58
+ * @property {string} taskId
59
+ * @property {string} status - CheckpointStatus
60
+ * @property {number} attempts - Number of execution attempts
61
+ * @property {string|null} lastAttempt - ISO timestamp of last attempt
62
+ * @property {string|null} output - Last output summary
63
+ * @property {string|null} error - Last error message (if failed)
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} BuildState
68
+ * @property {string} sessionId - Build session identifier
69
+ * @property {string} status - BuildStatus
70
+ * @property {TaskCheckpoint[]} checkpoints - Per-task checkpoints
71
+ * @property {string} startedAt - ISO timestamp
72
+ * @property {string|null} lastCheckpoint - ISO timestamp of last checkpoint
73
+ * @property {string|null} completedAt - ISO timestamp of completion
74
+ * @property {number} totalAttempts - Total execution attempts across all tasks
75
+ */
76
+
77
+ const BUILD_STATE_FILE = '.chati/build-state.json';
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // State Management
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /**
84
+ * Initialize a new build state.
85
+ *
86
+ * @param {string[]} taskIds - Array of task IDs to execute
87
+ * @returns {BuildState}
88
+ */
89
+ export function createBuildState(taskIds) {
90
+ return {
91
+ sessionId: `build-${Date.now()}`,
92
+ status: BuildStatus.PENDING,
93
+ checkpoints: taskIds.map((taskId) => ({
94
+ taskId,
95
+ status: CheckpointStatus.PENDING,
96
+ attempts: 0,
97
+ lastAttempt: null,
98
+ output: null,
99
+ error: null,
100
+ })),
101
+ startedAt: new Date().toISOString(),
102
+ lastCheckpoint: null,
103
+ completedAt: null,
104
+ totalAttempts: 0,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Load build state from disk.
110
+ *
111
+ * @param {string} projectDir
112
+ * @returns {BuildState|null}
113
+ */
114
+ export function loadBuildState(projectDir) {
115
+ const statePath = join(projectDir, BUILD_STATE_FILE);
116
+ if (!existsSync(statePath)) return null;
117
+ try {
118
+ const state = JSON.parse(readFileSync(statePath, 'utf-8'));
119
+
120
+ // Check for abandoned builds
121
+ if (state.status === BuildStatus.IN_PROGRESS && state.lastCheckpoint) {
122
+ const elapsed = Date.now() - new Date(state.lastCheckpoint).getTime();
123
+ if (elapsed > ABANDONED_THRESHOLD_MS) {
124
+ state.status = BuildStatus.ABANDONED;
125
+ }
126
+ }
127
+
128
+ return state;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Save build state to disk.
136
+ *
137
+ * @param {string} projectDir
138
+ * @param {BuildState} state
139
+ */
140
+ export function saveBuildState(projectDir, state) {
141
+ const statePath = join(projectDir, BUILD_STATE_FILE);
142
+ mkdirSync(dirname(statePath), { recursive: true });
143
+ writeFileSync(statePath, JSON.stringify(state, null, 2));
144
+ }
145
+
146
+ /**
147
+ * Update a task checkpoint.
148
+ *
149
+ * @param {BuildState} state
150
+ * @param {string} taskId
151
+ * @param {Partial<TaskCheckpoint>} update
152
+ * @returns {BuildState}
153
+ */
154
+ export function updateCheckpoint(state, taskId, update) {
155
+ const checkpoint = state.checkpoints.find((c) => c.taskId === taskId);
156
+ if (!checkpoint) {
157
+ throw new Error(`Task "${taskId}" not found in build state`);
158
+ }
159
+
160
+ Object.assign(checkpoint, update);
161
+ state.lastCheckpoint = new Date().toISOString();
162
+
163
+ if (update.attempts !== undefined) {
164
+ state.totalAttempts = state.checkpoints.reduce((sum, c) => sum + c.attempts, 0);
165
+ }
166
+
167
+ return state;
168
+ }
169
+
170
+ /**
171
+ * Mark the build as started.
172
+ *
173
+ * @param {BuildState} state
174
+ * @returns {BuildState}
175
+ */
176
+ export function startBuild(state) {
177
+ state.status = BuildStatus.IN_PROGRESS;
178
+ state.startedAt = new Date().toISOString();
179
+ return state;
180
+ }
181
+
182
+ /**
183
+ * Mark the build as completed.
184
+ *
185
+ * @param {BuildState} state
186
+ * @returns {BuildState}
187
+ */
188
+ export function completeBuild(state) {
189
+ state.status = BuildStatus.COMPLETED;
190
+ state.completedAt = new Date().toISOString();
191
+ return state;
192
+ }
193
+
194
+ /**
195
+ * Mark the build as failed.
196
+ *
197
+ * @param {BuildState} state
198
+ * @param {string} [reason]
199
+ * @returns {BuildState}
200
+ */
201
+ export function failBuild(state, _reason) {
202
+ state.status = BuildStatus.FAILED;
203
+ state.completedAt = new Date().toISOString();
204
+ return state;
205
+ }
206
+
207
+ /**
208
+ * Get the next pending task checkpoint.
209
+ *
210
+ * @param {BuildState} state
211
+ * @returns {TaskCheckpoint|null}
212
+ */
213
+ export function getNextPendingTask(state) {
214
+ return state.checkpoints.find(
215
+ (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
216
+ ) || null;
217
+ }
218
+
219
+ /**
220
+ * Check if a task has exceeded max iterations.
221
+ *
222
+ * @param {TaskCheckpoint} checkpoint
223
+ * @returns {boolean}
224
+ */
225
+ export function isTaskExhausted(checkpoint) {
226
+ return checkpoint.attempts >= MAX_ITERATIONS_PER_TASK;
227
+ }
228
+
229
+ /**
230
+ * Check if the global timeout has been exceeded.
231
+ *
232
+ * @param {BuildState} state
233
+ * @returns {boolean}
234
+ */
235
+ export function isTimedOut(state) {
236
+ const elapsed = Date.now() - new Date(state.startedAt).getTime();
237
+ return elapsed > GLOBAL_TIMEOUT_MS;
238
+ }
239
+
240
+ /**
241
+ * Get build progress summary.
242
+ *
243
+ * @param {BuildState} state
244
+ * @returns {{ total: number, completed: number, failed: number, pending: number, progress: number }}
245
+ */
246
+ export function getProgress(state) {
247
+ const total = state.checkpoints.length;
248
+ const completed = state.checkpoints.filter((c) => c.status === CheckpointStatus.COMPLETED).length;
249
+ const failed = state.checkpoints.filter((c) => c.status === CheckpointStatus.FAILED).length;
250
+ const pending = state.checkpoints.filter(
251
+ (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
252
+ ).length;
253
+ const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
254
+
255
+ return { total, completed, failed, pending, progress };
256
+ }
257
+
258
+ /**
259
+ * Clear build state (after completion or manual reset).
260
+ *
261
+ * @param {string} projectDir
262
+ */
263
+ export function clearBuildState(projectDir) {
264
+ const statePath = join(projectDir, BUILD_STATE_FILE);
265
+ if (existsSync(statePath)) {
266
+ const { unlinkSync } = require('fs');
267
+ unlinkSync(statePath);
268
+ }
269
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @fileoverview Execution profile manager.
3
+ *
4
+ * Manages 3 execution profiles that govern confirmation requirements:
5
+ * - explore: read-only, no writes
6
+ * - guided: confirm before writes (default)
7
+ * - autonomous: full autonomy with quality gates
8
+ *
9
+ * Profiles are orthogonal to mode governance (Article XI).
10
+ * Modes control WHERE; profiles control WHETHER.
11
+ *
12
+ * Constitution Article XVIII — Execution Profile Governance.
13
+ */
14
+
15
+ import { existsSync, readFileSync } from 'fs';
16
+ import { join } from 'path';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Profile Definitions
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Execution profile enum.
24
+ * @enum {string}
25
+ */
26
+ export const Profile = {
27
+ /** Read-only. Agents analyze and suggest but perform no writes. */
28
+ EXPLORE: 'explore',
29
+ /** Default. Agents propose actions and wait for user confirmation. */
30
+ GUIDED: 'guided',
31
+ /** Full autonomy. Agents execute without confirmation when gates pass. */
32
+ AUTONOMOUS: 'autonomous',
33
+ };
34
+
35
+ /** Minimum gate score required to activate autonomous profile */
36
+ export const AUTONOMOUS_GATE_THRESHOLD = 90;
37
+
38
+ /**
39
+ * Operations that ALWAYS require confirmation regardless of profile.
40
+ * Constitution Article XVIII, point 4.
41
+ */
42
+ export const ALWAYS_CONFIRM_OPERATIONS = [
43
+ 'file_delete',
44
+ 'database_drop',
45
+ 'force_push',
46
+ 'deploy_production',
47
+ 'deviation_activate',
48
+ 'backward_transition',
49
+ ];
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Profile Management
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Get the current execution profile from session state.
57
+ *
58
+ * @param {string} projectDir
59
+ * @returns {string} Current profile (defaults to 'guided')
60
+ */
61
+ export function getCurrentProfile(projectDir) {
62
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
63
+ if (!existsSync(sessionPath)) return Profile.GUIDED;
64
+
65
+ const raw = readFileSync(sessionPath, 'utf-8');
66
+ const match = raw.match(/^\s*execution_profile:\s*(.+)$/m);
67
+ const profile = match ? match[1].trim().replace(/^["']|["']$/g, '') : Profile.GUIDED;
68
+
69
+ return Object.values(Profile).includes(profile) ? profile : Profile.GUIDED;
70
+ }
71
+
72
+ /**
73
+ * Check if a write operation is allowed under the current profile.
74
+ *
75
+ * @param {string} profile - Current execution profile
76
+ * @param {string} operation - Operation type (e.g., 'file_write', 'file_delete')
77
+ * @returns {{ allowed: boolean, reason: string }}
78
+ */
79
+ export function isWriteAllowed(profile, operation) {
80
+ // Explore profile blocks ALL writes
81
+ if (profile === Profile.EXPLORE) {
82
+ return { allowed: false, reason: '[Article XVIII] Explore profile: read-only mode. No writes permitted.' };
83
+ }
84
+
85
+ // Always-confirm operations need user approval regardless of profile
86
+ if (ALWAYS_CONFIRM_OPERATIONS.includes(operation)) {
87
+ return { allowed: false, reason: `[Article XVIII] Operation "${operation}" always requires user confirmation.` };
88
+ }
89
+
90
+ // Guided profile: allowed but requires confirmation (caller handles confirmation)
91
+ if (profile === Profile.GUIDED) {
92
+ return { allowed: true, reason: 'Guided profile: confirmation required.' };
93
+ }
94
+
95
+ // Autonomous profile: allowed without confirmation
96
+ if (profile === Profile.AUTONOMOUS) {
97
+ return { allowed: true, reason: 'Autonomous profile: proceeding without confirmation.' };
98
+ }
99
+
100
+ return { allowed: true, reason: 'Default: allowed.' };
101
+ }
102
+
103
+ /**
104
+ * Validate whether autonomous profile can be activated.
105
+ *
106
+ * @param {string} projectDir
107
+ * @returns {{ canActivate: boolean, reason: string, currentScore: number }}
108
+ */
109
+ export function canActivateAutonomous(projectDir) {
110
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
111
+ if (!existsSync(sessionPath)) {
112
+ return { canActivate: false, reason: 'No active session', currentScore: 0 };
113
+ }
114
+
115
+ const raw = readFileSync(sessionPath, 'utf-8');
116
+
117
+ // Look for most recent QA gate score
118
+ const scoreMatches = raw.match(/score:\s*([\d.]+)/g);
119
+ if (!scoreMatches || scoreMatches.length === 0) {
120
+ return { canActivate: false, reason: 'No gate scores found. Run QA gates first.', currentScore: 0 };
121
+ }
122
+
123
+ const scores = scoreMatches.map((m) => parseFloat(m.replace('score:', '').trim()));
124
+ const latestScore = scores[scores.length - 1];
125
+
126
+ if (latestScore < AUTONOMOUS_GATE_THRESHOLD) {
127
+ return {
128
+ canActivate: false,
129
+ reason: `Gate score ${latestScore}% < required ${AUTONOMOUS_GATE_THRESHOLD}%`,
130
+ currentScore: latestScore,
131
+ };
132
+ }
133
+
134
+ return { canActivate: true, reason: 'Gate scores meet threshold', currentScore: latestScore };
135
+ }
136
+
137
+ /**
138
+ * Check if autonomous profile should be downgraded due to quality drop.
139
+ *
140
+ * @param {number} currentScore - Current quality score
141
+ * @returns {{ shouldDowngrade: boolean, reason: string }}
142
+ */
143
+ export function shouldDowngradeAutonomous(currentScore) {
144
+ if (currentScore < AUTONOMOUS_GATE_THRESHOLD) {
145
+ return {
146
+ shouldDowngrade: true,
147
+ reason: `Quality dropped to ${currentScore}% (< ${AUTONOMOUS_GATE_THRESHOLD}%). Auto-downgrading to guided profile.`,
148
+ };
149
+ }
150
+ return { shouldDowngrade: false, reason: '' };
151
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @fileoverview Context file generator for multi-CLI support.
3
+ *
4
+ * When alternative CLI providers are enabled, generates provider-specific
5
+ * context files (GEMINI.md, AGENTS.md) derived from CLAUDE.md content.
6
+ * Constitution Article XIX — context file generation is automatic.
7
+ *
8
+ * Provider conventions (verified Feb 2026):
9
+ * - Gemini CLI: auto-loads GEMINI.md, uses .gemini/commands/*.toml, no rules/
10
+ * - Codex CLI: auto-loads AGENTS.md, no commands/ or rules/ concept
11
+ * - Copilot CLI: auto-loads AGENTS.md + CLAUDE.md + GEMINI.md natively (no COPILOT.md needed)
12
+ */
13
+
14
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
15
+ import { join } from 'path';
16
+ import { getEnabledNonClaudeProviders } from '../utils/config-parser.js';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Replacement Maps
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Text replacements for Gemini CLI context file.
24
+ * Transforms Claude Code-specific references into Gemini CLI equivalents.
25
+ * Note: Gemini has no rules/ directory — references point to chati.dev/context/.
26
+ */
27
+ const GEMINI_REPLACEMENTS = [
28
+ ['Claude Code', 'Gemini CLI'],
29
+ ['claude code', 'Gemini CLI'],
30
+ ['CLAUDE.md', 'GEMINI.md'],
31
+ ['Claude.md', 'GEMINI.md'],
32
+ ['.claude/commands/', '.gemini/commands/'],
33
+ ['.claude/rules/', 'chati.dev/context/'],
34
+ ['.claude/mcp.json', '.gemini/settings.json'],
35
+ ['claude --print', 'gemini --prompt'],
36
+ ['claude -p', 'gemini --prompt'],
37
+ ['CLAUDE.local.md', 'GEMINI.local.md'],
38
+ ];
39
+
40
+ /**
41
+ * Sections to strip from Codex CLI context file.
42
+ * Codex does not support hooks, so hook-related content is removed.
43
+ */
44
+ const CODEX_STRIP_PATTERNS = [
45
+ /## (?:Hooks?|Hook System)[\s\S]*?(?=\n## |\n---|\n$)/gi,
46
+ /- \*\*Hooks?\*\*:.*\n/gi,
47
+ /hook[s]? \(.*?\)/gi,
48
+ ];
49
+
50
+ /**
51
+ * Text replacements for Codex CLI context file.
52
+ * Note: Codex has no commands/ or rules/ directories — references point to chati.dev/.
53
+ */
54
+ const CODEX_REPLACEMENTS = [
55
+ ['Claude Code', 'Codex CLI'],
56
+ ['claude code', 'Codex CLI'],
57
+ ['CLAUDE.md', 'AGENTS.md'],
58
+ ['Claude.md', 'AGENTS.md'],
59
+ ['.claude/commands/', 'chati.dev/orchestrator/'],
60
+ ['.claude/rules/', 'chati.dev/context/'],
61
+ ['.claude/mcp.json', '.codex/config.toml'],
62
+ ['claude --print', 'codex exec'],
63
+ ['claude -p', 'codex exec'],
64
+ ['CLAUDE.local.md', 'AGENTS.local.md'],
65
+ ];
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Generators
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /**
72
+ * Generate GEMINI.md content from CLAUDE.md content.
73
+ *
74
+ * Adapts the content by replacing Claude Code-specific references with
75
+ * Gemini CLI equivalents. Preserves structure and formatting.
76
+ *
77
+ * @param {string} content - Raw CLAUDE.md content
78
+ * @returns {string} Adapted content for GEMINI.md
79
+ */
80
+ export function generateGeminiMd(content) {
81
+ let result = content;
82
+
83
+ for (const [search, replace] of GEMINI_REPLACEMENTS) {
84
+ result = result.replaceAll(search, replace);
85
+ }
86
+
87
+ // Prepend a header noting this file is auto-generated
88
+ const header = [
89
+ '<!-- Auto-generated by chati.dev from CLAUDE.md — do not edit manually -->',
90
+ '',
91
+ ].join('\n');
92
+
93
+ return header + result;
94
+ }
95
+
96
+ /**
97
+ * Generate AGENTS.md content from CLAUDE.md content.
98
+ *
99
+ * Simplifies the content for Codex CLI: strips hook references,
100
+ * replaces CLI-specific paths, and produces a leaner context file
101
+ * focused on code execution.
102
+ *
103
+ * @param {string} content - Raw CLAUDE.md content
104
+ * @returns {string} Adapted content for AGENTS.md
105
+ */
106
+ export function generateAgentsMd(content) {
107
+ let result = content;
108
+
109
+ // Strip hook-related sections (Codex has no hooks support)
110
+ for (const pattern of CODEX_STRIP_PATTERNS) {
111
+ result = result.replace(pattern, '');
112
+ }
113
+
114
+ // Apply text replacements
115
+ for (const [search, replace] of CODEX_REPLACEMENTS) {
116
+ result = result.replaceAll(search, replace);
117
+ }
118
+
119
+ // Clean up any double blank lines left by stripping
120
+ result = result.replace(/\n{3,}/g, '\n\n');
121
+
122
+ // Prepend a header noting this file is auto-generated
123
+ const header = [
124
+ '<!-- Auto-generated by chati.dev from CLAUDE.md — do not edit manually -->',
125
+ '',
126
+ ].join('\n');
127
+
128
+ return header + result;
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Orchestrator
133
+ // ---------------------------------------------------------------------------
134
+
135
+ /**
136
+ * Generate context files for all enabled alternative providers.
137
+ *
138
+ * Reads config.yaml to determine which providers are enabled, reads
139
+ * CLAUDE.md from the project root, and writes the appropriate context
140
+ * files (GEMINI.md, AGENTS.md) when their providers are active.
141
+ *
142
+ * Copilot CLI does not need its own context file — it natively reads
143
+ * AGENTS.md, CLAUDE.md, and GEMINI.md.
144
+ *
145
+ * @param {string} projectDir - Project root directory
146
+ * @returns {{ generated: string[], skipped: string[], warning: string|null }}
147
+ */
148
+ export function generateContextFiles(projectDir) {
149
+ const result = { generated: [], skipped: [], warning: null };
150
+
151
+ // Read CLAUDE.md
152
+ const claudeMdPath = join(projectDir, 'CLAUDE.md');
153
+ if (!existsSync(claudeMdPath)) {
154
+ result.warning = 'CLAUDE.md not found — skipping context file generation';
155
+ return result;
156
+ }
157
+
158
+ const claudeContent = readFileSync(claudeMdPath, 'utf-8');
159
+
160
+ // Determine enabled providers from config.yaml
161
+ const enabledProviders = resolveEnabledProviders(projectDir);
162
+
163
+ // Provider-to-generator mapping
164
+ // Note: Copilot is intentionally excluded — it reads AGENTS.md + CLAUDE.md + GEMINI.md natively
165
+ const generators = {
166
+ gemini: {
167
+ filename: 'GEMINI.md',
168
+ generate: generateGeminiMd,
169
+ },
170
+ codex: {
171
+ filename: 'AGENTS.md',
172
+ generate: generateAgentsMd,
173
+ },
174
+ };
175
+
176
+ for (const [provider, config] of Object.entries(generators)) {
177
+ if (enabledProviders.includes(provider)) {
178
+ const outputPath = join(projectDir, config.filename);
179
+ const content = config.generate(claudeContent);
180
+ writeFileSync(outputPath, content, 'utf-8');
181
+ result.generated.push(config.filename);
182
+ } else {
183
+ result.skipped.push(config.filename);
184
+ }
185
+ }
186
+
187
+ return result;
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Helpers
192
+ // ---------------------------------------------------------------------------
193
+
194
+ /**
195
+ * Resolve which alternative providers are enabled from config.yaml.
196
+ * Delegates to shared config parser utility.
197
+ *
198
+ * @param {string} projectDir - Project root directory
199
+ * @returns {string[]} List of enabled non-Claude provider names
200
+ */
201
+ function resolveEnabledProviders(projectDir) {
202
+ return getEnabledNonClaudeProviders(projectDir);
203
+ }
@@ -41,12 +41,12 @@ export const IDE_CONFIGS = {
41
41
  },
42
42
  'gemini-cli': {
43
43
  name: 'Gemini CLI',
44
- description: 'Google AI for development',
44
+ description: 'Google AI terminal agent',
45
45
  recommended: false,
46
- configPath: '.gemini/agents/',
47
- rulesFile: '.gemini/instructions.md',
48
- mcpConfigFile: null,
49
- formatNotes: 'Gemini agent format',
46
+ configPath: '.gemini/commands/',
47
+ rulesFile: null,
48
+ mcpConfigFile: '.gemini/settings.json',
49
+ formatNotes: 'TOML command format',
50
50
  },
51
51
  'github-copilot': {
52
52
  name: 'GitHub Copilot',
@@ -55,7 +55,7 @@ export const IDE_CONFIGS = {
55
55
  configPath: '.github/agents/',
56
56
  rulesFile: '.github/copilot-instructions.md',
57
57
  mcpConfigFile: null,
58
- formatNotes: 'GitHub Copilot format',
58
+ formatNotes: 'GitHub Copilot agent format',
59
59
  },
60
60
  };
61
61
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { existsSync, readdirSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { GateBase } from './gate-base.js';
10
+ import { GateBase, determineVerdict } from './gate-base.js';
11
11
  import { loadSession } from '../orchestrator/session-manager.js';
12
12
  import { loadHandoff } from '../tasks/handoff.js';
13
13
 
@@ -148,6 +148,8 @@ export class QAPlanningGate extends GateBase {
148
148
  ? Math.round((criteriaResults.length / allCriteria.length) * 100)
149
149
  : 0;
150
150
 
151
- return { score, criteriaResults, allCriteria, warnings };
151
+ const verdict = determineVerdict(score, 95);
152
+
153
+ return { score, criteriaResults, allCriteria, warnings, verdict };
152
154
  }
153
155
  }
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { existsSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
- import { GateBase } from './gate-base.js';
17
+ import { GateBase, determineVerdict } from './gate-base.js';
18
18
  import { loadSession } from '../orchestrator/session-manager.js';
19
19
  import { loadHandoff } from '../tasks/handoff.js';
20
20
 
@@ -202,6 +202,9 @@ export class QAImplementationGate extends GateBase {
202
202
  ? Math.round((criteriaResults.length / allCriteria.length) * 100)
203
203
  : 0;
204
204
 
205
- return { score, criteriaResults, allCriteria, warnings };
205
+ const hasCriticalBlocker = !evidence.noCriticalBugs;
206
+ const verdict = determineVerdict(score, 95, hasCriticalBlocker);
207
+
208
+ return { score, criteriaResults, allCriteria, warnings, verdict };
206
209
  }
207
210
  }