k0ntext 3.3.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +225 -26
  2. package/dist/agents/cleanup-agent.d.ts.map +1 -1
  3. package/dist/agents/cleanup-agent.js +18 -6
  4. package/dist/agents/cleanup-agent.js.map +1 -1
  5. package/dist/agents/drift-agent.d.ts +7 -0
  6. package/dist/agents/drift-agent.d.ts.map +1 -1
  7. package/dist/agents/drift-agent.js +29 -8
  8. package/dist/agents/drift-agent.js.map +1 -1
  9. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  10. package/dist/cli/commands/cleanup.js +8 -1
  11. package/dist/cli/commands/cleanup.js.map +1 -1
  12. package/dist/cli/commands/drift-detect.d.ts.map +1 -1
  13. package/dist/cli/commands/drift-detect.js +21 -1
  14. package/dist/cli/commands/drift-detect.js.map +1 -1
  15. package/dist/cli/commands/embeddings-refresh.d.ts +11 -0
  16. package/dist/cli/commands/embeddings-refresh.d.ts.map +1 -0
  17. package/dist/cli/commands/embeddings-refresh.js +114 -0
  18. package/dist/cli/commands/embeddings-refresh.js.map +1 -0
  19. package/dist/cli/commands/migrate.d.ts +11 -0
  20. package/dist/cli/commands/migrate.d.ts.map +1 -0
  21. package/dist/cli/commands/migrate.js +195 -0
  22. package/dist/cli/commands/migrate.js.map +1 -0
  23. package/dist/cli/commands/restore.d.ts +12 -0
  24. package/dist/cli/commands/restore.d.ts.map +1 -0
  25. package/dist/cli/commands/restore.js +261 -0
  26. package/dist/cli/commands/restore.js.map +1 -0
  27. package/dist/cli/commands/sync-templates.d.ts +15 -0
  28. package/dist/cli/commands/sync-templates.d.ts.map +1 -0
  29. package/dist/cli/commands/sync-templates.js +181 -0
  30. package/dist/cli/commands/sync-templates.js.map +1 -0
  31. package/dist/cli/commands/version-check.d.ts +12 -0
  32. package/dist/cli/commands/version-check.d.ts.map +1 -0
  33. package/dist/cli/commands/version-check.js +133 -0
  34. package/dist/cli/commands/version-check.js.map +1 -0
  35. package/dist/cli/generate.d.ts +5 -0
  36. package/dist/cli/generate.d.ts.map +1 -1
  37. package/dist/cli/generate.js +80 -16
  38. package/dist/cli/generate.js.map +1 -1
  39. package/dist/cli/index.js +215 -1
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/repl/index.d.ts +1 -0
  42. package/dist/cli/repl/index.d.ts.map +1 -1
  43. package/dist/cli/repl/index.js +18 -6
  44. package/dist/cli/repl/index.js.map +1 -1
  45. package/dist/cli/utils/backup-manager.d.ts +94 -0
  46. package/dist/cli/utils/backup-manager.d.ts.map +1 -0
  47. package/dist/cli/utils/backup-manager.js +230 -0
  48. package/dist/cli/utils/backup-manager.js.map +1 -0
  49. package/dist/cli/utils/db-backup-manager.d.ts +55 -0
  50. package/dist/cli/utils/db-backup-manager.d.ts.map +1 -0
  51. package/dist/cli/utils/db-backup-manager.js +115 -0
  52. package/dist/cli/utils/db-backup-manager.js.map +1 -0
  53. package/dist/cli/utils/file-detector.d.ts +87 -0
  54. package/dist/cli/utils/file-detector.d.ts.map +1 -0
  55. package/dist/cli/utils/file-detector.js +131 -0
  56. package/dist/cli/utils/file-detector.js.map +1 -0
  57. package/dist/cli/utils/index.d.ts +9 -0
  58. package/dist/cli/utils/index.d.ts.map +1 -0
  59. package/dist/cli/utils/index.js +9 -0
  60. package/dist/cli/utils/index.js.map +1 -0
  61. package/dist/cli/utils/modification-prompt.d.ts +41 -0
  62. package/dist/cli/utils/modification-prompt.d.ts.map +1 -0
  63. package/dist/cli/utils/modification-prompt.js +84 -0
  64. package/dist/cli/utils/modification-prompt.js.map +1 -0
  65. package/dist/cli/version/checker.d.ts +47 -0
  66. package/dist/cli/version/checker.d.ts.map +1 -0
  67. package/dist/cli/version/checker.js +143 -0
  68. package/dist/cli/version/checker.js.map +1 -0
  69. package/dist/cli/version/comparator.d.ts +46 -0
  70. package/dist/cli/version/comparator.d.ts.map +1 -0
  71. package/dist/cli/version/comparator.js +99 -0
  72. package/dist/cli/version/comparator.js.map +1 -0
  73. package/dist/cli/version/index.d.ts +11 -0
  74. package/dist/cli/version/index.d.ts.map +1 -0
  75. package/dist/cli/version/index.js +11 -0
  76. package/dist/cli/version/index.js.map +1 -0
  77. package/dist/cli/version/parser.d.ts +38 -0
  78. package/dist/cli/version/parser.d.ts.map +1 -0
  79. package/dist/cli/version/parser.js +90 -0
  80. package/dist/cli/version/parser.js.map +1 -0
  81. package/dist/cli/version/prompt.d.ts +40 -0
  82. package/dist/cli/version/prompt.d.ts.map +1 -0
  83. package/dist/cli/version/prompt.js +162 -0
  84. package/dist/cli/version/prompt.js.map +1 -0
  85. package/dist/cli/version/types.d.ts +89 -0
  86. package/dist/cli/version/types.d.ts.map +1 -0
  87. package/dist/cli/version/types.js +7 -0
  88. package/dist/cli/version/types.js.map +1 -0
  89. package/dist/db/client.d.ts +79 -4
  90. package/dist/db/client.d.ts.map +1 -1
  91. package/dist/db/client.js +207 -12
  92. package/dist/db/client.js.map +1 -1
  93. package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts +14 -0
  94. package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts.map +1 -0
  95. package/dist/db/migrations/files/0014_add_schema_migrations_table.js +25 -0
  96. package/dist/db/migrations/files/0014_add_schema_migrations_table.js.map +1 -0
  97. package/dist/db/migrations/index.d.ts +9 -0
  98. package/dist/db/migrations/index.d.ts.map +1 -0
  99. package/dist/db/migrations/index.js +9 -0
  100. package/dist/db/migrations/index.js.map +1 -0
  101. package/dist/db/migrations/loader.d.ts +27 -0
  102. package/dist/db/migrations/loader.d.ts.map +1 -0
  103. package/dist/db/migrations/loader.js +106 -0
  104. package/dist/db/migrations/loader.js.map +1 -0
  105. package/dist/db/migrations/runner.d.ts +56 -0
  106. package/dist/db/migrations/runner.d.ts.map +1 -0
  107. package/dist/db/migrations/runner.js +266 -0
  108. package/dist/db/migrations/runner.js.map +1 -0
  109. package/dist/db/migrations/types.d.ts +71 -0
  110. package/dist/db/migrations/types.d.ts.map +1 -0
  111. package/dist/db/migrations/types.js +7 -0
  112. package/dist/db/migrations/types.js.map +1 -0
  113. package/dist/db/schema.d.ts +41 -2
  114. package/dist/db/schema.d.ts.map +1 -1
  115. package/dist/db/schema.js +77 -2
  116. package/dist/db/schema.js.map +1 -1
  117. package/dist/mcp.js +2 -2
  118. package/dist/mcp.js.map +1 -1
  119. package/dist/template-engine/data-transformer.d.ts +17 -0
  120. package/dist/template-engine/data-transformer.d.ts.map +1 -0
  121. package/dist/template-engine/data-transformer.js +343 -0
  122. package/dist/template-engine/data-transformer.js.map +1 -0
  123. package/dist/template-engine/engine.d.ts +74 -0
  124. package/dist/template-engine/engine.d.ts.map +1 -0
  125. package/dist/template-engine/engine.js +183 -0
  126. package/dist/template-engine/engine.js.map +1 -0
  127. package/dist/template-engine/helpers.d.ts +81 -0
  128. package/dist/template-engine/helpers.d.ts.map +1 -0
  129. package/dist/template-engine/helpers.js +153 -0
  130. package/dist/template-engine/helpers.js.map +1 -0
  131. package/dist/template-engine/index.d.ts +10 -0
  132. package/dist/template-engine/index.d.ts.map +1 -0
  133. package/dist/template-engine/index.js +10 -0
  134. package/dist/template-engine/index.js.map +1 -0
  135. package/dist/template-engine/types.d.ts +147 -0
  136. package/dist/template-engine/types.d.ts.map +1 -0
  137. package/dist/template-engine/types.js +7 -0
  138. package/dist/template-engine/types.js.map +1 -0
  139. package/dist/template-sync/comparator.d.ts +138 -0
  140. package/dist/template-sync/comparator.d.ts.map +1 -0
  141. package/dist/template-sync/comparator.js +353 -0
  142. package/dist/template-sync/comparator.js.map +1 -0
  143. package/dist/template-sync/conflict-resolver.d.ts +112 -0
  144. package/dist/template-sync/conflict-resolver.d.ts.map +1 -0
  145. package/dist/template-sync/conflict-resolver.js +328 -0
  146. package/dist/template-sync/conflict-resolver.js.map +1 -0
  147. package/dist/template-sync/engine.d.ts +93 -0
  148. package/dist/template-sync/engine.d.ts.map +1 -0
  149. package/dist/template-sync/engine.js +350 -0
  150. package/dist/template-sync/engine.js.map +1 -0
  151. package/dist/template-sync/hasher.d.ts +67 -0
  152. package/dist/template-sync/hasher.d.ts.map +1 -0
  153. package/dist/template-sync/hasher.js +94 -0
  154. package/dist/template-sync/hasher.js.map +1 -0
  155. package/dist/template-sync/index.d.ts +20 -0
  156. package/dist/template-sync/index.d.ts.map +1 -0
  157. package/dist/template-sync/index.js +14 -0
  158. package/dist/template-sync/index.js.map +1 -0
  159. package/dist/template-sync/manifest.d.ts +131 -0
  160. package/dist/template-sync/manifest.d.ts.map +1 -0
  161. package/dist/template-sync/manifest.js +309 -0
  162. package/dist/template-sync/manifest.js.map +1 -0
  163. package/dist/template-sync/merger.d.ts +125 -0
  164. package/dist/template-sync/merger.d.ts.map +1 -0
  165. package/dist/template-sync/merger.js +371 -0
  166. package/dist/template-sync/merger.js.map +1 -0
  167. package/dist/template-sync/scanner.d.ts +106 -0
  168. package/dist/template-sync/scanner.d.ts.map +1 -0
  169. package/dist/template-sync/scanner.js +196 -0
  170. package/dist/template-sync/scanner.js.map +1 -0
  171. package/dist/template-sync/types.d.ts +199 -0
  172. package/dist/template-sync/types.d.ts.map +1 -0
  173. package/dist/template-sync/types.js +30 -0
  174. package/dist/template-sync/types.js.map +1 -0
  175. package/package.json +2 -1
  176. package/src/agents/cleanup-agent.ts +21 -6
  177. package/src/agents/drift-agent.ts +31 -8
  178. package/src/cli/commands/cleanup.ts +9 -1
  179. package/src/cli/commands/drift-detect.ts +24 -1
  180. package/src/cli/commands/embeddings-refresh.ts +135 -0
  181. package/src/cli/commands/migrate.ts +231 -0
  182. package/src/cli/commands/restore.ts +318 -0
  183. package/src/cli/commands/sync-templates.ts +210 -0
  184. package/src/cli/commands/version-check.ts +158 -0
  185. package/src/cli/generate.ts +99 -17
  186. package/src/cli/index.ts +246 -1
  187. package/src/cli/repl/index.ts +16 -6
  188. package/src/cli/utils/backup-manager.ts +275 -0
  189. package/src/cli/utils/db-backup-manager.ts +146 -0
  190. package/src/cli/utils/file-detector.ts +181 -0
  191. package/src/cli/utils/index.ts +9 -0
  192. package/src/cli/utils/modification-prompt.ts +112 -0
  193. package/src/cli/version/checker.ts +172 -0
  194. package/src/cli/version/comparator.ts +106 -0
  195. package/src/cli/version/index.ts +11 -0
  196. package/src/cli/version/parser.ts +101 -0
  197. package/src/cli/version/prompt.ts +208 -0
  198. package/src/cli/version/types.ts +95 -0
  199. package/src/db/client.ts +285 -18
  200. package/src/db/migrations/files/0014_add_schema_migrations_table.sql +19 -0
  201. package/src/db/migrations/files/0014_add_schema_migrations_table.ts +30 -0
  202. package/src/db/migrations/index.ts +9 -0
  203. package/src/db/migrations/loader.ts +129 -0
  204. package/src/db/migrations/runner.ts +316 -0
  205. package/src/db/migrations/types.ts +71 -0
  206. package/src/db/schema.ts +109 -2
  207. package/src/mcp.ts +2 -2
  208. package/src/template-engine/data-transformer.ts +367 -0
  209. package/src/template-engine/engine.ts +213 -0
  210. package/src/template-engine/helpers.ts +163 -0
  211. package/src/template-engine/index.ts +10 -0
  212. package/src/template-engine/types.ts +158 -0
  213. package/src/template-sync/comparator.ts +452 -0
  214. package/src/template-sync/conflict-resolver.ts +401 -0
  215. package/src/template-sync/engine.ts +417 -0
  216. package/src/template-sync/hasher.ts +104 -0
  217. package/src/template-sync/index.ts +60 -0
  218. package/src/template-sync/manifest.ts +358 -0
  219. package/src/template-sync/merger.ts +454 -0
  220. package/src/template-sync/scanner.ts +254 -0
  221. package/src/template-sync/types.ts +247 -0
