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.
- package/LICENSE +21 -21
- package/README.md +331 -294
- package/bin/create-ai-context.js +1507 -764
- package/lib/adapters/aider.js +131 -131
- package/lib/adapters/antigravity.js +205 -205
- package/lib/adapters/claude.js +397 -397
- package/lib/adapters/cline.js +125 -125
- package/lib/adapters/continue.js +138 -138
- package/lib/adapters/copilot.js +131 -131
- package/lib/adapters/index.js +78 -78
- package/lib/adapters/windsurf.js +138 -138
- package/lib/ai-context-generator.js +234 -234
- package/lib/ai-orchestrator.js +432 -432
- package/lib/call-tracer.js +444 -444
- package/lib/content-preservation.js +243 -243
- package/lib/cross-tool-sync/file-watcher.js +274 -274
- package/lib/cross-tool-sync/index.js +41 -40
- package/lib/cross-tool-sync/sync-manager.js +540 -512
- package/lib/cross-tool-sync/sync-service.js +297 -297
- package/lib/detector.js +726 -726
- package/lib/doc-discovery.js +741 -741
- package/lib/drift-checker.js +920 -920
- package/lib/environment-detector.js +239 -239
- package/lib/index.js +399 -399
- package/lib/install-hooks.js +82 -82
- package/lib/installer.js +419 -419
- package/lib/migrate.js +328 -328
- package/lib/placeholder.js +632 -632
- package/lib/prompts.js +341 -341
- package/lib/smart-merge.js +540 -540
- package/lib/spinner.js +60 -60
- package/lib/static-analyzer.js +729 -729
- package/lib/template-coordination.js +148 -148
- package/lib/template-populator.js +843 -843
- package/lib/template-renderer.js +392 -392
- package/lib/utils/fs-wrapper.js +79 -79
- package/lib/utils/path-utils.js +60 -60
- package/lib/validate.js +155 -155
- package/package.json +1 -1
- package/templates/AI_CONTEXT.md.template +245 -245
- package/templates/base/README.md +260 -257
- package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
- package/templates/base/agents/api-developer.md +76 -76
- package/templates/base/agents/context-engineer.md +525 -525
- package/templates/base/agents/core-architect.md +76 -76
- package/templates/base/agents/database-ops.md +76 -76
- package/templates/base/agents/deployment-ops.md +76 -76
- package/templates/base/agents/integration-hub.md +76 -76
- package/templates/base/analytics/README.md +114 -114
- package/templates/base/automation/config.json +58 -58
- package/templates/base/automation/generators/code-mapper.js +308 -308
- package/templates/base/automation/generators/index-builder.js +321 -321
- package/templates/base/automation/hooks/post-commit.sh +83 -83
- package/templates/base/automation/hooks/pre-commit.sh +103 -103
- package/templates/base/ci-templates/README.md +108 -108
- package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
- package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
- package/templates/base/commands/analytics.md +238 -238
- package/templates/base/commands/auto-sync.md +172 -172
- package/templates/base/commands/collab.md +194 -194
- package/templates/base/commands/context-optimize.md +226 -0
- package/templates/base/commands/help.md +485 -450
- package/templates/base/commands/rpi-implement.md +164 -115
- package/templates/base/commands/rpi-plan.md +147 -93
- package/templates/base/commands/rpi-research.md +145 -88
- package/templates/base/commands/session-resume.md +144 -144
- package/templates/base/commands/session-save.md +112 -112
- package/templates/base/commands/validate-all.md +77 -77
- package/templates/base/commands/verify-docs-current.md +86 -86
- package/templates/base/config/base.json +57 -57
- package/templates/base/config/environments/development.json +13 -13
- package/templates/base/config/environments/production.json +17 -17
- package/templates/base/config/environments/staging.json +13 -13
- package/templates/base/config/local.json.example +21 -21
- package/templates/base/context/.meta/generated-at.json +18 -18
- package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
- package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
- package/templates/base/context/FILE_OWNERSHIP.md +57 -57
- package/templates/base/context/INTEGRATION_POINTS.md +92 -92
- package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
- package/templates/base/context/TESTING_MAP.md +95 -95
- package/templates/base/context/WORKFLOW_INDEX.md +129 -129
- package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
- package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
- package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
- package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
- package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
- package/templates/base/knowledge/README.md +98 -98
- package/templates/base/knowledge/sessions/README.md +88 -88
- package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
- package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
- package/templates/base/knowledge/shared/decisions/README.md +49 -49
- package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
- package/templates/base/knowledge/shared/patterns/README.md +62 -62
- package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
- package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
- package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
- package/templates/base/schemas/agent.schema.json +141 -141
- package/templates/base/schemas/anchors.schema.json +54 -54
- package/templates/base/schemas/automation.schema.json +93 -93
- package/templates/base/schemas/command.schema.json +134 -134
- package/templates/base/schemas/hashes.schema.json +40 -40
- package/templates/base/schemas/manifest.schema.json +117 -117
- package/templates/base/schemas/plan.schema.json +136 -136
- package/templates/base/schemas/research.schema.json +115 -115
- package/templates/base/schemas/roles.schema.json +34 -34
- package/templates/base/schemas/session.schema.json +77 -77
- package/templates/base/schemas/settings.schema.json +244 -244
- package/templates/base/schemas/staleness.schema.json +53 -53
- package/templates/base/schemas/team-config.schema.json +42 -42
- package/templates/base/schemas/workflow.schema.json +126 -126
- package/templates/base/session/checkpoints/.gitkeep +2 -2
- package/templates/base/session/current/state.json +20 -20
- package/templates/base/session/history/.gitkeep +2 -2
- package/templates/base/settings.json +3 -3
- package/templates/base/standards/COMPATIBILITY.md +219 -219
- package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
- package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
- package/templates/base/standards/README.md +66 -66
- package/templates/base/sync/anchors.json +6 -6
- package/templates/base/sync/hashes.json +6 -6
- package/templates/base/sync/staleness.json +10 -10
- package/templates/base/team/README.md +168 -168
- package/templates/base/team/config.json +79 -79
- package/templates/base/team/roles.json +145 -145
- package/templates/base/tools/bin/claude-context.js +151 -151
- package/templates/base/tools/lib/anchor-resolver.js +276 -276
- package/templates/base/tools/lib/config-loader.js +363 -363
- package/templates/base/tools/lib/detector.js +350 -350
- package/templates/base/tools/lib/diagnose.js +206 -206
- package/templates/base/tools/lib/drift-detector.js +373 -373
- package/templates/base/tools/lib/errors.js +199 -199
- package/templates/base/tools/lib/index.js +36 -36
- package/templates/base/tools/lib/init.js +192 -192
- package/templates/base/tools/lib/logger.js +230 -230
- package/templates/base/tools/lib/placeholder.js +201 -201
- package/templates/base/tools/lib/session-manager.js +354 -354
- package/templates/base/tools/lib/validate.js +521 -521
- package/templates/base/tools/package.json +49 -49
- package/templates/handlebars/aider-config.hbs +146 -80
- package/templates/handlebars/antigravity.hbs +377 -377
- package/templates/handlebars/claude.hbs +183 -183
- package/templates/handlebars/cline.hbs +62 -62
- package/templates/handlebars/continue-config.hbs +116 -116
- package/templates/handlebars/copilot.hbs +130 -130
- package/templates/handlebars/partials/gotcha-list.hbs +11 -11
- package/templates/handlebars/partials/header.hbs +3 -3
- package/templates/handlebars/partials/workflow-summary.hbs +16 -16
- package/templates/handlebars/windsurf-rules.hbs +69 -69
- package/templates/hooks/post-commit.hbs +28 -29
- package/templates/hooks/pre-commit.hbs +46 -46
package/bin/create-ai-context.js
CHANGED
|
@@ -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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
${chalk.cyan('
|
|
50
|
-
${chalk.cyan('
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
.option('--
|
|
80
|
-
.option('
|
|
81
|
-
.option('-
|
|
82
|
-
.option('--
|
|
83
|
-
.option('--
|
|
84
|
-
.option('--
|
|
85
|
-
.option('--
|
|
86
|
-
.option('--
|
|
87
|
-
.option('--
|
|
88
|
-
.option('--
|
|
89
|
-
.option('--
|
|
90
|
-
.option('--
|
|
91
|
-
.option('--
|
|
92
|
-
.option('-
|
|
93
|
-
.option('--
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.
|
|
143
|
-
.
|
|
144
|
-
.option('
|
|
145
|
-
.option('-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
spinner.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
spinner.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
spinner.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
results.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
.
|
|
227
|
-
.
|
|
228
|
-
.option('--
|
|
229
|
-
.option('
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
spinner
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (status.details?.
|
|
252
|
-
console.log(chalk.cyan(` • Rename
|
|
253
|
-
}
|
|
254
|
-
if (
|
|
255
|
-
console.log(chalk.cyan(` •
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
console.log(chalk.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
console.log(chalk.yellow(' ○
|
|
331
|
-
console.log(chalk.gray(' Run `npx create-ai-context
|
|
332
|
-
} else if (migrationStatus.status === '
|
|
333
|
-
console.log(chalk.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log(chalk.
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
.
|
|
374
|
-
.
|
|
375
|
-
.option('--
|
|
376
|
-
.option('-
|
|
377
|
-
.option('
|
|
378
|
-
.option('
|
|
379
|
-
.option('-
|
|
380
|
-
.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
console.log(
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
console.log(chalk.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
'',
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
'
|
|
471
|
-
'
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
`|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
.
|
|
517
|
-
.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
.
|
|
547
|
-
.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
console.
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
spinner.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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();
|