chati-dev 3.2.5 → 3.3.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 (98) hide show
  1. package/LICENSE +96 -0
  2. package/bin/chati.js +46 -0
  3. package/framework/agents/build/dev.md +122 -1
  4. package/framework/agents/deploy/devops.md +128 -3
  5. package/framework/agents/discover/brief.md +77 -15
  6. package/framework/agents/discover/brownfield-wu.md +2 -2
  7. package/framework/agents/discover/greenfield-wu.md +3 -3
  8. package/framework/agents/plan/architect.md +2 -2
  9. package/framework/agents/plan/detail.md +3 -3
  10. package/framework/agents/plan/phases.md +127 -2
  11. package/framework/agents/plan/tasks.md +127 -2
  12. package/framework/agents/plan/ux.md +269 -22
  13. package/framework/agents/quality/qa-implementation.md +172 -8
  14. package/framework/agents/quality/qa-planning.md +147 -2
  15. package/framework/config.yaml +9 -5
  16. package/framework/constitution.md +7 -1
  17. package/framework/context/quality.md +1 -1
  18. package/framework/context/root.md +1 -1
  19. package/framework/hooks/constitution-guard.js +18 -2
  20. package/framework/hooks/mode-governance.js +3 -3
  21. package/framework/hooks/read-protection.js +10 -2
  22. package/framework/i18n/en.yaml +6 -0
  23. package/framework/i18n/es.yaml +6 -0
  24. package/framework/i18n/fr.yaml +6 -0
  25. package/framework/i18n/pt.yaml +6 -0
  26. package/framework/orchestrator/chati.md +102 -6
  27. package/framework/schemas/task.schema.json +1 -1
  28. package/framework/tasks/architect-dep-audit.md +128 -0
  29. package/framework/tasks/architect-stack-selection.md +28 -0
  30. package/framework/workflows/brownfield-fullstack.yaml +2 -2
  31. package/framework/workflows/brownfield-service.yaml +2 -2
  32. package/framework/workflows/brownfield-ui.yaml +2 -2
  33. package/framework/workflows/greenfield-fullstack.yaml +6 -2
  34. package/framework/workflows/quick-flow.yaml +7 -5
  35. package/framework/workflows/standard-flow.yaml +171 -0
  36. package/package.json +4 -2
  37. package/src/api/index.js +129 -0
  38. package/src/autonomy/build-loop.js +93 -6
  39. package/src/autonomy/build-state.js +20 -2
  40. package/src/autonomy/cause-analyzer.js +177 -0
  41. package/src/autonomy/escalation.js +214 -0
  42. package/src/autonomy/safety-net.js +23 -5
  43. package/src/autonomy/worktree-manager.js +245 -0
  44. package/src/config/agent-customizer.js +227 -0
  45. package/src/config/ide-configs.js +57 -27
  46. package/src/decision/analyzer.js +148 -0
  47. package/src/decision/registry-healer.js +38 -21
  48. package/src/extensions/loader.js +151 -0
  49. package/src/extensions/registry.js +134 -0
  50. package/src/gates/circuit-breaker.js +32 -0
  51. package/src/gates/g3-implementation.js +30 -4
  52. package/src/gates/g4-qa-implementation.js +34 -5
  53. package/src/gates/gate-base.js +9 -0
  54. package/src/health/auto-fix.js +216 -0
  55. package/src/installer/core.js +24 -11
  56. package/src/installer/provider-overlay.js +82 -0
  57. package/src/installer/templates.js +22 -10
  58. package/src/installer/transaction.js +3 -2
  59. package/src/installer/validator.js +74 -0
  60. package/src/intelligence/context-status.js +9 -5
  61. package/src/intelligence/document-sharder.js +221 -0
  62. package/src/intelligence/elicitation.js +265 -0
  63. package/src/intelligence/timeline.js +5 -0
  64. package/src/memory/gotchas.js +78 -2
  65. package/src/merger/semantic-merger.js +292 -0
  66. package/src/orchestrator/agent-selector.js +20 -0
  67. package/src/orchestrator/handoff-engine.js +77 -0
  68. package/src/orchestrator/index.js +0 -8
  69. package/src/orchestrator/intent-classifier.js +182 -0
  70. package/src/orchestrator/pipeline-manager.js +125 -1
  71. package/src/orchestrator/session-manager.js +164 -2
  72. package/src/quality/metrics-collector.js +283 -0
  73. package/src/quality/test-runner.js +368 -0
  74. package/src/telemetry/collector.js +83 -0
  75. package/src/telemetry/config.js +119 -0
  76. package/src/telemetry/index.js +11 -0
  77. package/src/telemetry/schema.js +104 -0
  78. package/src/telemetry/sender.js +60 -0
  79. package/src/terminal/cli-registry.js +7 -1
  80. package/src/terminal/cost-tracker.js +197 -0
  81. package/src/terminal/handoff-parser.js +61 -4
  82. package/src/terminal/prompt-builder.js +56 -18
  83. package/src/terminal/rate-limiter.js +172 -0
  84. package/src/terminal/run-agent.js +39 -0
  85. package/src/terminal/run-parallel.js +22 -1
  86. package/src/terminal/spawner.js +181 -3
  87. package/src/upgrade/migrator.js +2 -2
  88. package/src/utils/event-bus.js +126 -0
  89. package/src/utils/file-lock.js +291 -0
  90. package/src/utils/schema-validator.js +226 -0
  91. package/src/wizard/i18n.js +11 -0
  92. package/src/wizard/index.js +42 -20
  93. package/src/wizard/questions.js +200 -39
  94. package/src/autonomy/execution-profile.js +0 -151
  95. package/src/intelligence/file-tracker.js +0 -117
  96. package/src/memory/gotchas-auto-capture.js +0 -253
  97. package/src/orchestrator/pipeline-state.js +0 -223
  98. package/src/terminal/wave-analyzer.js +0 -143
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @fileoverview Git worktree isolation for build loop retries.
3
+ *
4
+ * Each retry attempt can optionally run in an isolated git worktree,
5
+ * preventing failed attempts from corrupting the main working directory.
6
+ *
7
+ * Constitution Article XVII — Execution Mode Governance.
8
+ */
9
+
10
+ /**
11
+ * @deprecated Not currently imported by any production module.
12
+ * Retained for potential future integration. Remove if still unused by v4.0.
13
+ */
14
+
15
+ import { execSync } from 'child_process';
16
+ import { existsSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';
17
+ import { join, resolve } from 'path';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Directory for worktrees inside .chati/ */
24
+ const WORKTREE_DIR = '.chati/worktrees';
25
+
26
+ /** Maximum age (ms) before a worktree is considered stale (24h). */
27
+ const MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Helpers
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * Execute a git command in a given directory and return trimmed stdout.
35
+ *
36
+ * @param {string} cmd - Git command (without the `git` prefix)
37
+ * @param {string} cwd - Working directory
38
+ * @returns {string} Trimmed stdout
39
+ */
40
+ function git(cmd, cwd) {
41
+ return execSync(`git ${cmd}`, {
42
+ cwd,
43
+ encoding: 'utf-8',
44
+ stdio: ['pipe', 'pipe', 'pipe'],
45
+ }).trim();
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Public API
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Check if a directory is inside a git repository.
54
+ *
55
+ * @param {string} dir - Directory path
56
+ * @returns {boolean}
57
+ */
58
+ export function isGitRepo(dir) {
59
+ try {
60
+ git('rev-parse --is-inside-work-tree', dir);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Create an isolated git worktree for a retry attempt.
69
+ *
70
+ * The worktree is placed at `.chati/worktrees/<taskId>-attempt-<N>`.
71
+ * A new branch is created from the current HEAD.
72
+ *
73
+ * @param {string} projectDir - Root project directory
74
+ * @param {string} taskId - Task identifier
75
+ * @param {number} attempt - Attempt number
76
+ * @returns {{ path: string, branch: string, cleanup: () => void }}
77
+ * @throws {Error} If not a git repo or worktree creation fails
78
+ */
79
+ export function createWorktree(projectDir, taskId, attempt) {
80
+ const absProject = resolve(projectDir);
81
+
82
+ if (!isGitRepo(absProject)) {
83
+ throw new Error(`Not a git repository: ${absProject}`);
84
+ }
85
+
86
+ const safeName = `${taskId}-attempt-${attempt}`.replace(/[^a-zA-Z0-9._-]/g, '_');
87
+ const worktreeBase = join(absProject, WORKTREE_DIR);
88
+ const worktreePath = join(worktreeBase, safeName);
89
+ const branch = `chati/${safeName}`;
90
+
91
+ // Ensure worktree base directory exists
92
+ mkdirSync(worktreeBase, { recursive: true });
93
+
94
+ // Clean up if a worktree already exists at this path
95
+ if (existsSync(worktreePath)) {
96
+ try {
97
+ git(`worktree remove "${worktreePath}" --force`, absProject);
98
+ } catch {
99
+ rmSync(worktreePath, { recursive: true, force: true });
100
+ }
101
+ }
102
+
103
+ // Delete branch if it exists from a previous run
104
+ try {
105
+ git(`branch -D "${branch}"`, absProject);
106
+ } catch {
107
+ // Branch doesn't exist — fine
108
+ }
109
+
110
+ // Create worktree with new branch from HEAD
111
+ git(`worktree add -b "${branch}" "${worktreePath}"`, absProject);
112
+
113
+ const cleanup = () => {
114
+ try {
115
+ git(`worktree remove "${worktreePath}" --force`, absProject);
116
+ } catch {
117
+ // Worktree already removed — ignore
118
+ }
119
+ try {
120
+ git(`branch -D "${branch}"`, absProject);
121
+ } catch {
122
+ // Branch already deleted — ignore
123
+ }
124
+ };
125
+
126
+ return { path: worktreePath, branch, cleanup };
127
+ }
128
+
129
+ /**
130
+ * Merge changes from a worktree branch back to the main branch.
131
+ *
132
+ * @param {string} projectDir - Root project directory
133
+ * @param {string} worktreeBranch - Worktree branch name
134
+ * @param {string} [mainBranch] - Target branch (auto-detected if omitted)
135
+ * @returns {{ success: boolean, conflicts: string[] }}
136
+ */
137
+ export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
138
+ const absProject = resolve(projectDir);
139
+
140
+ // Auto-detect main branch if not specified
141
+ if (!mainBranch) {
142
+ try {
143
+ mainBranch = git('rev-parse --abbrev-ref HEAD', absProject);
144
+ } catch {
145
+ mainBranch = 'main';
146
+ }
147
+ }
148
+
149
+ // Ensure we're on the main branch
150
+ try {
151
+ git(`checkout "${mainBranch}"`, absProject);
152
+ } catch {
153
+ return { success: false, conflicts: [`Failed to checkout ${mainBranch}`] };
154
+ }
155
+
156
+ // Attempt merge
157
+ try {
158
+ git(`merge "${worktreeBranch}" --no-edit`, absProject);
159
+ return { success: true, conflicts: [] };
160
+ } catch (err) {
161
+ // Parse conflicts from error output
162
+ const conflicts = [];
163
+ try {
164
+ const status = git('diff --name-only --diff-filter=U', absProject);
165
+ if (status) {
166
+ conflicts.push(...status.split('\n').filter(Boolean));
167
+ }
168
+ } catch {
169
+ conflicts.push('Unknown conflict — check git status');
170
+ }
171
+
172
+ // Abort the failed merge
173
+ try {
174
+ git('merge --abort', absProject);
175
+ } catch {
176
+ // Already aborted or not in merging state
177
+ }
178
+
179
+ return { success: false, conflicts };
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Clean up stale worktrees that exceed the maximum age.
185
+ *
186
+ * @param {string} projectDir - Root project directory
187
+ * @param {{ maxAge?: number }} [options={}]
188
+ * @returns {{ cleaned: string[], errors: string[] }}
189
+ */
190
+ export function cleanupWorktrees(projectDir, options = {}) {
191
+ const absProject = resolve(projectDir);
192
+ const maxAge = options.maxAge || MAX_WORKTREE_AGE_MS;
193
+ const worktreeBase = join(absProject, WORKTREE_DIR);
194
+ const cleaned = [];
195
+ const errors = [];
196
+
197
+ if (!existsSync(worktreeBase)) {
198
+ return { cleaned, errors };
199
+ }
200
+
201
+ const now = Date.now();
202
+ let entries;
203
+ try {
204
+ entries = readdirSync(worktreeBase);
205
+ } catch {
206
+ return { cleaned, errors: ['Failed to read worktree directory'] };
207
+ }
208
+
209
+ for (const entry of entries) {
210
+ const entryPath = join(worktreeBase, entry);
211
+
212
+ try {
213
+ const stat = statSync(entryPath);
214
+ if (!stat.isDirectory()) continue;
215
+
216
+ const age = now - stat.mtimeMs;
217
+ if (age > maxAge) {
218
+ try {
219
+ git(`worktree remove "${entryPath}" --force`, absProject);
220
+ } catch {
221
+ rmSync(entryPath, { recursive: true, force: true });
222
+ }
223
+
224
+ // Also remove the branch
225
+ const branch = `chati/${entry}`;
226
+ try {
227
+ git(`branch -D "${branch}"`, absProject);
228
+ } catch {
229
+ // Branch already deleted
230
+ }
231
+
232
+ cleaned.push(entry);
233
+ }
234
+ } catch (err) {
235
+ errors.push(`${entry}: ${err.message}`);
236
+ }
237
+ }
238
+
239
+ return { cleaned, errors };
240
+ }
241
+
242
+ /**
243
+ * Exported constants for testing.
244
+ */
245
+ export { WORKTREE_DIR, MAX_WORKTREE_AGE_MS };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * @fileoverview Agent customization via per-project override files.
3
+ *
4
+ * Loads `.customize.yaml` files from `chati.dev/customize/<agent>.yaml`
5
+ * and applies overrides to agent prompts.
6
+ *
7
+ * Constitution Article XVI — Model Governance.
8
+ */
9
+
10
+ import { existsSync, readFileSync } from 'fs';
11
+ import { join } from 'path';
12
+ import yaml from 'js-yaml';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** Directory relative to project root where customization files are stored. */
19
+ const CUSTOMIZE_DIR = 'chati.dev/customize';
20
+
21
+ /** Valid override fields that customizations can set. */
22
+ export const VALID_OVERRIDE_FIELDS = [
23
+ 'model',
24
+ 'extra_context',
25
+ 'skip_sections',
26
+ 'add_sections',
27
+ 'provider',
28
+ 'timeout',
29
+ 'max_iterations',
30
+ ];
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Public API
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * @typedef {object} Customization
38
+ * @property {boolean} found - Whether a customization file was found
39
+ * @property {string} [model] - Override model name
40
+ * @property {string} [extra_context] - Additional context to inject
41
+ * @property {string[]} [skip_sections] - Sections to omit from prompt
42
+ * @property {{ name: string, content: string }[]} [add_sections] - Sections to append
43
+ * @property {string} [provider] - Override provider name
44
+ * @property {number} [timeout] - Override timeout in ms
45
+ * @property {number} [max_iterations] - Override max iterations
46
+ */
47
+
48
+ /**
49
+ * Load customization for a specific agent.
50
+ *
51
+ * @param {string} projectDir - Project root directory
52
+ * @param {string} agentName - Agent name (e.g., 'brief', 'dev')
53
+ * @returns {{ found: boolean, overrides: Customization }}
54
+ */
55
+ export function loadCustomization(projectDir, agentName) {
56
+ if (!projectDir || !agentName) {
57
+ return { found: false, overrides: {} };
58
+ }
59
+
60
+ const customizePath = join(projectDir, CUSTOMIZE_DIR, `${agentName}.yaml`);
61
+
62
+ if (!existsSync(customizePath)) {
63
+ return { found: false, overrides: {} };
64
+ }
65
+
66
+ try {
67
+ const raw = readFileSync(customizePath, 'utf-8');
68
+ const parsed = yaml.load(raw);
69
+
70
+ if (!parsed || typeof parsed !== 'object') {
71
+ return { found: true, overrides: {} };
72
+ }
73
+
74
+ // Filter to valid override fields only
75
+ const overrides = {};
76
+ for (const field of VALID_OVERRIDE_FIELDS) {
77
+ if (parsed[field] !== undefined) {
78
+ overrides[field] = parsed[field];
79
+ }
80
+ }
81
+
82
+ return { found: true, overrides };
83
+ } catch {
84
+ return { found: false, overrides: {} };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Apply customization overrides to an agent prompt.
90
+ *
91
+ * @param {string} agentPrompt - Original agent prompt
92
+ * @param {Customization} customization - Customization overrides
93
+ * @returns {string} Modified prompt
94
+ */
95
+ export function applyCustomization(agentPrompt, customization) {
96
+ if (!agentPrompt || !customization) {
97
+ return agentPrompt || '';
98
+ }
99
+
100
+ let prompt = agentPrompt;
101
+
102
+ // Skip sections
103
+ if (customization.skip_sections && Array.isArray(customization.skip_sections)) {
104
+ for (const sectionName of customization.skip_sections) {
105
+ // Match section heading + all content until the next heading of same or higher level, or end of text
106
+ const pattern = new RegExp(
107
+ `^#{1,3}\\s+${escapeRegex(sectionName)}\\s*\\n[\\s\\S]*?(?=\\n#{1,3}\\s|\\Z)`,
108
+ 'gm'
109
+ );
110
+ // \\Z is not JS — use two-pass: try lookahead for next heading, then fallback to end
111
+ const headingPattern = new RegExp(
112
+ `^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n([\\s\\S]*?)(?=^#{1,3}\\s)`,
113
+ 'gm'
114
+ );
115
+ let replaced = prompt.replace(headingPattern, '');
116
+ if (replaced === prompt) {
117
+ // Section is at the end — no next heading follows
118
+ const endPattern = new RegExp(
119
+ `^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n[\\s\\S]*$`,
120
+ 'gm'
121
+ );
122
+ replaced = prompt.replace(endPattern, '');
123
+ }
124
+ prompt = replaced;
125
+ }
126
+ }
127
+
128
+ // Add extra context at the end
129
+ if (customization.extra_context && typeof customization.extra_context === 'string') {
130
+ prompt += `\n\n---\n\n## Additional Context\n\n${customization.extra_context}\n`;
131
+ }
132
+
133
+ // Add new sections
134
+ if (customization.add_sections && Array.isArray(customization.add_sections)) {
135
+ for (const section of customization.add_sections) {
136
+ if (section.name && section.content) {
137
+ prompt += `\n\n## ${section.name}\n\n${section.content}\n`;
138
+ }
139
+ }
140
+ }
141
+
142
+ return prompt;
143
+ }
144
+
145
+ /**
146
+ * Validate a customization object.
147
+ *
148
+ * @param {object} customization
149
+ * @returns {{ valid: boolean, errors: string[] }}
150
+ */
151
+ export function validateCustomization(customization) {
152
+ const errors = [];
153
+
154
+ if (!customization || typeof customization !== 'object') {
155
+ errors.push('Customization must be a non-null object');
156
+ return { valid: false, errors };
157
+ }
158
+
159
+ // Check for unknown fields
160
+ for (const key of Object.keys(customization)) {
161
+ if (!VALID_OVERRIDE_FIELDS.includes(key)) {
162
+ errors.push(`Unknown override field: "${key}"`);
163
+ }
164
+ }
165
+
166
+ // Validate model
167
+ if (customization.model !== undefined && typeof customization.model !== 'string') {
168
+ errors.push('model must be a string');
169
+ }
170
+
171
+ // Validate extra_context
172
+ if (customization.extra_context !== undefined && typeof customization.extra_context !== 'string') {
173
+ errors.push('extra_context must be a string');
174
+ }
175
+
176
+ // Validate skip_sections
177
+ if (customization.skip_sections !== undefined) {
178
+ if (!Array.isArray(customization.skip_sections)) {
179
+ errors.push('skip_sections must be an array');
180
+ } else if (customization.skip_sections.some(s => typeof s !== 'string')) {
181
+ errors.push('skip_sections must contain only strings');
182
+ }
183
+ }
184
+
185
+ // Validate add_sections
186
+ if (customization.add_sections !== undefined) {
187
+ if (!Array.isArray(customization.add_sections)) {
188
+ errors.push('add_sections must be an array');
189
+ } else {
190
+ for (const section of customization.add_sections) {
191
+ if (!section.name || !section.content) {
192
+ errors.push('Each add_section must have name and content');
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ // Validate timeout
199
+ if (customization.timeout !== undefined) {
200
+ if (typeof customization.timeout !== 'number' || customization.timeout <= 0) {
201
+ errors.push('timeout must be a positive number');
202
+ }
203
+ }
204
+
205
+ // Validate max_iterations
206
+ if (customization.max_iterations !== undefined) {
207
+ if (typeof customization.max_iterations !== 'number' || customization.max_iterations <= 0) {
208
+ errors.push('max_iterations must be a positive number');
209
+ }
210
+ }
211
+
212
+ return { valid: errors.length === 0, errors };
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Helpers
217
+ // ---------------------------------------------------------------------------
218
+
219
+ /**
220
+ * Escape special regex characters in a string.
221
+ *
222
+ * @param {string} str
223
+ * @returns {string}
224
+ */
225
+ function escapeRegex(str) {
226
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
227
+ }
@@ -1,68 +1,98 @@
1
1
  /**
2
- * IDE Configuration Mapping (5 IDEs)
2
+ * IDE Configuration Mapping (7 IDEs)
3
3
  * Defines where chati.dev agents are deployed per IDE
4
4
  */
5
5
  export const IDE_CONFIGS = {
6
6
  'claude-code': {
7
7
  name: 'Claude Code',
8
8
  description: 'Anthropic official CLI',
9
+ group: 'cli',
9
10
  recommended: true,
10
11
  configPath: '.claude/commands/',
11
12
  rulesFile: '.claude/CLAUDE.md',
12
13
  mcpConfigFile: '.claude/mcp.json',
13
14
  formatNotes: 'Native markdown format',
14
15
  },
16
+ 'gemini-cli': {
17
+ name: 'Gemini CLI',
18
+ description: 'Google AI terminal agent',
19
+ group: 'cli',
20
+ recommended: false,
21
+ configPath: '.gemini/commands/',
22
+ rulesFile: 'GEMINI.md',
23
+ mcpConfigFile: '.gemini/settings.json',
24
+ formatNotes: 'TOML command format',
25
+ contextPath: '.gemini/context/',
26
+ hooksPath: '.gemini/hooks/',
27
+ },
28
+ 'codex-cli': {
29
+ name: 'Codex CLI',
30
+ description: 'OpenAI Codex terminal agent',
31
+ group: 'cli',
32
+ recommended: false,
33
+ configPath: '.agents/skills/chati/',
34
+ rulesFile: 'AGENTS.md',
35
+ mcpConfigFile: null,
36
+ formatNotes: 'Codex skill format (SKILL.md with YAML frontmatter)',
37
+ rulesPath: '.codex/rules/',
38
+ overrideFile: 'AGENTS.override.md',
39
+ },
15
40
  'vscode': {
16
41
  name: 'VS Code',
17
42
  description: 'Extensions: Continue, Copilot Chat, etc',
43
+ group: 'editor',
18
44
  recommended: false,
19
45
  configPath: '.vscode/chati/',
20
46
  rulesFile: '.vscode/chati/rules.md',
21
47
  mcpConfigFile: null,
22
48
  formatNotes: 'Markdown with VS Code extension support',
23
49
  },
24
- 'antigravity': {
25
- name: 'AntiGravity',
26
- description: 'Google agentic development platform',
27
- recommended: false,
28
- configPath: '.antigravity/agents/',
29
- rulesFile: '.antigravity/rules.md',
30
- mcpConfigFile: null,
31
- formatNotes: 'Google platform format',
32
- },
33
50
  'cursor': {
34
51
  name: 'Cursor',
35
52
  description: 'AI-first code editor',
53
+ group: 'editor',
36
54
  recommended: false,
37
55
  configPath: '.cursor/rules/',
38
56
  rulesFile: '.cursorrules',
39
57
  mcpConfigFile: null,
40
58
  formatNotes: 'Cursor rules format',
41
59
  },
42
- 'codex-cli': {
43
- name: 'Codex CLI',
44
- description: 'OpenAI Codex terminal agent',
60
+ 'windsurf': {
61
+ name: 'Windsurf',
62
+ description: 'AI-powered code editor by Codeium',
63
+ group: 'editor',
45
64
  recommended: false,
46
- configPath: '.agents/skills/chati/',
47
- rulesFile: 'AGENTS.md',
65
+ configPath: '.windsurf/rules/',
66
+ rulesFile: '.windsurfrules',
48
67
  mcpConfigFile: null,
49
- formatNotes: 'Codex skill format (SKILL.md with YAML frontmatter)',
50
- rulesPath: '.codex/rules/',
51
- overrideFile: 'AGENTS.override.md',
68
+ formatNotes: 'Windsurf rules format (markdown)',
52
69
  },
53
- 'gemini-cli': {
54
- name: 'Gemini CLI',
55
- description: 'Google AI terminal agent',
70
+ 'antigravity': {
71
+ name: 'AntiGravity',
72
+ description: 'Google agentic development platform',
73
+ group: 'editor',
56
74
  recommended: false,
57
- configPath: '.gemini/commands/',
58
- rulesFile: 'GEMINI.md',
59
- mcpConfigFile: '.gemini/settings.json',
60
- formatNotes: 'TOML command format',
61
- contextPath: '.gemini/context/',
62
- hooksPath: '.gemini/hooks/',
75
+ configPath: '.antigravity/agents/',
76
+ rulesFile: '.antigravity/rules.md',
77
+ mcpConfigFile: null,
78
+ formatNotes: 'Google platform format',
63
79
  },
64
80
  };
65
81
 
82
+ /**
83
+ * Map IDE key to its CLI provider name.
84
+ * Editor-based IDEs (vscode, cursor, windsurf, antigravity) don't have a CLI provider.
85
+ */
86
+ export const IDE_TO_PROVIDER = {
87
+ 'claude-code': 'claude',
88
+ 'gemini-cli': 'gemini',
89
+ 'codex-cli': 'codex',
90
+ 'vscode': null,
91
+ 'cursor': null,
92
+ 'windsurf': null,
93
+ 'antigravity': null,
94
+ };
95
+
66
96
  /**
67
97
  * Get list of IDEs for selection prompt
68
98
  */