@memberjunction/query-gen 0.0.1 → 2.126.1

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 (138) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +34 -0
  3. package/COORDINATOR.md +768 -0
  4. package/IMPLEMENTATION_PLAN.md +1753 -0
  5. package/LLM_ENTITY_GROUPING_PLAN.md +977 -0
  6. package/README.md +675 -29
  7. package/dist/cli/commands/export.d.ts +15 -0
  8. package/dist/cli/commands/export.d.ts.map +1 -0
  9. package/dist/cli/commands/export.js +178 -0
  10. package/dist/cli/commands/export.js.map +1 -0
  11. package/dist/cli/commands/generate.d.ts +19 -0
  12. package/dist/cli/commands/generate.d.ts.map +1 -0
  13. package/dist/cli/commands/generate.js +282 -0
  14. package/dist/cli/commands/generate.js.map +1 -0
  15. package/dist/cli/commands/validate.d.ts +17 -0
  16. package/dist/cli/commands/validate.d.ts.map +1 -0
  17. package/dist/cli/commands/validate.js +193 -0
  18. package/dist/cli/commands/validate.js.map +1 -0
  19. package/dist/cli/config.d.ts +51 -0
  20. package/dist/cli/config.d.ts.map +1 -0
  21. package/dist/cli/config.js +142 -0
  22. package/dist/cli/config.js.map +1 -0
  23. package/dist/cli/index.d.ts +13 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +57 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/core/EntityGrouper.d.ts +74 -0
  28. package/dist/core/EntityGrouper.d.ts.map +1 -0
  29. package/dist/core/EntityGrouper.js +246 -0
  30. package/dist/core/EntityGrouper.js.map +1 -0
  31. package/dist/core/MetadataExporter.d.ts +59 -0
  32. package/dist/core/MetadataExporter.d.ts.map +1 -0
  33. package/dist/core/MetadataExporter.js +151 -0
  34. package/dist/core/MetadataExporter.js.map +1 -0
  35. package/dist/core/QueryDatabaseWriter.d.ts +50 -0
  36. package/dist/core/QueryDatabaseWriter.d.ts.map +1 -0
  37. package/dist/core/QueryDatabaseWriter.js +152 -0
  38. package/dist/core/QueryDatabaseWriter.js.map +1 -0
  39. package/dist/core/QueryFixer.d.ts +48 -0
  40. package/dist/core/QueryFixer.d.ts.map +1 -0
  41. package/dist/core/QueryFixer.js +115 -0
  42. package/dist/core/QueryFixer.js.map +1 -0
  43. package/dist/core/QueryRefiner.d.ts +94 -0
  44. package/dist/core/QueryRefiner.d.ts.map +1 -0
  45. package/dist/core/QueryRefiner.js +267 -0
  46. package/dist/core/QueryRefiner.js.map +1 -0
  47. package/dist/core/QueryTester.d.ts +70 -0
  48. package/dist/core/QueryTester.d.ts.map +1 -0
  49. package/dist/core/QueryTester.js +243 -0
  50. package/dist/core/QueryTester.js.map +1 -0
  51. package/dist/core/QueryWriter.d.ts +57 -0
  52. package/dist/core/QueryWriter.d.ts.map +1 -0
  53. package/dist/core/QueryWriter.js +184 -0
  54. package/dist/core/QueryWriter.js.map +1 -0
  55. package/dist/core/QuestionGenerator.d.ts +58 -0
  56. package/dist/core/QuestionGenerator.d.ts.map +1 -0
  57. package/dist/core/QuestionGenerator.js +145 -0
  58. package/dist/core/QuestionGenerator.js.map +1 -0
  59. package/dist/data/schema.d.ts +230 -0
  60. package/dist/data/schema.d.ts.map +1 -0
  61. package/dist/data/schema.js +6 -0
  62. package/dist/data/schema.js.map +1 -0
  63. package/dist/index.d.ts +28 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +77 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/prompts/PromptNames.d.ts +32 -0
  68. package/dist/prompts/PromptNames.d.ts.map +1 -0
  69. package/dist/prompts/PromptNames.js +35 -0
  70. package/dist/prompts/PromptNames.js.map +1 -0
  71. package/dist/utils/category-builder.d.ts +28 -0
  72. package/dist/utils/category-builder.d.ts.map +1 -0
  73. package/dist/utils/category-builder.js +90 -0
  74. package/dist/utils/category-builder.js.map +1 -0
  75. package/dist/utils/entity-helpers.d.ts +49 -0
  76. package/dist/utils/entity-helpers.d.ts.map +1 -0
  77. package/dist/utils/entity-helpers.js +189 -0
  78. package/dist/utils/entity-helpers.js.map +1 -0
  79. package/dist/utils/error-handlers.d.ts +19 -0
  80. package/dist/utils/error-handlers.d.ts.map +1 -0
  81. package/dist/utils/error-handlers.js +41 -0
  82. package/dist/utils/error-handlers.js.map +1 -0
  83. package/dist/utils/graph-helpers.d.ts +51 -0
  84. package/dist/utils/graph-helpers.d.ts.map +1 -0
  85. package/dist/utils/graph-helpers.js +82 -0
  86. package/dist/utils/graph-helpers.js.map +1 -0
  87. package/dist/utils/prompt-helpers.d.ts +25 -0
  88. package/dist/utils/prompt-helpers.d.ts.map +1 -0
  89. package/dist/utils/prompt-helpers.js +66 -0
  90. package/dist/utils/prompt-helpers.js.map +1 -0
  91. package/dist/utils/query-helpers.d.ts +23 -0
  92. package/dist/utils/query-helpers.d.ts.map +1 -0
  93. package/dist/utils/query-helpers.js +34 -0
  94. package/dist/utils/query-helpers.js.map +1 -0
  95. package/dist/utils/user-helpers.d.ts +15 -0
  96. package/dist/utils/user-helpers.d.ts.map +1 -0
  97. package/dist/utils/user-helpers.js +32 -0
  98. package/dist/utils/user-helpers.js.map +1 -0
  99. package/dist/vectors/EmbeddingService.d.ts +58 -0
  100. package/dist/vectors/EmbeddingService.d.ts.map +1 -0
  101. package/dist/vectors/EmbeddingService.js +90 -0
  102. package/dist/vectors/EmbeddingService.js.map +1 -0
  103. package/dist/vectors/SimilaritySearch.d.ts +51 -0
  104. package/dist/vectors/SimilaritySearch.d.ts.map +1 -0
  105. package/dist/vectors/SimilaritySearch.js +85 -0
  106. package/dist/vectors/SimilaritySearch.js.map +1 -0
  107. package/docs/API.md +1040 -0
  108. package/docs/ARCHITECTURE.md +1120 -0
  109. package/examples/advanced-usage.ts +401 -0
  110. package/examples/basic-usage.ts +285 -0
  111. package/package.json +48 -6
  112. package/src/cli/commands/export.ts +173 -0
  113. package/src/cli/commands/generate.ts +330 -0
  114. package/src/cli/commands/validate.ts +185 -0
  115. package/src/cli/config.ts +203 -0
  116. package/src/cli/index.ts +63 -0
  117. package/src/core/EntityGrouper.ts +318 -0
  118. package/src/core/MetadataExporter.ts +148 -0
  119. package/src/core/QueryDatabaseWriter.ts +187 -0
  120. package/src/core/QueryFixer.ts +153 -0
  121. package/src/core/QueryRefiner.ts +382 -0
  122. package/src/core/QueryTester.ts +264 -0
  123. package/src/core/QueryWriter.ts +239 -0
  124. package/src/core/QuestionGenerator.ts +199 -0
  125. package/src/data/golden-queries.json +1371 -0
  126. package/src/data/schema.ts +252 -0
  127. package/src/index.ts +49 -0
  128. package/src/prompts/PromptNames.ts +36 -0
  129. package/src/utils/category-builder.ts +97 -0
  130. package/src/utils/entity-helpers.ts +203 -0
  131. package/src/utils/error-handlers.ts +41 -0
  132. package/src/utils/graph-helpers.ts +99 -0
  133. package/src/utils/prompt-helpers.ts +79 -0
  134. package/src/utils/query-helpers.ts +32 -0
  135. package/src/utils/user-helpers.ts +39 -0
  136. package/src/vectors/EmbeddingService.ts +109 -0
  137. package/src/vectors/SimilaritySearch.ts +108 -0
  138. package/tsconfig.json +39 -0
package/package.json CHANGED
@@ -1,10 +1,52 @@
1
1
  {
2
2
  "name": "@memberjunction/query-gen",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/query-gen",
3
+ "version": "2.126.1",
4
+ "description": "AI-powered generation of domain-specific SQL query templates with automatic testing, refinement, and metadata export",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "mj-querygen": "./dist/cli/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "watch": "tsc --watch",
13
+ "clean": "rm -rf dist",
14
+ "lint": "eslint src --ext .ts",
15
+ "format": "prettier --write \"src/**/*.ts\""
16
+ },
5
17
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
18
+ "memberjunction",
19
+ "sql",
20
+ "query",
21
+ "generation",
22
+ "ai",
23
+ "templates"
24
+ ],
25
+ "author": "MemberJunction",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@memberjunction/ai": "2.126.1",
29
+ "@memberjunction/ai-core-plus": "2.126.1",
30
+ "@memberjunction/aiengine": "2.126.1",
31
+ "@memberjunction/ai-prompts": "2.126.1",
32
+ "@memberjunction/ai-vectors-memory": "2.126.1",
33
+ "@memberjunction/core": "2.126.1",
34
+ "@memberjunction/core-entities": "2.126.1",
35
+ "@memberjunction/global": "2.126.1",
36
+ "@memberjunction/sqlserver-dataprovider": "2.126.1",
37
+ "chalk": "^4.1.2",
38
+ "commander": "^11.1.0",
39
+ "nunjucks": "^3.2.4",
40
+ "ora": "^5.4.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "20.14.2",
44
+ "@types/nunjucks": "^3.2.6",
45
+ "ts-node-dev": "^2.0.0",
46
+ "typescript": "^5.4.5"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/MemberJunction/MJ"
51
+ }
10
52
  }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Export command - Export queries from database to metadata files
3
+ *
4
+ * Reads existing queries from the database and exports them to metadata format:
5
+ * - Loads all queries from Queries table
6
+ * - Includes related Query Fields and Query Params
7
+ * - Exports to JSON files in MJ metadata format
8
+ */
9
+
10
+ import ora from 'ora';
11
+ import chalk from 'chalk';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { Metadata, RunView, UserInfo } from '@memberjunction/core';
15
+ import { getSystemUser } from '../../utils/user-helpers';
16
+ import { QueryEntity } from '@memberjunction/core-entities';
17
+ import { extractErrorMessage } from '../../utils/error-handlers';
18
+ import { QueryMetadataRecord } from '../../data/schema';
19
+
20
+ /**
21
+ * Execute the export command
22
+ *
23
+ * Loads queries from database and exports them to metadata files.
24
+ */
25
+ export async function exportCommand(options: Record<string, unknown>): Promise<void> {
26
+ const spinner = ora('Initializing export...').start();
27
+
28
+ try {
29
+ const outputPath = String(options.output || './metadata/queries');
30
+ const verbose = Boolean(options.verbose);
31
+
32
+ // 1. Get system user from UserCache (populated by provider initialization)
33
+ const contextUser = getSystemUser();
34
+
35
+ // 2. Verify database connection and load metadata
36
+ spinner.text = 'Loading metadata...';
37
+ // Assume provider is already configured by the calling application
38
+ if (!Metadata.Provider) {
39
+ throw new Error('Metadata provider not configured. Please ensure database connection is set up before running CLI.');
40
+ }
41
+ spinner.succeed('Metadata loaded');
42
+
43
+ // 3. Load queries from database
44
+ spinner.start('Loading queries from database...');
45
+ const queries = await loadQueriesFromDatabase(contextUser);
46
+ spinner.succeed(chalk.green(`Found ${queries.length} queries`));
47
+
48
+ // 4. Create output directory if it doesn't exist
49
+ if (!fs.existsSync(outputPath)) {
50
+ fs.mkdirSync(outputPath, { recursive: true });
51
+ }
52
+
53
+ // 5. Export each query to metadata format
54
+ let exportCount = 0;
55
+ const errors: Array<{ query: string; error: string }> = [];
56
+
57
+ for (let i = 0; i < queries.length; i++) {
58
+ const query = queries[i];
59
+ const queryPrefix = chalk.cyan(`[${i + 1}/${queries.length}]`);
60
+
61
+ spinner.start(`${queryPrefix} Exporting ${chalk.bold(query.Name)}...`);
62
+
63
+ try {
64
+ const metadataRecord = await convertQueryToMetadata(query, contextUser);
65
+ const filename = sanitizeFilename(query.Name) + '.json';
66
+ const fullPath = path.join(outputPath, filename);
67
+
68
+ fs.writeFileSync(fullPath, JSON.stringify(metadataRecord, null, 2), 'utf-8');
69
+ exportCount++;
70
+
71
+ if (verbose) {
72
+ spinner.info(`${queryPrefix} ${chalk.green('✓')} Exported ${query.Name}`);
73
+ }
74
+ } catch (error: unknown) {
75
+ const errorMsg = extractErrorMessage(error, 'Query Export');
76
+ errors.push({ query: query.Name, error: errorMsg });
77
+ if (verbose) {
78
+ spinner.warn(`${queryPrefix} ${chalk.red('✗')} ${query.Name}: ${errorMsg}`);
79
+ }
80
+ }
81
+ }
82
+
83
+ // 6. Summary
84
+ if (errors.length === 0) {
85
+ spinner.succeed(chalk.green.bold(`✓ All ${exportCount} queries exported successfully!`));
86
+ console.log('\n' + chalk.green.bold('✓ Export complete!\n'));
87
+ console.log(chalk.bold('Summary:'));
88
+ console.log(` Total Queries: ${chalk.cyan(exportCount.toString())}`);
89
+ console.log(` Exported: ${chalk.green(exportCount.toString())}`);
90
+ console.log(` Output Location: ${chalk.dim(outputPath)}`);
91
+ process.exit(0);
92
+ } else {
93
+ spinner.fail(chalk.yellow(`Export completed with ${errors.length} errors`));
94
+ console.log('\n' + chalk.yellow.bold('⚠ Export completed with errors\n'));
95
+ console.log(chalk.bold('Summary:'));
96
+ console.log(` Total Queries: ${chalk.cyan(queries.length.toString())}`);
97
+ console.log(` Exported: ${chalk.green(exportCount.toString())}`);
98
+ console.log(` Failed: ${chalk.red(errors.length.toString())}`);
99
+ console.log(` Output Location: ${chalk.dim(outputPath)}`);
100
+
101
+ if (errors.length > 0) {
102
+ console.log('\n' + chalk.bold('Errors:'));
103
+ for (const { query, error } of errors) {
104
+ console.log(chalk.red(` ${query}: ${error}`));
105
+ }
106
+ }
107
+ process.exit(1);
108
+ }
109
+
110
+ } catch (error: unknown) {
111
+ spinner.fail(chalk.red('Export failed'));
112
+ console.error(chalk.red(extractErrorMessage(error, 'Query Export')));
113
+ process.exit(1);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Load all queries from the database
119
+ */
120
+ async function loadQueriesFromDatabase(contextUser: UserInfo): Promise<QueryEntity[]> {
121
+ const rv = new RunView();
122
+ const result = await rv.RunView<QueryEntity>(
123
+ {
124
+ EntityName: 'Queries',
125
+ ExtraFilter: '',
126
+ OrderBy: 'Name',
127
+ ResultType: 'entity_object',
128
+ },
129
+ contextUser
130
+ );
131
+
132
+ if (!result.Success) {
133
+ throw new Error(`Failed to load queries: ${result.ErrorMessage}`);
134
+ }
135
+
136
+ return result.Results || [];
137
+ }
138
+
139
+ /**
140
+ * Convert QueryEntity to metadata record format
141
+ *
142
+ * Note: For the export command, we only export the Query entity itself.
143
+ * QueryFields and QueryParameters are managed by QueryEntity.server.ts and
144
+ * will be automatically extracted when the query is imported/saved.
145
+ */
146
+ async function convertQueryToMetadata(
147
+ query: QueryEntity,
148
+ contextUser: UserInfo
149
+ ): Promise<QueryMetadataRecord> {
150
+ return {
151
+ fields: {
152
+ Name: query.Name,
153
+ CategoryID: query.CategoryID || 'unknown',
154
+ UserQuestion: query.UserQuestion || '',
155
+ Description: query.Description || '',
156
+ TechnicalDescription: query.TechnicalDescription || '',
157
+ SQL: query.SQL || '',
158
+ OriginalSQL: query.OriginalSQL || query.SQL || '',
159
+ UsesTemplate: query.UsesTemplate || false,
160
+ Status: query.Status || 'Active',
161
+ },
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Sanitize filename by removing invalid characters
167
+ */
168
+ function sanitizeFilename(name: string): string {
169
+ return name
170
+ .replace(/[^a-zA-Z0-9-_\s]/g, '')
171
+ .replace(/\s+/g, '_')
172
+ .toLowerCase();
173
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Generate command - Main query generation workflow
3
+ *
4
+ * Orchestrates the full query generation pipeline:
5
+ * 1. Load entity groups
6
+ * 2. Generate business questions
7
+ * 3. Generate SQL queries
8
+ * 4. Test and fix queries
9
+ * 5. Refine queries
10
+ * 6. Export results
11
+ */
12
+
13
+ import ora from 'ora';
14
+ import chalk from 'chalk';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { Metadata, DatabaseProviderBase, EntityInfo, LogStatus } from '@memberjunction/core';
18
+ import { loadConfig } from '../config';
19
+ import { getSystemUser } from '../../utils/user-helpers';
20
+ import { EntityGrouper } from '../../core/EntityGrouper';
21
+ import { QuestionGenerator } from '../../core/QuestionGenerator';
22
+ import { QueryWriter } from '../../core/QueryWriter';
23
+ import { QueryTester } from '../../core/QueryTester';
24
+ import { QueryRefiner } from '../../core/QueryRefiner';
25
+ import { MetadataExporter } from '../../core/MetadataExporter';
26
+ import { QueryDatabaseWriter } from '../../core/QueryDatabaseWriter';
27
+ import { EmbeddingService } from '../../vectors/EmbeddingService';
28
+ import { SimilaritySearch } from '../../vectors/SimilaritySearch';
29
+ import { formatEntityMetadataForPrompt } from '../../utils/entity-helpers';
30
+ import { extractErrorMessage } from '../../utils/error-handlers';
31
+ import { buildQueryCategory, extractUniqueCategories } from '../../utils/category-builder';
32
+ import { ValidatedQuery, GoldenQuery, EntityGroup, QueryCategoryInfo } from '../../data/schema';
33
+
34
+ /**
35
+ * Execute the generate command
36
+ *
37
+ * Full orchestration of query generation workflow with progress reporting.
38
+ * Uses ora for spinners and chalk for colored output.
39
+ */
40
+ export async function generateCommand(options: Record<string, unknown>): Promise<void> {
41
+ const spinner = ora('Initializing query generation...').start();
42
+
43
+ try {
44
+ // 1. Load configuration
45
+ spinner.text = 'Loading configuration...';
46
+ const config = loadConfig(options);
47
+
48
+ // Show model/vendor overrides if configured
49
+ if (config.modelOverride || config.vendorOverride) {
50
+ const overrideMsg = [];
51
+ if (config.modelOverride) overrideMsg.push(`Model: ${config.modelOverride}`);
52
+ if (config.vendorOverride) overrideMsg.push(`Vendor: ${config.vendorOverride}`);
53
+ spinner.info(chalk.cyan(`Using overrides - ${overrideMsg.join(', ')}`));
54
+ }
55
+
56
+ if (config.verbose) {
57
+ spinner.info(chalk.dim('Configuration loaded'));
58
+ console.log(chalk.dim(JSON.stringify(config, null, 2)));
59
+ }
60
+
61
+ // 2. Get system user from UserCache (populated by provider initialization)
62
+ const contextUser = getSystemUser();
63
+
64
+ // 3. Verify database connection and metadata
65
+ spinner.text = 'Loading metadata...';
66
+ // Assume provider and AIEngine are already configured by the calling application (MJCLI)
67
+ if (!Metadata.Provider) {
68
+ throw new Error('Metadata provider not configured. Please ensure database connection is set up before running CLI.');
69
+ }
70
+ spinner.succeed('Metadata loaded');
71
+
72
+ // 4. Filter and build entity groups
73
+ spinner.start('Filtering entities...');
74
+ const md = new Metadata();
75
+
76
+ // Apply entity filtering (includeEntities takes precedence over excludeEntities)
77
+ let filteredEntities = md.Entities.filter(
78
+ e => !config.excludeSchemas.includes(e.SchemaName || '')
79
+ );
80
+
81
+ if (config.includeEntities.length > 0) {
82
+ // Allowlist: only include specified entities
83
+ filteredEntities = filteredEntities.filter(e => config.includeEntities.includes(e.Name));
84
+ spinner.info(chalk.dim(`Including only ${config.includeEntities.length} specified entities`));
85
+ } else if (config.excludeEntities.length > 0) {
86
+ // Denylist: exclude specified entities
87
+ filteredEntities = filteredEntities.filter(e => !config.excludeEntities.includes(e.Name));
88
+ spinner.info(chalk.dim(`Excluded ${config.excludeEntities.length} entities`));
89
+ }
90
+
91
+ // 4. Group entities by schema and generate entity groups
92
+ spinner.text = 'Analyzing entity relationships...';
93
+
94
+ // Count entities per schema for informational logging
95
+ const schemaCount = new Set(filteredEntities.map(e => e.SchemaName)).size;
96
+ if (config.verbose && schemaCount > 1) {
97
+ spinner.info(chalk.dim(`Processing ${schemaCount} schemas separately`));
98
+ }
99
+
100
+ const grouper = new EntityGrouper(config);
101
+ const entityGroups = await grouper.generateEntityGroups(filteredEntities, contextUser);
102
+ spinner.succeed(chalk.green(`Found ${entityGroups.length} entity groups across ${schemaCount} ${schemaCount === 1 ? 'schema' : 'schemas'}`));
103
+
104
+
105
+ // 5. Initialize vector similarity search
106
+ spinner.start('Embedding golden queries...');
107
+ const embeddingService = new EmbeddingService(config.embeddingModel);
108
+ const goldenQueries = await loadGoldenQueries(config);
109
+ const embeddedGolden = await embeddingService.embedGoldenQueries(goldenQueries);
110
+ spinner.succeed(chalk.green(`Embedded ${goldenQueries.length} golden queries`));
111
+
112
+ // 5b. Build category structure for all entity groups upfront
113
+ spinner.start('Building category structure...');
114
+ const categoryMap = new Map<string, QueryCategoryInfo>();
115
+ for (const group of entityGroups) {
116
+ const category = buildQueryCategory(config, group);
117
+ // Use primary entity name as key for lookup during query generation
118
+ categoryMap.set(group.primaryEntity.Name, category);
119
+ }
120
+ const uniqueCategories = extractUniqueCategories(Array.from(categoryMap.values()));
121
+ spinner.succeed(chalk.green(`Created ${uniqueCategories.length} ${uniqueCategories.length === 1 ? 'category' : 'categories'}`));
122
+
123
+ // 6. Generate queries for each entity group
124
+ const totalGroups = entityGroups.length;
125
+ let processedGroups = 0;
126
+ const allValidatedQueries: ValidatedQuery[] = [];
127
+
128
+ for (const group of entityGroups) {
129
+ processedGroups++;
130
+ const groupPrefix = chalk.cyan(`[${processedGroups}/${totalGroups}]`);
131
+ spinner.start(`${groupPrefix} Processing ${chalk.bold(group.primaryEntity.Name)}...`);
132
+
133
+ let queriesCreatedForGroup = 0;
134
+
135
+ try {
136
+ // 6a. Generate business questions for this entity group
137
+ const questionGenerator = new QuestionGenerator(contextUser, config);
138
+ const questions = await questionGenerator.generateQuestions(group);
139
+
140
+ if (config.verbose) {
141
+ spinner.info(`${groupPrefix} Generated ${questions.length} questions`);
142
+ }
143
+
144
+ // 6b. For each question, generate and validate query
145
+ for (const question of questions) {
146
+ spinner.text = `${groupPrefix} Generating query: ${chalk.italic(question.userQuestion)}`;
147
+
148
+ // Embed question for similarity search
149
+ const questionEmbedding = await embeddingService.embedQuery({
150
+ userQuestion: question.userQuestion,
151
+ description: question.description,
152
+ technicalDescription: question.technicalDescription,
153
+ });
154
+
155
+ // Find similar golden queries
156
+ const similaritySearch = new SimilaritySearch();
157
+ const fewShotResults = await similaritySearch.findSimilarQueries(
158
+ questionEmbedding,
159
+ embeddedGolden,
160
+ config.topSimilarQueries
161
+ );
162
+ const fewShotExamples = fewShotResults.map(s => s.query);
163
+
164
+ // Generate SQL query
165
+ const queryWriter = new QueryWriter(contextUser, config);
166
+ const generatedQuery = await queryWriter.generateQuery(
167
+ question,
168
+ group.entities.map((e: EntityInfo) => formatEntityMetadataForPrompt(e, group.entities)),
169
+ fewShotExamples
170
+ );
171
+
172
+ // Test and fix query
173
+ // Access the database provider through Metadata.Provider
174
+ const dataProvider = Metadata.Provider as DatabaseProviderBase;
175
+ const entityMetadata = group.entities.map((e: EntityInfo) => formatEntityMetadataForPrompt(e, group.entities));
176
+ const queryTester = new QueryTester(
177
+ dataProvider,
178
+ entityMetadata,
179
+ question,
180
+ contextUser,
181
+ config
182
+ );
183
+ const testResult = await queryTester.testQuery(
184
+ generatedQuery,
185
+ config.maxFixingIterations
186
+ );
187
+
188
+ if (!testResult.success) {
189
+ spinner.warn(
190
+ chalk.yellow(`${groupPrefix} Query failed after ${config.maxFixingIterations} attempts: ${question.userQuestion}`)
191
+ );
192
+ continue;
193
+ }
194
+
195
+ // Refine query
196
+ const queryRefiner = new QueryRefiner(queryTester, contextUser, config);
197
+ const refinedResult = await queryRefiner.refineQuery(
198
+ generatedQuery,
199
+ question,
200
+ entityMetadata,
201
+ config.maxRefinementIterations
202
+ );
203
+
204
+ // Get pre-built category from map
205
+ const category = categoryMap.get(group.primaryEntity.Name);
206
+ if (!category) {
207
+ throw new Error(`Category not found for entity group: ${group.primaryEntity.Name}`);
208
+ }
209
+
210
+ allValidatedQueries.push({
211
+ businessQuestion: question,
212
+ query: refinedResult.query,
213
+ testResult: refinedResult.testResult,
214
+ evaluation: refinedResult.evaluation,
215
+ entityGroup: group,
216
+ category,
217
+ });
218
+
219
+ queriesCreatedForGroup++;
220
+
221
+ if (config.verbose) {
222
+ spinner.info(`${groupPrefix} ${chalk.green('✓')} ${question.userQuestion}`);
223
+ }
224
+ }
225
+
226
+ // Format entity list with primary entity in bold
227
+ const entityDisplay = group.entities
228
+ .map(e => e.Name === group.primaryEntity.Name ? chalk.bold(e.Name) : e.Name)
229
+ .join(', ');
230
+
231
+ spinner.succeed(
232
+ `${groupPrefix} ${entityDisplay} complete (${chalk.green(queriesCreatedForGroup + ' queries')})`
233
+ );
234
+
235
+ } catch (error: unknown) {
236
+ // Format entity list with primary entity in bold
237
+ const entityDisplay = group.entities
238
+ .map(e => e.Name === group.primaryEntity.Name ? chalk.bold(e.Name) : e.Name)
239
+ .join(', ');
240
+
241
+ spinner.warn(
242
+ chalk.yellow(
243
+ `${groupPrefix} Error processing ${entityDisplay}: ${extractErrorMessage(error, 'Query Generation')}`
244
+ )
245
+ );
246
+ }
247
+ }
248
+
249
+ // 7. Export results
250
+ spinner.start(`Exporting ${allValidatedQueries.length} queries...`);
251
+
252
+ if (config.outputMode === 'metadata' || config.outputMode === 'both') {
253
+ const exporter = new MetadataExporter();
254
+ const exportResult = await exporter.exportQueries(
255
+ allValidatedQueries,
256
+ uniqueCategories,
257
+ config.outputDirectory,
258
+ config.outputCategoryDirectory
259
+ );
260
+ spinner.succeed(chalk.green(`Exported to ${exportResult.outputPath}`));
261
+ }
262
+
263
+ if (config.outputMode === 'database' || config.outputMode === 'both') {
264
+ const dbWriter = new QueryDatabaseWriter();
265
+ await dbWriter.writeQueriesToDatabase(allValidatedQueries, contextUser);
266
+ spinner.succeed(chalk.green(`Wrote ${allValidatedQueries.length} queries to database`));
267
+ }
268
+
269
+ // 8. Summary
270
+ console.log('\n' + chalk.green.bold('✓ Query generation complete!\n'));
271
+ console.log(chalk.bold('Summary:'));
272
+ console.log(` Entity Groups Processed: ${chalk.cyan(processedGroups.toString())}`);
273
+ console.log(` Queries Generated: ${chalk.green(allValidatedQueries.length.toString())}`);
274
+ console.log(` Output Location: ${chalk.dim(config.outputDirectory)}`);
275
+
276
+ process.exit(0);
277
+
278
+ } catch (error: unknown) {
279
+ spinner.fail(chalk.red('Query generation failed'));
280
+ console.error(chalk.red(extractErrorMessage(error, 'Query Generation')));
281
+ process.exit(1);
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Load golden queries from JSON file
287
+ *
288
+ * Golden queries are example queries used for few-shot learning.
289
+ * They are stored in the data/golden-queries.json file.
290
+ *
291
+ * @param config - QueryGen configuration with verbose flag
292
+ * @returns Array of golden queries, or empty array if file not found/invalid
293
+ */
294
+ async function loadGoldenQueries(config: { verbose: boolean }): Promise<GoldenQuery[]> {
295
+ try {
296
+ // Resolve path to golden-queries.json in the data directory
297
+ // __dirname points to dist/cli/commands, so we go up to dist, then to data
298
+ const goldenQueriesPath = path.join(__dirname, '../../data/golden-queries.json');
299
+
300
+ // Check if file exists
301
+ if (!fs.existsSync(goldenQueriesPath)) {
302
+ if (config.verbose) {
303
+ LogStatus(`[Warning] Golden queries file not found at: ${goldenQueriesPath}`);
304
+ }
305
+ return [];
306
+ }
307
+
308
+ // Read and parse JSON file
309
+ const fileContent = fs.readFileSync(goldenQueriesPath, 'utf-8');
310
+ const goldenQueries = JSON.parse(fileContent) as GoldenQuery[];
311
+
312
+ // Validate that it's an array
313
+ if (!Array.isArray(goldenQueries)) {
314
+ if (config.verbose) {
315
+ LogStatus('[Warning] Golden queries file does not contain an array');
316
+ }
317
+ return [];
318
+ }
319
+
320
+ if (config.verbose) {
321
+ LogStatus(`[Info] Loaded ${goldenQueries.length} golden queries for few-shot learning`);
322
+ }
323
+ return goldenQueries;
324
+ } catch (error: unknown) {
325
+ if (config.verbose) {
326
+ LogStatus(`[Warning] Failed to load golden queries: ${extractErrorMessage(error, 'loadGoldenQueries')}`);
327
+ }
328
+ return [];
329
+ }
330
+ }