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/README.md CHANGED
@@ -29,36 +29,68 @@ That's it. The CLI automatically:
29
29
 
30
30
  ```bash
31
31
  # Basic usage
32
- npx create-ai-context # Auto-detect and generate for all tools
32
+ npx create-universal-ai-context # Auto-detect and generate for all tools
33
33
 
34
34
  # Select specific AI tools
35
- npx create-ai-context --ai claude # Claude Code only
36
- npx create-ai-context --ai copilot # GitHub Copilot only
37
- npx create-ai-context --ai cline # Cline only
38
- npx create-ai-context --ai antigravity # Antigravity only
39
- npx create-ai-context --ai all # All tools (default)
35
+ npx create-universal-ai-context --ai claude # Claude Code only
36
+ npx create-universal-ai-context --ai copilot # GitHub Copilot only
37
+ npx create-universal-ai-context --ai cline # Cline only
38
+ npx create-universal-ai-context --ai antigravity # Antigravity only
39
+ npx create-universal-ai-context --ai all # All tools (default)
40
40
 
41
41
  # Analysis modes
42
- npx create-ai-context --static # Force static analysis only
43
- npx create-ai-context --force-ai # Require Claude Code session
42
+ npx create-universal-ai-context --static # Force static analysis only
43
+ npx create-universal-ai-context --force-ai # Require Claude Code session
44
44
 
45
45
  # Other options
46
- npx create-ai-context --yes # Accept all defaults
47
- npx create-ai-context --dry-run # Preview without changes
48
- npx create-ai-context my-project # Create in new directory
46
+ npx create-universal-ai-context --yes # Accept all defaults
47
+ npx create-universal-ai-context --dry-run # Preview without changes
48
+ npx create-universal-ai-context my-project # Create in new directory
49
49
  ```
50
50
 
51
51
  ## Subcommands
52
52
 
53
53
  ```bash
54
54
  # Generate context for specific tools
55
- npx create-ai-context generate --ai copilot
55
+ npx create-universal-ai-context generate --ai copilot
56
56
 
57
57
  # Check installation status
58
- npx create-ai-context status
58
+ npx create-universal-ai-context status
59
59
 
60
60
  # Migrate from v1.x
61
- npx create-ai-context migrate
61
+ npx create-universal-ai-context migrate
62
+
63
+ # Check documentation drift
64
+ npx create-universal-ai-context drift --all # Check all docs
65
+ npx create-universal-ai-context drift --file README.md # Check specific file
66
+ npx create-universal-ai-context drift --fix # Auto-fix issues
67
+ npx create-universal-ai-context drift --strict # Exit 1 on issues (CI)
68
+ ```
69
+
70
+ ## Existing Documentation Detection
71
+
72
+ The CLI automatically detects existing AI context files:
73
+
74
+ ```bash
75
+ npx create-universal-ai-context
76
+
77
+ # Found existing documentation: Claude context (v1), README.md
78
+ # ? How would you like to proceed?
79
+ # > Merge: Use existing docs as base, add new structure (recommended)
80
+ # Fresh: Start fresh but import key values
81
+ # Overwrite: Replace everything with new templates
82
+ # Skip: Cancel initialization
83
+ ```
84
+
85
+ ### Merge Mode Options
86
+
87
+ ```bash
88
+ npx create-universal-ai-context --mode merge # Preserve customizations (default)
89
+ npx create-universal-ai-context --mode fresh # New structure, keep values
90
+ npx create-universal-ai-context --mode overwrite # Replace everything
91
+ npx create-universal-ai-context --preserve-custom # Keep user customizations
92
+ npx create-universal-ai-context --update-refs # Auto-fix line numbers
93
+ npx create-universal-ai-context --backup # Create backup first
62
94
  ```
63
95
 
64
96
  ## What Gets Analyzed
@@ -104,14 +136,14 @@ your-project/
104
136
  ## Tech Stack Presets
105
137
 
106
138
  ```bash
107
- npx create-ai-context -t python-fastapi
108
- npx create-ai-context -t python-django
109
- npx create-ai-context -t node-express
110
- npx create-ai-context -t node-nestjs
111
- npx create-ai-context -t typescript-nextjs
112
- npx create-ai-context -t go-gin
113
- npx create-ai-context -t rust-actix
114
- npx create-ai-context -t ruby-rails
139
+ npx create-universal-ai-context -t python-fastapi
140
+ npx create-universal-ai-context -t python-django
141
+ npx create-universal-ai-context -t node-express
142
+ npx create-universal-ai-context -t node-nestjs
143
+ npx create-universal-ai-context -t typescript-nextjs
144
+ npx create-universal-ai-context -t go-gin
145
+ npx create-universal-ai-context -t rust-actix
146
+ npx create-universal-ai-context -t ruby-rails
115
147
  ```
