create-universal-ai-context 2.3.0 → 2.5.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.
@@ -40,6 +40,7 @@ const {
40
40
  getSyncHistory,
41
41
  CONFLICT_STRATEGY
42
42
  } = require('../lib/cross-tool-sync');
43
+ const { getAdapterNames } = require('../lib/adapters');
43
44
  const packageJson = require('../package.json');
44
45
 
45
46
  // ASCII Banner
@@ -51,7 +52,7 @@ ${chalk.cyan('╚═════════════════════
51
52
  `;
52
53
 
53
54
  // Supported AI tools
54
- const AI_TOOLS = ['claude', 'copilot', 'cline', 'antigravity', 'all'];
55
+ const AI_TOOLS = ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'all'];
55
56
 
56
57
  // Parse AI tools helper
57
58
  function parseAiTools(toolsString) {
@@ -62,7 +63,8 @@ function parseAiTools(toolsString) {
62
63
  console.error(chalk.gray(` Valid options: ${AI_TOOLS.join(', ')}`));
63
64
  process.exit(1);
64
65
  }
65
- return tools.includes('all') ? ['claude', 'copilot', 'cline', 'antigravity'] : tools;
66
+ const allTools = ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue'];
67
+ return tools.includes('all') ? allTools : tools;
66
68
  }
67
69
 
68
70
  program
@@ -79,7 +81,7 @@ program
79
81
  .option('--no-git', 'Skip git initialization')
80
82
  .option('-n, --dry-run', 'Show what would be done without making changes')
81
83
  .option('-v, --verbose', 'Show detailed output')
82
- .option('--ai <tools>', 'Generate for specific AI tools (comma-separated: claude,copilot,cline,antigravity,all)', 'all')
84
+ .option('--ai <tools>', 'Generate for specific AI tools (comma-separated: claude,copilot,cline,antigravity,windsurf,aider,continue,all)', 'all')
83
85
  .option('--force-ai', 'Force AI-enhanced mode (creates INIT_REQUEST.md)')
84
86
  .option('--static', 'Force standalone mode (static analysis only, no AI setup)')
85
87
  .option('--analyze-only', 'Run codebase analysis without installation')
@@ -89,6 +91,8 @@ program
89
91
  .option('--preserve-custom', 'Keep user customizations when merging (default: true)', true)
90
92
  .option('--update-refs', 'Auto-fix drifted line references')
91
93
  .option('--backup', 'Create backup before modifying existing files')
94
+ .option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
95
+ .option('--fail-on-unreplaced', 'Error if any placeholders remain unreplaced')
92
96
  .action(async (projectName, options) => {
93
97
  console.log(banner);
94
98
 
@@ -118,7 +122,10 @@ program
118
122
  mode: options.mode,
119
123
  preserveCustom: options.preserveCustom,
120
124
  updateRefs: options.updateRefs,
121
- backup: options.backup
125
+ backup: options.backup,
126
+ force: options.force || false,
127
+ // Placeholder validation
128
+ failOnUnreplaced: options.failOnUnreplaced === true
122
129
  });
123
130
  } catch (error) {
124
131
  console.error(chalk.red('\n✖ Error:'), error.message);
@@ -137,6 +144,7 @@ program
137
144
  .option('-d, --dryRun', 'Show what would be done without making changes')
138
145
  .option('-v, --verbose', 'Show detailed output')
139
146
  .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
147
+ .option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
140
148
  .action(async (options) => {
141
149
  console.log(banner);
142
150
 
@@ -169,7 +177,8 @@ program
169
177
  const config = {
170
178
  projectName: path.basename(projectRoot),
171
179
  aiTools,
172
- verbose: options.verbose
180
+ verbose: options.verbose,
181
+ force: options.force || false
173
182
  };
174
183
 
175
184
  if (options.dryRun) {
@@ -552,7 +561,7 @@ program
552
561
 
553
562
  const config = {
554
563
  projectName: path.basename(projectRoot),
555
- aiTools: ['claude', 'copilot', 'cline', 'antigravity']
564
+ aiTools: getAdapterNames()
556
565
  };
557
566
 
558
567
  const results = await syncAllFromCodebase(projectRoot, config);
@@ -568,7 +577,12 @@ program
568
577
 
569
578
  console.log(chalk.red('\nErrors:'));
570
579
  for (const error of results.errors) {
571
- console.error(chalk.red(` ✖ ${error.message || error.tool}`));
580
+ // error object has: { tool, errors: [] } or { tool, message }
581
+ const errorText = error.message ||
582
+ (error.errors && error.errors.length > 0
583
+ ? error.errors.map(e => e.message || e).join('; ')
584
+ : error.tool);
585
+ console.error(chalk.red(` ✖ ${error.tool || 'Unknown'}: ${errorText}`));
572
586
  }
573
587
  process.exit(1);
574
588
  } else {
@@ -600,7 +614,7 @@ program
600
614
  const projectRoot = path.resolve(options.path);
601
615
  const spinner = createSpinner();
602
616
 
603
- const validTools = ['claude', 'copilot', 'cline', 'antigravity'];
617
+ const validTools = getAdapterNames();
604
618
  if (!validTools.includes(sourceTool)) {
605
619
  console.error(chalk.red(`\n✖ Error: Invalid tool: ${sourceTool}`));
606
620
  console.error(chalk.gray(` Valid options: ${validTools.join(', ')}`));
@@ -632,7 +646,11 @@ program
632
646
 
633
647
  console.log(chalk.red('\nErrors:'));
634
648
  for (const error of results.errors) {
635
- console.error(chalk.red(` ✖ ${error.tool || error.message}`));
649
+ const errorText = error.message ||
650
+ (error.errors && error.errors.length > 0
651
+ ? error.errors.map(e => e.message || e).join('; ')
652
+ : 'Unknown error');
653
+ console.error(chalk.red(` ✖ ${error.tool || 'Unknown'}: ${errorText}`));
636
654
  }
637
655
  process.exit(1);
638
656
  } else {
@@ -674,7 +692,7 @@ program
674
692
 
675
693
  const config = {
676
694
  projectName: path.basename(projectRoot),
677
- aiTools: ['claude', 'copilot', 'cline', 'antigravity']
695
+ aiTools: getAdapterNames()
678
696
  };
679
697
 
680
698
  const result = await resolveConflict(
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Aider Adapter
3
+ *
4
+ * Generates .aider.conf.yml file for Aider AI pair-programmer
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { renderTemplateByName, buildContext } = require('../template-renderer');
10
+ const { isManagedFile } = require('../template-coordination');
11
+
12
+ /**
13
+ * Adapter metadata
14
+ */
15
+ const adapter = {
16
+ name: 'aider',
17
+ displayName: 'Aider',
18
+ description: 'Configuration file for Aider AI pair-programmer',
19
+ outputType: 'single-file',
20
+ outputPath: '.aider.conf.yml'
21
+ };
22
+
23
+ /**
24
+ * Get output path for Aider config file
25
+ * @param {string} projectRoot - Project root directory
26
+ * @returns {string} Output file path
27
+ */
28
+ function getOutputPath(projectRoot) {
29
+ return path.join(projectRoot, '.aider.conf.yml');
30
+ }
31
+
32
+ /**
33
+ * Check if Aider output already exists
34
+ * @param {string} projectRoot - Project root directory
35
+ * @returns {boolean}
36
+ */
37
+ function exists(projectRoot) {
38
+ const configPath = getOutputPath(projectRoot);
39
+ return fs.existsSync(configPath);
40
+ }
41
+
42
+ /**
43
+ * Generate Aider config file
44
+ * @param {object} analysis - Analysis results from static analyzer
45
+ * @param {object} config - Configuration from CLI
46
+ * @param {string} projectRoot - Project root directory
47
+ * @returns {object} Generation result
48
+ */
49
+ async function generate(analysis, config, projectRoot) {
50
+ const result = {
51
+ success: false,
52
+ adapter: adapter.name,
53
+ files: [],
54
+ errors: []
55
+ };
56
+
57
+ try {
58
+ const configPath = getOutputPath(projectRoot);
59
+
60
+ // Check if file exists and is custom (not managed by us)
61
+ if (fs.existsSync(configPath) && !config.force) {
62
+ if (!isManagedFile(configPath)) {
63
+ result.errors.push({
64
+ message: '.aider.conf.yml exists and appears to be custom. Use --force to overwrite.',
65
+ code: 'EXISTS_CUSTOM',
66
+ severity: 'error'
67
+ });
68
+ return result;
69
+ }
70
+ }
71
+
72
+ // Build context from analysis
73
+ const context = buildContext(analysis, config, 'aider');
74
+
75
+ // Render template
76
+ const content = renderTemplateByName('aider-config', context);
77
+
78
+ // Write output file
79
+ fs.writeFileSync(configPath, content, 'utf-8');
80
+
81
+ result.success = true;
82
+ result.files.push({
83
+ path: configPath,
84
+ relativePath: '.aider.conf.yml',
85
+ size: content.length
86
+ });
87
+ } catch (error) {
88
+ result.errors.push({
89
+ message: error.message,
90
+ stack: error.stack
91
+ });
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Validate Aider output
99
+ * @param {string} projectRoot - Project root directory
100
+ * @returns {object} Validation result
101
+ */
102
+ function validate(projectRoot) {
103
+ const issues = [];
104
+ const configPath = getOutputPath(projectRoot);
105
+
106
+ if (!fs.existsSync(configPath)) {
107
+ issues.push({ file: '.aider.conf.yml', error: 'not found' });
108
+ } else {
109
+ const content = fs.readFileSync(configPath, 'utf-8');
110
+ const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
111
+ if (placeholderMatch && placeholderMatch.length > 0) {
112
+ issues.push({
113
+ file: '.aider.conf.yml',
114
+ error: `Found ${placeholderMatch.length} unreplaced placeholders`
115
+ });
116
+ }
117
+ }
118
+
119
+ return {
120
+ valid: issues.filter(i => i.severity !== 'warning').length === 0,
121
+ issues
122
+ };
123
+ }
124
+
125
+ module.exports = {
126
+ ...adapter,
127
+ getOutputPath,
128
+ exists,
129
+ generate,
130
+ validate
131
+ };
@@ -7,6 +7,7 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const { renderMultiFileTemplate, buildContext } = require('../template-renderer');
10
+ const { isManagedFile } = require('../template-coordination');
10
11
 
11
12
  /**
12
13
  * Adapter metadata
@@ -53,8 +54,23 @@ async function generate(analysis, config, projectRoot) {
53
54
  };
54
55
 
55
56
  try {
57
+ const outputDir = getOutputPath(projectRoot);
58
+
59
+ // Check if .agent/ directory exists and contains custom files
60
+ if (fs.existsSync(outputDir) && !config.force) {
61
+ const hasCustomFiles = checkForCustomFiles(outputDir);
62
+ if (hasCustomFiles) {
63
+ result.errors.push({
64
+ message: '.agent/ directory exists and contains custom files. Use --force to overwrite.',
65
+ code: 'EXISTS_CUSTOM',
66
+ severity: 'error'
67
+ });
68
+ return result;
69
+ }
70
+ }
71
+
56
72
  // Build context from analysis
57
- const context = buildContext(analysis, config);
73
+ const context = buildContext(analysis, config, 'antigravity');
58
74
 
59
75
  // Get template path
60
76
  const templatePath = path.join(__dirname, '..', '..', 'templates', 'handlebars', 'antigravity.hbs');
@@ -63,7 +79,6 @@ async function generate(analysis, config, projectRoot) {
63
79
  const files = renderMultiFileTemplate(templatePath, context);
64
80
 
65
81
  // Create output directory
66
- const outputDir = getOutputPath(projectRoot);
67
82
  if (!fs.existsSync(outputDir)) {
68
83
  fs.mkdirSync(outputDir, { recursive: true });
69
84
  }
@@ -99,6 +114,36 @@ async function generate(analysis, config, projectRoot) {
99
114
  return result;
100
115
  }
101
116
 
117
+ /**
118
+ * Check if directory contains custom (non-managed) files
119
+ * @param {string} dir - Directory to check
120
+ * @returns {boolean} True if custom files found
121
+ */
122
+ function checkForCustomFiles(dir) {
123
+ const walkDir = (currentDir, depth = 0) => {
124
+ if (depth > 10) return false;
125
+
126
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
127
+ for (const entry of entries) {
128
+ if (entry.isDirectory()) {
129
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
130
+ if (walkDir(path.join(currentDir, entry.name), depth + 1)) {
131
+ return true;
132
+ }
133
+ }
134
+ } else if (entry.name.endsWith('.md')) {
135
+ const filePath = path.join(currentDir, entry.name);
136
+ if (!isManagedFile(filePath)) {
137
+ return true;
138
+ }
139
+ }
140
+ }
141
+ return false;
142
+ };
143
+
144
+ return walkDir(dir);
145
+ }
146
+
102
147
  /**
103
148
  * Validate Antigravity output
104
149
  * @param {string} projectRoot - Project root directory
@@ -8,6 +8,8 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
  const { renderTemplateByName, buildContext } = require('../template-renderer');
11
+ const { isManagedFile } = require('../template-coordination');
12
+ const { findCustomContentInClaude, migrateCustomContent } = require('../content-preservation');
11
13
 
12
14
  /**
13
15
  * Adapter metadata
@@ -56,19 +58,44 @@ async function generate(analysis, config, projectRoot) {
56
58
  };
57
59
 
58
60
  try {
59
- // 1. Generate AI_CONTEXT.md at project root (existing behavior)
60
- const context = buildContext(analysis, config);
61
- const content = renderTemplateByName('claude', context);
61
+ // 1. Generate AI_CONTEXT.md at project root
62
62
  const outputPath = getOutputPath(projectRoot);
63
- fs.writeFileSync(outputPath, content, 'utf-8');
64
- result.files.push({
65
- path: outputPath,
66
- relativePath: 'AI_CONTEXT.md',
67
- size: content.length
68
- });
69
63
 
70
- // 2. Generate .claude/ directory structure (NEW)
71
- const claudeDirResult = await generateClaudeDirectory(projectRoot, context, result);
64
+ // Check if file exists and is custom (not managed by us)
65
+ if (fs.existsSync(outputPath) && !config.force) {
66
+ if (!isManagedFile(outputPath)) {
67
+ result.errors.push({
68
+ message: 'AI_CONTEXT.md exists and appears to be custom. Use --force to overwrite.',
69
+ code: 'EXISTS_CUSTOM',
70
+ severity: 'error'
71
+ });
72
+ // Don't return early - still try to generate .claude/ directory
73
+ } else {
74
+ // File is managed by us, safe to overwrite
75
+ const context = buildContext(analysis, config, 'claude');
76
+ const content = renderTemplateByName('claude', context);
77
+ fs.writeFileSync(outputPath, content, 'utf-8');
78
+ result.files.push({
79
+ path: outputPath,
80
+ relativePath: 'AI_CONTEXT.md',
81
+ size: content.length
82
+ });
83
+ }
84
+ } else {
85
+ // File doesn't exist or force is enabled
86
+ const context = buildContext(analysis, config, 'claude');
87
+ const content = renderTemplateByName('claude', context);
88
+ fs.writeFileSync(outputPath, content, 'utf-8');
89
+ result.files.push({
90
+ path: outputPath,
91
+ relativePath: 'AI_CONTEXT.md',
92
+ size: content.length
93
+ });
94
+ }
95
+
96
+ // 2. Generate .claude/ directory structure
97
+ const context = buildContext(analysis, config, 'claude');
98
+ const claudeDirResult = await generateClaudeDirectory(projectRoot, context, config, result);
72
99
  if (claudeDirResult) {
73
100
  result.files.push(...claudeDirResult);
74
101
  }
@@ -90,28 +117,46 @@ async function generate(analysis, config, projectRoot) {
90
117
  * Generate .claude/ directory with symlinks to .ai-context/
91
118
  * @param {string} projectRoot - Project root directory
92
119
  * @param {object} context - Template context
120
+ * @param {object} config - Configuration from CLI
93
121
  * @param {object} result - Result object to track files/errors
94
122
  * @returns {Array} List of generated files
95
123
  */
96
- async function generateClaudeDirectory(projectRoot, context, result) {
124
+ async function generateClaudeDirectory(projectRoot, context, config, result) {
97
125
  const { copyDirectory } = require('../installer');
98
126
  const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
99
127
  const aiContextDir = path.join(projectRoot, '.ai-context');
100
128
  const claudeDir = path.join(projectRoot, '.claude');
101
129
 
102
- // Don't overwrite existing .claude/ directory
103
- if (fs.existsSync(claudeDir)) {
104
- result.errors.push({
105
- message: '.claude/ directory already exists, skipping structure generation',
106
- code: 'EXISTS',
107
- severity: 'warning'
108
- });
109
- return [{
110
- path: claudeDir,
111
- relativePath: '.claude/',
112
- size: 0,
113
- skipped: true
114
- }];
130
+ // Check for existing .claude/ directory
131
+ if (fs.existsSync(claudeDir) && !config.force) {
132
+ // Check if it has custom files
133
+ const hasCustomFiles = checkForCustomFiles(claudeDir);
134
+ if (hasCustomFiles) {
135
+ // Migrate custom content before skipping
136
+ const customItems = findCustomContentInClaude(claudeDir);
137
+ if (customItems.length > 0) {
138
+ const migrated = migrateCustomContent(claudeDir, aiContextDir, customItems);
139
+ result.errors.push({
140
+ message: `Migrated ${migrated.length} custom items from .claude/ to .ai-context/custom/`,
141
+ code: 'MIGRATED_CUSTOM',
142
+ severity: 'info',
143
+ migratedItems: migrated
144
+ });
145
+ }
146
+
147
+ result.errors.push({
148
+ message: '.claude/ directory exists and contains custom files. Use --force to overwrite. Skipping directory generation.',
149
+ code: 'EXISTS_CUSTOM',
150
+ severity: 'warning'
151
+ });
152
+ return [{
153
+ path: claudeDir,
154
+ relativePath: '.claude/',
155
+ size: 0,
156
+ skipped: true
157
+ }];
158
+ }
159
+ // Directory exists but only has managed files, we can regenerate
115
160
  }
116
161
 
117
162
  try {
@@ -258,6 +303,36 @@ See \`AI_CONTEXT.md\` at project root for universal AI context (works with all t
258
303
  }
259
304
  }
260
305
 
306
+ /**
307
+ * Check if directory contains custom (non-managed) files
308
+ * @param {string} dir - Directory to check
309
+ * @returns {boolean} True if custom files found
310
+ */
311
+ function checkForCustomFiles(dir) {
312
+ const walkDir = (currentDir, depth = 0) => {
313
+ if (depth > 10) return false;
314
+
315
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
316
+ for (const entry of entries) {
317
+ if (entry.isDirectory()) {
318
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
319
+ if (walkDir(path.join(currentDir, entry.name), depth + 1)) {
320
+ return true;
321
+ }
322
+ }
323
+ } else if (entry.name.endsWith('.md')) {
324
+ const filePath = path.join(currentDir, entry.name);
325
+ if (!isManagedFile(filePath)) {
326
+ return true;
327
+ }
328
+ }
329
+ }
330
+ return false;
331
+ };
332
+
333
+ return walkDir(dir);
334
+ }
335
+
261
336
  /**
262
337
  * Validate Claude output
263
338
  * @param {string} projectRoot - Project root directory
@@ -7,6 +7,7 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const { renderTemplateByName, buildContext } = require('../template-renderer');
10
+ const { isManagedFile } = require('../template-coordination');
10
11
 
11
12
  /**
12
13
  * Adapter metadata
@@ -53,14 +54,27 @@ async function generate(analysis, config, projectRoot) {
53
54
  };
54
55
 
55
56
  try {
57
+ const outputPath = getOutputPath(projectRoot);
58
+
59
+ // Check if file exists and is custom (not managed by us)
60
+ if (fs.existsSync(outputPath) && !config.force) {
61
+ if (!isManagedFile(outputPath)) {
62
+ result.errors.push({
63
+ message: '.clinerules exists and appears to be custom. Use --force to overwrite.',
64
+ code: 'EXISTS_CUSTOM',
65
+ severity: 'error'
66
+ });
67
+ return result;
68
+ }
69
+ }
70
+
56
71
  // Build context from analysis
57
- const context = buildContext(analysis, config);
72
+ const context = buildContext(analysis, config, 'cline');
58
73
 
59
74
  // Render template
60
75
  const content = renderTemplateByName('cline', context);
61
76
 
62
77
  // Write output file
63
- const outputPath = getOutputPath(projectRoot);
64
78
  fs.writeFileSync(outputPath, content, 'utf-8');
65
79
 
66
80
  result.success = true;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Continue Adapter
3
+ *
4
+ * Generates .continue/config.json file for Continue extension
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { renderTemplateByName, buildContext } = require('../template-renderer');
10
+ const { isManagedFile } = require('../template-coordination');
11
+
12
+ /**
13
+ * Adapter metadata
14
+ */
15
+ const adapter = {
16
+ name: 'continue',
17
+ displayName: 'Continue',
18
+ description: 'Configuration file for Continue VS Code/JetBrains extension',
19
+ outputType: 'single-file',
20
+ outputPath: '.continue/config.json'
21
+ };
22
+
23
+ /**
24
+ * Get output path for Continue config file
25
+ * @param {string} projectRoot - Project root directory
26
+ * @returns {string} Output file path
27
+ */
28
+ function getOutputPath(projectRoot) {
29
+ return path.join(projectRoot, '.continue', 'config.json');
30
+ }
31
+
32
+ /**
33
+ * Check if Continue output already exists
34
+ * @param {string} projectRoot - Project root directory
35
+ * @returns {boolean}
36
+ */
37
+ function exists(projectRoot) {
38
+ const configPath = getOutputPath(projectRoot);
39
+ const continueDir = path.join(projectRoot, '.continue');
40
+ return fs.existsSync(configPath) || fs.existsSync(continueDir);
41
+ }
42
+
43
+ /**
44
+ * Generate Continue config file
45
+ * @param {object} analysis - Analysis results from static analyzer
46
+ * @param {object} config - Configuration from CLI
47
+ * @param {string} projectRoot - Project root directory
48
+ * @returns {object} Generation result
49
+ */
50
+ async function generate(analysis, config, projectRoot) {
51
+ const result = {
52
+ success: false,
53
+ adapter: adapter.name,
54
+ files: [],
55
+ errors: []
56
+ };
57
+
58
+ try {
59
+ const configPath = getOutputPath(projectRoot);
60
+
61
+ // Check if file exists and is custom (not managed by us)
62
+ if (fs.existsSync(configPath) && !config.force) {
63
+ if (!isManagedFile(configPath)) {
64
+ result.errors.push({
65
+ message: '.continue/config.json exists and appears to be custom. Use --force to overwrite.',
66
+ code: 'EXISTS_CUSTOM',
67
+ severity: 'error'
68
+ });
69
+ return result;
70
+ }
71
+ }
72
+
73
+ // Build context from analysis
74
+ const context = buildContext(analysis, config, 'continue');
75
+
76
+ // Render template
77
+ const content = renderTemplateByName('continue-config', context);
78
+
79
+ // Create .continue directory if it doesn't exist
80
+ const continueDir = path.dirname(configPath);
81
+ if (!fs.existsSync(continueDir)) {
82
+ fs.mkdirSync(continueDir, { recursive: true });
83
+ }
84
+
85
+ // Write output file
86
+ fs.writeFileSync(configPath, content, 'utf-8');
87
+
88
+ result.success = true;
89
+ result.files.push({
90
+ path: configPath,
91
+ relativePath: '.continue/config.json',
92
+ size: content.length
93
+ });
94
+ } catch (error) {
95
+ result.errors.push({
96
+ message: error.message,
97
+ stack: error.stack
98
+ });
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Validate Continue output
106
+ * @param {string} projectRoot - Project root directory
107
+ * @returns {object} Validation result
108
+ */
109
+ function validate(projectRoot) {
110
+ const issues = [];
111
+ const configPath = getOutputPath(projectRoot);
112
+
113
+ if (!fs.existsSync(configPath)) {
114
+ issues.push({ file: '.continue/config.json', error: 'not found' });
115
+ } else {
116
+ const content = fs.readFileSync(configPath, 'utf-8');
117
+ const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
118
+ if (placeholderMatch && placeholderMatch.length > 0) {
119
+ issues.push({
120
+ file: '.continue/config.json',
121
+ error: `Found ${placeholderMatch.length} unreplaced placeholders`
122
+ });
123
+ }
124
+ }
125
+
126
+ return {
127
+ valid: issues.filter(i => i.severity !== 'warning').length === 0,
128
+ issues
129
+ };
130
+ }
131
+
132
+ module.exports = {
133
+ ...adapter,
134
+ getOutputPath,
135
+ exists,
136
+ generate,
137
+ validate
138
+ };