@kiyeonjeon21/datacontext 0.2.0 → 0.3.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 (51) hide show
  1. package/.cursorrules +12 -0
  2. package/.env.example +8 -0
  3. package/.github/workflows/ci.yml +21 -1
  4. package/.github/workflows/publish.yml +21 -1
  5. package/CHANGELOG.md +41 -0
  6. package/README.md +247 -239
  7. package/datacontext.db +0 -0
  8. package/dist/api/server.d.ts.map +1 -1
  9. package/dist/api/server.js +145 -0
  10. package/dist/api/server.js.map +1 -1
  11. package/dist/api/start-server.d.ts +10 -0
  12. package/dist/api/start-server.d.ts.map +1 -0
  13. package/dist/api/start-server.js +73 -0
  14. package/dist/api/start-server.js.map +1 -0
  15. package/dist/cli/index.js +462 -0
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/core/context-service.d.ts +58 -0
  18. package/dist/core/context-service.d.ts.map +1 -1
  19. package/dist/core/context-service.js +121 -0
  20. package/dist/core/context-service.js.map +1 -1
  21. package/dist/core/index.d.ts +2 -0
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/core/index.js +5 -1
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/core/llm-service.d.ts +141 -0
  26. package/dist/core/llm-service.d.ts.map +1 -0
  27. package/dist/core/llm-service.js +284 -0
  28. package/dist/core/llm-service.js.map +1 -0
  29. package/dist/knowledge/store.d.ts +56 -3
  30. package/dist/knowledge/store.d.ts.map +1 -1
  31. package/dist/knowledge/store.js +193 -7
  32. package/dist/knowledge/store.js.map +1 -1
  33. package/dist/knowledge/types.d.ts +43 -1
  34. package/dist/knowledge/types.d.ts.map +1 -1
  35. package/dist/knowledge/types.js.map +1 -1
  36. package/dist/mcp/tools.d.ts.map +1 -1
  37. package/dist/mcp/tools.js +365 -0
  38. package/dist/mcp/tools.js.map +1 -1
  39. package/docs/API.md +173 -0
  40. package/docs/DEMO_SCRIPT.md +210 -0
  41. package/docs/SYNC_GUIDE.md +242 -0
  42. package/package.json +4 -1
  43. package/src/api/server.ts +160 -0
  44. package/src/api/start-server.ts +78 -0
  45. package/src/cli/index.ts +534 -0
  46. package/src/core/context-service.ts +157 -0
  47. package/src/core/index.ts +7 -0
  48. package/src/core/llm-service.ts +359 -0
  49. package/src/knowledge/store.ts +232 -7
  50. package/src/knowledge/types.ts +45 -1
  51. package/src/mcp/tools.ts +415 -0
@@ -12,8 +12,10 @@ import type {
12
12
  TableDescription,
13
13
  QueryExample,
14
14
  BusinessRule,
15
+ BusinessTerm,
15
16
  KnowledgeEntry,
16
17
  ColumnDescription,
18
+ TermCategory,
17
19
  } from './types.js';
18
20
  import { generateId, createKnowledgeMeta } from './types.js';
19
21
 
@@ -321,6 +323,177 @@ export class KnowledgeStore {
321
323
  return rule;
322
324
  }
323
325
 