package/src/cli/index.ts CHANGED
@@ -16,6 +16,7 @@ import { fileURLToPath } from 'url';
16
16
 
17
17
  import { createIntelligentAnalyzer } from '../analyzer/intelligent-analyzer.js';
18
18
  import { hasOpenRouterKey } from '../embeddings/openrouter.js';
19
+ import type { DatabaseClient } from '../db/client.js';
19
20
  import { generateCommand } from './generate.js';
20
21
  import { syncCommand } from './sync.js';
21
22
  import { cleanupCommand } from './commands/cleanup.js';
@@ -29,6 +30,11 @@ import { crossSyncCommand } from './commands/cross-sync.js';
29
30
  import { hooksCommand } from './commands/hooks.js';
30
31
  import { factCheckCommand } from './commands/fact-check.js';
31
32
  import { batchIndexCommand } from './commands/batch-index.js';
33
+ import { versionCheckCommand } from './commands/version-check.js';
34
+ import { restoreCommand } from './commands/restore.js';
35
+ import { syncTemplatesCommand, templateStatusCommand } from './commands/sync-templates.js';
36
+ import { migrateCommand } from './commands/migrate.js';
37
+ import { embeddingsRefreshCommand } from './commands/embeddings-refresh.js';
32
38
 
33
39
  const __filename = fileURLToPath(import.meta.url);
