create-universal-ai-context 2.4.0 → 2.6.0-final

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.
Files changed (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +331 -294
  3. package/bin/create-ai-context.js +1507 -764
  4. package/lib/adapters/aider.js +131 -131
  5. package/lib/adapters/antigravity.js +205 -205
  6. package/lib/adapters/claude.js +397 -397
  7. package/lib/adapters/cline.js +125 -125
  8. package/lib/adapters/continue.js +138 -138
  9. package/lib/adapters/copilot.js +131 -131
  10. package/lib/adapters/index.js +78 -78
  11. package/lib/adapters/windsurf.js +138 -138
  12. package/lib/ai-context-generator.js +234 -234
  13. package/lib/ai-orchestrator.js +432 -432
  14. package/lib/call-tracer.js +444 -444
  15. package/lib/content-preservation.js +243 -243
  16. package/lib/cross-tool-sync/file-watcher.js +274 -274
  17. package/lib/cross-tool-sync/index.js +41 -40
  18. package/lib/cross-tool-sync/sync-manager.js +540 -512
  19. package/lib/cross-tool-sync/sync-service.js +297 -297
  20. package/lib/detector.js +726 -726
  21. package/lib/doc-discovery.js +741 -741
  22. package/lib/drift-checker.js +920 -920
  23. package/lib/environment-detector.js +239 -239
  24. package/lib/index.js +399 -399
  25. package/lib/install-hooks.js +82 -82
  26. package/lib/installer.js +419 -419
  27. package/lib/migrate.js +328 -328
  28. package/lib/placeholder.js +632 -632
  29. package/lib/prompts.js +341 -341
  30. package/lib/smart-merge.js +540 -540
  31. package/lib/spinner.js +60 -60
  32. package/lib/static-analyzer.js +729 -729
  33. package/lib/template-coordination.js +148 -148
  34. package/lib/template-populator.js +843 -843
  35. package/lib/template-renderer.js +392 -392
  36. package/lib/utils/fs-wrapper.js +79 -79
  37. package/lib/utils/path-utils.js +60 -60
  38. package/lib/validate.js +155 -155
  39. package/package.json +1 -1
  40. package/templates/AI_CONTEXT.md.template +245 -245
  41. package/templates/base/README.md +260 -257
  42. package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
  43. package/templates/base/agents/api-developer.md +76 -76
  44. package/templates/base/agents/context-engineer.md +525 -525
  45. package/templates/base/agents/core-architect.md +76 -76
  46. package/templates/base/agents/database-ops.md +76 -76
  47. package/templates/base/agents/deployment-ops.md +76 -76
  48. package/templates/base/agents/integration-hub.md +76 -76
  49. package/templates/base/analytics/README.md +114 -114
  50. package/templates/base/automation/config.json +58 -58
  51. package/templates/base/automation/generators/code-mapper.js +308 -308
  52. package/templates/base/automation/generators/index-builder.js +321 -321
  53. package/templates/base/automation/hooks/post-commit.sh +83 -83
  54. package/templates/base/automation/hooks/pre-commit.sh +103 -103
  55. package/templates/base/ci-templates/README.md +108 -108
  56. package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
  57. package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
  58. package/templates/base/commands/analytics.md +238 -238
  59. package/templates/base/commands/auto-sync.md +172 -172
  60. package/templates/base/commands/collab.md +194 -194
  61. package/templates/base/commands/context-optimize.md +226 -0
  62. package/templates/base/commands/help.md +485 -450
  63. package/templates/base/commands/rpi-implement.md +164 -115
  64. package/templates/base/commands/rpi-plan.md +147 -93
  65. package/templates/base/commands/rpi-research.md +145 -88
  66. package/templates/base/commands/session-resume.md +144 -144
  67. package/templates/base/commands/session-save.md +112 -112
  68. package/templates/base/commands/validate-all.md +77 -77
  69. package/templates/base/commands/verify-docs-current.md +86 -86
  70. package/templates/base/config/base.json +57 -57
  71. package/templates/base/config/environments/development.json +13 -13
  72. package/templates/base/config/environments/production.json +17 -17
  73. package/templates/base/config/environments/staging.json +13 -13
  74. package/templates/base/config/local.json.example +21 -21
  75. package/templates/base/context/.meta/generated-at.json +18 -18
  76. package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
  77. package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
  78. package/templates/base/context/FILE_OWNERSHIP.md +57 -57
  79. package/templates/base/context/INTEGRATION_POINTS.md +92 -92
  80. package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
  81. package/templates/base/context/TESTING_MAP.md +95 -95
  82. package/templates/base/context/WORKFLOW_INDEX.md +129 -129
  83. package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
  84. package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
  85. package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
  86. package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
  87. package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
  88. package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
  89. package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
  90. package/templates/base/knowledge/README.md +98 -98
  91. package/templates/base/knowledge/sessions/README.md +88 -88
  92. package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
  93. package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
  94. package/templates/base/knowledge/shared/decisions/README.md +49 -49
  95. package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
  96. package/templates/base/knowledge/shared/patterns/README.md +62 -62
  97. package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
  98. package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
  99. package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
  100. package/templates/base/schemas/agent.schema.json +141 -141
  101. package/templates/base/schemas/anchors.schema.json +54 -54
  102. package/templates/base/schemas/automation.schema.json +93 -93
  103. package/templates/base/schemas/command.schema.json +134 -134
  104. package/templates/base/schemas/hashes.schema.json +40 -40
  105. package/templates/base/schemas/manifest.schema.json +117 -117
  106. package/templates/base/schemas/plan.schema.json +136 -136
  107. package/templates/base/schemas/research.schema.json +115 -115
  108. package/templates/base/schemas/roles.schema.json +34 -34
  109. package/templates/base/schemas/session.schema.json +77 -77
  110. package/templates/base/schemas/settings.schema.json +244 -244
  111. package/templates/base/schemas/staleness.schema.json +53 -53
  112. package/templates/base/schemas/team-config.schema.json +42 -42
  113. package/templates/base/schemas/workflow.schema.json +126 -126
  114. package/templates/base/session/checkpoints/.gitkeep +2 -2
  115. package/templates/base/session/current/state.json +20 -20
  116. package/templates/base/session/history/.gitkeep +2 -2
  117. package/templates/base/settings.json +3 -3
  118. package/templates/base/standards/COMPATIBILITY.md +219 -219
  119. package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
  120. package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
  121. package/templates/base/standards/README.md +66 -66
  122. package/templates/base/sync/anchors.json +6 -6
  123. package/templates/base/sync/hashes.json +6 -6
  124. package/templates/base/sync/staleness.json +10 -10
  125. package/templates/base/team/README.md +168 -168
  126. package/templates/base/team/config.json +79 -79
  127. package/templates/base/team/roles.json +145 -145
  128. package/templates/base/tools/bin/claude-context.js +151 -151
  129. package/templates/base/tools/lib/anchor-resolver.js +276 -276
  130. package/templates/base/tools/lib/config-loader.js +363 -363
  131. package/templates/base/tools/lib/detector.js +350 -350
  132. package/templates/base/tools/lib/diagnose.js +206 -206
  133. package/templates/base/tools/lib/drift-detector.js +373 -373
  134. package/templates/base/tools/lib/errors.js +199 -199
  135. package/templates/base/tools/lib/index.js +36 -36
  136. package/templates/base/tools/lib/init.js +192 -192
  137. package/templates/base/tools/lib/logger.js +230 -230
  138. package/templates/base/tools/lib/placeholder.js +201 -201
  139. package/templates/base/tools/lib/session-manager.js +354 -354
  140. package/templates/base/tools/lib/validate.js +521 -521
  141. package/templates/base/tools/package.json +49 -49
  142. package/templates/handlebars/aider-config.hbs +146 -80
  143. package/templates/handlebars/antigravity.hbs +377 -377
  144. package/templates/handlebars/claude.hbs +183 -183
  145. package/templates/handlebars/cline.hbs +62 -62
  146. package/templates/handlebars/continue-config.hbs +116 -116
  147. package/templates/handlebars/copilot.hbs +130 -130
  148. package/templates/handlebars/partials/gotcha-list.hbs +11 -11
  149. package/templates/handlebars/partials/header.hbs +3 -3
  150. package/templates/handlebars/partials/workflow-summary.hbs +16 -16
  151. package/templates/handlebars/windsurf-rules.hbs +69 -69
  152. package/templates/hooks/post-commit.hbs +28 -29
  153. package/templates/hooks/pre-commit.hbs +46 -46
@@ -1,764 +1,1507 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * create-ai-context CLI
5
- *
6
- * Universal AI Context Engineering - Set up context for Claude, Copilot, Cline, Antigravity, and more.
7
- *
8
- * Usage:
9
- * npx create-ai-context # Initialize in current directory
10
- * npx create-ai-context my-project # Initialize in new directory
11
- * npx create-ai-context --yes # Skip prompts
12
- * npx create-ai-context --ai copilot # Generate for specific tool
13
- * npx create-ai-context generate # Regenerate context files
14
- * npx create-ai-context generate --ai cline # Regenerate for specific tool
15
- * npx create-ai-context migrate # Migrate from v1.x
16
- */
17
-
18
- const { program } = require('commander');
19
- const chalk = require('chalk');
20
- const path = require('path');
21
- const fs = require('fs');
22
- const { run } = require('../lib');
23
- const { generateAll, getSupportedTools } = require('../lib/ai-context-generator');
24
- const { migrateV1ToV2, getMigrationStatus } = require('../lib/migrate');
25
- const { detectTechStack } = require('../lib/detector');
26
- const { analyzeCodebase } = require('../lib/static-analyzer');
27
- const { createSpinner } = require('../lib/spinner');
28
- const {
29
- findDocumentationFiles,
30
- generateDriftReport,
31
- checkDocumentDrift,
32
- formatDriftReportConsole
33
- } = require('../lib/drift-checker');
34
- const {
35
- checkSyncStatus,
36
- syncAllFromCodebase,
37
- propagateContextChange,
38
- resolveConflict,
39
- formatSyncStatus,
40
- getSyncHistory,
41
- CONFLICT_STRATEGY
42
- } = require('../lib/cross-tool-sync');
43
- const packageJson = require('../package.json');
44
-
45
- // ASCII Banner
46
- const banner = `
47
- ${chalk.cyan('╔═══════════════════════════════════════════════════════════╗')}
48
- ${chalk.cyan('║')} ${chalk.bold.white('AI Context Engineering')} ${chalk.gray('v' + packageJson.version)} ${chalk.cyan('║')}
49
- ${chalk.cyan('')} ${chalk.gray('Universal context for Claude, Copilot, Cline & more')} ${chalk.cyan('║')}
50
- ${chalk.cyan('╚═══════════════════════════════════════════════════════════╝')}
51
- `;
52
-
53
- // Supported AI tools
54
- const AI_TOOLS = ['claude', 'copilot', 'cline', 'antigravity', 'all'];
55
-
56
- // Parse AI tools helper
57
- function parseAiTools(toolsString) {
58
- const tools = toolsString.split(',').map(t => t.trim().toLowerCase());
59
- const invalid = tools.filter(t => !AI_TOOLS.includes(t));
60
- if (invalid.length > 0) {
61
- console.error(chalk.red(`\n✖ Error: Invalid AI tools: ${invalid.join(', ')}`));
62
- console.error(chalk.gray(` Valid options: ${AI_TOOLS.join(', ')}`));
63
- process.exit(1);
64
- }
65
- return tools.includes('all') ? ['claude', 'copilot', 'cline', 'antigravity'] : tools;
66
- }
67
-
68
- program
69
- .name('create-ai-context')
70
- .description('Universal AI Context Engineering - Set up context for multiple AI coding assistants')
71
- .version(packageJson.version);
72
-
73
- // Main init command (default)
74
- program
75
- .argument('[project-name]', 'Name of the project (defaults to current directory name)')
76
- .option('-y, --yes', 'Skip prompts and use defaults')
77
- .option('--no-plugin', 'Skip plugin installation')
78
- .option('-t, --template <preset>', 'Use a tech stack preset (python-fastapi, node-express, etc.)')
79
- .option('--no-git', 'Skip git initialization')
80
- .option('-n, --dry-run', 'Show what would be done without making changes')
81
- .option('-v, --verbose', 'Show detailed output')
82
- .option('--ai <tools>', 'Generate for specific AI tools (comma-separated: claude,copilot,cline,antigravity,all)', 'all')
83
- .option('--force-ai', 'Force AI-enhanced mode (creates INIT_REQUEST.md)')
84
- .option('--static', 'Force standalone mode (static analysis only, no AI setup)')
85
- .option('--analyze-only', 'Run codebase analysis without installation')
86
- .option('--monorepo', 'Initialize in monorepo mode with federation support')
87
- .option('--federate', 'Run federation to generate context for subprojects')
88
- .option('--mode <mode>', 'How to handle existing docs: merge, overwrite, interactive', 'merge')
89
- .option('--preserve-custom', 'Keep user customizations when merging (default: true)', true)
90
- .option('--update-refs', 'Auto-fix drifted line references')
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')
94
- .action(async (projectName, options) => {
95
- console.log(banner);
96
-
97
- const aiTools = parseAiTools(options.ai);
98
-
99
- if (options.forceAi && options.static) {
100
- console.error(chalk.red('\n✖ Error: --force-ai and --static are mutually exclusive'));
101
- process.exit(1);
102
- }
103
-
104
- try {
105
- await run({
106
- projectName,
107
- skipPrompts: options.yes,
108
- installPlugin: options.plugin !== false,
109
- template: options.template,
110
- initGit: options.git !== false,
111
- dryRun: options.dryRun,
112
- verbose: options.verbose,
113
- aiTools,
114
- forceAi: options.forceAi,
115
- forceStatic: options.static,
116
- analyzeOnly: options.analyzeOnly,
117
- monorepo: options.monorepo,
118
- federate: options.federate,
119
- // Merge options
120
- mode: options.mode,
121
- preserveCustom: options.preserveCustom,
122
- updateRefs: options.updateRefs,
123
- backup: options.backup,
124
- force: options.force || false,
125
- // Placeholder validation
126
- failOnUnreplaced: options.failOnUnreplaced || false
127
- });
128
- } catch (error) {
129
- console.error(chalk.red('\n✖ Error:'), error.message);
130
- if (options.verbose) {
131
- console.error(chalk.gray(error.stack));
132
- }
133
- process.exit(1);
134
- }
135
- });
136
-
137
- // Generate subcommand - regenerate context files
138
- program
139
- .command('generate')
140
- .description('Regenerate AI context files for an existing project')
141
- .option('--ai <tools>', 'Generate for specific AI tools (comma-separated)', 'all')
142
- .option('-d, --dryRun', 'Show what would be done without making changes')
143
- .option('-v, --verbose', 'Show detailed output')
144
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
145
- .option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
146
- .action(async (options) => {
147
- console.log(banner);
148
-
149
- const projectRoot = path.resolve(options.path);
150
- const aiTools = parseAiTools(options.ai);
151
- const spinner = createSpinner();
152
-
153
- // Check if project has .ai-context
154
- const contextDir = path.join(projectRoot, '.ai-context');
155
- if (!fs.existsSync(contextDir)) {
156
- console.error(chalk.red('\n✖ Error: No .ai-context directory found.'));
157
- console.error(chalk.gray(' Run `npx create-ai-context` first to initialize.'));
158
- process.exit(1);
159
- }
160
-
161
- try {
162
- // Detect tech stack
163
- spinner.start('Detecting technology stack...');
164
- const techStack = await detectTechStack(projectRoot);
165
- spinner.succeed(`Detected: ${techStack.summary || 'Generic project'}`);
166
-
167
- // Analyze codebase
168
- spinner.start('Analyzing codebase...');
169
- const analysis = await analyzeCodebase(projectRoot, { techStack });
170
- analysis.techStack = techStack;
171
- spinner.succeed(`Analyzed: ${analysis.summary?.totalFiles || 0} files`);
172
-
173
- // Generate context files
174
- spinner.start('Generating AI context files...');
175
- const config = {
176
- projectName: path.basename(projectRoot),
177
- aiTools,
178
- verbose: options.verbose,
179
- force: options.force || false
180
- };
181
-
182
- if (options.dryRun) {
183
- spinner.info('Dry run - no files will be written');
184
- console.log(chalk.gray('\nWould generate for:'));
185
- aiTools.forEach(tool => {
186
- console.log(chalk.gray(` • ${tool}`));
187
- });
188
- } else {
189
- const results = await generateAll(analysis, config, projectRoot, {
190
- aiTools,
191
- verbose: options.verbose
192
- });
193
-
194
- if (results.success) {
195
- spinner.succeed(`Generated ${results.summary.files} files for ${results.summary.successful} AI tools`);
196
-
197
- console.log(chalk.bold('\nGenerated files:'));
198
- results.generated.forEach(g => {
199
- console.log(chalk.green(` ✓ ${g.displayName}`));
200
- g.files.forEach(f => {
201
- console.log(chalk.gray(` ${f.relativePath}`));
202
- });
203
- });
204
- } else {
205
- spinner.warn(`Generated ${results.summary.successful}/${results.summary.total} tools`);
206
- results.errors.forEach(e => {
207
- console.error(chalk.red(` ✖ ${e.adapter}: ${e.message || e.errors?.[0]?.message}`));
208
- });
209
- }
210
- }
211
- } catch (error) {
212
- spinner.fail('Generation failed');
213
- console.error(chalk.red('\n✖ Error:'), error.message);
214
- if (options.verbose) {
215
- console.error(chalk.gray(error.stack));
216
- }
217
- process.exit(1);
218
- }
219
- });
220
-
221
- // Migrate subcommand - upgrade from v1.x
222
- program
223
- .command('migrate')
224
- .description('Migrate from v1.x (.claude/) to v2.0 (.ai-context/)')
225
- .option('-d, --dryRun', 'Show what would be done without making changes')
226
- .option('--force', 'Overwrite existing v2.0 installation')
227
- .option('--backup', 'Create backup of existing files')
228
- .option('--no-update-refs', 'Skip updating internal references')
229
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
230
- .action(async (options) => {
231
- console.log(banner);
232
-
233
- const projectRoot = path.resolve(options.path);
234
- const spinner = createSpinner();
235
-
236
- // Check migration status
237
- spinner.start('Checking installation...');
238
- const status = getMigrationStatus(projectRoot);
239
- spinner.succeed(`Status: ${status.message}`);
240
-
241
- if (!status.needsMigration) {
242
- console.log(chalk.green('\n✓ No migration needed.'));
243
- process.exit(0);
244
- }
245
-
246
- // Show what will happen
247
- console.log(chalk.bold('\nMigration plan:'));
248
- if (status.details?.oldDir) {
249
- console.log(chalk.cyan(` • Rename .claude/ .ai-context/`));
250
- }
251
- if (status.details?.oldFile) {
252
- console.log(chalk.cyan(` • Rename CLAUDE.mdAI_CONTEXT.md`));
253
- }
254
- if (options.updateRefs !== false) {
255
- console.log(chalk.cyan(` • Update internal references in files`));
256
- }
257
-
258
- if (options.dryRun) {
259
- console.log(chalk.yellow('\n[dry-run] No changes will be made'));
260
- process.exit(0);
261
- }
262
-
263
- // Perform migration
264
- spinner.start('Migrating...');
265
- try {
266
- const result = await migrateV1ToV2(projectRoot, {
267
- dryRun: false,
268
- force: options.force,
269
- backup: options.backup,
270
- updateReferences: options.updateRefs !== false
271
- });
272
-
273
- if (result.success) {
274
- spinner.succeed('Migration completed successfully');
275
-
276
- console.log(chalk.bold('\nChanges made:'));
277
- result.changes.forEach(change => {
278
- if (change.type === 'rename') {
279
- console.log(chalk.green(` ✓ ${change.action} ${change.from} → ${change.to}`));
280
- } else if (change.type === 'update') {
281
- console.log(chalk.green(` ✓ ${change.action} ${change.file}`));
282
- } else if (change.type === 'backup') {
283
- console.log(chalk.blue(` ↺ ${change.action} ${change.from} ${change.to}`));
284
- }
285
- });
286
-
287
- if (result.warnings.length > 0) {
288
- console.log(chalk.yellow('\nWarnings:'));
289
- result.warnings.forEach(w => console.log(chalk.yellow(` ⚠ ${w}`)));
290
- }
291
-
292
- console.log(chalk.bold('\nNext steps:'));
293
- console.log(chalk.gray(' 1. Review AI_CONTEXT.md for any needed updates'));
294
- console.log(chalk.gray(' 2. Run `npx create-ai-context generate` to regenerate AI tool files'));
295
- console.log(chalk.gray(' 3. Commit the changes'));
296
- } else {
297
- spinner.fail('Migration failed');
298
- result.errors.forEach(e => {
299
- console.error(chalk.red(` ✖ ${e}`));
300
- });
301
- process.exit(1);
302
- }
303
- } catch (error) {
304
- spinner.fail('Migration failed');
305
- console.error(chalk.red('\n✖ Error:'), error.message);
306
- process.exit(1);
307
- }
308
- });
309
-
310
- // Status subcommand - show current installation status
311
- program
312
- .command('status')
313
- .description('Show current AI context installation status')
314
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
315
- .action(async (options) => {
316
- console.log(banner);
317
-
318
- const projectRoot = path.resolve(options.path);
319
-
320
- // Check migration status
321
- const migrationStatus = getMigrationStatus(projectRoot);
322
-
323
- console.log(chalk.bold('Installation Status:\n'));
324
-
325
- // Version status
326
- if (migrationStatus.status === 'none') {
327
- console.log(chalk.yellow(' ○ Not initialized'));
328
- console.log(chalk.gray(' Run `npx create-ai-context` to initialize\n'));
329
- } else if (migrationStatus.status === 'v1') {
330
- console.log(chalk.yellow(' ○ v1.x installation found'));
331
- console.log(chalk.gray(' Run `npx create-ai-context migrate` to upgrade to v2.0\n'));
332
- } else if (migrationStatus.status === 'v2') {
333
- console.log(chalk.green(' v2.0 installation'));
334
- } else if (migrationStatus.status === 'mixed') {
335
- console.log(chalk.yellow(' ⚠ Mixed v1.x and v2.0 installation'));
336
- console.log(chalk.gray(' Run `npx create-ai-context migrate --force` to clean up\n'));
337
- }
338
-
339
- // Check AI tool outputs
340
- if (migrationStatus.status === 'v2' || migrationStatus.status === 'mixed') {
341
- console.log(chalk.bold('\nAI Tool Outputs:'));
342
-
343
- const tools = getSupportedTools();
344
- for (const tool of tools) {
345
- let outputPath;
346
- if (tool.name === 'claude') {
347
- outputPath = path.join(projectRoot, 'AI_CONTEXT.md');
348
- } else if (tool.name === 'copilot') {
349
- outputPath = path.join(projectRoot, '.github', 'copilot-instructions.md');
350
- } else if (tool.name === 'cline') {
351
- outputPath = path.join(projectRoot, '.clinerules');
352
- } else if (tool.name === 'antigravity') {
353
- outputPath = path.join(projectRoot, '.agent');
354
- }
355
-
356
- const exists = fs.existsSync(outputPath);
357
- if (exists) {
358
- console.log(chalk.green(` ✓ ${tool.displayName} (${tool.outputPath})`));
359
- } else {
360
- console.log(chalk.gray(` ○ ${tool.displayName} (not generated)`));
361
- }
362
- }
363
-
364
- console.log(chalk.gray('\n Run `npx create-ai-context generate` to regenerate'));
365
- }
366
- });
367
-
368
- // Drift subcommand - check documentation drift
369
- program
370
- .command('drift')
371
- .description('Check documentation drift against codebase')
372
- .option('-f, --file <path>', 'Check specific documentation file')
373
- .option('-a, --all', 'Check all documentation files')
374
- .option('--fix', 'Show suggested fixes for issues')
375
- .option('--strict', 'Exit with error if drift detected')
376
- .option('-o, --output <format>', 'Output format: console, json, markdown', 'console')
377
- .option('-t, --threshold <percent>', 'Health score threshold for --strict', '70')
378
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
379
- .option('-v, --verbose', 'Show detailed output')
380
- .action(async (options) => {
381
- console.log(banner);
382
-
383
- const projectRoot = path.resolve(options.path);
384
- const spinner = createSpinner();
385
-
386
- try {
387
- // Determine which files to check
388
- let filesToCheck = [];
389
-
390
- if (options.file) {
391
- // Single file mode
392
- filesToCheck = [options.file];
393
- } else if (options.all) {
394
- // All documentation files
395
- spinner.start('Finding documentation files...');
396
- filesToCheck = await findDocumentationFiles(projectRoot);
397
- spinner.succeed(`Found ${filesToCheck.length} documentation files`);
398
- } else {
399
- // Default: check main context files
400
- const defaultFiles = ['CLAUDE.md', 'AI_CONTEXT.md', 'README.md'];
401
- filesToCheck = defaultFiles.filter(f =>
402
- fs.existsSync(path.join(projectRoot, f))
403
- );
404
-
405
- if (filesToCheck.length === 0) {
406
- console.log(chalk.yellow('\nNo documentation files found.'));
407
- console.log(chalk.gray('Use --all to scan for all markdown files, or --file to check a specific file.'));
408
- process.exit(0);
409
- }
410
- }
411
-
412
- // Generate drift report
413
- spinner.start('Checking documentation drift...');
414
- const report = generateDriftReport(filesToCheck, projectRoot);
415
- spinner.succeed(`Checked ${report.summary.totalDocuments} documents`);
416
-
417
- // Output results
418
- if (options.output === 'json') {
419
- console.log(JSON.stringify(report, null, 2));
420
- } else if (options.output === 'markdown') {
421
- console.log(formatDriftReportMarkdown(report));
422
- } else {
423
- // Console output
424
- console.log(formatDriftReportConsole(report));
425
- }
426
-
427
- // Show suggested fixes if requested
428
- if (options.fix && report.suggestedFixes.length > 0) {
429
- console.log(chalk.bold('\nSuggested Fixes:'));
430
- for (const fix of report.suggestedFixes) {
431
- console.log(chalk.cyan(`\n ${fix.document}:`));
432
- console.log(chalk.red(` - ${fix.original}`));
433
- console.log(chalk.green(` + ${fix.suggestion}`));
434
- }
435
- }
436
-
437
- // Strict mode - exit with error if below threshold
438
- if (options.strict) {
439
- const threshold = parseInt(options.threshold, 10);
440
- if (report.summary.overallHealthScore < threshold) {
441
- console.log(chalk.red(`\n✖ Health score ${report.summary.overallHealthScore}% is below threshold ${threshold}%`));
442
- process.exit(1);
443
- } else {
444
- console.log(chalk.green(`\n Health score ${report.summary.overallHealthScore}% meets threshold ${threshold}%`));
445
- }
446
- }
447
-
448
- } catch (error) {
449
- spinner.fail('Drift check failed');
450
- console.error(chalk.red('\n✖ Error:'), error.message);
451
- if (options.verbose) {
452
- console.error(chalk.gray(error.stack));
453
- }
454
- process.exit(1);
455
- }
456
- });
457
-
458
- /**
459
- * Format drift report as markdown
460
- */
461
- function formatDriftReportMarkdown(report) {
462
- const lines = [
463
- '# Documentation Drift Report',
464
- '',
465
- `**Generated:** ${report.generatedAt}`,
466
- `**Overall Health:** ${report.summary.overallHealthScore}%`,
467
- '',
468
- '## Summary',
469
- '',
470
- '| Metric | Value |',
471
- '|--------|-------|',
472
- `| Documents Analyzed | ${report.summary.totalDocuments} |`,
473
- `| Healthy | ${report.summary.healthyDocuments} |`,
474
- `| With Issues | ${report.summary.documentsWithIssues} |`,
475
- `| References Valid | ${report.summary.validReferences}/${report.summary.totalReferences} |`,
476
- ''
477
- ];
478
-
479
- if (report.documents.length > 0) {
480
- lines.push('## Documents', '');
481
- for (const doc of report.documents) {
482
- const emoji = doc.status === 'healthy' ? '✓' :
483
- doc.status === 'needs_update' ? '⚠' : '';
484
- lines.push(`### ${doc.document} (${doc.healthScore}% ${emoji})`);
485
- lines.push('');
486
-
487
- if (doc.references.invalid.length > 0) {
488
- lines.push('**Issues:**', '');
489
- for (const issue of doc.references.invalid) {
490
- lines.push(`- \`${issue.original}\` - ${issue.issue}`);
491
- if (issue.suggestion) {
492
- lines.push(` - Suggestion: ${issue.suggestion}`);
493
- }
494
- }
495
- lines.push('');
496
- }
497
- }
498
- }
499
-
500
- if (report.suggestedFixes.length > 0) {
501
- lines.push('## Suggested Fixes', '');
502
- for (const fix of report.suggestedFixes) {
503
- lines.push(`- **${fix.document}**: \`${fix.original}\``);
504
- lines.push(` - ${fix.suggestion}`);
505
- }
506
- }
507
-
508
- return lines.join('\n');
509
- }
510
-
511
- // Sync subcommands
512
- program
513
- .command('sync:check')
514
- .description('Check if AI tool contexts are synchronized')
515
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
516
- .option('--json', 'Output as JSON')
517
- .action(async (options) => {
518
- console.log(banner);
519
-
520
- const projectRoot = path.resolve(options.path);
521
-
522
- try {
523
- const status = checkSyncStatus(projectRoot);
524
-
525
- if (options.json) {
526
- console.log(JSON.stringify(status, null, 2));
527
- } else {
528
- console.log(formatSyncStatus(status));
529
-
530
- if (!status.inSync) {
531
- console.log(chalk.yellow('\nTo sync all contexts, run:'));
532
- console.log(chalk.gray(' npx create-ai-context sync:all'));
533
- process.exit(1);
534
- }
535
- }
536
- } catch (error) {
537
- console.error(chalk.red('\n✖ Error:'), error.message);
538
- process.exit(1);
539
- }
540
- });
541
-
542
- program
543
- .command('sync:all')
544
- .description('Synchronize all AI tool contexts from codebase')
545
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
546
- .option('--quiet', 'Suppress output')
547
- .action(async (options) => {
548
- if (!options.quiet) {
549
- console.log(banner);
550
- }
551
-
552
- const projectRoot = path.resolve(options.path);
553
- const spinner = createSpinner();
554
-
555
- try {
556
- if (!options.quiet) {
557
- spinner.start('Analyzing codebase...');
558
- }
559
-
560
- const config = {
561
- projectName: path.basename(projectRoot),
562
- aiTools: ['claude', 'copilot', 'cline', 'antigravity']
563
- };
564
-
565
- const results = await syncAllFromCodebase(projectRoot, config);
566
-
567
- if (!options.quiet) {
568
- if (results.errors.length > 0) {
569
- spinner.warn('Sync completed with errors');
570
-
571
- console.log(chalk.bold('\nSynced tools:'));
572
- for (const tool of results.tools) {
573
- console.log(chalk.green(` ✓ ${tool.tool} (${tool.fileCount} files)`));
574
- }
575
-
576
- console.log(chalk.red('\nErrors:'));
577
- for (const error of results.errors) {
578
- console.error(chalk.red(` ✖ ${error.message || error.tool}`));
579
- }
580
- process.exit(1);
581
- } else {
582
- spinner.succeed(`Synced ${results.tools.length} AI tools`);
583
-
584
- console.log(chalk.bold('\nSynced tools:'));
585
- for (const tool of results.tools) {
586
- console.log(chalk.green(` ${tool.tool} (${tool.fileCount} files)`));
587
- }
588
- }
589
- }
590
- } catch (error) {
591
- if (!options.quiet) {
592
- spinner.fail('Sync failed');
593
- console.error(chalk.red('\n✖ Error:'), error.message);
594
- }
595
- process.exit(1);
596
- }
597
- });
598
-
599
- program
600
- .command('sync:from <tool>')
601
- .description('Propagate context from a specific tool to all others')
602
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
603
- .option('-s, --strategy <strategy>', 'Conflict resolution strategy', 'source_wins')
604
- .action(async (sourceTool, options) => {
605
- console.log(banner);
606
-
607
- const projectRoot = path.resolve(options.path);
608
- const spinner = createSpinner();
609
-
610
- const validTools = ['claude', 'copilot', 'cline', 'antigravity'];
611
- if (!validTools.includes(sourceTool)) {
612
- console.error(chalk.red(`\n✖ Error: Invalid tool: ${sourceTool}`));
613
- console.error(chalk.gray(` Valid options: ${validTools.join(', ')}`));
614
- process.exit(1);
615
- }
616
-
617
- try {
618
- spinner.start(`Propagating from ${sourceTool}...`);
619
-
620
- const config = {
621
- projectName: path.basename(projectRoot),
622
- aiTools: validTools
623
- };
624
-
625
- const results = await propagateContextChange(
626
- sourceTool,
627
- projectRoot,
628
- config,
629
- options.strategy
630
- );
631
-
632
- if (results.errors.length > 0) {
633
- spinner.warn('Propagation completed with errors');
634
-
635
- console.log(chalk.bold('\nPropagated to:'));
636
- for (const tool of results.propagated) {
637
- console.log(chalk.green(` ✓ ${tool.displayName}`));
638
- }
639
-
640
- console.log(chalk.red('\nErrors:'));
641
- for (const error of results.errors) {
642
- console.error(chalk.red(` ✖ ${error.tool || error.message}`));
643
- }
644
- process.exit(1);
645
- } else {
646
- spinner.succeed(`Propagated to ${results.propagated.length} tools`);
647
-
648
- console.log(chalk.bold('\nPropagated to:'));
649
- for (const tool of results.propagated) {
650
- console.log(chalk.green(` ✓ ${tool.displayName}`));
651
- }
652
- }
653
- } catch (error) {
654
- spinner.fail('Propagation failed');
655
- console.error(chalk.red('\n✖ Error:'), error.message);
656
- process.exit(1);
657
- }
658
- });
659
-
660
- program
661
- .command('sync:resolve')
662
- .description('Resolve conflicts between AI tool contexts')
663
- .option('-s, --strategy <strategy>', 'Strategy: source_wins, regenerate_all, newest, manual', 'regenerate_all')
664
- .option('-t, --tool <tool>', 'Preferred tool (for source_wins strategy)')
665
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
666
- .action(async (options) => {
667
- console.log(banner);
668
-
669
- const projectRoot = path.resolve(options.path);
670
- const spinner = createSpinner();
671
-
672
- const validStrategies = Object.values(CONFLICT_STRATEGY);
673
- if (!validStrategies.includes(options.strategy)) {
674
- console.error(chalk.red(`\n✖ Error: Invalid strategy: ${options.strategy}`));
675
- console.error(chalk.gray(` Valid options: ${validStrategies.join(', ')}`));
676
- process.exit(1);
677
- }
678
-
679
- try {
680
- spinner.start(`Resolving conflicts (${options.strategy})...`);
681
-
682
- const config = {
683
- projectName: path.basename(projectRoot),
684
- aiTools: ['claude', 'copilot', 'cline', 'antigravity']
685
- };
686
-
687
- const result = await resolveConflict(
688
- projectRoot,
689
- config,
690
- options.strategy,
691
- options.tool
692
- );
693
-
694
- if (result.resolved) {
695
- spinner.succeed(result.message);
696
- } else {
697
- spinner.warn('Unable to resolve');
698
- console.log(chalk.yellow(`\n${result.message}`));
699
-
700
- if (result.status) {
701
- console.log(formatSyncStatus(result.status));
702
- }
703
- process.exit(1);
704
- }
705
- } catch (error) {
706
- spinner.fail('Resolution failed');
707
- console.error(chalk.red('\n✖ Error:'), error.message);
708
- process.exit(1);
709
- }
710
- });
711
-
712
- program
713
- .command('sync:history')
714
- .description('Show sync history')
715
- .option('-n, --limit <number>', 'Number of entries to show', '10')
716
- .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
717
- .action(async (options) => {
718
- console.log(banner);
719
-
720
- const projectRoot = path.resolve(options.path);
721
-
722
- try {
723
- const history = getSyncHistory(projectRoot, parseInt(options.limit, 10));
724
-
725
- console.log(chalk.bold('\nSync History:\n'));
726
-
727
- if (history.length === 0) {
728
- console.log(chalk.gray(' No sync history found\n'));
729
- } else {
730
- for (const entry of history.reverse()) {
731
- const date = new Date(entry.timestamp).toLocaleString();
732
- console.log(chalk.cyan(` ${date}`));
733
- console.log(chalk.gray(` Source: ${entry.source || entry.sourceTool}`));
734
- console.log(chalk.gray(` Strategy: ${entry.strategy}`));
735
- console.log(chalk.gray(` Propagated: ${entry.propagatedCount} tools`));
736
-
737
- if (entry.errorCount > 0) {
738
- console.log(chalk.red(` Errors: ${entry.errorCount}`));
739
- }
740
- console.log('');
741
- }
742
- }
743
- } catch (error) {
744
- console.error(chalk.red('\n✖ Error:'), error.message);
745
- process.exit(1);
746
- }
747
- });
748
-
749
- program
750
- .command('hooks:install')
751
- .description('Install git hooks for automatic sync')
752
- .action(async () => {
753
- console.log(banner);
754
-
755
- try {
756
- const { installHooks } = require('../lib/install-hooks');
757
- installHooks();
758
- } catch (error) {
759
- console.error(chalk.red('\n✖ Error:'), error.message);
760
- process.exit(1);
761
- }
762
- });
763
-
764
- program.parse();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-ai-context CLI
5
+ *
6
+ * Universal AI Context Engineering - Set up context for Claude, Copilot, Cline, Antigravity, and more.
7
+ *
8
+ * Usage:
9
+ * npx create-ai-context # Initialize in current directory
10
+ * npx create-ai-context my-project # Initialize in new directory
11
+ * npx create-ai-context --yes # Skip prompts
12
+ * npx create-ai-context --ai copilot # Generate for specific tool
13
+ * npx create-ai-context generate # Regenerate context files
14
+ * npx create-ai-context generate --ai cline # Regenerate for specific tool
15
+ * npx create-ai-context migrate # Migrate from v1.x
16
+ */
17
+
18
+ const { program } = require('commander');
19
+ const chalk = require('chalk');
20
+ const path = require('path');
21
+ const fs = require('fs');
22
+ const { run } = require('../lib');
23
+ const { generateAll, getSupportedTools } = require('../lib/ai-context-generator');
24
+ const { migrateV1ToV2, getMigrationStatus } = require('../lib/migrate');
25
+ const { detectTechStack } = require('../lib/detector');
26
+ const { analyzeCodebase } = require('../lib/static-analyzer');
27
+ const { createSpinner } = require('../lib/spinner');
28
+ const {
29
+ findDocumentationFiles,
30
+ generateDriftReport,
31
+ checkDocumentDrift,
32
+ formatDriftReportConsole
33
+ } = require('../lib/drift-checker');
34
+ const {
35
+ checkSyncStatus,
36
+ syncAllFromCodebase,
37
+ updateSyncStateOnly,
38
+ propagateContextChange,
39
+ resolveConflict,
40
+ formatSyncStatus,
41
+ getSyncHistory,
42
+ CONFLICT_STRATEGY
43
+ } = require('../lib/cross-tool-sync');
44
+ const { getAdapterNames } = require('../lib/adapters');
45
+ const packageJson = require('../package.json');
46
+
47
+ // ASCII Banner
48
+ const banner = `
49
+ ${chalk.cyan('╔═══════════════════════════════════════════════════════════╗')}
50
+ ${chalk.cyan('')} ${chalk.bold.white('AI Context Engineering')} ${chalk.gray('v' + packageJson.version)} ${chalk.cyan('║')}
51
+ ${chalk.cyan('║')} ${chalk.gray('Universal context for Claude, Copilot, Cline & more')} ${chalk.cyan('║')}
52
+ ${chalk.cyan('╚═══════════════════════════════════════════════════════════╝')}
53
+ `;
54
+
55
+ // Supported AI tools
56
+ const AI_TOOLS = ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'all'];
57
+
58
+ // Parse AI tools helper
59
+ function parseAiTools(toolsString) {
60
+ const tools = toolsString.split(',').map(t => t.trim().toLowerCase());
61
+ const invalid = tools.filter(t => !AI_TOOLS.includes(t));
62
+ if (invalid.length > 0) {
63
+ console.error(chalk.red(`\n✖ Error: Invalid AI tools: ${invalid.join(', ')}`));
64
+ console.error(chalk.gray(` Valid options: ${AI_TOOLS.join(', ')}`));
65
+ process.exit(1);
66
+ }
67
+ const allTools = ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue'];
68
+ return tools.includes('all') ? allTools : tools;
69
+ }
70
+
71
+ program
72
+ .name('create-ai-context')
73
+ .description('Universal AI Context Engineering - Set up context for multiple AI coding assistants')
74
+ .version(packageJson.version);
75
+
76
+ // Main init command (default)
77
+ program
78
+ .argument('[project-name]', 'Name of the project (defaults to current directory name)')
79
+ .option('-y, --yes', 'Skip prompts and use defaults')
80
+ .option('--no-plugin', 'Skip plugin installation')
81
+ .option('-t, --template <preset>', 'Use a tech stack preset (python-fastapi, node-express, etc.)')
82
+ .option('--no-git', 'Skip git initialization')
83
+ .option('-n, --dry-run', 'Show what would be done without making changes')
84
+ .option('-v, --verbose', 'Show detailed output')
85
+ .option('--ai <tools>', 'Generate for specific AI tools (comma-separated: claude,copilot,cline,antigravity,windsurf,aider,continue,all)', 'all')
86
+ .option('--force-ai', 'Force AI-enhanced mode (creates INIT_REQUEST.md)')
87
+ .option('--static', 'Force standalone mode (static analysis only, no AI setup)')
88
+ .option('--analyze-only', 'Run codebase analysis without installation')
89
+ .option('--monorepo', 'Initialize in monorepo mode with federation support')
90
+ .option('--federate', 'Run federation to generate context for subprojects')
91
+ .option('--mode <mode>', 'How to handle existing docs: merge, overwrite, interactive', 'merge')
92
+ .option('--preserve-custom', 'Keep user customizations when merging (default: true)', true)
93
+ .option('--update-refs', 'Auto-fix drifted line references')
94
+ .option('--backup', 'Create backup before modifying existing files')
95
+ .option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
96
+ .option('--fail-on-unreplaced', 'Error if any placeholders remain unreplaced')
97
+ .action(async (projectName, options) => {
98
+ console.log(banner);
99
+
100
+ const aiTools = parseAiTools(options.ai);
101
+
102
+ if (options.forceAi && options.static) {
103
+ console.error(chalk.red('\n✖ Error: --force-ai and --static are mutually exclusive'));
104
+ process.exit(1);
105
+ }
106
+
107
+ try {
108
+ await run({
109
+ projectName,
110
+ skipPrompts: options.yes,
111
+ installPlugin: options.plugin !== false,
112
+ template: options.template,
113
+ initGit: options.git !== false,
114
+ dryRun: options.dryRun,
115
+ verbose: options.verbose,
116
+ aiTools,
117
+ forceAi: options.forceAi,
118
+ forceStatic: options.static,
119
+ analyzeOnly: options.analyzeOnly,
120
+ monorepo: options.monorepo,
121
+ federate: options.federate,
122
+ // Merge options
123
+ mode: options.mode,
124
+ preserveCustom: options.preserveCustom,
125
+ updateRefs: options.updateRefs,
126
+ backup: options.backup,
127
+ force: options.force || false,
128
+ // Placeholder validation
129
+ failOnUnreplaced: options.failOnUnreplaced === true
130
+ });
131
+ } catch (error) {
132
+ console.error(chalk.red('\n✖ Error:'), error.message);
133
+ if (options.verbose) {
134
+ console.error(chalk.gray(error.stack));
135
+ }
136
+ process.exit(1);
137
+ }
138
+ });
139
+
140
+ // Generate subcommand - regenerate context files
141
+ program
142
+ .command('generate')
143
+ .description('Regenerate AI context files for an existing project')
144
+ .option('--ai <tools>', 'Generate for specific AI tools (comma-separated)', 'all')
145
+ .option('-d, --dryRun', 'Show what would be done without making changes')
146
+ .option('-v, --verbose', 'Show detailed output')
147
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
148
+ .option('-f, --force', 'Force overwrite of existing custom files (use with caution)')
149
+ .action(async (options) => {
150
+ console.log(banner);
151
+
152
+ const projectRoot = path.resolve(options.path);
153
+ const aiTools = parseAiTools(options.ai);
154
+ const spinner = createSpinner();
155
+
156
+ // Check if project has .ai-context
157
+ const contextDir = path.join(projectRoot, '.ai-context');
158
+ if (!fs.existsSync(contextDir)) {
159
+ console.error(chalk.red('\n✖ Error: No .ai-context directory found.'));
160
+ console.error(chalk.gray(' Run `npx create-ai-context` first to initialize.'));
161
+ process.exit(1);
162
+ }
163
+
164
+ try {
165
+ // Detect tech stack
166
+ spinner.start('Detecting technology stack...');
167
+ const techStack = await detectTechStack(projectRoot);
168
+ spinner.succeed(`Detected: ${techStack.summary || 'Generic project'}`);
169
+
170
+ // Analyze codebase
171
+ spinner.start('Analyzing codebase...');
172
+ const analysis = await analyzeCodebase(projectRoot, { techStack });
173
+ analysis.techStack = techStack;
174
+ spinner.succeed(`Analyzed: ${analysis.summary?.totalFiles || 0} files`);
175
+
176
+ // Generate context files
177
+ spinner.start('Generating AI context files...');
178
+ const config = {
179
+ projectName: path.basename(projectRoot),
180
+ aiTools,
181
+ verbose: options.verbose,
182
+ force: options.force || false
183
+ };
184
+
185
+ if (options.dryRun) {
186
+ spinner.info('Dry run - no files will be written');
187
+ console.log(chalk.gray('\nWould generate for:'));
188
+ aiTools.forEach(tool => {
189
+ console.log(chalk.gray(` • ${tool}`));
190
+ });
191
+ } else {
192
+ const results = await generateAll(analysis, config, projectRoot, {
193
+ aiTools,
194
+ verbose: options.verbose
195
+ });
196
+
197
+ if (results.success) {
198
+ spinner.succeed(`Generated ${results.summary.files} files for ${results.summary.successful} AI tools`);
199
+
200
+ console.log(chalk.bold('\nGenerated files:'));
201
+ results.generated.forEach(g => {
202
+ console.log(chalk.green(` ✓ ${g.displayName}`));
203
+ g.files.forEach(f => {
204
+ console.log(chalk.gray(` ${f.relativePath}`));
205
+ });
206
+ });
207
+ } else {
208
+ spinner.warn(`Generated ${results.summary.successful}/${results.summary.total} tools`);
209
+ results.errors.forEach(e => {
210
+ console.error(chalk.red(` ✖ ${e.adapter}: ${e.message || e.errors?.[0]?.message}`));
211
+ });
212
+ }
213
+ }
214
+ } catch (error) {
215
+ spinner.fail('Generation failed');
216
+ console.error(chalk.red('\n✖ Error:'), error.message);
217
+ if (options.verbose) {
218
+ console.error(chalk.gray(error.stack));
219
+ }
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ // Migrate subcommand - upgrade from v1.x
225
+ program
226
+ .command('migrate')
227
+ .description('Migrate from v1.x (.claude/) to v2.0 (.ai-context/)')
228
+ .option('-d, --dryRun', 'Show what would be done without making changes')
229
+ .option('--force', 'Overwrite existing v2.0 installation')
230
+ .option('--backup', 'Create backup of existing files')
231
+ .option('--no-update-refs', 'Skip updating internal references')
232
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
233
+ .action(async (options) => {
234
+ console.log(banner);
235
+
236
+ const projectRoot = path.resolve(options.path);
237
+ const spinner = createSpinner();
238
+
239
+ // Check migration status
240
+ spinner.start('Checking installation...');
241
+ const status = getMigrationStatus(projectRoot);
242
+ spinner.succeed(`Status: ${status.message}`);
243
+
244
+ if (!status.needsMigration) {
245
+ console.log(chalk.green('\n✓ No migration needed.'));
246
+ process.exit(0);
247
+ }
248
+
249
+ // Show what will happen
250
+ console.log(chalk.bold('\nMigration plan:'));
251
+ if (status.details?.oldDir) {
252
+ console.log(chalk.cyan(` • Rename .claude/ → .ai-context/`));
253
+ }
254
+ if (status.details?.oldFile) {
255
+ console.log(chalk.cyan(` • Rename CLAUDE.md AI_CONTEXT.md`));
256
+ }
257
+ if (options.updateRefs !== false) {
258
+ console.log(chalk.cyan(` • Update internal references in files`));
259
+ }
260
+
261
+ if (options.dryRun) {
262
+ console.log(chalk.yellow('\n[dry-run] No changes will be made'));
263
+ process.exit(0);
264
+ }
265
+
266
+ // Perform migration
267
+ spinner.start('Migrating...');
268
+ try {
269
+ const result = await migrateV1ToV2(projectRoot, {
270
+ dryRun: false,
271
+ force: options.force,
272
+ backup: options.backup,
273
+ updateReferences: options.updateRefs !== false
274
+ });
275
+
276
+ if (result.success) {
277
+ spinner.succeed('Migration completed successfully');
278
+
279
+ console.log(chalk.bold('\nChanges made:'));
280
+ result.changes.forEach(change => {
281
+ if (change.type === 'rename') {
282
+ console.log(chalk.green(` ✓ ${change.action} ${change.from} ${change.to}`));
283
+ } else if (change.type === 'update') {
284
+ console.log(chalk.green(` ✓ ${change.action} ${change.file}`));
285
+ } else if (change.type === 'backup') {
286
+ console.log(chalk.blue(` ↺ ${change.action} ${change.from} → ${change.to}`));
287
+ }
288
+ });
289
+
290
+ if (result.warnings.length > 0) {
291
+ console.log(chalk.yellow('\nWarnings:'));
292
+ result.warnings.forEach(w => console.log(chalk.yellow(` ⚠ ${w}`)));
293
+ }
294
+
295
+ console.log(chalk.bold('\nNext steps:'));
296
+ console.log(chalk.gray(' 1. Review AI_CONTEXT.md for any needed updates'));
297
+ console.log(chalk.gray(' 2. Run `npx create-ai-context generate` to regenerate AI tool files'));
298
+ console.log(chalk.gray(' 3. Commit the changes'));
299
+ } else {
300
+ spinner.fail('Migration failed');
301
+ result.errors.forEach(e => {
302
+ console.error(chalk.red(` ✖ ${e}`));
303
+ });
304
+ process.exit(1);
305
+ }
306
+ } catch (error) {
307
+ spinner.fail('Migration failed');
308
+ console.error(chalk.red('\n✖ Error:'), error.message);
309
+ process.exit(1);
310
+ }
311
+ });
312
+
313
+ // Status subcommand - show current installation status
314
+ program
315
+ .command('status')
316
+ .description('Show current AI context installation status')
317
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
318
+ .action(async (options) => {
319
+ console.log(banner);
320
+
321
+ const projectRoot = path.resolve(options.path);
322
+
323
+ // Check migration status
324
+ const migrationStatus = getMigrationStatus(projectRoot);
325
+
326
+ console.log(chalk.bold('Installation Status:\n'));
327
+
328
+ // Version status
329
+ if (migrationStatus.status === 'none') {
330
+ console.log(chalk.yellow(' ○ Not initialized'));
331
+ console.log(chalk.gray(' Run `npx create-ai-context` to initialize\n'));
332
+ } else if (migrationStatus.status === 'v1') {
333
+ console.log(chalk.yellow(' v1.x installation found'));
334
+ console.log(chalk.gray(' Run `npx create-ai-context migrate` to upgrade to v2.0\n'));
335
+ } else if (migrationStatus.status === 'v2') {
336
+ console.log(chalk.green(' v2.0 installation'));
337
+ } else if (migrationStatus.status === 'mixed') {
338
+ console.log(chalk.yellow(' ⚠ Mixed v1.x and v2.0 installation'));
339
+ console.log(chalk.gray(' Run `npx create-ai-context migrate --force` to clean up\n'));
340
+ }
341
+
342
+ // Check AI tool outputs
343
+ if (migrationStatus.status === 'v2' || migrationStatus.status === 'mixed') {
344
+ console.log(chalk.bold('\nAI Tool Outputs:'));
345
+
346
+ const tools = getSupportedTools();
347
+ for (const tool of tools) {
348
+ let outputPath;
349
+ if (tool.name === 'claude') {
350
+ outputPath = path.join(projectRoot, 'AI_CONTEXT.md');
351
+ } else if (tool.name === 'copilot') {
352
+ outputPath = path.join(projectRoot, '.github', 'copilot-instructions.md');
353
+ } else if (tool.name === 'cline') {
354
+ outputPath = path.join(projectRoot, '.clinerules');
355
+ } else if (tool.name === 'antigravity') {
356
+ outputPath = path.join(projectRoot, '.agent');
357
+ }
358
+
359
+ const exists = fs.existsSync(outputPath);
360
+ if (exists) {
361
+ console.log(chalk.green(` ✓ ${tool.displayName} (${tool.outputPath})`));
362
+ } else {
363
+ console.log(chalk.gray(` ○ ${tool.displayName} (not generated)`));
364
+ }
365
+ }
366
+
367
+ console.log(chalk.gray('\n Run `npx create-ai-context generate` to regenerate'));
368
+ }
369
+ });
370
+
371
+ // Drift subcommand - check documentation drift
372
+ program
373
+ .command('drift')
374
+ .description('Check documentation drift against codebase')
375
+ .option('-f, --file <path>', 'Check specific documentation file')
376
+ .option('-a, --all', 'Check all documentation files')
377
+ .option('--fix', 'Show suggested fixes for issues')
378
+ .option('--strict', 'Exit with error if drift detected')
379
+ .option('-o, --output <format>', 'Output format: console, json, markdown', 'console')
380
+ .option('-t, --threshold <percent>', 'Health score threshold for --strict', '70')
381
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
382
+ .option('-v, --verbose', 'Show detailed output')
383
+ .action(async (options) => {
384
+ console.log(banner);
385
+
386
+ const projectRoot = path.resolve(options.path);
387
+ const spinner = createSpinner();
388
+
389
+ try {
390
+ // Determine which files to check
391
+ let filesToCheck = [];
392
+
393
+ if (options.file) {
394
+ // Single file mode
395
+ filesToCheck = [options.file];
396
+ } else if (options.all) {
397
+ // All documentation files
398
+ spinner.start('Finding documentation files...');
399
+ filesToCheck = await findDocumentationFiles(projectRoot);
400
+ spinner.succeed(`Found ${filesToCheck.length} documentation files`);
401
+ } else {
402
+ // Default: check main context files
403
+ const defaultFiles = ['CLAUDE.md', 'AI_CONTEXT.md', 'README.md'];
404
+ filesToCheck = defaultFiles.filter(f =>
405
+ fs.existsSync(path.join(projectRoot, f))
406
+ );
407
+
408
+ if (filesToCheck.length === 0) {
409
+ console.log(chalk.yellow('\nNo documentation files found.'));
410
+ console.log(chalk.gray('Use --all to scan for all markdown files, or --file to check a specific file.'));
411
+ process.exit(0);
412
+ }
413
+ }
414
+
415
+ // Generate drift report
416
+ spinner.start('Checking documentation drift...');
417
+ const report = generateDriftReport(filesToCheck, projectRoot);
418
+ spinner.succeed(`Checked ${report.summary.totalDocuments} documents`);
419
+
420
+ // Output results
421
+ if (options.output === 'json') {
422
+ console.log(JSON.stringify(report, null, 2));
423
+ } else if (options.output === 'markdown') {
424
+ console.log(formatDriftReportMarkdown(report));
425
+ } else {
426
+ // Console output
427
+ console.log(formatDriftReportConsole(report));
428
+ }
429
+
430
+ // Show suggested fixes if requested
431
+ if (options.fix && report.suggestedFixes.length > 0) {
432
+ console.log(chalk.bold('\nSuggested Fixes:'));
433
+ for (const fix of report.suggestedFixes) {
434
+ console.log(chalk.cyan(`\n ${fix.document}:`));
435
+ console.log(chalk.red(` - ${fix.original}`));
436
+ console.log(chalk.green(` + ${fix.suggestion}`));
437
+ }
438
+ }
439
+
440
+ // Strict mode - exit with error if below threshold
441
+ if (options.strict) {
442
+ const threshold = parseInt(options.threshold, 10);
443
+ if (report.summary.overallHealthScore < threshold) {
444
+ console.log(chalk.red(`\n Health score ${report.summary.overallHealthScore}% is below threshold ${threshold}%`));
445
+ process.exit(1);
446
+ } else {
447
+ console.log(chalk.green(`\n✓ Health score ${report.summary.overallHealthScore}% meets threshold ${threshold}%`));
448
+ }
449
+ }
450
+
451
+ } catch (error) {
452
+ spinner.fail('Drift check failed');
453
+ console.error(chalk.red('\n✖ Error:'), error.message);
454
+ if (options.verbose) {
455
+ console.error(chalk.gray(error.stack));
456
+ }
457
+ process.exit(1);
458
+ }
459
+ });
460
+
461
+ /**
462
+ * Format drift report as markdown
463
+ */
464
+ function formatDriftReportMarkdown(report) {
465
+ const lines = [
466
+ '# Documentation Drift Report',
467
+ '',
468
+ `**Generated:** ${report.generatedAt}`,
469
+ `**Overall Health:** ${report.summary.overallHealthScore}%`,
470
+ '',
471
+ '## Summary',
472
+ '',
473
+ '| Metric | Value |',
474
+ '|--------|-------|',
475
+ `| Documents Analyzed | ${report.summary.totalDocuments} |`,
476
+ `| Healthy | ${report.summary.healthyDocuments} |`,
477
+ `| With Issues | ${report.summary.documentsWithIssues} |`,
478
+ `| References Valid | ${report.summary.validReferences}/${report.summary.totalReferences} |`,
479
+ ''
480
+ ];
481
+
482
+ if (report.documents.length > 0) {
483
+ lines.push('## Documents', '');
484
+ for (const doc of report.documents) {
485
+ const emoji = doc.status === 'healthy' ? '✓' :
486
+ doc.status === 'needs_update' ? '⚠' : '✗';
487
+ lines.push(`### ${doc.document} (${doc.healthScore}% ${emoji})`);
488
+ lines.push('');
489
+
490
+ if (doc.references.invalid.length > 0) {
491
+ lines.push('**Issues:**', '');
492
+ for (const issue of doc.references.invalid) {
493
+ lines.push(`- \`${issue.original}\` - ${issue.issue}`);
494
+ if (issue.suggestion) {
495
+ lines.push(` - Suggestion: ${issue.suggestion}`);
496
+ }
497
+ }
498
+ lines.push('');
499
+ }
500
+ }
501
+ }
502
+
503
+ if (report.suggestedFixes.length > 0) {
504
+ lines.push('## Suggested Fixes', '');
505
+ for (const fix of report.suggestedFixes) {
506
+ lines.push(`- **${fix.document}**: \`${fix.original}\``);
507
+ lines.push(` - → ${fix.suggestion}`);
508
+ }
509
+ }
510
+
511
+ return lines.join('\n');
512
+ }
513
+
514
+ // Sync subcommands
515
+ program
516
+ .command('sync:check')
517
+ .description('Check if AI tool contexts are synchronized')
518
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
519
+ .option('--json', 'Output as JSON')
520
+ .action(async (options) => {
521
+ console.log(banner);
522
+
523
+ const projectRoot = path.resolve(options.path);
524
+
525
+ try {
526
+ const status = checkSyncStatus(projectRoot);
527
+
528
+ if (options.json) {
529
+ console.log(JSON.stringify(status, null, 2));
530
+ } else {
531
+ console.log(formatSyncStatus(status));
532
+
533
+ if (!status.inSync) {
534
+ console.log(chalk.yellow('\nTo sync all contexts, run:'));
535
+ console.log(chalk.gray(' npx create-ai-context sync:all'));
536
+ process.exit(1);
537
+ }
538
+ }
539
+ } catch (error) {
540
+ console.error(chalk.red('\n✖ Error:'), error.message);
541
+ process.exit(1);
542
+ }
543
+ });
544
+
545
+ program
546
+ .command('sync:all')
547
+ .description('Synchronize all AI tool contexts from codebase')
548
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
549
+ .option('--quiet', 'Suppress output')
550
+ .action(async (options) => {
551
+ if (!options.quiet) {
552
+ console.log(banner);
553
+ }
554
+
555
+ const projectRoot = path.resolve(options.path);
556
+ const spinner = createSpinner();
557
+
558
+ try {
559
+ if (!options.quiet) {
560
+ spinner.start('Analyzing codebase...');
561
+ }
562
+
563
+ const config = {
564
+ projectName: path.basename(projectRoot),
565
+ aiTools: getAdapterNames()
566
+ };
567
+
568
+ const results = await syncAllFromCodebase(projectRoot, config);
569
+
570
+ if (!options.quiet) {
571
+ if (results.errors.length > 0) {
572
+ spinner.warn('Sync completed with errors');
573
+
574
+ console.log(chalk.bold('\nSynced tools:'));
575
+ for (const tool of results.tools) {
576
+ console.log(chalk.green(` ✓ ${tool.tool} (${tool.fileCount} files)`));
577
+ }
578
+
579
+ console.log(chalk.red('\nErrors:'));
580
+ for (const error of results.errors) {
581
+ // error object has: { tool, errors: [] } or { tool, message }
582
+ const errorText = error.message ||
583
+ (error.errors && error.errors.length > 0
584
+ ? error.errors.map(e => e.message || e).join('; ')
585
+ : error.tool);
586
+ console.error(chalk.red(` ${error.tool || 'Unknown'}: ${errorText}`));
587
+ }
588
+ process.exit(1);
589
+ } else {
590
+ spinner.succeed(`Synced ${results.tools.length} AI tools`);
591
+
592
+ console.log(chalk.bold('\nSynced tools:'));
593
+ for (const tool of results.tools) {
594
+ console.log(chalk.green(` ✓ ${tool.tool} (${tool.fileCount} files)`));
595
+ }
596
+ }
597
+ }
598
+ } catch (error) {
599
+ if (!options.quiet) {
600
+ spinner.fail('Sync failed');
601
+ console.error(chalk.red('\n✖ Error:'), error.message);
602
+ }
603
+ process.exit(1);
604
+ }
605
+ });
606
+
607
+ program
608
+ .command('sync:state')
609
+ .description('Update sync state without regenerating files (used by post-commit hooks)')
610
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
611
+ .option('--quiet', 'Suppress output')
612
+ .action(async (options) => {
613
+ if (!options.quiet) {
614
+ console.log(banner);
615
+ }
616
+
617
+ const projectRoot = path.resolve(options.path);
618
+
619
+ try {
620
+ const result = updateSyncStateOnly(projectRoot);
621
+
622
+ if (!options.quiet) {
623
+ console.log(chalk.green('✓ Sync state updated'));
624
+ console.log(chalk.gray(` Timestamp: ${result.timestamp}`));
625
+ console.log(chalk.gray(` Tools tracked: ${Object.keys(result.hashes).length}`));
626
+ }
627
+ } catch (error) {
628
+ if (!options.quiet) {
629
+ console.error(chalk.red('\n✖ Error:'), error.message);
630
+ }
631
+ process.exit(1);
632
+ }
633
+ });
634
+
635
+ program
636
+ .command('sync:from <tool>')
637
+ .description('Propagate context from a specific tool to all others')
638
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
639
+ .option('-s, --strategy <strategy>', 'Conflict resolution strategy', 'source_wins')
640
+ .action(async (sourceTool, options) => {
641
+ console.log(banner);
642
+
643
+ const projectRoot = path.resolve(options.path);
644
+ const spinner = createSpinner();
645
+
646
+ const validTools = getAdapterNames();
647
+ if (!validTools.includes(sourceTool)) {
648
+ console.error(chalk.red(`\n✖ Error: Invalid tool: ${sourceTool}`));
649
+ console.error(chalk.gray(` Valid options: ${validTools.join(', ')}`));
650
+ process.exit(1);
651
+ }
652
+
653
+ try {
654
+ spinner.start(`Propagating from ${sourceTool}...`);
655
+
656
+ const config = {
657
+ projectName: path.basename(projectRoot),
658
+ aiTools: validTools
659
+ };
660
+
661
+ const results = await propagateContextChange(
662
+ sourceTool,
663
+ projectRoot,
664
+ config,
665
+ options.strategy
666
+ );
667
+
668
+ if (results.errors.length > 0) {
669
+ spinner.warn('Propagation completed with errors');
670
+
671
+ console.log(chalk.bold('\nPropagated to:'));
672
+ for (const tool of results.propagated) {
673
+ console.log(chalk.green(` ✓ ${tool.displayName}`));
674
+ }
675
+
676
+ console.log(chalk.red('\nErrors:'));
677
+ for (const error of results.errors) {
678
+ const errorText = error.message ||
679
+ (error.errors && error.errors.length > 0
680
+ ? error.errors.map(e => e.message || e).join('; ')
681
+ : 'Unknown error');
682
+ console.error(chalk.red(` ✖ ${error.tool || 'Unknown'}: ${errorText}`));
683
+ }
684
+ process.exit(1);
685
+ } else {
686
+ spinner.succeed(`Propagated to ${results.propagated.length} tools`);
687
+
688
+ console.log(chalk.bold('\nPropagated to:'));
689
+ for (const tool of results.propagated) {
690
+ console.log(chalk.green(` ✓ ${tool.displayName}`));
691
+ }
692
+ }
693
+ } catch (error) {
694
+ spinner.fail('Propagation failed');
695
+ console.error(chalk.red('\n✖ Error:'), error.message);
696
+ process.exit(1);
697
+ }
698
+ });
699
+
700
+ program
701
+ .command('sync:resolve')
702
+ .description('Resolve conflicts between AI tool contexts')
703
+ .option('-s, --strategy <strategy>', 'Strategy: source_wins, regenerate_all, newest, manual', 'regenerate_all')
704
+ .option('-t, --tool <tool>', 'Preferred tool (for source_wins strategy)')
705
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
706
+ .action(async (options) => {
707
+ console.log(banner);
708
+
709
+ const projectRoot = path.resolve(options.path);
710
+ const spinner = createSpinner();
711
+
712
+ const validStrategies = Object.values(CONFLICT_STRATEGY);
713
+ if (!validStrategies.includes(options.strategy)) {
714
+ console.error(chalk.red(`\n✖ Error: Invalid strategy: ${options.strategy}`));
715
+ console.error(chalk.gray(` Valid options: ${validStrategies.join(', ')}`));
716
+ process.exit(1);
717
+ }
718
+
719
+ try {
720
+ spinner.start(`Resolving conflicts (${options.strategy})...`);
721
+
722
+ const config = {
723
+ projectName: path.basename(projectRoot),
724
+ aiTools: getAdapterNames()
725
+ };
726
+
727
+ const result = await resolveConflict(
728
+ projectRoot,
729
+ config,
730
+ options.strategy,
731
+ options.tool
732
+ );
733
+
734
+ if (result.resolved) {
735
+ spinner.succeed(result.message);
736
+ } else {
737
+ spinner.warn('Unable to resolve');
738
+ console.log(chalk.yellow(`\n${result.message}`));
739
+
740
+ if (result.status) {
741
+ console.log(formatSyncStatus(result.status));
742
+ }
743
+ process.exit(1);
744
+ }
745
+ } catch (error) {
746
+ spinner.fail('Resolution failed');
747
+ console.error(chalk.red('\n✖ Error:'), error.message);
748
+ process.exit(1);
749
+ }
750
+ });
751
+
752
+ program
753
+ .command('sync:history')
754
+ .description('Show sync history')
755
+ .option('-n, --limit <number>', 'Number of entries to show', '10')
756
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
757
+ .action(async (options) => {
758
+ console.log(banner);
759
+
760
+ const projectRoot = path.resolve(options.path);
761
+
762
+ try {
763
+ const history = getSyncHistory(projectRoot, parseInt(options.limit, 10));
764
+
765
+ console.log(chalk.bold('\nSync History:\n'));
766
+
767
+ if (history.length === 0) {
768
+ console.log(chalk.gray(' No sync history found\n'));
769
+ } else {
770
+ for (const entry of history.reverse()) {
771
+ const date = new Date(entry.timestamp).toLocaleString();
772
+ console.log(chalk.cyan(` ${date}`));
773
+ console.log(chalk.gray(` Source: ${entry.source || entry.sourceTool}`));
774
+ console.log(chalk.gray(` Strategy: ${entry.strategy}`));
775
+ console.log(chalk.gray(` Propagated: ${entry.propagatedCount} tools`));
776
+
777
+ if (entry.errorCount > 0) {
778
+ console.log(chalk.red(` Errors: ${entry.errorCount}`));
779
+ }
780
+ console.log('');
781
+ }
782
+ }
783
+ } catch (error) {
784
+ console.error(chalk.red('\n✖ Error:'), error.message);
785
+ process.exit(1);
786
+ }
787
+ });
788
+
789
+ program
790
+ .command('hooks:install')
791
+ .description('Install git hooks for automatic sync')
792
+ .action(async () => {
793
+ console.log(banner);
794
+
795
+ try {
796
+ const { installHooks } = require('../lib/install-hooks');
797
+ installHooks();
798
+ } catch (error) {
799
+ console.error(chalk.red('\n✖ Error:'), error.message);
800
+ process.exit(1);
801
+ }
802
+ });
803
+
804
+ // MCP Server Commands
805
+ program
806
+ .command('mcp:start')
807
+ .description('Start the AI Context MCP server for Claude Desktop')
808
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
809
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
810
+ .action(async (options) => {
811
+ console.log(banner);
812
+
813
+ const projectRoot = path.resolve(options.path);
814
+
815
+ console.log(chalk.cyan('\n Starting MCP server...'));
816
+ console.log(chalk.gray(` Project: ${projectRoot}`));
817
+ console.log(chalk.gray(` Database: ${options.db || '.ai-context.db'}`));
818
+ console.log();
819
+
820
+ try {
821
+ // Check if database exists
822
+ const dbPath = path.join(projectRoot, options.db || '.ai-context.db');
823
+ const dbExists = fs.existsSync(dbPath);
824
+
825
+ if (!dbExists) {
826
+ console.log(chalk.yellow(' ⚠ Database not found. Run `npx create-ai-context mcp:init` first.'));
827
+ console.log(chalk.gray(' Continuing with empty database...'));
828
+ console.log();
829
+ }
830
+
831
+ // Set environment variables for the MCP server
832
+ process.env.AI_CONTEXT_PROJECT_ROOT = projectRoot;
833
+ if (options.db) {
834
+ process.env.AI_CONTEXT_DB_PATH = options.db;
835
+ }
836
+
837
+ // Import and start the MCP server
838
+ // The MCP server package should be installed alongside this CLI
839
+ try {
840
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
841
+ const { main } = require(mcpServerPath);
842
+ await main();
843
+ } catch (resolveError) {
844
+ // Try relative path as fallback (for development)
845
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/server.js');
846
+ if (fs.existsSync(devPath)) {
847
+ const { main } = require(devPath);
848
+ await main();
849
+ } else {
850
+ console.error(chalk.red('\n✖ MCP server package not found.'));
851
+ console.error(chalk.gray(' Install with: npm install @ai-context/mcp-server'));
852
+ console.error(chalk.gray(' Or run from the ai-context-mcp-server package directly.'));
853
+ process.exit(1);
854
+ }
855
+ }
856
+ } catch (error) {
857
+ console.error(chalk.red('\n✖ Error:'), error.message);
858
+ process.exit(1);
859
+ }
860
+ });
861
+
862
+ program
863
+ .command('mcp:init')
864
+ .description('Initialize MCP database and index existing context')
865
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
866
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
867
+ .option('--skip-code', 'Skip code indexing')
868
+ .option('--skip-git', 'Skip git history indexing')
869
+ .action(async (options) => {
870
+ console.log(banner);
871
+
872
+ const projectRoot = path.resolve(options.path);
873
+ const spinner = createSpinner();
874
+
875
+ console.log(chalk.cyan('\n Initializing MCP database...'));
876
+ console.log(chalk.gray(` Project: ${projectRoot}`));
877
+ console.log();
878
+
879
+ try {
880
+ // Set environment variables
881
+ process.env.AI_CONTEXT_PROJECT_ROOT = projectRoot;
882
+ if (options.db) {
883
+ process.env.AI_CONTEXT_DB_PATH = options.db;
884
+ }
885
+
886
+ // Import MCP server components
887
+ let mcpPackage;
888
+ try {
889
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
890
+ mcpPackage = require(mcpServerPath);
891
+ } catch {
892
+ // Try relative path as fallback (for development)
893
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
894
+ if (fs.existsSync(devPath)) {
895
+ mcpPackage = require(devPath);
896
+ } else {
897
+ console.error(chalk.red('\n✖ MCP server package not found.'));
898
+ console.error(chalk.gray(' Install with: npm install @ai-context/mcp-server'));
899
+ process.exit(1);
900
+ }
901
+ }
902
+
903
+ const { DatabaseClient, EmbeddingsManager, ContextIndexer, CodeIndexer, GitIndexer } = mcpPackage;
904
+
905
+ // Initialize database
906
+ spinner.start('Creating database...');
907
+ const db = new DatabaseClient(projectRoot, options.db || '.ai-context.db');
908
+ spinner.succeed(`Database created: ${options.db || '.ai-context.db'}`);
909
+
910
+ // Create mock embeddings manager (no API key required for init)
911
+ const embeddings = {
912
+ search: async () => [],
913
+ queueForEmbedding: () => {},
914
+ processQueue: async () => 0,
915
+ getCount: () => 0,
916
+ deleteEmbedding: () => false
917
+ };
918
+
919
+ // Index context documents
920
+ spinner.start('Indexing context documents...');
921
+ const contextIndexer = new ContextIndexer(db, embeddings, projectRoot);
922
+ const contextResult = await contextIndexer.indexAll();
923
+ spinner.succeed(`Indexed ${contextResult.indexed} context documents`);
924
+
925
+ // Index code (unless skipped)
926
+ if (!options.skipCode) {
927
+ spinner.start('Indexing source code...');
928
+ const codeIndexer = new CodeIndexer(db, embeddings, projectRoot);
929
+ const codeResult = await codeIndexer.indexAll();
930
+ spinner.succeed(`Indexed ${codeResult.files} source files (${codeResult.chunks} chunks)`);
931
+ }
932
+
933
+ // Index git history (unless skipped)
934
+ if (!options.skipGit) {
935
+ spinner.start('Indexing git history...');
936
+ const gitIndexer = new GitIndexer(db, embeddings, projectRoot);
937
+ const gitResult = await gitIndexer.indexHistory({ maxCommits: 100 });
938
+ spinner.succeed(`Indexed ${gitResult.commits} commits`);
939
+ }
940
+
941
+ // Print summary
942
+ const stats = db.getStats();
943
+ console.log(chalk.bold('\n Database Summary:'));
944
+ console.log(chalk.gray(` • Context items: ${stats.items}`));
945
+ console.log(chalk.gray(` • Relations: ${stats.relations}`));
946
+ console.log(chalk.gray(` • Commits: ${stats.commits}`));
947
+
948
+ console.log(chalk.bold('\n Next steps:'));
949
+ console.log(chalk.gray(' 1. Set OPENROUTER_API_KEY for embeddings (optional)'));
950
+ console.log(chalk.gray(' 2. Run `npx create-ai-context mcp:start` to start the server'));
951
+ console.log(chalk.gray(' 3. Configure Claude Desktop to use the MCP server'));
952
+ console.log();
953
+
954
+ // Close database
955
+ db.close();
956
+ } catch (error) {
957
+ spinner.fail('Initialization failed');
958
+ console.error(chalk.red('\n✖ Error:'), error.message);
959
+ process.exit(1);
960
+ }
961
+ });
962
+
963
+ program
964
+ .command('mcp:status')
965
+ .description('Show MCP database status and statistics')
966
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
967
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
968
+ .action(async (options) => {
969
+ console.log(banner);
970
+
971
+ const projectRoot = path.resolve(options.path);
972
+ const dbFile = options.db || '.ai-context.db';
973
+ const dbPath = path.join(projectRoot, dbFile);
974
+
975
+ console.log(chalk.bold('\nMCP Database Status:\n'));
976
+
977
+ // Check if database exists
978
+ if (!fs.existsSync(dbPath)) {
979
+ console.log(chalk.yellow(' ○ Database not initialized'));
980
+ console.log(chalk.gray(` Run \`npx create-ai-context mcp:init\` to create database\n`));
981
+ process.exit(0);
982
+ }
983
+
984
+ try {
985
+ // Import MCP server components
986
+ let mcpPackage;
987
+ try {
988
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
989
+ mcpPackage = require(mcpServerPath);
990
+ } catch {
991
+ // Try relative path as fallback (for development)
992
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
993
+ if (fs.existsSync(devPath)) {
994
+ mcpPackage = require(devPath);
995
+ } else {
996
+ console.error(chalk.red('\n✖ MCP server package not found.'));
997
+ process.exit(1);
998
+ }
999
+ }
1000
+
1001
+ const { DatabaseClient, SCHEMA_VERSION } = mcpPackage;
1002
+
1003
+ // Open database
1004
+ const db = new DatabaseClient(projectRoot, dbFile);
1005
+ const stats = db.getStats();
1006
+
1007
+ console.log(chalk.green(' ✓ Database initialized'));
1008
+ console.log(chalk.gray(` Path: ${dbPath}`));
1009
+ console.log(chalk.gray(` Schema: v${SCHEMA_VERSION}`));
1010
+ console.log();
1011
+
1012
+ console.log(chalk.bold(' Statistics:'));
1013
+ console.log(chalk.gray(` • Context items: ${stats.items}`));
1014
+ console.log(chalk.gray(` • Knowledge relations: ${stats.relations}`));
1015
+ console.log(chalk.gray(` • Indexed commits: ${stats.commits}`));
1016
+ console.log(chalk.gray(` • Embeddings: ${stats.embeddings}`));
1017
+ console.log();
1018
+
1019
+ // Show items by type
1020
+ const types = ['workflow', 'agent', 'command', 'code', 'commit', 'knowledge', 'config'];
1021
+ console.log(chalk.bold(' Items by type:'));
1022
+ for (const type of types) {
1023
+ const items = db.getItemsByType(type);
1024
+ if (items.length > 0) {
1025
+ console.log(chalk.gray(` • ${type}: ${items.length}`));
1026
+ }
1027
+ }
1028
+ console.log();
1029
+
1030
+ // Check embeddings status
1031
+ if (!process.env.OPENROUTER_API_KEY) {
1032
+ console.log(chalk.yellow(' ⚠ Embeddings disabled (OPENROUTER_API_KEY not set)'));
1033
+ console.log(chalk.gray(' Set OPENROUTER_API_KEY to enable semantic search\n'));
1034
+ } else {
1035
+ console.log(chalk.green(' ✓ Embeddings enabled'));
1036
+ }
1037
+
1038
+ db.close();
1039
+ } catch (error) {
1040
+ console.error(chalk.red('\n✖ Error:'), error.message);
1041
+ process.exit(1);
1042
+ }
1043
+ });
1044
+
1045
+ // MCP Watch Command - Auto-sync file changes
1046
+ program
1047
+ .command('mcp:watch')
1048
+ .description('Watch for file changes and auto-sync to MCP database')
1049
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
1050
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
1051
+ .option('--debounce <ms>', 'Debounce delay in milliseconds', '500')
1052
+ .option('-v, --verbose', 'Show detailed output')
1053
+ .action(async (options) => {
1054
+ console.log(banner);
1055
+
1056
+ const projectRoot = path.resolve(options.path);
1057
+ const spinner = createSpinner();
1058
+
1059
+ console.log(chalk.cyan('\n Starting file watcher...'));
1060
+ console.log(chalk.gray(` Project: ${projectRoot}`));
1061
+ console.log(chalk.gray(` Debounce: ${options.debounce}ms`));
1062
+ console.log();
1063
+
1064
+ try {
1065
+ // Check if database exists
1066
+ const dbPath = path.join(projectRoot, options.db || '.ai-context.db');
1067
+ if (!fs.existsSync(dbPath)) {
1068
+ console.log(chalk.yellow(' ⚠ Database not found. Run `npx create-ai-context mcp:init` first.'));
1069
+ process.exit(1);
1070
+ }
1071
+
1072
+ // Import MCP server components
1073
+ let mcpPackage;
1074
+ try {
1075
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
1076
+ mcpPackage = require(mcpServerPath);
1077
+ } catch {
1078
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
1079
+ if (fs.existsSync(devPath)) {
1080
+ mcpPackage = require(devPath);
1081
+ } else {
1082
+ console.error(chalk.red('\n✖ MCP server package not found.'));
1083
+ process.exit(1);
1084
+ }
1085
+ }
1086
+
1087
+ const { DatabaseClient, SyncService, EmbeddingsManager } = mcpPackage;
1088
+
1089
+ // Initialize database
1090
+ const db = new DatabaseClient(projectRoot, options.db || '.ai-context.db');
1091
+
1092
+ // Create mock embeddings manager
1093
+ const embeddings = {
1094
+ search: async () => [],
1095
+ queueForEmbedding: () => {},
1096
+ processQueue: async () => 0,
1097
+ getCount: () => 0,
1098
+ deleteEmbedding: () => false
1099
+ };
1100
+
1101
+ // Create sync service
1102
+ const syncService = new SyncService({
1103
+ projectRoot,
1104
+ db,
1105
+ embeddings,
1106
+ watcherConfig: {
1107
+ debounceMs: parseInt(options.debounce, 10),
1108
+ verbose: options.verbose
1109
+ },
1110
+ verbose: options.verbose
1111
+ });
1112
+
1113
+ // Set up event handlers
1114
+ syncService.on('started', () => {
1115
+ console.log(chalk.green(' ✓ File watcher started'));
1116
+ console.log(chalk.gray(' Watching for changes... (Press Ctrl+C to stop)\n'));
1117
+ });
1118
+
1119
+ syncService.on('sync-complete', (result) => {
1120
+ const timestamp = new Date().toLocaleTimeString();
1121
+ console.log(chalk.gray(` [${timestamp}] Synced: ${result.indexed} indexed, ${result.removed} removed (${result.duration}ms)`));
1122
+ });
1123
+
1124
+ syncService.on('sync-error', ({ error }) => {
1125
+ console.error(chalk.red(' ✖ Sync error:'), error.message);
1126
+ });
1127
+
1128
+ // Start watching
1129
+ await syncService.start();
1130
+
1131
+ // Handle graceful shutdown
1132
+ process.on('SIGINT', () => {
1133
+ console.log(chalk.yellow('\n Stopping file watcher...'));
1134
+ syncService.stop();
1135
+ db.close();
1136
+ process.exit(0);
1137
+ });
1138
+
1139
+ } catch (error) {
1140
+ console.error(chalk.red('\n✖ Error:'), error.message);
1141
+ process.exit(1);
1142
+ }
1143
+ });
1144
+
1145
+ // MCP Migrate Command - Migrate file-based context to database
1146
+ program
1147
+ .command('mcp:migrate')
1148
+ .description('Migrate existing file-based AI context to MCP database')
1149
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
1150
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
1151
+ .option('--dry-run', 'Show what would be migrated without making changes')
1152
+ .action(async (options) => {
1153
+ console.log(banner);
1154
+
1155
+ const projectRoot = path.resolve(options.path);
1156
+ const spinner = createSpinner();
1157
+
1158
+ console.log(chalk.cyan('\n Migrating file-based context to MCP database...'));
1159
+ console.log(chalk.gray(` Project: ${projectRoot}`));
1160
+ if (options.dryRun) {
1161
+ console.log(chalk.yellow(' Mode: Dry run (no changes will be made)'));
1162
+ }
1163
+ console.log();
1164
+
1165
+ try {
1166
+ // Find existing context files
1167
+ const contextPatterns = [
1168
+ '.claude/**/*.md',
1169
+ '.ai-context/**/*.md',
1170
+ 'CLAUDE.md',
1171
+ 'AI_CONTEXT.md',
1172
+ '.clinerules',
1173
+ '.github/copilot-instructions.md'
1174
+ ];
1175
+
1176
+ const foundFiles = [];
1177
+ for (const pattern of contextPatterns) {
1178
+ const { glob } = require('glob');
1179
+ const files = await glob(pattern, {
1180
+ cwd: projectRoot,
1181
+ ignore: ['**/node_modules/**'],
1182
+ absolute: true
1183
+ });
1184
+ foundFiles.push(...files);
1185
+ }
1186
+
1187
+ if (foundFiles.length === 0) {
1188
+ console.log(chalk.yellow(' ○ No context files found to migrate'));
1189
+ process.exit(0);
1190
+ }
1191
+
1192
+ console.log(chalk.bold(` Found ${foundFiles.length} context files:\n`));
1193
+ for (const file of foundFiles.slice(0, 10)) {
1194
+ console.log(chalk.gray(` • ${path.relative(projectRoot, file)}`));
1195
+ }
1196
+ if (foundFiles.length > 10) {
1197
+ console.log(chalk.gray(` ... and ${foundFiles.length - 10} more`));
1198
+ }
1199
+ console.log();
1200
+
1201
+ if (options.dryRun) {
1202
+ console.log(chalk.yellow(' Dry run complete. Use without --dry-run to perform migration.'));
1203
+ process.exit(0);
1204
+ }
1205
+
1206
+ // Import MCP server components
1207
+ let mcpPackage;
1208
+ try {
1209
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
1210
+ mcpPackage = require(mcpServerPath);
1211
+ } catch {
1212
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
1213
+ if (fs.existsSync(devPath)) {
1214
+ mcpPackage = require(devPath);
1215
+ } else {
1216
+ console.error(chalk.red('\n✖ MCP server package not found.'));
1217
+ process.exit(1);
1218
+ }
1219
+ }
1220
+
1221
+ const { DatabaseClient, ContextIndexer } = mcpPackage;
1222
+
1223
+ // Initialize database
1224
+ spinner.start('Creating database...');
1225
+ const db = new DatabaseClient(projectRoot, options.db || '.ai-context.db');
1226
+ spinner.succeed('Database ready');
1227
+
1228
+ // Create mock embeddings manager
1229
+ const embeddings = {
1230
+ search: async () => [],
1231
+ queueForEmbedding: () => {},
1232
+ processQueue: async () => 0,
1233
+ getCount: () => 0,
1234
+ deleteEmbedding: () => false
1235
+ };
1236
+
1237
+ // Index context files
1238
+ spinner.start('Migrating context files...');
1239
+ const contextIndexer = new ContextIndexer(db, embeddings, projectRoot);
1240
+ const result = await contextIndexer.indexAll();
1241
+ spinner.succeed(`Migrated ${result.indexed} context documents`);
1242
+
1243
+ if (result.errors.length > 0) {
1244
+ console.log(chalk.yellow(`\n ⚠ ${result.errors.length} errors during migration:`));
1245
+ for (const error of result.errors.slice(0, 5)) {
1246
+ console.log(chalk.gray(` • ${error}`));
1247
+ }
1248
+ }
1249
+
1250
+ // Print summary
1251
+ const stats = db.getStats();
1252
+ console.log(chalk.bold('\n Migration complete:'));
1253
+ console.log(chalk.gray(` • Context items: ${stats.items}`));
1254
+ console.log(chalk.gray(` • Database: ${options.db || '.ai-context.db'}`));
1255
+ console.log();
1256
+
1257
+ db.close();
1258
+ } catch (error) {
1259
+ spinner.fail('Migration failed');
1260
+ console.error(chalk.red('\n✖ Error:'), error.message);
1261
+ process.exit(1);
1262
+ }
1263
+ });
1264
+
1265
+ // MCP Export Command - Export database to files
1266
+ program
1267
+ .command('mcp:export')
1268
+ .description('Export MCP database to markdown files')
1269
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
1270
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
1271
+ .option('-o, --output <dir>', 'Output directory for exported files', '.ai-context-export')
1272
+ .option('--format <format>', 'Export format: shadow (individual files) or single (one file)', 'shadow')
1273
+ .action(async (options) => {
1274
+ console.log(banner);
1275
+
1276
+ const projectRoot = path.resolve(options.path);
1277
+ const outputDir = path.resolve(projectRoot, options.output);
1278
+ const spinner = createSpinner();
1279
+
1280
+ console.log(chalk.cyan('\n Exporting MCP database...'));
1281
+ console.log(chalk.gray(` Project: ${projectRoot}`));
1282
+ console.log(chalk.gray(` Output: ${options.output}`));
1283
+ console.log(chalk.gray(` Format: ${options.format}`));
1284
+ console.log();
1285
+
1286
+ try {
1287
+ // Check if database exists
1288
+ const dbPath = path.join(projectRoot, options.db || '.ai-context.db');
1289
+ if (!fs.existsSync(dbPath)) {
1290
+ console.log(chalk.yellow(' ⚠ Database not found. Run `npx create-ai-context mcp:init` first.'));
1291
+ process.exit(1);
1292
+ }
1293
+
1294
+ // Import MCP server components
1295
+ let mcpPackage;
1296
+ try {
1297
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
1298
+ mcpPackage = require(mcpServerPath);
1299
+ } catch {
1300
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
1301
+ if (fs.existsSync(devPath)) {
1302
+ mcpPackage = require(devPath);
1303
+ } else {
1304
+ console.error(chalk.red('\n✖ MCP server package not found.'));
1305
+ process.exit(1);
1306
+ }
1307
+ }
1308
+
1309
+ const { DatabaseClient, ShadowGenerator } = mcpPackage;
1310
+
1311
+ // Open database
1312
+ const db = new DatabaseClient(projectRoot, options.db || '.ai-context.db');
1313
+
1314
+ // Create output directory
1315
+ if (!fs.existsSync(outputDir)) {
1316
+ fs.mkdirSync(outputDir, { recursive: true });
1317
+ }
1318
+
1319
+ if (options.format === 'single') {
1320
+ // Export to single file
1321
+ spinner.start('Exporting to single file...');
1322
+ const shadowGen = new ShadowGenerator(db, projectRoot, { outputDir: options.output });
1323
+ const outputPath = path.join(outputDir, 'AI_CONTEXT_EXPORT.md');
1324
+ shadowGen.exportToSingleFile(outputPath);
1325
+ spinner.succeed(`Exported to ${options.output}/AI_CONTEXT_EXPORT.md`);
1326
+ } else {
1327
+ // Export as shadow files
1328
+ spinner.start('Generating shadow files...');
1329
+ const shadowGen = new ShadowGenerator(db, projectRoot, { outputDir: options.output });
1330
+ const result = await shadowGen.generateAll();
1331
+ spinner.succeed(`Generated ${result.generated.length + result.updated.length} files`);
1332
+
1333
+ if (result.errors.length > 0) {
1334
+ console.log(chalk.yellow(`\n ⚠ ${result.errors.length} errors during export:`));
1335
+ for (const error of result.errors.slice(0, 5)) {
1336
+ console.log(chalk.gray(` • ${error}`));
1337
+ }
1338
+ }
1339
+ }
1340
+
1341
+ // Print summary
1342
+ const stats = db.getStats();
1343
+ console.log(chalk.bold('\n Export complete:'));
1344
+ console.log(chalk.gray(` • Context items: ${stats.items}`));
1345
+ console.log(chalk.gray(` • Output: ${options.output}/`));
1346
+ console.log();
1347
+
1348
+ db.close();
1349
+ } catch (error) {
1350
+ spinner.fail('Export failed');
1351
+ console.error(chalk.red('\n✖ Error:'), error.message);
1352
+ process.exit(1);
1353
+ }
1354
+ });
1355
+
1356
+ // MCP Sync command - export database to all AI tool formats
1357
+ program
1358
+ .command('mcp:sync')
1359
+ .description('Export MCP database to all AI tool formats (Copilot, Cline, Antigravity, Windsurf, Aider, Continue)')
1360
+ .option('-p, --path <dir>', 'Project directory (defaults to current)', '.')
1361
+ .option('--db <path>', 'Database filename (defaults to .ai-context.db)')
1362
+ .option('--tools <tools>', 'Comma-separated list of tools to export (copilot,cline,antigravity,windsurf,aider,continue,all)', 'all')
1363
+ .option('-f, --force', 'Force overwrite of existing non-managed files')
1364
+ .option('--status', 'Show sync status without exporting')
1365
+ .option('-v, --verbose', 'Show detailed output')
1366
+ .action(async (options) => {
1367
+ console.log(banner);
1368
+
1369
+ const projectRoot = path.resolve(options.path);
1370
+ const spinner = createSpinner();
1371
+
1372
+ console.log(chalk.cyan('\n Cross-Tool Sync...'));
1373
+ console.log(chalk.gray(` Project: ${projectRoot}`));
1374
+ console.log();
1375
+
1376
+ try {
1377
+ // Check if database exists
1378
+ const dbPath = path.join(projectRoot, options.db || '.ai-context.db');
1379
+ if (!fs.existsSync(dbPath)) {
1380
+ console.log(chalk.yellow(' ⚠ Database not found. Run `npx create-ai-context mcp:init` first.'));
1381
+ process.exit(1);
1382
+ }
1383
+
1384
+ // Import MCP server components
1385
+ let mcpPackage;
1386
+ try {
1387
+ const mcpServerPath = require.resolve('@ai-context/mcp-server', { paths: [projectRoot, __dirname] });
1388
+ mcpPackage = require(mcpServerPath);
1389
+ } catch {
1390
+ const devPath = path.join(__dirname, '../../ai-context-mcp-server/dist/index.js');
1391
+ if (fs.existsSync(devPath)) {
1392
+ mcpPackage = require(devPath);
1393
+ } else {
1394
+ console.error(chalk.red('\n✖ MCP server package not found.'));
1395
+ process.exit(1);
1396
+ }
1397
+ }
1398
+
1399
+ const { DatabaseClient, CrossToolExporter, getSupportedTools, getToolDisplayName, getToolOutputPath } = mcpPackage;
1400
+
1401
+ // Open database
1402
+ const db = new DatabaseClient(projectRoot, options.db || '.ai-context.db');
1403
+
1404
+ // Parse tools
1405
+ const allTools = getSupportedTools();
1406
+ let selectedTools = allTools;
1407
+ if (options.tools && options.tools !== 'all') {
1408
+ selectedTools = options.tools.split(',').map(t => t.trim().toLowerCase());
1409
+ const invalid = selectedTools.filter(t => !allTools.includes(t));
1410
+ if (invalid.length > 0) {
1411
+ console.error(chalk.red(`\n✖ Invalid tools: ${invalid.join(', ')}`));
1412
+ console.error(chalk.gray(` Valid options: ${allTools.join(', ')}`));
1413
+ db.close();
1414
+ process.exit(1);
1415
+ }
1416
+ }
1417
+
1418
+ // Create exporter
1419
+ const exporter = new CrossToolExporter(db, projectRoot, {
1420
+ tools: selectedTools,
1421
+ force: options.force || false,
1422
+ verbose: options.verbose || false
1423
+ });
1424
+
1425
+ // Status mode
1426
+ if (options.status) {
1427
+ console.log(chalk.bold(' Sync Status:'));
1428
+ console.log();
1429
+
1430
+ const status = exporter.getSyncStatus();
1431
+ for (const tool of allTools) {
1432
+ const toolStatus = status[tool];
1433
+ const displayName = getToolDisplayName(tool);
1434
+ const outputPath = getToolOutputPath(tool);
1435
+
1436
+ let statusIcon = '✗';
1437
+ let statusColor = chalk.gray;
1438
+ let statusText = 'Not exported';
1439
+
1440
+ if (toolStatus.exists) {
1441
+ if (toolStatus.managed) {
1442
+ statusIcon = '✓';
1443
+ statusColor = chalk.green;
1444
+ statusText = toolStatus.lastSync
1445
+ ? `Synced ${new Date(toolStatus.lastSync).toLocaleString()}`
1446
+ : 'Synced';
1447
+ } else {
1448
+ statusIcon = '⚠';
1449
+ statusColor = chalk.yellow;
1450
+ statusText = 'Exists (not managed)';
1451
+ }
1452
+ }
1453
+
1454
+ console.log(` ${statusColor(statusIcon)} ${displayName.padEnd(20)} ${chalk.gray(outputPath)}`);
1455
+ console.log(` ${chalk.gray(statusText)}`);
1456
+ }
1457
+
1458
+ console.log();
1459
+ db.close();
1460
+ return;
1461
+ }
1462
+
1463
+ // Export mode
1464
+ spinner.start(`Exporting to ${selectedTools.length} tools...`);
1465
+
1466
+ const result = await exporter.exportAll();
1467
+
1468
+ spinner.succeed(`Exported to ${result.totalFiles} files`);
1469
+
1470
+ console.log(chalk.bold('\n Export Results:'));
1471
+ console.log();
1472
+
1473
+ for (const toolResult of result.results) {
1474
+ const displayName = getToolDisplayName(toolResult.tool);
1475
+
1476
+ if (toolResult.success) {
1477
+ console.log(` ${chalk.green('✓')} ${displayName}`);
1478
+ for (const file of toolResult.files) {
1479
+ console.log(chalk.gray(` → ${file}`));
1480
+ }
1481
+ } else {
1482
+ console.log(` ${chalk.red('✗')} ${displayName}`);
1483
+ for (const error of toolResult.errors) {
1484
+ console.log(chalk.yellow(` ⚠ ${error}`));
1485
+ }
1486
+ }
1487
+ }
1488
+
1489
+ if (result.totalErrors > 0) {
1490
+ console.log(chalk.yellow(`\n ⚠ ${result.totalErrors} errors during export.`));
1491
+ console.log(chalk.gray(' Use --force to overwrite existing non-managed files.'));
1492
+ }
1493
+
1494
+ console.log();
1495
+ db.close();
1496
+
1497
+ } catch (error) {
1498
+ spinner.fail('Sync failed');
1499
+ console.error(chalk.red('\n✖ Error:'), error.message);
1500
+ if (options.verbose) {
1501
+ console.error(chalk.gray(error.stack));
1502
+ }
1503
+ process.exit(1);
1504
+ }
1505
+ });
1506
+
1507
+ program.parse();