116
148
 
117
149
  ## Features
@@ -134,7 +166,7 @@ npx create-ai-context -t ruby-rails
134
166
  If you have an existing `.claude/` directory:
135
167
 
136
168
  ```bash
137
- npx create-ai-context migrate
169
+ npx create-universal-ai-context migrate
138
170
  ```
139
171
 
140
172
  This will:
@@ -25,6 +25,12 @@ const { migrateV1ToV2, getMigrationStatus } = require('../lib/migrate');
25
25
  const { detectTechStack } = require('../lib/detector');
26
26
  const { analyzeCodebase } = require('../lib/static-analyzer');
27
27
  const { createSpinner } = require('../lib/spinner');
28
+ const {
29
+ findDocumentationFiles,
30
+ generateDriftReport,
31
+ checkDocumentDrift,
32
+ formatDriftReportConsole
33
+ } = require('../lib/drift-checker');
28
34
  const packageJson = require('../package.json');
29
35
 
30
36
  // ASCII Banner
@@ -70,6 +76,10 @@ program
70
76
  .option('--analyze-only', 'Run codebase analysis without installation')
71
77
  .option('--monorepo', 'Initialize in monorepo mode with federation support')
72
78
  .option('--federate', 'Run federation to generate context for subprojects')
79
+ .option('--mode <mode>', 'How to handle existing docs: merge, overwrite, interactive', 'merge')
80
+ .option('--preserve-custom', 'Keep user customizations when merging (default: true)', true)
81
+ .option('--update-refs', 'Auto-fix drifted line references')
82
+ .option('--backup', 'Create backup before modifying existing files')
73
83
  .action(async (projectName, options) => {
74
84
  console.log(banner);
75
85
 
@@ -94,7 +104,12 @@ program
94
104
  forceStatic: options.static,
95
105
  analyzeOnly: options.analyzeOnly,
96
106
  monorepo: options.monorepo,
97
- federate: options.federate
107
+ federate: options.federate,
108
+ // Merge options
109
+ mode: options.mode,
110
+ preserveCustom: options.preserveCustom,
111
+ updateRefs: options.updateRefs,
112
+ backup: options.backup
98
113
  });
99
114
  } catch (error) {
100
115
  console.error(chalk.red('\n✖ Error:'), error.message);
@@ -334,4 +349,147 @@ program
334
349
  }
335
350
  });
336
351
 
