create-universal-ai-context 2.2.2 → 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 +177 -53
- 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,24 +58,51 @@ 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
|
}
|
|
75
102
|
|
|
76
|
-
|
|
103
|
+
// Success if no actual errors (warnings and info are OK)
|
|
104
|
+
result.success = result.errors.length === 0 ||
|
|
105
|
+
result.errors.every(e => e.code === 'EXISTS' || e.severity === 'info' || e.severity === 'warning');
|
|
77
106
|
} catch (error) {
|
|
78
107
|
result.errors.push({
|
|
79
108
|
message: error.message,
|
|
@@ -85,59 +114,101 @@ async function generate(analysis, config, projectRoot) {
|
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
/**
|
|
88
|
-
* Generate .claude/ directory with
|
|
117
|
+
* Generate .claude/ directory with symlinks to .ai-context/
|
|
89
118
|
* @param {string} projectRoot - Project root directory
|
|
90
119
|
* @param {object} context - Template context
|
|
120
|
+
* @param {object} config - Configuration from CLI
|
|
91
121
|
* @param {object} result - Result object to track files/errors
|
|
92
122
|
* @returns {Array} List of generated files
|
|
93
123
|
*/
|
|
94
|
-
async function generateClaudeDirectory(projectRoot, context, result) {
|
|
124
|
+
async function generateClaudeDirectory(projectRoot, context, config, result) {
|
|
95
125
|
const { copyDirectory } = require('../installer');
|
|
96
126
|
const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
|
|
127
|
+
const aiContextDir = path.join(projectRoot, '.ai-context');
|
|
97
128
|
const claudeDir = path.join(projectRoot, '.claude');
|
|
98
129
|
|
|
99
|
-
//
|
|
100
|
-
if (fs.existsSync(claudeDir)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
112
160
|
}
|
|
113
161
|
|
|
114
162
|
try {
|
|
115
163
|
// Create .claude/ directory
|
|
116
164
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
117
165
|
|
|
118
|
-
//
|
|
119
|
-
const
|
|
166
|
+
// Subdirectories to symlink from .ai-context/
|
|
167
|
+
const subdirsToLink = [
|
|
120
168
|
'agents',
|
|
121
169
|
'commands',
|
|
122
170
|
'indexes',
|
|
123
171
|
'context',
|
|
124
172
|
'schemas',
|
|
125
|
-
'standards'
|
|
173
|
+
'standards',
|
|
174
|
+
'tools'
|
|
126
175
|
];
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
if (context.features?.tools !== false) {
|
|
130
|
-
subdirsToCopy.push('tools');
|
|
131
|
-
}
|
|
132
|
-
|
|
177
|
+
let linksCreated = 0;
|
|
133
178
|
let filesCopied = 0;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
|
|
180
|
+
for (const subdir of subdirsToLink) {
|
|
181
|
+
const srcPath = path.join(aiContextDir, subdir);
|
|
182
|
+
const destPath = path.join(claudeDir, subdir);
|
|
183
|
+
|
|
184
|
+
// Skip if source doesn't exist
|
|
185
|
+
if (!fs.existsSync(srcPath)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Try to create symlink, fallback to copy
|
|
190
|
+
try {
|
|
191
|
+
// Remove dest if it exists (shouldn't, but safety)
|
|
192
|
+
if (fs.existsSync(destPath)) {
|
|
193
|
+
if (fs.lstatSync(destPath).isSymbolicLink()) {
|
|
194
|
+
fs.unlinkSync(destPath);
|
|
195
|
+
} else {
|
|
196
|
+
// Existing directory, skip
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create symlink
|
|
202
|
+
fs.symlinkSync(srcPath, destPath, 'junction');
|
|
203
|
+
linksCreated++;
|
|
204
|
+
} catch (symlinkError) {
|
|
205
|
+
// Symlink failed (likely Windows permissions or filesystem limitation)
|
|
206
|
+
// Fallback: copy directory contents
|
|
207
|
+
if (!fs.existsSync(destPath)) {
|
|
208
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
209
|
+
const count = await copyDirectory(srcPath, destPath);
|
|
210
|
+
filesCopied += count;
|
|
211
|
+
}
|
|
141
212
|
}
|
|
142
213
|
}
|
|
143
214
|
|
|
@@ -145,7 +216,7 @@ async function generateClaudeDirectory(projectRoot, context, result) {
|
|
|
145
216
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
146
217
|
const settings = {
|
|
147
218
|
'$schema': './schemas/settings.schema.json',
|
|
148
|
-
version: '2.
|
|
219
|
+
version: '2.2.2',
|
|
149
220
|
project: {
|
|
150
221
|
name: context.project?.name || 'Project',
|
|
151
222
|
tech_stack: context.project?.tech_stack || 'Not detected'
|
|
@@ -170,7 +241,26 @@ async function generateClaudeDirectory(projectRoot, context, result) {
|
|
|
170
241
|
const readmePath = path.join(claudeDir, 'README.md');
|
|
171
242
|
const readme = `# .claude Configuration - ${context.project?.name || 'Project'}
|
|
172
243
|
|
|
173
|
-
This directory
|
|
244
|
+
This directory provides Claude Code with auto-discovered commands, agents, and configuration.
|
|
245
|
+
|
|
246
|
+
## Architecture
|
|
247
|
+
|
|
248
|
+
This directory uses **symlinks** to \`../.ai-context/\` for all shared content:
|
|
249
|
+
|
|
250
|
+
\`\`\`
|
|
251
|
+
.claude/
|
|
252
|
+
├── agents → ../.ai-context/agents/
|
|
253
|
+
├── commands → ../.ai-context/commands/
|
|
254
|
+
├── indexes → ../.ai-context/indexes/
|
|
255
|
+
├── context → ../.ai-context/context/
|
|
256
|
+
├── schemas → ../.ai-context/schemas/
|
|
257
|
+
├── standards → ../.ai-context/standards/
|
|
258
|
+
├── tools → ../.ai-context/tools/
|
|
259
|
+
├── settings.json (this file - Claude-specific)
|
|
260
|
+
└── README.md (this file)
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
**Single source of truth:** All content lives in \`.ai-context/\`. Edit there, not here.
|
|
174
264
|
|
|
175
265
|
## Quick Start
|
|
176
266
|
|
|
@@ -182,22 +272,26 @@ This directory contains Claude Code-specific context engineering files.
|
|
|
182
272
|
|
|
183
273
|
See \`AI_CONTEXT.md\` at project root for universal AI context (works with all tools).
|
|
184
274
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **agents/** - Specialized agents for different tasks
|
|
188
|
-
- **commands/** - Custom slash commands
|
|
189
|
-
- **indexes/** - 3-level navigation system
|
|
190
|
-
- **context/** - Workflow documentation
|
|
191
|
-
|
|
192
|
-
*Generated by create-universal-ai-context v${context.version || '2.1.0'}*
|
|
275
|
+
*Generated by create-universal-ai-context v${context.version || '2.3.0'}*
|
|
193
276
|
`;
|
|
194
277
|
fs.writeFileSync(readmePath, readme);
|
|
195
278
|
filesCopied++;
|
|
196
279
|
|
|
280
|
+
// Add info message about symlink approach
|
|
281
|
+
if (linksCreated > 0) {
|
|
282
|
+
result.errors.push({
|
|
283
|
+
message: `Created ${linksCreated} symlinks from .claude/ to .ai-context/ (single source of truth)`,
|
|
284
|
+
code: 'SYMLINKS_CREATED',
|
|
285
|
+
severity: 'info'
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
197
289
|
return [{
|
|
198
290
|
path: claudeDir,
|
|
199
291
|
relativePath: '.claude/',
|
|
200
|
-
size: filesCopied
|
|
292
|
+
size: filesCopied,
|
|
293
|
+
symlinks: linksCreated,
|
|
294
|
+
details: `${linksCreated} symlinks, ${filesCopied} files`
|
|
201
295
|
}];
|
|
202
296
|
|
|
203
297
|
} catch (error) {
|
|
@@ -209,6 +303,36 @@ See \`AI_CONTEXT.md\` at project root for universal AI context (works with all t
|
|
|
209
303
|
}
|
|
210
304
|
}
|
|
211
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
|
+
|
|
212
336
|
/**
|
|
213
337
|
* Validate Claude output
|
|
214
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;
|