326
+ // ============ Business Terms (Glossary) ============
327
+
328
+ /**
329
+ * Get all business terms
330
+ */
331
+ getBusinessTerms(): BusinessTerm[] {
332
+ return this.data?.businessTerms ?? [];
333
+ }
334
+
335
+ /**
336
+ * Get active business terms
337
+ */
338
+ getActiveTerms(): BusinessTerm[] {
339
+ return this.data?.businessTerms.filter(t => t.isActive) ?? [];
340
+ }
341
+
342
+ /**
343
+ * Get business terms for specific tables
344
+ */
345
+ getTermsForTables(tables: string[]): BusinessTerm[] {
346
+ return this.data?.businessTerms.filter(
347
+ term => term.isActive &&
348
+ (term.appliesTo?.tables?.some(t => tables.includes(t)) ?? true)
349
+ ) ?? [];
350
+ }
351
+
352
+ /**
353
+ * Find terms matching a query (by term name or synonyms)
354
+ */
355
+ findMatchingTerms(query: string): BusinessTerm[] {
356
+ const lowerQuery = query.toLowerCase();
357
+ return this.data?.businessTerms.filter(term => {
358
+ if (!term.isActive) return false;
359
+ // Check term name
360
+ if (term.term.toLowerCase().includes(lowerQuery)) return true;
361
+ if (lowerQuery.includes(term.term.toLowerCase())) return true;
362
+ // Check synonyms
363
+ for (const syn of term.synonyms) {
364
+ if (syn.toLowerCase().includes(lowerQuery)) return true;
365
+ if (lowerQuery.includes(syn.toLowerCase())) return true;
366
+ }
367
+ return false;
368
+ }) ?? [];
369
+ }
370
+
371
+ /**
372
+ * Add a business term
373
+ */
374
+ async addBusinessTerm(
375
+ term: string,
376
+ definition: string,
377
+ options: {
378
+ synonyms?: string[];
379
+ sqlExpression?: string;
380
+ appliesTo?: { tables?: string[]; columns?: string[] };
381
+ category?: TermCategory;
382
+ examples?: string[];
383
+ schemaHash?: string;
384
+ } = {}
385
+ ): Promise<BusinessTerm> {
386
+ if (!this.data) {
387
+ throw new Error('Knowledge store not loaded');
388
+ }
389
+
390
+ // Check for duplicates
391
+ const existing = this.data.businessTerms.find(
392
+ t => t.term.toLowerCase() === term.toLowerCase()
393
+ );
394
+ if (existing) {
395
+ // Update existing term
396
+ return this.updateBusinessTerm(existing.id, {
397
+ definition,
398
+ ...options,
399
+ });
400
+ }
401
+
402
+ const schemaHash = options.schemaHash ?? this.data.schemaHash;
403
+ const newTerm: BusinessTerm = {
404
+ ...createKnowledgeMeta('user', schemaHash),
405
+ type: 'business_term',
406
+ term,
407
+ synonyms: options.synonyms ?? [],
408
+ definition,
409
+ sqlExpression: options.sqlExpression,
410
+ appliesTo: options.appliesTo,
411
+ category: options.category,
412
+ examples: options.examples,
413
+ isActive: true,
414
+ };
415
+
416
+ this.data.businessTerms.push(newTerm);
417
+ await this.save();
418
+ return newTerm;
419
+ }
420
+
421
+ /**
422
+ * Add multiple business terms at once
423
+ */
424
+ async addBusinessTerms(terms: BusinessTerm[]): Promise<BusinessTerm[]> {
425
+ if (!this.data) {
426
+ throw new Error('Knowledge store not loaded');
427
+ }
428
+
429
+ const added: BusinessTerm[] = [];
430
+ for (const term of terms) {
431
+ // Check for duplicates
432
+ const existingIndex = this.data.businessTerms.findIndex(
433
+ t => t.term.toLowerCase() === term.term.toLowerCase()
434
+ );
435
+
436
+ if (existingIndex >= 0) {
437
+ // Update existing
438
+ this.data.businessTerms[existingIndex] = {
439
+ ...this.data.businessTerms[existingIndex],
440
+ ...term,
441
+ updatedAt: new Date().toISOString(),
442
+ };
443
+ added.push(this.data.businessTerms[existingIndex]);
444
+ } else {
445
+ // Add new
446
+ this.data.businessTerms.push(term);
447
+ added.push(term);
448
+ }
449
+ }
450
+
451
+ await this.save();
452
+ return added;
453
+ }
454
+
455
+ /**
456
+ * Update a business term
457
+ */
458
+ async updateBusinessTerm(
459
+ id: string,
460
+ updates: Partial<Omit<BusinessTerm, 'id' | 'type' | 'createdAt'>>
461
+ ): Promise<BusinessTerm> {
462
+ if (!this.data) {
463
+ throw new Error('Knowledge store not loaded');
464
+ }
465
+
466
+ const index = this.data.businessTerms.findIndex(t => t.id === id);
467
+ if (index < 0) {
468
+ throw new Error(`Business term not found: ${id}`);
469
+ }
470
+
471
+ const updated: BusinessTerm = {
472
+ ...this.data.businessTerms[index],
473
+ ...updates,
474
+ updatedAt: new Date().toISOString(),
475
+ };
476
+
477
+ this.data.businessTerms[index] = updated;
478
+ await this.save();
479
+ return updated;
480
+ }
481
+
482
+ /**
483
+ * Delete a business term
484
+ */
485
+ async deleteBusinessTerm(id: string): Promise<void> {
486
+ if (!this.data) {
487
+ throw new Error('Knowledge store not loaded');
488
+ }
489
+
490
+ const index = this.data.businessTerms.findIndex(t => t.id === id);
491
+ if (index >= 0) {
492
+ this.data.businessTerms.splice(index, 1);
493
+ await this.save();
494
+ }
495
+ }
496
+
324
497
  // ============ Utilities ============