352
+ // Drift subcommand - check documentation drift
353
+ program
354
+ .command('drift')
355
+ .description('Check documentation drift against codebase')
356
+ .option('-f, --file <path>', 'Check specific documentation file')
357
+ .option('-a, --all', 'Check all documentation files')
358
+ .option('--fix', 'Show suggested fixes for issues')
359
+ .option('--strict', 'Exit with error if drift detected')
360
+ .option('-o, --output <format>', 'Output format: console, json, markdown', 'console')
361
+ .option('-t, --threshold <percent>', 'Health score threshold for --strict', '70')
362
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
363
+ .option('-v, --verbose', 'Show detailed output')
364
+ .action(async (options) => {
365
+ console.log(banner);
366
+
367
+ const projectRoot = path.resolve(options.path);
368
+ const spinner = createSpinner();
369
+
370
+ try {
371
+ // Determine which files to check
372
+ let filesToCheck = [];
373
+
374
+ if (options.file) {
375
+ // Single file mode
376
+ filesToCheck = [options.file];
377
+ } else if (options.all) {
378
+ // All documentation files
379
+ spinner.start('Finding documentation files...');
380
+ filesToCheck = await findDocumentationFiles(projectRoot);
381
+ spinner.succeed(`Found ${filesToCheck.length} documentation files`);
382
+ } else {
383
+ // Default: check main context files
384
+ const defaultFiles = ['CLAUDE.md', 'AI_CONTEXT.md', 'README.md'];
385
+ filesToCheck = defaultFiles.filter(f =>
386
+ fs.existsSync(path.join(projectRoot, f))
387
+ );
388
+
389
+ if (filesToCheck.length === 0) {
390
+ console.log(chalk.yellow('\nNo documentation files found.'));
391
+ console.log(chalk.gray('Use --all to scan for all markdown files, or --file to check a specific file.'));
392
+ process.exit(0);
393
+ }
394
+ }
395
+
396
+ // Generate drift report
397
+ spinner.start('Checking documentation drift...');
398
+ const report = generateDriftReport(filesToCheck, projectRoot);
399
+ spinner.succeed(`Checked ${report.summary.totalDocuments} documents`);
400
+
401
+ // Output results
402
+ if (options.output === 'json') {
403
+ console.log(JSON.stringify(report, null, 2));
404
+ } else if (options.output === 'markdown') {
405
+ console.log(formatDriftReportMarkdown(report));
406
+ } else {
407
+ // Console output
408
+ console.log(formatDriftReportConsole(report));
409
+ }
410
+
411
+ // Show suggested fixes if requested
412
+ if (options.fix && report.suggestedFixes.length > 0) {
413
+ console.log(chalk.bold('\nSuggested Fixes:'));
414
+ for (const fix of report.suggestedFixes) {
415
+ console.log(chalk.cyan(`\n ${fix.document}:`));
416
+ console.log(chalk.red(` - ${fix.original}`));
417
+ console.log(chalk.green(` + ${fix.suggestion}`));
418
+ }
419
+ }
420
+
421
+ // Strict mode - exit with error if below threshold
422
+ if (options.strict) {
423
+ const threshold = parseInt(options.threshold, 10);
424
+ if (report.summary.overallHealthScore < threshold) {
425
+ console.log(chalk.red(`\n✖ Health score ${report.summary.overallHealthScore}% is below threshold ${threshold}%`));
426
+ process.exit(1);
427
+ } else {
428
+ console.log(chalk.green(`\n✓ Health score ${report.summary.overallHealthScore}% meets threshold ${threshold}%`));
429
+ }
430
+ }
431
+
432
+ } catch (error) {
433
+ spinner.fail('Drift check failed');
434
+ console.error(chalk.red('\n✖ Error:'), error.message);
435
+ if (options.verbose) {
436
+ console.error(chalk.gray(error.stack));
437
+ }
438
+ process.exit(1);
439
+ }
440
+ });
441
+
442
+ /**
443
+ * Format drift report as markdown
444
+ */
445
+ function formatDriftReportMarkdown(report) {
446
+ const lines = [
447
+ '# Documentation Drift Report',
448
+ '',
449
+ `**Generated:** ${report.generatedAt}`,
450
+ `**Overall Health:** ${report.summary.overallHealthScore}%`,
451
+ '',
452
+ '## Summary',
453
+ '',
454
+ '| Metric | Value |',
455
+ '|--------|-------|',
456
+ `| Documents Analyzed | ${report.summary.totalDocuments} |`,
457
+ `| Healthy | ${report.summary.healthyDocuments} |`,
458
+ `| With Issues | ${report.summary.documentsWithIssues} |`,
459
+ `| References Valid | ${report.summary.validReferences}/${report.summary.totalReferences} |`,
460
+ ''
461
+ ];
462
+
463
+ if (report.documents.length > 0) {
464
+ lines.push('## Documents', '');
465
+ for (const doc of report.documents) {
466
+ const emoji = doc.status === 'healthy' ? '✓' :
467
+ doc.status === 'needs_update' ? '⚠' : '✗';
468
+ lines.push(`### ${doc.document} (${doc.healthScore}% ${emoji})`);
469
+ lines.push('');
470
+
471
+ if (doc.references.invalid.length > 0) {
472
+ lines.push('**Issues:**', '');
473
+ for (const issue of doc.references.invalid) {
474
+ lines.push(`- \`${issue.original}\` - ${issue.issue}`);
475
+ if (issue.suggestion) {
476
+ lines.push(` - Suggestion: ${issue.suggestion}`);
477
+ }
478
+ }
479
+ lines.push('');
480
+ }
481
+ }
482
+ }
483
+
484
+ if (report.suggestedFixes.length > 0) {
485
+ lines.push('## Suggested Fixes', '');
486
+ for (const fix of report.suggestedFixes) {
487
+ lines.push(`- **${fix.document}**: \`${fix.original}\``);
488
+ lines.push(` - → ${fix.suggestion}`);
489
+ }
490
+ }
491
+
492
+ return lines.join('\n');
493
+ }
494
+
337
495
  program.parse();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Claude Adapter
3
3
  *
4
- * Generates AI_CONTEXT.md and .ai-context/ directory structure.
4
+ * Generates AI_CONTEXT.md and .claude/ directory structure.
5
5
  * This is the primary/universal format.
6
6
  */
7
7
 
@@ -16,8 +16,8 @@ const adapter = {
16
16
  name: 'claude',
17
17
  displayName: 'Claude Code',
18
18
  description: 'Universal AI context format for Claude Code and other AI assistants',
19
- outputType: 'single-file',
20
- outputPath: 'AI_CONTEXT.md'
19
+ outputType: 'multi-file',
20
+ outputPath: '.claude/'
21
21
  };
22
22
 
23
23
  /**
@@ -35,11 +35,13 @@ function getOutputPath(projectRoot) {
35
35
  * @returns {boolean}
36
36
  */
37
37
  function exists(projectRoot) {
38
- return fs.existsSync(getOutputPath(projectRoot));
38
+ const aiContextPath = getOutputPath(projectRoot);
39
+ const claudeDir = path.join(projectRoot, '.claude');
40
+ return fs.existsSync(aiContextPath) || fs.existsSync(claudeDir);
39
41
  }
40
42
 
41
43
  /**
42
- * Generate Claude context file
44
+ * Generate Claude context file and .claude/ directory structure
43
45
  * @param {object} analysis - Analysis results from static analyzer
44
46
  * @param {object} config - Configuration from CLI
45
47
  * @param {string} projectRoot - Project root directory
@@ -54,22 +56,24 @@ async function generate(analysis, config, projectRoot) {
54
56
  };
55
57
 
56
58
  try {
57
- // Build context from analysis
59
+ // 1. Generate AI_CONTEXT.md at project root (existing behavior)
58
60
  const context = buildContext(analysis, config);
59
-
60
- // Render template
61
61
  const content = renderTemplateByName('claude', context);
62
-
63
- // Write output file
64
62
  const outputPath = getOutputPath(projectRoot);
65
63
  fs.writeFileSync(outputPath, content, 'utf-8');
66
-
67
- result.success = true;
68
64
  result.files.push({
69
65
  path: outputPath,
70
66
  relativePath: 'AI_CONTEXT.md',
71
67
  size: content.length
72
68
  });
69
+
70
+ // 2. Generate .claude/ directory structure (NEW)
71
+ const claudeDirResult = await generateClaudeDirectory(projectRoot, context, result);
72
+ if (claudeDirResult) {
73
+ result.files.push(...claudeDirResult);
74
+ }
75
+
76
+ result.success = result.errors.length === 0 || result.errors.some(e => e.code === 'EXISTS');
73
77
  } catch (error) {
74
78
  result.errors.push({
75
79
  message: error.message,
@@ -80,36 +84,183 @@ async function generate(analysis, config, projectRoot) {
80
84
  return result;
81
85
  }
82
86
 
87
+ /**
88
+ * Generate .claude/ directory with full structure
89
+ * @param {string} projectRoot - Project root directory
90
+ * @param {object} context - Template context
91
+ * @param {object} result - Result object to track files/errors
92
+ * @returns {Array} List of generated files
93
+ */
94
+ async function generateClaudeDirectory(projectRoot, context, result) {
95
+ const { copyDirectory } = require('../installer');
96
+ const templatesDir = path.join(__dirname, '..', '..', 'templates', 'base');
97
+ const claudeDir = path.join(projectRoot, '.claude');
98
+
99
+ // Don't overwrite existing .claude/ directory
100
+ if (fs.existsSync(claudeDir)) {
101
+ result.errors.push({
102
+ message: '.claude/ directory already exists, skipping structure generation',
103
+ code: 'EXISTS',
104
+ severity: 'warning'
105
+ });
106
+ return [{
107
+ path: claudeDir,
108
+ relativePath: '.claude/',
109
+ size: 0,
110
+ skipped: true
111
+ }];
112
+ }
113
+
114
+ try {
115
+ // Create .claude/ directory
116
+ fs.mkdirSync(claudeDir, { recursive: true });
117
+
118
+ // Copy relevant subdirectories from templates/base to .claude/
119
+ const subdirsToCopy = [
120
+ 'agents',
121
+ 'commands',
122
+ 'indexes',
123
+ 'context',
124
+ 'schemas',
125
+ 'standards'
126
+ ];
127
+
128
+ // Only copy tools if explicitly enabled
129
+ if (context.features?.tools !== false) {
130
+ subdirsToCopy.push('tools');
131
+ }
132
+
133
+ let filesCopied = 0;
134
+ for (const subdir of subdirsToCopy) {
135
+ const srcPath = path.join(templatesDir, subdir);
136
+ if (fs.existsSync(srcPath)) {
137
+ const destPath = path.join(claudeDir, subdir);
138
+ fs.mkdirSync(destPath, { recursive: true });
139
+ const count = await copyDirectory(srcPath, destPath);
140
+ filesCopied += count;
141
+ }
142
+ }
143
+
144
+ // Create minimal .claude/settings.json
145
+ const settingsPath = path.join(claudeDir, 'settings.json');
146
+ const settings = {
147
+ '$schema': './schemas/settings.schema.json',
148
+ version: '2.1.0',
149
+ project: {
150
+ name: context.project?.name || 'Project',
151
+ tech_stack: context.project?.tech_stack || 'Not detected'
152
+ },
153
+ agents: {
154
+ context_engineer: 'enabled',
155
+ core_architect: 'enabled',
156
+ api_developer: 'enabled',
157
+ database_ops: 'enabled',
158
+ integration_hub: 'enabled',
159
+ deployment_ops: 'enabled'
160
+ },
161
+ commands: {
162
+ rpi_workflow: 'enabled',
163
+ validation: 'enabled'
164
+ }
165
+ };
166
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
167
+ filesCopied++;
168
+
169
+ // Create .claude/README.md
170
+ const readmePath = path.join(claudeDir, 'README.md');
171
+ const readme = `# .claude Configuration - ${context.project?.name || 'Project'}
172
+
173
+ This directory contains Claude Code-specific context engineering files.
174
+
175
+ ## Quick Start
176
+
177
+ 1. Load agents: \`@context-engineer\`
178
+ 2. Use commands: \`/rpi-research\`, \`/rpi-plan\`, \`/rpi-implement\`
179
+ 3. Validate: \`/verify-docs-current\`
180
+
181
+ ## Universal Context
182
+
183
+ See \`AI_CONTEXT.md\` at project root for universal AI context (works with all tools).
184
+
185
+ ## Claude-Specific Files
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'}*
193
+ `;
194
+ fs.writeFileSync(readmePath, readme);
195
+ filesCopied++;
196
+
197
+ return [{
198
+ path: claudeDir,
199
+ relativePath: '.claude/',
200
+ size: filesCopied
201
+ }];
202
+
203
+ } catch (error) {
204
+ result.errors.push({
205
+ message: `Failed to generate .claude/ directory: ${error.message}`,
206
+ stack: error.stack
207
+ });
208
+ return null;
209
+ }
210
+ }
211
+
83
212
  /**
84
213
  * Validate Claude output
85
214
  * @param {string} projectRoot - Project root directory
86
215
  * @returns {object} Validation result
87
216
  */