34
40
  const __dirname = path.dirname(__filename);
@@ -145,6 +151,8 @@ function createProgram(): Command {
145
151
  .description('Initialize AI context for a project with intelligent analysis')
146
152
  .argument('[project-name]', 'Name of the project (defaults to current directory)')
147
153
  .option('--no-intelligent', 'Skip OpenRouter-powered intelligent analysis')
154
+ .option('--no-version-check', 'Skip checking for outdated context files')
155
+ .option('--no-template-sync', 'Skip template synchronization')
148
156
  .action(async (projectName, options) => {
149
157
  showBanner();
150
158
 
@@ -204,6 +212,198 @@ function createProgram(): Command {
204
212
  await configureMcpServer(targetDir);
205
213
  spinner.succeed('MCP server configured');
206
214
 
215
+ // Sync templates from package
216
+ if (options.templateSync !== false) {
217
+ spinner.start('Checking template sync...');
218
+
219
+ try {
220
+ const { TemplateSyncEngine } = await import('../template-sync/index.js');
221
+ const { DatabaseClient: DBClientClass } = await import('../db/client.js');
222
+
223
+ const db = new DBClientClass(targetDir);
224
+ const templateRoot = path.join(__dirname, '../../templates/base');
225
+ const engine = new TemplateSyncEngine(db, targetDir, templateRoot);
226
+
227
+ // Check if sync is needed
228
+ const needsSync = await engine.needsSync();
229
+
230
+ if (needsSync) {
231
+ spinner.text = 'Syncing templates from package...';
232
+
233
+ const syncResult = await engine.sync({
234
+ dryRun: false,
235
+ force: false,
236
+ verbose: options.verbose
237
+ });
238
+
239
+ if (syncResult.conflicts.length === 0) {
240
+ spinner.succeed(chalk.green(`Templates synced (${syncResult.updated} updated, ${syncResult.created} created)`));
241
+ } else {
242
+ spinner.warn(chalk.yellow(`Templates synced with ${syncResult.conflicts.length} conflict(s)`));
243
+ console.log(chalk.dim(` Run 'k0ntext sync-templates' to resolve conflicts`));
244
+ }
245
+ } else {
246
+ spinner.succeed('Templates already up to date');
247
+ }
248
+
249
+ db.close();
250
+ } catch (error) {
251
+ spinner.stop();
252
+ // Template sync is optional, don't fail on error
253
+ if (options.verbose) {
254
+ console.warn(chalk.dim(`Template sync skipped: ${error instanceof Error ? error.message : error}`));
255
+ }
256
+ }
257
+ }
258
+
259
+ // Check for database migrations
260
+ spinner.start('Checking database migrations...');
261
+
262
+ try {
263
+ const { MigrationRunner } = await import('../db/migrations/index.js');
264
+ const { DatabaseClient: DBClientClass } = await import('../db/client.js');
265
+
266
+ const db = await DBClientClass.create(targetDir);
267
+ const runner = new MigrationRunner(db, targetDir);
268
+
269
+ const migrationStatus = await runner.getStatus();
270
+
271
+ if (migrationStatus.needsMigration) {
272
+ spinner.stop();
273
+
274
+ console.log('');
275
+ console.log(chalk.yellow(`⚠ Database schema updates available: ${migrationStatus.currentVersion || 'none'} → ${migrationStatus.targetVersion}`));
276
+ console.log('');
277
+ console.log(chalk.bold('Pending migrations:'));
278
+ for (const migration of migrationStatus.pending) {
279
+ const breaks = migration.breaks ? chalk.red(' [breaking]') : '';
280
+ console.log(chalk.dim(` • ${migration.version}: ${migration.description}${breaks}`));
281
+ }
282
+ console.log('');
283
+
284
+ const { confirm } = await import('@inquirer/prompts');
285
+ const shouldMigrate = await confirm({
286
+ message: 'Apply database migrations now?',
287
+ default: true
288
+ });
289
+
290
+ if (shouldMigrate) {
291
+ spinner.start('Applying migrations...');
292
+
293
+ const migrationResults = await runner.migrate({
294
+ backup: true,
295
+ onProgress: (current, total, migration) => {
296
+ spinner.text = `Applying ${current}/${total}: ${migration.description}`;
297
+ }
298
+ });
299
+
300
+ const successful = migrationResults.filter(r => r.success).length;
301
+ const failed = migrationResults.filter(r => !r.success).length;
302
+
303
+ if (failed === 0) {
304
+ spinner.succeed(chalk.green(`Applied ${successful} migration(s)`));
305
+ } else {
306
+ spinner.warn(chalk.yellow(`Applied ${successful} migration(s), ${failed} failed`));
307
+ if (options.verbose) {
308
+ for (const result of migrationResults) {
309
+ if (!result.success) {
310
+ console.log(chalk.red(` ✗ ${result.version}: ${result.error}`));
311
+ }
312
+ }
313
+ }
314
+ }
315
+ } else {
316
+ spinner.start('Continuing...');
317
+ spinner.succeed('Database migrations skipped');
318
+ console.log(chalk.dim('\n Run: k0ntext migrate up\n'));
319
+ }
320
+ } else {
321
+ spinner.succeed('Database schema up to date');
322
+ }
323
+
324
+ db.close();
325
+ } catch (error) {
326
+ spinner.warn(`Database migration check failed: ${error instanceof Error ? error.message : error}`);
327
+ if (options.verbose) {
328
+ console.warn(chalk.dim(` ${error instanceof Error ? error.stack : error}`));
329
+ }
330
+ }
331
+
332
+ // Check for outdated context files
333
+ if (options.versionCheck !== false) {
334
+ spinner.start('Checking context file versions...');
335
+
336
+ try {
337
+ const { checkContextFiles, showVersionSummary, promptRegeneration } = await import('./version/index.js');
338
+ const { DatabaseClient: DBClientClass } = await import('../db/client.js');
339
+
340
+ let db: DatabaseClient | undefined;
341
+ try {
342
+ db = new DBClientClass(targetDir);
343
+ } catch {
344
+ // Database might not exist yet
345
+ }
346
+
347
+ const result = await checkContextFiles({
348
+ projectRoot: targetDir,
349
+ currentVersion: packageJson.version,
350
+ checkModifications: !!db
351
+ }, db);
352
+
353
+ spinner.stop();
354
+
355
+ if (result.outdated.length > 0) {
356
+ showVersionSummary(result);
357
+ console.log('');
358
+
359
+ const promptResult = await promptRegeneration(result.outdated);
360
+
361
+ if (promptResult.choice !== 'skip') {
362
+ let filesToRegenerate = result.outdated;
363
+
364
+ if (promptResult.choice === 'select' && promptResult.selectedTools) {
365
+ filesToRegenerate = result.outdated.filter(f =>
366
+ promptResult.selectedTools?.includes(f.tool)
367
+ );
368
+ }
369
+
370
+ if (!promptResult.includeModified) {
371
+ filesToRegenerate = filesToRegenerate.filter(f => !f.userModified);
372
+ }
373
+
374
+ if (filesToRegenerate.length > 0 && db) {
375
+ spinner.start('Regenerating context files...');
376
+
377
+ const { generateForTool } = await import('./generate.js');
378
+
379
+ for (const file of filesToRegenerate) {
380
+ spinner.text = `Regenerating ${file.tool} context...`;
381
+ try {
382
+ await generateForTool(file.tool, db, true, false, { verbose: options.verbose });
383
+ } catch (error) {
384
+ spinner.warn(chalk.yellow(`Failed to regenerate ${file.tool}`));
385
+ }
386
+ }
387
+
388
+ spinner.succeed(chalk.green(`Regenerated ${filesToRegenerate.length} file(s)`));
389
+ }
390
+ }
391
+ } else {
392
+ spinner.stop();
393
+ }
394
+
395
+ if (db) {
396
+ db.close();
397
+ }
398
+ } catch (error) {
399
+ spinner.stop();
400
+ // Version check is optional, don't fail on error
401
+ if (options.verbose) {
402
+ console.warn(chalk.dim(`Version check skipped: ${error instanceof Error ? error.message : error}`));
403
+ }
404
+ }
405
+ }
406
+
207
407
  console.log(`\n${chalk.bold('Next Steps:')}`);
208
408
  console.log(` ${chalk.cyan('1.')} Run ${chalk.white('k0ntext stats')} to view database statistics`);
209
409
  console.log(` ${chalk.cyan('2.')} Run ${chalk.white('k0ntext mcp')} to start the MCP server`);
@@ -223,7 +423,7 @@ function createProgram(): Command {
223
423
  program
224
424
  .command('mcp')
225
425
  .description('Start the MCP server for AI tools to connect')
226
- .option('--db <path>', 'Database file path', '.ai-context.db')
426
+ .option('--db <path>', 'Database file path', '.k0ntext.db')
227
427
  .action(async (options) => {
228
428
  const projectRoot = process.cwd();
229
429
 
@@ -277,6 +477,20 @@ function createProgram(): Command {
277
477
  // ==================== Fact-Check Command ====================
278
478
  program.addCommand(factCheckCommand);
279
479
 
480
+ // ==================== Version Check Command ====================
481
+ program.addCommand(versionCheckCommand);
482
+
483
+ // ==================== Restore Command ====================
484
+ program.addCommand(restoreCommand);
485
+
486
+ // ==================== Template Sync Commands ====================
487
+ program.addCommand(syncTemplatesCommand);
488
+ program.addCommand(templateStatusCommand);
489
+
490
+ // ==================== Migration Commands ====================
491
+ program.addCommand(migrateCommand);
492
+ program.addCommand(embeddingsRefreshCommand);
493
+
280
494
  // ==================== Index Command ====================
281
495
  program
282
496
  .command('index')
@@ -290,6 +504,7 @@ function createProgram(): Command {
290
504
  showBanner();
291
505
 
292
506
  const spinner = ora();
507
+ const verbose = options.verbose || false;
293
508
  let db: any;
294
509
 
295
510
  try {
@@ -317,6 +532,11 @@ function createProgram(): Command {
317
532
 
318
533
  // Store docs in database
319
534
  for (const doc of docs) {
535
+ if (verbose) {
536
+ spinner.stop();
537
+ console.log(chalk.dim(` Indexing: ${doc.relativePath}`));
538
+ spinner.start();
539
+ }
320
540
  const content = fs.existsSync(doc.path) ? fs.readFileSync(doc.path, 'utf-8').slice(0, 50000) : '';
321
541
  const item = db.upsertItem({
322
542
  type: 'doc',
@@ -332,6 +552,11 @@ function createProgram(): Command {
332
552
 
333
553
  // Store tool configs in database
334
554
  for (const config of tools) {
555
+ if (verbose) {
556
+ spinner.stop();
557
+ console.log(chalk.dim(` Indexing: ${config.relativePath}`));
558
+ spinner.start();
559
+ }
335
560
  const content = fs.existsSync(config.path) ? fs.readFileSync(config.path, 'utf-8').slice(0, 50000) : '';
336
561
  const item = db.upsertItem({
337
562
  type: 'tool_config',
@@ -348,6 +573,11 @@ function createProgram(): Command {
348
573
  // Store code in database (first N files to avoid overwhelming the db)
349
574
  const maxCodeFiles = 100;
350
575
  for (const codeFile of code.slice(0, maxCodeFiles)) {
576
+ if (verbose) {
577
+ spinner.stop();
578
+ console.log(chalk.dim(` Indexing: ${codeFile.relativePath}`));
579
+ spinner.start();
580
+ }
351
581
  const content = fs.existsSync(codeFile.path) ? fs.readFileSync(codeFile.path, 'utf-8').slice(0, 20000) : '';
352
582
  const item = db.upsertItem({
353
583
  type: 'code',
@@ -369,6 +599,11 @@ function createProgram(): Command {
369
599
  discoveredCount += docs.length;
370
600
  spinner.text = `Indexing ${docs.length} docs...`;
371
601
  for (const doc of docs) {
602
+ if (verbose) {
603
+ spinner.stop();
604
+ console.log(chalk.dim(` Indexing: ${doc.relativePath}`));
605
+ spinner.start();
606
+ }
372
607
  const content = fs.existsSync(doc.path) ? fs.readFileSync(doc.path, 'utf-8').slice(0, 50000) : '';
373
608
  const item = db.upsertItem({
374
609
  type: 'doc',
@@ -388,6 +623,11 @@ function createProgram(): Command {
388
623
  const maxCodeFiles = 100;
389
624
  spinner.text = `Indexing code files...`;
390
625
  for (const codeFile of code.slice(0, maxCodeFiles)) {
626
+ if (verbose) {
627
+ spinner.stop();
628
+ console.log(chalk.dim(` Indexing: ${codeFile.relativePath}`));
629
+ spinner.start();
630
+ }
391
631
  const content = fs.existsSync(codeFile.path) ? fs.readFileSync(codeFile.path, 'utf-8').slice(0, 20000) : '';
392
632
  const item = db.upsertItem({
393
633
  type: 'code',
@@ -409,6 +649,11 @@ function createProgram(): Command {
409
649
  discoveredCount += tools.length;
410
650
  spinner.text = `Indexing ${tools.length} tool configs...`;
411
651
  for (const config of tools) {
652
+ if (verbose) {
653
+ spinner.stop();
654
+ console.log(chalk.dim(` Indexing: ${config.relativePath}`));
655
+ spinner.start();
656
+ }
412
657
  const content = fs.existsSync(config.path) ? fs.readFileSync(config.path, 'utf-8').slice(0, 50000) : '';
413
658
  const item = db.upsertItem({
414
659
  type: 'tool_config',
@@ -39,6 +39,7 @@ export class REPLShell {
39
39
  private readline: readline.Interface;
40
40
  private isActive: boolean = false;
41
41
  private noTUI: boolean;
42
+ private readlineClosed: boolean = false;
42
43
 
43
44
  // Enhanced panels
44
45
  private searchPanel: AdvancedSearchPanel;
@@ -84,12 +85,12 @@ export class REPLShell {
84
85
  */
85
86
  private setupEventHandlers(): void {
86
87
  this.readline.on('line', async (input) => {
87
- if (!this.isActive) return;
88
+ if (!this.isActive || this.readlineClosed) return;
88
89
 
89
90
  const trimmed = input.trim();
90
91
 
91
92
  if (!trimmed) {
92
- this.readline.prompt();
93
+ if (!this.readlineClosed) this.readline.prompt();
93
94
  return;
94
95
  }
95
96
 
@@ -121,16 +122,17 @@ export class REPLShell {
121
122
  console.log(K0NTEXT_THEME.warning('\n⚠ Invalid command. Type "help" for available commands.'));
122
123
  }
123
124
 
124
- this.readline.prompt();
125
+ if (!this.readlineClosed) this.readline.prompt();
125
126
  });
126
127
 
127
128
  this.readline.on('SIGINT', async () => {
128
129
  console.log('');
129
130
  console.log(K0NTEXT_THEME.warning('\n⚠ Use "exit" to quit the REPL.'));
130
- this.readline.prompt();
131
+ if (!this.readlineClosed) this.readline.prompt();
131
132
  });
132
133
 
133
134
  this.readline.on('close', async () => {
135
+ this.readlineClosed = true;
134
136
  await this.stop();
135
137
  });
136
138
  }
@@ -519,11 +521,19 @@ export class REPLShell {
519
521
  * Stop the REPL
520
522
  */
521
523
  async stop(): Promise<void> {
524
+ if (!this.isActive) return; // Already stopping
522
525
  this.isActive = false;
523
526
  this.session.end();
527
+ this.readlineClosed = true;
528
+
529
+ // Only print goodbye if not in piped mode
530
+ const isPiped = !process.stdin.isTTY;
524
531
  this.readline.close();
525
- console.log('');
526
- console.log(K0NTEXT_THEME.success('✓ Session saved. Goodbye!'));
532
+
533
+ if (!isPiped) {
534
+ console.log('');
535
+ console.log(K0NTEXT_THEME.success('✓ Session saved. Goodbye!'));
536
+ }
527
537
  }
528
538
 
529
539
  /**
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Backup Manager
3
+ *
4
+ * Manages backups of generated context files before overwriting.
5
+ * Supports both file-copy and git-stash based backup strategies.
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { execSync } from 'child_process';
11
+ import type { DatabaseClient } from '../../db/client.js';
12
+
13
+ /**
14
+ * Backup result
15
+ */
16
+ export interface BackupResult {
17
+ /** Whether backup was successful */
18
+ success: boolean;
19
+ /** Path to backup file */
20
+ backupPath?: string;
21
+ /** Backup method used */
22
+ method: 'file-copy' | 'git-stash' | 'none';
23
+ /** Error message if failed */
24
+ error?: string;
25
+ }
26
+
27
+ /**
28
+ * Backup manager options
29
+ */
30
+ export interface BackupManagerOptions {
31
+ /** Base directory for backups (default: .k0ntext/backups) */
32
+ backupDir?: string;
33
+ /** Use git stash if available (default: true) */
34
+ useGitStash?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Backup manager
39
+ *
40
+ * Creates and manages backups of generated files.
41
+ */
42
+ export class BackupManager {
43
+ private db: DatabaseClient;
44
+ private projectRoot: string;
45
+ private backupDir: string;
46
+ private useGitStash: boolean;
47
+
48
+ constructor(db: DatabaseClient, projectRoot: string, options: BackupManagerOptions = {}) {
49
+ this.db = db;
50
+ this.projectRoot = projectRoot;
51
+ this.backupDir = options.backupDir || path.join(projectRoot, '.k0ntext', 'backups');
52
+ this.useGitStash = options.useGitStash !== false;
53
+ }
54
+
55
+ /**
56
+ * Create a backup of a file before overwriting
57
+ *
58
+ * @param filePath - Path to file to backup (can be relative or absolute)
59
+ * @param tool - Tool name for organization
60
+ * @returns Backup result
61
+ */
62
+ async createBackup(filePath: string, tool: string): Promise<BackupResult> {
63
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(this.projectRoot, filePath);
64
+
65
+ try {
66
+ // Check if file exists
67
+ try {
68
+ await fs.access(fullPath);
69
+ } catch {
70
+ // File doesn't exist, no backup needed
71
+ return { success: true, method: 'none' };
72
+ }
73
+
74
+ // Try git stash first if enabled
75
+ if (this.useGitStash && await this.isGitRepository()) {
76
+ const stashResult = await this.backupWithGitStash(fullPath, tool);
77
+ if (stashResult.success) {
78
+ return stashResult;
79
+ }
80
+ // Fall back to file copy if git stash fails
81
+ }
82
+
83
+ // Fall back to file copy
84
+ return await this.backupWithFileCopy(fullPath, tool);
85
+ } catch (error) {
86
+ return {
87
+ success: false,
88
+ method: 'none',
89
+ error: error instanceof Error ? error.message : String(error)
90
+ };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Restore a file from backup
96
+ *
97
+ * @param backupPath - Path to backup file
98
+ * @param targetPath - Path to restore to (can be relative or absolute)
99
+ * @returns True if restore was successful
100
+ */
101
+ async restoreFromBackup(backupPath: string, targetPath: string): Promise<boolean> {
102
+ const fullTargetPath = path.isAbsolute(targetPath) ? targetPath : path.join(this.projectRoot, targetPath);
103
+
104
+ try {
105
+ // Ensure target directory exists
106
+ await fs.mkdir(path.dirname(fullTargetPath), { recursive: true });
107
+
108
+ // Copy backup to target
109
+ await fs.copyFile(backupPath, fullTargetPath);
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * List all backups for a tool
118
+ *
119
+ * @param tool - Tool name
120
+ * @returns Array of backup paths
121
+ */
122
+ async listBackups(tool: string): Promise<string[]> {
123
+ const toolBackupDir = path.join(this.backupDir, tool);
124
+
125
+ try {
126
+ await fs.access(toolBackupDir);
127
+ } catch {
128
+ return [];
129
+ }
130
+
131
+ const entries = await fs.readdir(toolBackupDir, { withFileTypes: true });
132
+ return entries
133
+ .filter(e => e.isFile())
134
+ .map(e => path.join(toolBackupDir, e.name));
135
+ }
136
+
137
+ /**
138
+ * Backup using file copy strategy
139
+ *
140
+ * @param fullPath - Full path to file
141
+ * @param tool - Tool name
142
+ * @returns Backup result
143
+ */
144
+ private async backupWithFileCopy(fullPath: string, tool: string): Promise<BackupResult> {
145
+ try {
146
+ // Create backup directory
147
+ const toolBackupDir = path.join(this.backupDir, tool);
148
+ await fs.mkdir(toolBackupDir, { recursive: true });
149
+
150
+ // Generate backup filename with timestamp
151
+ const fileName = path.basename(fullPath);
152
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
153
+ const backupFileName = `${fileName}.${timestamp}.bak`;
154
+ const backupPath = path.join(toolBackupDir, backupFileName);
155
+
156
+ // Copy file
157
+ await fs.copyFile(fullPath, backupPath);
158
+
159
+ // Update database with backup path
160
+ const relativePath = path.relative(this.projectRoot, fullPath);
161
+ const record = this.db.getGeneratedFileInfo(tool, relativePath);
162
+ if (record) {
163
+ this.db.upsertGeneratedFile({
164
+ tool,
165
+ filePath: relativePath,
166
+ contentHash: record.contentHash,
167
+ backupPath
168
+ });
169
+ }
170
+
171
+ return {
172
+ success: true,
173
+ backupPath,
174
+ method: 'file-copy'
175
+ };
176
+ } catch (error) {
177
+ return {
178
+ success: false,
179
+ method: 'none',
180
+ error: error instanceof Error ? error.message : String(error)
181
+ };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Backup using git stash strategy
187
+ *
188
+ * @param fullPath - Full path to file
189
+ * @param tool - Tool name
190
+ * @returns Backup result
191
+ */
192
+ private async backupWithGitStash(fullPath: string, tool: string): Promise<BackupResult> {
193
+ try {
194
+ const relativePath = path.relative(this.projectRoot, fullPath);
195
+
196
+ // Create a stash with a descriptive message
197
+ const stashMessage = `k0ntext-backup-${tool}-${Date.now()}`;
198
+ const stashCommand = `git stash push -m "${stashMessage}" -- "${relativePath}"`;
199
+
200
+ execSync(stashCommand, { cwd: this.projectRoot, stdio: 'pipe' });
201
+
202
+ // Get the stash hash for reference
203
+ const listCommand = 'git stash list --grep="^k0ntext-backup" -n 1';
204
+ const stashList = execSync(listCommand, { cwd: this.projectRoot, encoding: 'utf-8' });
205
+ const stashRef = stashList.split(':')[0].trim();
206
+
207
+ // Update database with stash reference
208
+ const record = this.db.getGeneratedFileInfo(tool, relativePath);
209
+ if (record) {
210
+ this.db.upsertGeneratedFile({
211
+ tool,
212
+ filePath: relativePath,
213
+ contentHash: record.contentHash,
214
+ backupPath: `git-stash:${stashRef}`
215
+ });
216
+ }
217
+
218
+ return {
219
+ success: true,
220
+ backupPath: `git-stash:${stashRef}`,
221
+ method: 'git-stash'
222
+ };
223
+ } catch {
224
+ return {
225
+ success: false,
226
+ method: 'none',
227
+ error: 'Git stash failed'
228
+ };
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Check if current directory is a git repository
234
+ *
235
+ * @returns True if git repository
236
+ */
237
+ private async isGitRepository(): Promise<boolean> {
238
+ try {
239
+ const gitDir = path.join(this.projectRoot, '.git');
240
+ await fs.access(gitDir);
241
+ return true;
242
+ } catch {
243
+ return false;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Clean up old backups
249
+ *
250
+ * @param tool - Tool name (optional, if not provided cleans all)
251
+ * @param keepLast - Number of recent backups to keep (default: 5)
252
+ */
253
+ async cleanupOldBackups(tool?: string, keepLast = 5): Promise<void> {
254
+ const tools = tool ? [tool] : ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'cursor', 'gemini'];
255
+
256
+ for (const toolName of tools) {
257
+ const backups = await this.listBackups(toolName);
258
+
259
+ if (backups.length > keepLast) {
260
+ // Sort by name (which includes timestamp)
261
+ backups.sort();
262
+
263
+ // Delete oldest backups
264
+ const toDelete = backups.slice(0, backups.length - keepLast);
265
+ for (const backupPath of toDelete) {
266
+ try {
267
+ await fs.unlink(backupPath);
268
+ } catch {
269
+ // Ignore errors during cleanup
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }