create-universal-ai-context 2.0.0 → 2.1.2

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/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
- // Get configuration (prompts or defaults)
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 1: Create target directory if needed
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/installer.js CHANGED
@@ -415,4 +415,5 @@ module.exports = {
415
415
  DIRECTORY_STRUCTURE,
416
416
  AI_CONTEXT_DIR,
417
417
  AI_CONTEXT_FILE,
418
+ copyDirectory, // Export for use by adapters
418
419
  };
@@ -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
- return {
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-claude-context
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
  };