@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.
- package/.cursorrules +12 -0
- package/.env.example +8 -0
- package/.github/workflows/ci.yml +21 -1
- package/.github/workflows/publish.yml +21 -1
- package/CHANGELOG.md +41 -0
- package/README.md +247 -239
- package/datacontext.db +0 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +145 -0
- package/dist/api/server.js.map +1 -1
- package/dist/api/start-server.d.ts +10 -0
- package/dist/api/start-server.d.ts.map +1 -0
- package/dist/api/start-server.js +73 -0
- package/dist/api/start-server.js.map +1 -0
- package/dist/cli/index.js +462 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/context-service.d.ts +58 -0
- package/dist/core/context-service.d.ts.map +1 -1
- package/dist/core/context-service.js +121 -0
- package/dist/core/context-service.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/llm-service.d.ts +141 -0
- package/dist/core/llm-service.d.ts.map +1 -0
- package/dist/core/llm-service.js +284 -0
- package/dist/core/llm-service.js.map +1 -0
- package/dist/knowledge/store.d.ts +56 -3
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +193 -7
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +43 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +365 -0
- package/dist/mcp/tools.js.map +1 -1
- package/docs/API.md +173 -0
- package/docs/DEMO_SCRIPT.md +210 -0
- package/docs/SYNC_GUIDE.md +242 -0
- package/package.json +4 -1
- package/src/api/server.ts +160 -0
- package/src/api/start-server.ts +78 -0
- package/src/cli/index.ts +534 -0
- package/src/core/context-service.ts +157 -0
- package/src/core/index.ts +7 -0
- package/src/core/llm-service.ts +359 -0
- package/src/knowledge/store.ts +232 -7
- package/src/knowledge/types.ts +45 -1
- package/src/mcp/tools.ts +415 -0
package/src/knowledge/store.ts
CHANGED
|
@@ -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(`-
|
|
612
|
+
parts.push(`- **${rule.name}**: ${rule.description}`);
|
|
394
613
|
if (rule.condition) {
|
|
395
|
-
parts.push(`
|
|
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(
|
|
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(`
|
|
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
|
}
|
package/src/knowledge/types.ts
CHANGED
|
@@ -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 */
|