325
498
 
326
499
  /**
@@ -351,15 +524,60 @@ export class KnowledgeStore {
351
524
  }
352
525
  }
353
526
 
527
+ for (const bt of this.data.businessTerms ?? []) {
528
+ if (bt.schemaHash !== currentSchemaHash) {
529
+ outdated.push(bt);
530
+ }
531
+ }
532
+
354
533
  return outdated;
355
534
  }
356
535
 
357
536
  /**
358
537
  * Build context for AI from knowledge store
538
+ *
539
+ * Generates a comprehensive context string including:
540
+ * - Table descriptions and column details
541
+ * - Business glossary/terms with SQL mappings
542
+ * - Business rules and conditions
543
+ * - Query examples
544
+ *
545
+ * @param tables - Tables to include context for
546
+ * @param userQuery - Optional user query to match relevant terms
547
+ * @returns Formatted context string for AI
359
548
  */
360
- buildContext(tables: string[]): string {
549
+ buildContext(tables: string[], userQuery?: string): string {
361
550
  const parts: string[] = [];
362
551
 
552
+ // Add business glossary FIRST (helps AI understand terminology)
553
+ const terms = userQuery
554
+ ? this.findMatchingTerms(userQuery)
555
+ : this.getTermsForTables(tables);
556
+
557
+ if (terms.length > 0) {
558
+ parts.push('## Business Glossary');
559
+ parts.push('Use these term definitions when interpreting user requests:');
560
+ parts.push('');
561
+ for (const term of terms) {
562
+ let termInfo = `**${term.term}**`;
563
+ if (term.synonyms.length > 0) {
564
+ termInfo += ` (also: ${term.synonyms.join(', ')})`;
565
+ }
566
+ parts.push(termInfo);
567
+ parts.push(` Definition: ${term.definition}`);
568
+ if (term.sqlExpression) {
569
+ parts.push(` SQL: \`${term.sqlExpression}\``);
570
+ }
571
+ if (term.appliesTo?.tables?.length) {
572
+ parts.push(` Applies to: ${term.appliesTo.tables.join(', ')}`);
573
+ }
574
+ if (term.examples?.length) {
575
+ parts.push(` Example usage: "${term.examples[0]}"`);
576
+ }
577
+ parts.push('');
578
+ }
579
+ }
580
+
363
581
  // Add table descriptions
364
582
  for (const tableName of tables) {
365
583
  const desc = this.getTableDescription(tableName);
@@ -389,10 +607,11 @@ export class KnowledgeStore {
389
607
  const rules = this.getActiveRulesForTables(tables);
390
608
  if (rules.length > 0) {
391
609
  parts.push('## Business Rules');
610
+ parts.push('Always apply these rules when querying:');
392
611
  for (const rule of rules) {
393
- parts.push(`- ${rule.name}: ${rule.description}`);
612
+ parts.push(`- **${rule.name}**: ${rule.description}`);
394
613
  if (rule.condition) {
395
- parts.push(` Condition: ${rule.condition}`);
614
+ parts.push(` SQL condition: \`${rule.condition}\``);
396
615
  }
397
616
  }
398
617
  parts.push('');
@@ -402,11 +621,14 @@ export class KnowledgeStore {
402
621
  const examples = this.getQueryExamplesForTables(tables);
403
622
  if (examples.length > 0) {
404
623
  parts.push('## Query Examples');
624
+ parts.push('Reference these verified examples:');
405
625
  for (const ex of examples.slice(0, 5)) { // Limit to 5 examples
406
- parts.push(`Intent: ${ex.intent}`);
407
- parts.push(`SQL: ${ex.sql}`);
626
+ parts.push(`Intent: "${ex.intent}"`);
627
+ parts.push('```sql');
628
+ parts.push(ex.sql);
629
+ parts.push('```');
408
630
  if (ex.explanation) {
409
- parts.push(`Explanation: ${ex.explanation}`);
631
+ parts.push(`Note: ${ex.explanation}`);
410
632
  }
411
633
  parts.push('');
412
634
  }
@@ -424,6 +646,7 @@ export class KnowledgeStore {
424
646
  tableDescriptions: [],
425
647
  queryExamples: [],
426
648
  businessRules: [],
649
+ businessTerms: [],
427
650
  };
428
651
  }
429
652
 
@@ -439,9 +662,10 @@ export class KnowledgeStore {
439
662
  }>;
440
663
  queryExamples: QueryExample[];
441
664
  businessRules: BusinessRule[];
665
+ businessTerms: BusinessTerm[];
442
666
  } {
443
667
  if (!this.data) {
444
- return { tables: [], queryExamples: [], businessRules: [] };
668
+ return { tables: [], queryExamples: [], businessRules: [], businessTerms: [] };
445
669
  }
446
670
 
447
671
  return {
@@ -453,6 +677,7 @@ export class KnowledgeStore {
453
677
  })),
454
678
  queryExamples: this.data.queryExamples,
455
679
  businessRules: this.data.businessRules,
680
+ businessTerms: this.data.businessTerms ?? [],
456
681
  };
457
682
  }
458
683
  }
@@ -92,8 +92,50 @@ export interface BusinessRule extends KnowledgeMeta {
92
92
  priority: number;
93
93
  }
94
94
 
95
+ /** Term category for classification */
96
+ export type TermCategory = 'status' | 'time' | 'money' | 'entity' | 'metric' | 'filter' | 'custom';
97
+
98
+ /**
99
+ * Business term / glossary entry
100
+ *
101
+ * Represents a domain-specific term and its SQL meaning.
102
+ * Used to translate natural language into accurate SQL conditions.
103
+ *
104
+ * @example
105
+ * {
106
+ * term: "활성 사용자",
107
+ * synonyms: ["active user", "활성화된 사용자"],
108
+ * definition: "status 컬럼이 1인 사용자",
109
+ * sqlExpression: "status = 1",
110
+ * appliesTo: { tables: ["users"] },
111
+ * category: "status"
112
+ * }
113
+ */
114
+ export interface BusinessTerm extends KnowledgeMeta {
115
+ type: 'business_term';
116
+ /** Business term (natural language) */
117
+ term: string;
118
+ /** Alternative names/synonyms for this term */
119
+ synonyms: string[];
120
+ /** Human-readable definition */
121
+ definition: string;
122
+ /** SQL expression to apply when this term is used */
123
+ sqlExpression?: string;
124
+ /** Tables/columns this term applies to */
125
+ appliesTo?: {
126
+ tables?: string[];
127
+ columns?: string[];
128
+ };
129
+ /** Category for grouping */
130
+ category?: TermCategory;
131
+ /** Usage examples */
132
+ examples?: string[];
133
+ /** Whether this term is active */
134
+ isActive: boolean;
135
+ }
136
+
95
137
  /** Union type for all knowledge entries */
96
- export type KnowledgeEntry = TableDescription | QueryExample | BusinessRule;
138
+ export type KnowledgeEntry = TableDescription | QueryExample | BusinessRule | BusinessTerm;
97
139
 
98
140
  /** Complete knowledge data structure */
99
141
  export interface KnowledgeData {
@@ -111,6 +153,8 @@ export interface KnowledgeData {
111
153
  queryExamples: QueryExample[];
112
154
  /** Business rules */
113
155
  businessRules: BusinessRule[];
156
+ /** Business terms / glossary */
157
+ businessTerms: BusinessTerm[];
114
158
  }
115
159
 
116
160
  /** Generate a unique ID */