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.
- package/bin/create-ai-context.js +28 -10
- package/lib/adapters/aider.js +131 -0
- package/lib/adapters/antigravity.js +47 -2
- package/lib/adapters/claude.js +100 -25
- package/lib/adapters/cline.js +16 -2
- package/lib/adapters/continue.js +138 -0
- package/lib/adapters/copilot.js +16 -2
- package/lib/adapters/index.js +11 -2
- package/lib/adapters/windsurf.js +138 -0
- package/lib/ai-orchestrator.js +2 -1
- package/lib/content-preservation.js +243 -0
- package/lib/index.js +11 -4
- package/lib/placeholder.js +82 -1
- package/lib/template-coordination.js +148 -0
- package/lib/template-renderer.js +15 -5
- package/lib/utils/fs-wrapper.js +79 -0
- package/lib/utils/path-utils.js +60 -0
- package/package.json +1 -1
- package/templates/handlebars/aider-config.hbs +80 -0
- package/templates/handlebars/antigravity.hbs +40 -0
- package/templates/handlebars/claude.hbs +1 -2
- package/templates/handlebars/cline.hbs +1 -2
- package/templates/handlebars/continue-config.hbs +116 -0
- package/templates/handlebars/copilot.hbs +1 -2
- package/templates/handlebars/partials/header.hbs +1 -1
- package/templates/handlebars/windsurf-rules.hbs +69 -0
package/bin/create-ai-context.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
package/lib/adapters/claude.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
71
|
-
|
|
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
|
-
//
|
|
103
|
-
if (fs.existsSync(claudeDir)) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
package/lib/adapters/cline.js
CHANGED
|
@@ -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
|
+
};
|