create-universal-ai-context 2.2.1 → 2.3.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/lib/adapters/claude.js +77 -28
- package/lib/cross-tool-sync/sync-manager.js +3 -3
- package/lib/migrate.js +17 -8
- package/package.json +1 -1
package/lib/adapters/claude.js
CHANGED
|
@@ -73,7 +73,9 @@ async function generate(analysis, config, projectRoot) {
|
|
|
73
73
|
result.files.push(...claudeDirResult);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// Success if no actual errors (warnings and info are OK)
|
|
77
|
+
result.success = result.errors.length === 0 ||
|
|
78
|
+
result.errors.every(e => e.code === 'EXISTS' || e.severity === 'info' || e.severity === 'warning');
|
|
77
79
|
} catch (error) {
|
|
78
80
|
result.errors.push({
|
|
79
81
|
message: error.message,
|
|
@@ -85,7 +87,7 @@ async function generate(analysis, config, projectRoot) {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/**
|
|
88
|
-
* Generate .claude/ directory with
|
|
90
|
+
* Generate .claude/ directory with symlinks to .ai-context/
|
|
89
91
|
* @param {string} projectRoot - Project root directory
|
|
90
92
|
* @param {object} context - Template context
|
|
91
93
|
* @param {object} result - Result object to track files/errors
|
|
@@ -94,6 +96,7 @@ async function generate(analysis, config, projectRoot) {
|
|
|
94
96
|
async function generateClaudeDirectory(projectRoot, context, result) {
|
|
95
97
|
const { copyDirectory } = require('../installer');
|
|
96
98
|
const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
|
|
99
|
+
const aiContextDir = path.join(projectRoot, '.ai-context');
|
|
97
100
|
const claudeDir = path.join(projectRoot, '.claude');
|
|
98
101
|
|
|
99
102
|
// Don't overwrite existing .claude/ directory
|
|
@@ -115,29 +118,52 @@ async function generateClaudeDirectory(projectRoot, context, result) {
|
|
|
115
118
|
// Create .claude/ directory
|
|
116
119
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
117
120
|
|
|
118
|
-
//
|
|
119
|
-
const
|
|
121
|
+
// Subdirectories to symlink from .ai-context/
|
|
122
|
+
const subdirsToLink = [
|
|
120
123
|
'agents',
|
|
121
124
|
'commands',
|
|
122
125
|
'indexes',
|
|
123
126
|
'context',
|
|
124
127
|
'schemas',
|
|
125
|
-
'standards'
|
|
128
|
+
'standards',
|
|
129
|
+
'tools'
|
|
126
130
|
];
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
if (context.features?.tools !== false) {
|
|
130
|
-
subdirsToCopy.push('tools');
|
|
131
|
-
}
|
|
132
|
-
|
|
132
|
+
let linksCreated = 0;
|
|
133
133
|
let filesCopied = 0;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
|
|
135
|
+
for (const subdir of subdirsToLink) {
|
|
136
|
+
const srcPath = path.join(aiContextDir, subdir);
|
|
137
|
+
const destPath = path.join(claudeDir, subdir);
|
|
138
|
+
|
|
139
|
+
// Skip if source doesn't exist
|
|
140
|
+
if (!fs.existsSync(srcPath)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Try to create symlink, fallback to copy
|
|
145
|
+
try {
|
|
146
|
+
// Remove dest if it exists (shouldn't, but safety)
|
|
147
|
+
if (fs.existsSync(destPath)) {
|
|
148
|
+
if (fs.lstatSync(destPath).isSymbolicLink()) {
|
|
149
|
+
fs.unlinkSync(destPath);
|
|
150
|
+
} else {
|
|
151
|
+
// Existing directory, skip
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Create symlink
|
|
157
|
+
fs.symlinkSync(srcPath, destPath, 'junction');
|
|
158
|
+
linksCreated++;
|
|
159
|
+
} catch (symlinkError) {
|
|
160
|
+
// Symlink failed (likely Windows permissions or filesystem limitation)
|
|
161
|
+
// Fallback: copy directory contents
|
|
162
|
+
if (!fs.existsSync(destPath)) {
|
|
163
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
164
|
+
const count = await copyDirectory(srcPath, destPath);
|
|
165
|
+
filesCopied += count;
|
|
166
|
+
}
|
|
141
167
|
}
|
|
142
168
|
}
|
|
143
169
|
|
|
@@ -145,7 +171,7 @@ async function generateClaudeDirectory(projectRoot, context, result) {
|
|
|
145
171
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
146
172
|
const settings = {
|
|
147
173
|
'$schema': './schemas/settings.schema.json',
|
|
148
|
-
version: '2.
|
|
174
|
+
version: '2.2.2',
|
|
149
175
|
project: {
|
|
150
176
|
name: context.project?.name || 'Project',
|
|
151
177
|
tech_stack: context.project?.tech_stack || 'Not detected'
|
|
@@ -170,7 +196,26 @@ async function generateClaudeDirectory(projectRoot, context, result) {
|
|
|
170
196
|
const readmePath = path.join(claudeDir, 'README.md');
|
|
171
197
|
const readme = `# .claude Configuration - ${context.project?.name || 'Project'}
|
|
172
198
|
|
|
173
|
-
This directory
|
|
199
|
+
This directory provides Claude Code with auto-discovered commands, agents, and configuration.
|
|
200
|
+
|
|
201
|
+
## Architecture
|
|
202
|
+
|
|
203
|
+
This directory uses **symlinks** to \`../.ai-context/\` for all shared content:
|
|
204
|
+
|
|
205
|
+
\`\`\`
|
|
206
|
+
.claude/
|
|
207
|
+
├── agents → ../.ai-context/agents/
|
|
208
|
+
├── commands → ../.ai-context/commands/
|
|
209
|
+
├── indexes → ../.ai-context/indexes/
|
|
210
|
+
├── context → ../.ai-context/context/
|
|
211
|
+
├── schemas → ../.ai-context/schemas/
|
|
212
|
+
├── standards → ../.ai-context/standards/
|
|
213
|
+
├── tools → ../.ai-context/tools/
|
|
214
|
+
├── settings.json (this file - Claude-specific)
|
|
215
|
+
└── README.md (this file)
|
|
216
|
+
\`\`\`
|
|
217
|
+
|
|
218
|
+
**Single source of truth:** All content lives in \`.ai-context/\`. Edit there, not here.
|
|
174
219
|
|
|
175
220
|
## Quick Start
|
|
176
221
|
|
|
@@ -182,22 +227,26 @@ This directory contains Claude Code-specific context engineering files.
|
|
|
182
227
|
|
|
183
228
|
See \`AI_CONTEXT.md\` at project root for universal AI context (works with all tools).
|
|
184
229
|
|
|
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'}*
|
|
230
|
+
*Generated by create-universal-ai-context v${context.version || '2.3.0'}*
|
|
193
231
|
`;
|
|
194
232
|
fs.writeFileSync(readmePath, readme);
|
|
195
233
|
filesCopied++;
|
|
196
234
|
|
|
235
|
+
// Add info message about symlink approach
|
|
236
|
+
if (linksCreated > 0) {
|
|
237
|
+
result.errors.push({
|
|
238
|
+
message: `Created ${linksCreated} symlinks from .claude/ to .ai-context/ (single source of truth)`,
|
|
239
|
+
code: 'SYMLINKS_CREATED',
|
|
240
|
+
severity: 'info'
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
197
244
|
return [{
|
|
198
245
|
path: claudeDir,
|
|
199
246
|
relativePath: '.claude/',
|
|
200
|
-
size: filesCopied
|
|
247
|
+
size: filesCopied,
|
|
248
|
+
symlinks: linksCreated,
|
|
249
|
+
details: `${linksCreated} symlinks, ${filesCopied} files`
|
|
201
250
|
}];
|
|
202
251
|
|
|
203
252
|
} catch (error) {
|
|
@@ -9,7 +9,7 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const crypto = require('crypto');
|
|
11
11
|
const { getAdapter, getAllAdapters, getAdapterNames } = require('../adapters');
|
|
12
|
-
const {
|
|
12
|
+
const { analyzeCodebase } = require('../static-analyzer');
|
|
13
13
|
const { generateAll, initialize: initGenerator } = require('../ai-context-generator');
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -221,7 +221,7 @@ async function propagateContextChange(sourceTool, projectRoot, config, strategy
|
|
|
221
221
|
// 1. Re-analyze codebase to get fresh analysis
|
|
222
222
|
let analysis;
|
|
223
223
|
try {
|
|
224
|
-
analysis = await
|
|
224
|
+
analysis = await analyzeCodebase(projectRoot, config);
|
|
225
225
|
} catch (error) {
|
|
226
226
|
results.errors.push({
|
|
227
227
|
message: `Failed to analyze project: ${error.message}`
|
|
@@ -328,7 +328,7 @@ async function syncAllFromCodebase(projectRoot, config) {
|
|
|
328
328
|
|
|
329
329
|
try {
|
|
330
330
|
// Analyze project
|
|
331
|
-
const analysis = await
|
|
331
|
+
const analysis = await analyzeCodebase(projectRoot, config);
|
|
332
332
|
|
|
333
333
|
// Generate for all tools
|
|
334
334
|
const generateResults = await generateAll(analysis, config, projectRoot, {
|
package/lib/migrate.js
CHANGED
|
@@ -274,6 +274,11 @@ async function migrateV1ToV2(projectRoot, options = {}) {
|
|
|
274
274
|
function getMigrationStatus(projectRoot) {
|
|
275
275
|
const detection = detectV1Installation(projectRoot);
|
|
276
276
|
|
|
277
|
+
// In v2.0, both .ai-context/ AND .claude/ can exist
|
|
278
|
+
// The presence of AI_CONTEXT.md indicates v2.0 installation
|
|
279
|
+
// .claude/ in v2.0 is created by the Claude adapter for Claude-specific features
|
|
280
|
+
const hasAiContextMd = fs.existsSync(path.join(projectRoot, MIGRATIONS.entryFile.new));
|
|
281
|
+
|
|
277
282
|
if (!detection.hasV1 && !detection.hasV2) {
|
|
278
283
|
return {
|
|
279
284
|
status: 'none',
|
|
@@ -282,24 +287,28 @@ function getMigrationStatus(projectRoot) {
|
|
|
282
287
|
};
|
|
283
288
|
}
|
|
284
289
|
|
|
285
|
-
if
|
|
290
|
+
// If AI_CONTEXT.md exists, this is v2.0 (even if .claude/ also exists)
|
|
291
|
+
if (hasAiContextMd) {
|
|
286
292
|
return {
|
|
287
|
-
status: '
|
|
288
|
-
message: '
|
|
289
|
-
needsMigration:
|
|
293
|
+
status: 'v2',
|
|
294
|
+
message: 'v2.0 installation found, no migration needed',
|
|
295
|
+
needsMigration: false,
|
|
290
296
|
details: detection
|
|
291
297
|
};
|
|
292
298
|
}
|
|
293
299
|
|
|
294
|
-
|
|
300
|
+
// If .claude/ exists but no AI_CONTEXT.md, this is v1.x
|
|
301
|
+
if (detection.hasV1 && !detection.hasV2) {
|
|
295
302
|
return {
|
|
296
|
-
status: '
|
|
297
|
-
message: '
|
|
298
|
-
needsMigration:
|
|
303
|
+
status: 'v1',
|
|
304
|
+
message: 'v1.x installation found, migration available',
|
|
305
|
+
needsMigration: true,
|
|
299
306
|
details: detection
|
|
300
307
|
};
|
|
301
308
|
}
|
|
302
309
|
|
|
310
|
+
// True mixed state: both v1.x indicators and v2.0 indicators exist
|
|
311
|
+
// This shouldn't happen normally, but handle it
|
|
303
312
|
if (detection.hasV1 && detection.hasV2) {
|
|
304
313
|
return {
|
|
305
314
|
status: 'mixed',
|
package/package.json
CHANGED