create-universal-ai-context 2.0.0 → 2.1.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/README.md +55 -23
- package/bin/create-ai-context.js +159 -1
- package/lib/doc-discovery.js +741 -0
- package/lib/drift-checker.js +920 -0
- package/lib/index.js +89 -7
- package/lib/placeholder.js +11 -1
- package/lib/prompts.js +55 -1
- package/lib/smart-merge.js +540 -0
- package/lib/spinner.js +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const chalk = require('chalk');
|
|
24
|
-
const { runPrompts, getDefaults } = require('./prompts');
|
|
24
|
+
const { runPrompts, getDefaults, runDiscoveryPrompts } = require('./prompts');
|
|
25
25
|
const { createSpinner } = require('./spinner');
|
|
26
26
|
const {
|
|
27
27
|
createDirectoryStructure,
|
|
@@ -45,6 +45,13 @@ const {
|
|
|
45
45
|
const { populateAllTemplates } = require('./template-populator');
|
|
46
46
|
const { generateAll: generateAllContexts, getSupportedTools } = require('./ai-context-generator');
|
|
47
47
|
|
|
48
|
+
// Documentation discovery for existing docs detection
|
|
49
|
+
const {
|
|
50
|
+
discoverExistingDocs,
|
|
51
|
+
formatDiscoverySummary,
|
|
52
|
+
buildMergedValues
|
|
53
|
+
} = require('./doc-discovery');
|
|
54
|
+
|
|
48
55
|
/**
|
|
49
56
|
* Main entry point
|
|
50
57
|
*/
|
|
@@ -65,7 +72,12 @@ async function run(options = {}) {
|
|
|
65
72
|
analyzeOnly = false,
|
|
66
73
|
// Monorepo options
|
|
67
74
|
monorepo = false,
|
|
68
|
-
federate = false
|
|
75
|
+
federate = false,
|
|
76
|
+
// Merge options (new)
|
|
77
|
+
mode = 'merge', // 'merge' | 'overwrite' | 'interactive'
|
|
78
|
+
preserveCustom = true,
|
|
79
|
+
updateRefs = false,
|
|
80
|
+
backup = false
|
|
69
81
|
} = options;
|
|
70
82
|
|
|
71
83
|
// Determine target directory
|
|
@@ -76,12 +88,70 @@ async function run(options = {}) {
|
|
|
76
88
|
const projectNameResolved = projectName || path.basename(targetDir);
|
|
77
89
|
const contextDir = path.join(targetDir, AI_CONTEXT_DIR);
|
|
78
90
|
|
|
79
|
-
|
|
91
|
+
const spinner = createSpinner();
|
|
92
|
+
|
|
93
|
+
// ========================================
|
|
94
|
+
// Phase 0: Documentation Discovery
|
|
95
|
+
// ========================================
|
|
96
|
+
spinner.start('Scanning for existing documentation...');
|
|
97
|
+
let discovery = null;
|
|
98
|
+
let mergeStrategy = mode;
|
|
99
|
+
let discoveredValues = {};
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
discovery = await discoverExistingDocs(targetDir);
|
|
103
|
+
|
|
104
|
+
if (discovery.hasExistingDocs) {
|
|
105
|
+
spinner.succeed('Found existing documentation');
|
|
106
|
+
|
|
107
|
+
if (verbose) {
|
|
108
|
+
console.log(chalk.gray(formatDiscoverySummary(discovery)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle discovery based on mode and prompts
|
|
112
|
+
if (!skipPrompts && mode !== 'overwrite') {
|
|
113
|
+
// Run discovery prompts
|
|
114
|
+
const discoveryAnswers = await runDiscoveryPrompts(discovery);
|
|
115
|
+
|
|
116
|
+
if (discoveryAnswers.existingDocsStrategy === 'skip') {
|
|
117
|
+
console.log(chalk.yellow('\nInitialization cancelled by user.\n'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
mergeStrategy = discoveryAnswers.existingDocsStrategy;
|
|
122
|
+
discoveredValues = buildMergedValues(
|
|
123
|
+
discovery,
|
|
124
|
+
mergeStrategy,
|
|
125
|
+
discoveryAnswers.conflictResolutions || {}
|
|
126
|
+
);
|
|
127
|
+
} else if (mode === 'merge') {
|
|
128
|
+
// Auto-merge without prompts
|
|
129
|
+
discoveredValues = buildMergedValues(discovery, 'merge', {});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Show what we're doing
|
|
133
|
+
if (mergeStrategy === 'merge') {
|
|
134
|
+
const valueCount = Object.keys(discoveredValues).length;
|
|
135
|
+
console.log(chalk.cyan(` Preserving ${valueCount} values from existing docs`));
|
|
136
|
+
} else if (mergeStrategy === 'overwrite') {
|
|
137
|
+
console.log(chalk.yellow(' Overwrite mode: existing docs will be replaced'));
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
spinner.succeed('No existing documentation found - starting fresh');
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
spinner.warn(`Discovery partial: ${error.message}`);
|
|
144
|
+
discovery = { hasExistingDocs: false };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ========================================
|
|
148
|
+
// Phase 1: Configuration (prompts or defaults)
|
|
149
|
+
// ========================================
|
|
80
150
|
let config;
|
|
81
151
|
if (skipPrompts) {
|
|
82
152
|
config = await getDefaults(targetDir, template);
|
|
83
153
|
} else {
|
|
84
|
-
config = await runPrompts(targetDir, template);
|
|
154
|
+
config = await runPrompts(targetDir, template, discovery);
|
|
85
155
|
}
|
|
86
156
|
|
|
87
157
|
config.projectName = projectNameResolved;
|
|
@@ -92,16 +162,28 @@ async function run(options = {}) {
|
|
|
92
162
|
config.verbose = verbose;
|
|
93
163
|
config.aiTools = aiTools;
|
|
94
164
|
config.monorepo = monorepo;
|
|
165
|
+
// Add merge config
|
|
166
|
+
config.mergeStrategy = mergeStrategy;
|
|
167
|
+
config.preserveCustom = preserveCustom;
|
|
168
|
+
config.updateRefs = updateRefs;
|
|
169
|
+
config.backup = backup;
|
|
170
|
+
config.discoveredValues = discoveredValues;
|
|
171
|
+
config.discovery = discovery;
|
|
95
172
|
|
|
96
173
|
if (dryRun) {
|
|
97
174
|
console.log(chalk.yellow('\n--dry-run mode: No changes will be made\n'));
|
|
98
175
|
console.log('Configuration:', JSON.stringify(config, null, 2));
|
|
176
|
+
if (discovery?.hasExistingDocs) {
|
|
177
|
+
console.log('\nDiscovery:', JSON.stringify({
|
|
178
|
+
tools: discovery.tools,
|
|
179
|
+
extractedValues: discovery.extractedValues,
|
|
180
|
+
conflicts: discovery.conflicts
|
|
181
|
+
}, null, 2));
|
|
182
|
+
}
|
|
99
183
|
return;
|
|
100
184
|
}
|
|
101
185
|
|
|
102
|
-
// Phase
|
|
103
|
-
const spinner = createSpinner();
|
|
104
|
-
|
|
186
|
+
// Phase 2: Create target directory if needed
|
|
105
187
|
if (projectName && !fs.existsSync(targetDir)) {
|
|
106
188
|
spinner.start('Creating project directory...');
|
|
107
189
|
fs.mkdirSync(targetDir, { recursive: true });
|
package/lib/placeholder.js
CHANGED
|
@@ -57,8 +57,14 @@ const KNOWN_PLACEHOLDERS = {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Get default placeholder values based on config, tech stack, and analysis
|
|
60
|
+
* @param {object} config - Configuration from CLI (includes discoveredValues from merge)
|
|
61
|
+
* @param {object} techStack - Detected tech stack
|
|
62
|
+
* @param {object} analysis - Codebase analysis results
|
|
63
|
+
* @returns {object} Placeholder values
|
|
60
64
|
*/
|
|
61
65
|
function getDefaultValues(config = {}, techStack = {}, analysis = {}) {
|
|
66
|
+
// Get discovered values from merge phase (if available)
|
|
67
|
+
const discoveredValues = config.discoveredValues || {};
|
|
62
68
|
const today = new Date().toISOString().split('T')[0];
|
|
63
69
|
const projectName = config.projectName || 'my-project';
|
|
64
70
|
const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
@@ -147,7 +153,7 @@ function getDefaultValues(config = {}, techStack = {}, analysis = {}) {
|
|
|
147
153
|
// Entry point count
|
|
148
154
|
const entryPointCount = analysis.entryPoints?.length || techStack.entryPoints?.length || 0;
|
|
149
155
|
|
|
150
|
-
|
|
156
|
+
const defaults = {
|
|
151
157
|
// Project identity
|
|
152
158
|
PROJECT_NAME: projectName,
|
|
153
159
|
PROJECT_SLUG: projectSlug,
|
|
@@ -234,6 +240,10 @@ function getDefaultValues(config = {}, techStack = {}, analysis = {}) {
|
|
|
234
240
|
PRIMARY_FRAMEWORK: techStack.frameworks?.[0] || '',
|
|
235
241
|
DATABASE_TYPE: techStack.databases?.[0] || '',
|
|
236
242
|
};
|
|
243
|
+
|
|
244
|
+
// Merge with discovered values - discovered values take precedence
|
|
245
|
+
// This preserves user customizations from existing documentation
|
|
246
|
+
return { ...defaults, ...discoveredValues };
|
|
237
247
|
}
|
|
238
248
|
|
|
239
249
|
/**
|
package/lib/prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Interactive prompts for create-
|
|
2
|
+
* Interactive prompts for create-universal-ai-context
|
|
3
3
|
*
|
|
4
4
|
* Uses enquirer for beautiful, user-friendly prompts
|
|
5
5
|
*/
|
|
@@ -280,8 +280,62 @@ async function getDefaults(targetDir, presetName = null) {
|
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Run discovery prompts for handling existing documentation
|
|
285
|
+
* @param {object} discovery - Discovery results from doc-discovery.js
|
|
286
|
+
* @returns {Promise<object>} User's choices for handling existing docs
|
|
287
|
+
*/
|
|
288
|
+
async function runDiscoveryPrompts(discovery) {
|
|
289
|
+
if (!discovery || !discovery.hasExistingDocs) {
|
|
290
|
+
return { existingDocsStrategy: 'fresh' };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const { generateDiscoveryPrompts } = require('./doc-discovery');
|
|
294
|
+
const prompts = generateDiscoveryPrompts(discovery);
|
|
295
|
+
|
|
296
|
+
if (prompts.length === 0) {
|
|
297
|
+
return { existingDocsStrategy: 'fresh' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(chalk.gray('\nExisting documentation detected.\n'));
|
|
301
|
+
|
|
302
|
+
const answers = await prompt(prompts);
|
|
303
|
+
|
|
304
|
+
// Handle conflict resolution if user chose 'ask'
|
|
305
|
+
if (answers.conflictResolution === 'ask' && discovery.conflicts.length > 0) {
|
|
306
|
+
const conflictResolutions = {};
|
|
307
|
+
|
|
308
|
+
for (const conflict of discovery.conflicts) {
|
|
309
|
+
const conflictAnswer = await prompt([{
|
|
310
|
+
type: 'select',
|
|
311
|
+
name: 'resolution',
|
|
312
|
+
message: `Conflict for ${chalk.cyan(conflict.key)}:\n Existing (${conflict.existingSource}): "${truncate(conflict.existingValue, 40)}"\n New (${conflict.newSource}): "${truncate(conflict.newValue, 40)}"`,
|
|
313
|
+
choices: [
|
|
314
|
+
{ name: 'existing', message: 'Keep existing value' },
|
|
315
|
+
{ name: 'new', message: 'Use new value' }
|
|
316
|
+
]
|
|
317
|
+
}]);
|
|
318
|
+
conflictResolutions[conflict.key] = conflictAnswer.resolution;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
answers.conflictResolutions = conflictResolutions;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return answers;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Truncate a string with ellipsis
|
|
329
|
+
*/
|
|
330
|
+
function truncate(str, maxLength) {
|
|
331
|
+
if (!str) return '';
|
|
332
|
+
if (str.length <= maxLength) return str;
|
|
333
|
+
return str.substring(0, maxLength - 3) + '...';
|
|
334
|
+
}
|
|
335
|
+
|
|
283
336
|
module.exports = {
|
|
284
337
|
runPrompts,
|
|
285
338
|
getDefaults,
|
|
339
|
+
runDiscoveryPrompts,
|
|
286
340
|
PRESETS
|
|
287
341
|
};
|