88
217
  function validate(projectRoot) {
89
- const outputPath = getOutputPath(projectRoot);
218
+ const issues = [];
90
219
 
220
+ // 1. Validate AI_CONTEXT.md
221
+ const outputPath = getOutputPath(projectRoot);
91
222
  if (!fs.existsSync(outputPath)) {
92
- return {
93
- valid: false,
94
- error: 'AI_CONTEXT.md not found'
95
- };
223
+ issues.push({ file: 'AI_CONTEXT.md', error: 'not found' });
224
+ } else {
225
+ const content = fs.readFileSync(outputPath, 'utf-8');
226
+ const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
227
+ if (placeholderMatch && placeholderMatch.length > 0) {
228
+ issues.push({
229
+ file: 'AI_CONTEXT.md',
230
+ error: `Found ${placeholderMatch.length} unreplaced placeholders`
231
+ });
232
+ }
96
233
  }
97
234
 
98
- const content = fs.readFileSync(outputPath, 'utf-8');
99
-
100
- // Check for unreplaced placeholders
101
- const placeholderMatch = content.match(/\{\{[A-Z_]+\}\}/g);
102
- if (placeholderMatch && placeholderMatch.length > 0) {
103
- return {
104
- valid: false,
105
- error: `Found ${placeholderMatch.length} unreplaced placeholders`,
106
- placeholders: placeholderMatch
107
- };
235
+ // 2. Validate .claude/ directory (optional, warn if missing)
236
+ const claudeDir = path.join(projectRoot, '.claude');
237
+ if (!fs.existsSync(claudeDir)) {
238
+ issues.push({
239
+ file: '.claude/',
240
+ error: 'directory not found (optional but recommended)',
241
+ severity: 'warning'
242
+ });
243
+ } else {
244
+ // Check for critical files
245
+ const criticalFiles = [
246
+ 'settings.json',
247
+ 'README.md'
248
+ ];
249
+ for (const file of criticalFiles) {
250
+ if (!fs.existsSync(path.join(claudeDir, file))) {
251
+ issues.push({
252
+ file: `.claude/${file}`,
253
+ error: 'missing',
254
+ severity: 'warning'
255
+ });
256
+ }
257
+ }
108
258
  }
109
259
 
110
260
  return {
111
- valid: true,
112
- size: content.length
261
+ valid: issues.filter(i => i.severity !== 'warning').length === 0,
262
+ issues,
263
+ warnings: issues.filter(i => i.severity === 'warning').length
113
264
  };
114
265
  }
115
266