create-universal-ai-context 2.3.0 → 2.4.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 +9 -2
- 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
|
@@ -89,6 +89,8 @@ program
|
|
|
89
89
|
.option('--preserve-custom', 'Keep user customizations when merging (default: true)', true)
|
|
90
90
|
.option('--update-refs', 'Auto-fix drifted line references')
|
|
91
91
|
.option('--backup', 'Create backup before modifying existing files')
|
|
92
|
+
.option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
|
|
93
|
+
.option('--fail-on-unreplaced', 'Error if any placeholders remain unreplaced')
|
|
92
94
|
.action(async (projectName, options) => {
|
|
93
95
|
console.log(banner);
|
|
94
96
|
|
|
@@ -118,7 +120,10 @@ program
|
|
|
118
120
|
mode: options.mode,
|
|
119
121
|
preserveCustom: options.preserveCustom,
|
|
120
122
|
updateRefs: options.updateRefs,
|
|
121
|
-
backup: options.backup
|
|
123
|
+
backup: options.backup,
|
|
124
|
+
force: options.force || false,
|
|
125
|
+
// Placeholder validation
|
|
126
|
+
failOnUnreplaced: options.failOnUnreplaced || false
|
|
122
127
|
});
|
|
123
128
|
} catch (error) {
|
|
124
129
|
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
@@ -137,6 +142,7 @@ program
|
|
|
137
142
|
.option('-d, --dryRun', 'Show what would be done without making changes')
|
|
138
143
|
.option('-v, --verbose', 'Show detailed output')
|
|
139
144
|
.option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
|
|
145
|
+
.option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
|
|
140
146
|
.action(async (options) => {
|
|
141
147
|
console.log(banner);
|
|
142
148
|
|
|
@@ -169,7 +175,8 @@ program
|
|
|
169
175
|
const config = {
|
|
170
176
|
projectName: path.basename(projectRoot),
|
|
171
177
|
aiTools,
|
|
172
|
-
verbose: options.verbose
|
|
178
|
+
verbose: options.verbose,
|
|
179
|
+
force: options.force || false
|
|
173
180
|
};
|
|
174
181
|
|
|
175
182
|
if (options.dryRun) {
|
|
@@ -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
|
+
};
|
package/lib/adapters/copilot.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: '.github/copilot-instructions.md 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, 'copilot');
|
|
58
73
|
|
|
59
74
|
// Render template
|
|
60
75
|
const content = renderTemplateByName('copilot', context);
|
|
61
76
|
|
|
62
77
|
// Ensure .github directory exists
|
|
63
|
-
const outputPath = getOutputPath(projectRoot);
|
|
64
78
|
const outputDir = path.dirname(outputPath);
|
|
65
79
|
if (!fs.existsSync(outputDir)) {
|
|
66
80
|
fs.mkdirSync(outputDir, { recursive: true });
|
package/lib/adapters/index.js
CHANGED
|
@@ -8,6 +8,9 @@ const claude = require('./claude');
|
|
|
8
8
|
const copilot = require('./copilot');
|
|
9
9
|
const cline = require('./cline');
|
|
10
10
|
const antigravity = require('./antigravity');
|
|
11
|
+
const windsurf = require('./windsurf');
|
|
12
|
+
const aider = require('./aider');
|
|
13
|
+
const continueAdapter = require('./continue');
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* All available adapters
|
|
@@ -16,7 +19,10 @@ const adapters = {
|
|
|
16
19
|
claude,
|
|
17
20
|
copilot,
|
|
18
21
|
cline,
|
|
19
|
-
antigravity
|
|
22
|
+
antigravity,
|
|
23
|
+
windsurf,
|
|
24
|
+
aider,
|
|
25
|
+
continue: continueAdapter
|
|
20
26
|
};
|
|
21
27
|
|
|
22
28
|
/**
|
|
@@ -65,5 +71,8 @@ module.exports = {
|
|
|
65
71
|
claude,
|
|
66
72
|
copilot,
|
|
67
73
|
cline,
|
|
68
|
-
antigravity
|
|
74
|
+
antigravity,
|
|
75
|
+
windsurf,
|
|
76
|
+
aider,
|
|
77
|
+
continue: continueAdapter
|
|
69
78
|
};
|