@memberjunction/db-auto-doc 2.116.0 → 2.118.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 (251) hide show
  1. package/README.md +652 -165
  2. package/bin/run.js +7 -0
  3. package/dist/api/DBAutoDocAPI.d.ts +252 -0
  4. package/dist/api/DBAutoDocAPI.d.ts.map +1 -0
  5. package/dist/api/DBAutoDocAPI.js +530 -0
  6. package/dist/api/DBAutoDocAPI.js.map +1 -0
  7. package/dist/api/index.d.ts +7 -0
  8. package/dist/api/index.d.ts.map +1 -0
  9. package/dist/api/index.js +10 -0
  10. package/dist/api/index.js.map +1 -0
  11. package/dist/commands/analyze.d.ts +6 -4
  12. package/dist/commands/analyze.d.ts.map +1 -1
  13. package/dist/commands/analyze.js +58 -71
  14. package/dist/commands/analyze.js.map +1 -1
  15. package/dist/commands/export.d.ts +14 -4
  16. package/dist/commands/export.d.ts.map +1 -1
  17. package/dist/commands/export.js +156 -61
  18. package/dist/commands/export.js.map +1 -1
  19. package/dist/commands/init.d.ts +3 -4
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +155 -146
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/reset.d.ts +4 -1
  24. package/dist/commands/reset.d.ts.map +1 -1
  25. package/dist/commands/reset.js +33 -19
  26. package/dist/commands/reset.js.map +1 -1
  27. package/dist/commands/status.d.ts +10 -0
  28. package/dist/commands/status.d.ts.map +1 -0
  29. package/dist/commands/status.js +66 -0
  30. package/dist/commands/status.js.map +1 -0
  31. package/dist/core/AnalysisEngine.d.ts +108 -0
  32. package/dist/core/AnalysisEngine.d.ts.map +1 -0
  33. package/dist/core/AnalysisEngine.js +716 -0
  34. package/dist/core/AnalysisEngine.js.map +1 -0
  35. package/dist/core/AnalysisOrchestrator.d.ts +37 -0
  36. package/dist/core/AnalysisOrchestrator.d.ts.map +1 -0
  37. package/dist/core/AnalysisOrchestrator.js +294 -0
  38. package/dist/core/AnalysisOrchestrator.js.map +1 -0
  39. package/dist/core/BackpropagationEngine.d.ts +32 -0
  40. package/dist/core/BackpropagationEngine.d.ts.map +1 -0
  41. package/dist/core/BackpropagationEngine.js +121 -0
  42. package/dist/core/BackpropagationEngine.js.map +1 -0
  43. package/dist/core/ConvergenceDetector.d.ts +27 -0
  44. package/dist/core/ConvergenceDetector.d.ts.map +1 -0
  45. package/dist/core/ConvergenceDetector.js +92 -0
  46. package/dist/core/ConvergenceDetector.js.map +1 -0
  47. package/dist/core/GuardrailsManager.d.ts +78 -0
  48. package/dist/core/GuardrailsManager.d.ts.map +1 -0
  49. package/dist/core/GuardrailsManager.js +367 -0
  50. package/dist/core/GuardrailsManager.js.map +1 -0
  51. package/dist/core/index.d.ts +7 -0
  52. package/dist/core/index.d.ts.map +1 -0
  53. package/dist/core/index.js +13 -0
  54. package/dist/core/index.js.map +1 -0
  55. package/dist/database/Database.d.ts +56 -0
  56. package/dist/database/Database.d.ts.map +1 -0
  57. package/dist/database/Database.js +172 -0
  58. package/dist/database/Database.js.map +1 -0
  59. package/dist/database/TopologicalSorter.d.ts +25 -0
  60. package/dist/database/TopologicalSorter.d.ts.map +1 -0
  61. package/dist/database/TopologicalSorter.js +150 -0
  62. package/dist/database/TopologicalSorter.js.map +1 -0
  63. package/dist/database/index.d.ts +6 -0
  64. package/dist/database/index.d.ts.map +1 -0
  65. package/dist/database/index.js +14 -0
  66. package/dist/database/index.js.map +1 -0
  67. package/dist/discovery/ColumnStatsCache.d.ts +91 -0
  68. package/dist/discovery/ColumnStatsCache.d.ts.map +1 -0
  69. package/dist/discovery/ColumnStatsCache.js +231 -0
  70. package/dist/discovery/ColumnStatsCache.js.map +1 -0
  71. package/dist/discovery/DiscoveryEngine.d.ts +100 -0
  72. package/dist/discovery/DiscoveryEngine.d.ts.map +1 -0
  73. package/dist/discovery/DiscoveryEngine.js +726 -0
  74. package/dist/discovery/DiscoveryEngine.js.map +1 -0
  75. package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts +57 -0
  76. package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts.map +1 -0
  77. package/dist/discovery/DiscoveryTriggerAnalyzer.js +186 -0
  78. package/dist/discovery/DiscoveryTriggerAnalyzer.js.map +1 -0
  79. package/dist/discovery/FKDetector.d.ts +47 -0
  80. package/dist/discovery/FKDetector.d.ts.map +1 -0
  81. package/dist/discovery/FKDetector.js +317 -0
  82. package/dist/discovery/FKDetector.js.map +1 -0
  83. package/dist/discovery/LLMDiscoveryValidator.d.ts +64 -0
  84. package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -0
  85. package/dist/discovery/LLMDiscoveryValidator.js +431 -0
  86. package/dist/discovery/LLMDiscoveryValidator.js.map +1 -0
  87. package/dist/discovery/LLMSanityChecker.d.ts +38 -0
  88. package/dist/discovery/LLMSanityChecker.d.ts.map +1 -0
  89. package/dist/discovery/LLMSanityChecker.js +156 -0
  90. package/dist/discovery/LLMSanityChecker.js.map +1 -0
  91. package/dist/discovery/PKDetector.d.ts +62 -0
  92. package/dist/discovery/PKDetector.d.ts.map +1 -0
  93. package/dist/discovery/PKDetector.js +436 -0
  94. package/dist/discovery/PKDetector.js.map +1 -0
  95. package/dist/discovery/index.d.ts +9 -0
  96. package/dist/discovery/index.d.ts.map +1 -0
  97. package/dist/discovery/index.js +25 -0
  98. package/dist/discovery/index.js.map +1 -0
  99. package/dist/drivers/BaseAutoDocDriver.d.ts +132 -0
  100. package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -0
  101. package/dist/drivers/BaseAutoDocDriver.js +121 -0
  102. package/dist/drivers/BaseAutoDocDriver.js.map +1 -0
  103. package/dist/drivers/MySQLDriver.d.ts +61 -0
  104. package/dist/drivers/MySQLDriver.d.ts.map +1 -0
  105. package/dist/drivers/MySQLDriver.js +668 -0
  106. package/dist/drivers/MySQLDriver.js.map +1 -0
  107. package/dist/drivers/PostgreSQLDriver.d.ts +65 -0
  108. package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -0
  109. package/dist/drivers/PostgreSQLDriver.js +704 -0
  110. package/dist/drivers/PostgreSQLDriver.js.map +1 -0
  111. package/dist/drivers/SQLServerDriver.d.ts +61 -0
  112. package/dist/drivers/SQLServerDriver.d.ts.map +1 -0
  113. package/dist/drivers/SQLServerDriver.js +667 -0
  114. package/dist/drivers/SQLServerDriver.js.map +1 -0
  115. package/dist/generators/CSVGenerator.d.ts +35 -0
  116. package/dist/generators/CSVGenerator.d.ts.map +1 -0
  117. package/dist/generators/CSVGenerator.js +154 -0
  118. package/dist/generators/CSVGenerator.js.map +1 -0
  119. package/dist/generators/HTMLGenerator.d.ts +29 -0
  120. package/dist/generators/HTMLGenerator.d.ts.map +1 -0
  121. package/dist/generators/HTMLGenerator.js +710 -0
  122. package/dist/generators/HTMLGenerator.js.map +1 -0
  123. package/dist/generators/MarkdownGenerator.d.ts +27 -0
  124. package/dist/generators/MarkdownGenerator.d.ts.map +1 -0
  125. package/dist/generators/MarkdownGenerator.js +361 -0
  126. package/dist/generators/MarkdownGenerator.js.map +1 -0
  127. package/dist/generators/MermaidGenerator.d.ts +35 -0
  128. package/dist/generators/MermaidGenerator.d.ts.map +1 -0
  129. package/dist/generators/MermaidGenerator.js +321 -0
  130. package/dist/generators/MermaidGenerator.js.map +1 -0
  131. package/dist/generators/ReportGenerator.d.ts +22 -0
  132. package/dist/generators/ReportGenerator.d.ts.map +1 -0
  133. package/dist/generators/ReportGenerator.js +176 -0
  134. package/dist/generators/ReportGenerator.js.map +1 -0
  135. package/dist/generators/SQLGenerator.d.ts +31 -0
  136. package/dist/generators/SQLGenerator.d.ts.map +1 -0
  137. package/dist/generators/SQLGenerator.js +168 -0
  138. package/dist/generators/SQLGenerator.js.map +1 -0
  139. package/dist/generators/index.d.ts +10 -0
  140. package/dist/generators/index.d.ts.map +1 -0
  141. package/dist/generators/index.js +19 -0
  142. package/dist/generators/index.js.map +1 -0
  143. package/dist/index.d.ts +11 -20
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/index.js +19 -20
  146. package/dist/index.js.map +1 -1
  147. package/dist/prompts/PromptEngine.d.ts +65 -0
  148. package/dist/prompts/PromptEngine.d.ts.map +1 -0
  149. package/dist/prompts/PromptEngine.js +282 -0
  150. package/dist/prompts/PromptEngine.js.map +1 -0
  151. package/dist/prompts/PromptFileLoader.d.ts +21 -0
  152. package/dist/prompts/PromptFileLoader.d.ts.map +1 -0
  153. package/dist/prompts/PromptFileLoader.js +74 -0
  154. package/dist/prompts/PromptFileLoader.js.map +1 -0
  155. package/dist/prompts/index.d.ts +6 -0
  156. package/dist/prompts/index.d.ts.map +1 -0
  157. package/dist/prompts/index.js +11 -0
  158. package/dist/prompts/index.js.map +1 -0
  159. package/dist/state/IterationTracker.d.ts +64 -0
  160. package/dist/state/IterationTracker.d.ts.map +1 -0
  161. package/dist/state/IterationTracker.js +136 -0
  162. package/dist/state/IterationTracker.js.map +1 -0
  163. package/dist/state/StateManager.d.ts +79 -0
  164. package/dist/state/StateManager.d.ts.map +1 -0
  165. package/dist/state/StateManager.js +348 -0
  166. package/dist/state/StateManager.js.map +1 -0
  167. package/dist/state/StateValidator.d.ts +24 -0
  168. package/dist/state/StateValidator.d.ts.map +1 -0
  169. package/dist/state/StateValidator.js +147 -0
  170. package/dist/state/StateValidator.js.map +1 -0
  171. package/dist/state/index.d.ts +7 -0
  172. package/dist/state/index.d.ts.map +1 -0
  173. package/dist/state/index.js +13 -0
  174. package/dist/state/index.js.map +1 -0
  175. package/dist/types/analysis.d.ts +76 -0
  176. package/dist/types/analysis.d.ts.map +1 -0
  177. package/dist/types/analysis.js +6 -0
  178. package/dist/types/analysis.js.map +1 -0
  179. package/dist/types/config.d.ts +132 -0
  180. package/dist/types/config.d.ts.map +1 -0
  181. package/dist/types/config.js +7 -0
  182. package/dist/types/config.js.map +1 -0
  183. package/dist/types/discovery.d.ts +277 -0
  184. package/dist/types/discovery.d.ts.map +1 -0
  185. package/dist/types/discovery.js +7 -0
  186. package/dist/types/discovery.js.map +1 -0
  187. package/dist/types/driver.d.ts +148 -0
  188. package/dist/types/driver.d.ts.map +1 -0
  189. package/dist/types/driver.js +7 -0
  190. package/dist/types/driver.js.map +1 -0
  191. package/dist/types/index.d.ts +8 -0
  192. package/dist/types/index.d.ts.map +1 -0
  193. package/dist/types/index.js +24 -0
  194. package/dist/types/index.js.map +1 -0
  195. package/dist/types/prompts.d.ts +158 -0
  196. package/dist/types/prompts.d.ts.map +1 -0
  197. package/dist/types/prompts.js +6 -0
  198. package/dist/types/prompts.js.map +1 -0
  199. package/dist/types/state.d.ts +278 -0
  200. package/dist/types/state.d.ts.map +1 -0
  201. package/dist/types/state.js +7 -0
  202. package/dist/types/state.js.map +1 -0
  203. package/dist/utils/config-loader.d.ts +29 -0
  204. package/dist/utils/config-loader.d.ts.map +1 -0
  205. package/dist/utils/config-loader.js +163 -0
  206. package/dist/utils/config-loader.js.map +1 -0
  207. package/dist/utils/index.d.ts +5 -0
  208. package/dist/utils/index.d.ts.map +1 -0
  209. package/dist/utils/index.js +9 -0
  210. package/dist/utils/index.js.map +1 -0
  211. package/package.json +24 -3
  212. package/dist/ai/simple-ai-client.d.ts +0 -70
  213. package/dist/ai/simple-ai-client.d.ts.map +0 -1
  214. package/dist/ai/simple-ai-client.js +0 -181
  215. package/dist/ai/simple-ai-client.js.map +0 -1
  216. package/dist/analyzers/analyzer.d.ts +0 -23
  217. package/dist/analyzers/analyzer.d.ts.map +0 -1
  218. package/dist/analyzers/analyzer.js +0 -127
  219. package/dist/analyzers/analyzer.js.map +0 -1
  220. package/dist/cli-old/cli.d.ts +0 -3
  221. package/dist/cli-old/cli.d.ts.map +0 -1
  222. package/dist/cli-old/cli.js +0 -388
  223. package/dist/cli-old/cli.js.map +0 -1
  224. package/dist/commands/review.d.ts +0 -11
  225. package/dist/commands/review.d.ts.map +0 -1
  226. package/dist/commands/review.js +0 -82
  227. package/dist/commands/review.js.map +0 -1
  228. package/dist/database/connection.d.ts +0 -40
  229. package/dist/database/connection.d.ts.map +0 -1
  230. package/dist/database/connection.js +0 -136
  231. package/dist/database/connection.js.map +0 -1
  232. package/dist/database/introspection.d.ts +0 -59
  233. package/dist/database/introspection.d.ts.map +0 -1
  234. package/dist/database/introspection.js +0 -124
  235. package/dist/database/introspection.js.map +0 -1
  236. package/dist/generators/markdown-generator.d.ts +0 -8
  237. package/dist/generators/markdown-generator.d.ts.map +0 -1
  238. package/dist/generators/markdown-generator.js +0 -106
  239. package/dist/generators/markdown-generator.js.map +0 -1
  240. package/dist/generators/sql-generator.d.ts +0 -20
  241. package/dist/generators/sql-generator.d.ts.map +0 -1
  242. package/dist/generators/sql-generator.js +0 -83
  243. package/dist/generators/sql-generator.js.map +0 -1
  244. package/dist/state/state-manager.d.ts +0 -95
  245. package/dist/state/state-manager.d.ts.map +0 -1
  246. package/dist/state/state-manager.js +0 -236
  247. package/dist/state/state-manager.js.map +0 -1
  248. package/dist/types/state-file.d.ts +0 -124
  249. package/dist/types/state-file.d.ts.map +0 -1
  250. package/dist/types/state-file.js +0 -79
  251. package/dist/types/state-file.js.map +0 -1
@@ -0,0 +1,726 @@
1
+ "use strict";
2
+ /**
3
+ * Discovery Engine
4
+ * Orchestrates relationship discovery with iterative refinement and backpropagation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DiscoveryEngine = void 0;
8
+ const PKDetector_js_1 = require("./PKDetector.js");
9
+ const FKDetector_js_1 = require("./FKDetector.js");
10
+ const LLMDiscoveryValidator_js_1 = require("./LLMDiscoveryValidator.js");
11
+ const LLMSanityChecker_js_1 = require("./LLMSanityChecker.js");
12
+ const ColumnStatsCache_js_1 = require("./ColumnStatsCache.js");
13
+ class DiscoveryEngine {
14
+ constructor(options) {
15
+ this.driver = options.driver;
16
+ this.config = options.config;
17
+ this.aiConfig = options.aiConfig;
18
+ this.schemas = options.schemas;
19
+ this.onProgress = options.onProgress || (() => { });
20
+ // Create stats cache and detectors
21
+ this.statsCache = new ColumnStatsCache_js_1.ColumnStatsCache();
22
+ this.pkDetector = new PKDetector_js_1.PKDetector(this.driver, this.config, this.statsCache);
23
+ this.fkDetector = new FKDetector_js_1.FKDetector(this.driver, this.config);
24
+ // Create LLM sanity checker (runs once after statistical detection)
25
+ this.sanityChecker = new LLMSanityChecker_js_1.LLMSanityChecker(this.aiConfig);
26
+ // Create LLM validator if enabled (runs per-table validation)
27
+ if (this.config.llmValidation?.enabled) {
28
+ this.llmValidator = new LLMDiscoveryValidator_js_1.LLMDiscoveryValidator(this.driver, this.config, this.aiConfig, this.statsCache, this.schemas);
29
+ }
30
+ }
31
+ /**
32
+ * Get the column statistics cache
33
+ */
34
+ getStatsCache() {
35
+ return this.statsCache;
36
+ }
37
+ /**
38
+ * Analyze if discovery should be triggered based on schema state
39
+ */
40
+ analyzeTrigger() {
41
+ let totalTables = 0;
42
+ let tablesWithPK = 0;
43
+ let totalFKs = 0;
44
+ for (const schema of this.schemas) {
45
+ totalTables += schema.tables.length;
46
+ for (const table of schema.tables) {
47
+ // Check if table has PK
48
+ const hasPK = table.columns.some(col => col.isPrimaryKey);
49
+ if (hasPK) {
50
+ tablesWithPK++;
51
+ }
52
+ // Count FKs
53
+ totalFKs += table.columns.filter(col => col.isForeignKey).length;
54
+ }
55
+ }
56
+ const tablesWithoutPK = totalTables - tablesWithPK;
57
+ // Heuristic: expect at least 0.3 FKs per table on average
58
+ const expectedMinFKs = Math.floor(totalTables * 0.3);
59
+ const fkDeficit = Math.max(0, expectedMinFKs - totalFKs);
60
+ const fkDeficitPercentage = expectedMinFKs > 0 ? fkDeficit / expectedMinFKs : 0;
61
+ // Determine if discovery should run
62
+ let shouldRun = false;
63
+ let reason = '';
64
+ if (tablesWithoutPK > 0 && this.config.triggers.runOnMissingPKs) {
65
+ shouldRun = true;
66
+ reason = `${tablesWithoutPK} table(s) missing primary keys`;
67
+ }
68
+ if (fkDeficitPercentage >= this.config.triggers.fkDeficitThreshold &&
69
+ this.config.triggers.runOnInsufficientFKs) {
70
+ shouldRun = true;
71
+ if (reason) {
72
+ reason += ` and FK deficit of ${(fkDeficitPercentage * 100).toFixed(1)}%`;
73
+ }
74
+ else {
75
+ reason = `FK deficit of ${(fkDeficitPercentage * 100).toFixed(1)}% (${totalFKs}/${expectedMinFKs})`;
76
+ }
77
+ }
78
+ return {
79
+ shouldRun,
80
+ reason: shouldRun ? reason : 'Discovery not needed - schema is well-defined',
81
+ details: {
82
+ totalTables,
83
+ tablesWithPK,
84
+ tablesWithoutPK,
85
+ totalFKs,
86
+ expectedMinFKs,
87
+ fkDeficit,
88
+ fkDeficitPercentage
89
+ }
90
+ };
91
+ }
92
+ /**
93
+ * Execute the discovery process
94
+ */
95
+ async discover(maxTokens, triggerAnalysis) {
96
+ const phase = this.initializePhase(maxTokens, triggerAnalysis);
97
+ this.onProgress('Starting relationship discovery', {
98
+ maxTokens,
99
+ trigger: triggerAnalysis.reason
100
+ });
101
+ let iteration = 1;
102
+ let tokensUsed = 0;
103
+ let guardrailsReached = false;
104
+ let guardrailReason;
105
+ while (iteration <= this.config.backpropagation.maxIterations) {
106
+ // Check guardrails before starting iteration
107
+ const guardrailCheck = this.checkGuardrails(tokensUsed, maxTokens, iteration);
108
+ if (!guardrailCheck.canContinue) {
109
+ guardrailsReached = true;
110
+ guardrailReason = guardrailCheck.reason;
111
+ this.onProgress('Guardrails reached', { reason: guardrailReason });
112
+ break;
113
+ }
114
+ this.onProgress(`Discovery iteration ${iteration}`, { tokensUsed, maxTokens });
115
+ // Execute iteration
116
+ const iterationResult = await this.executeIteration(iteration, phase, maxTokens - tokensUsed);
117
+ phase.iterations.push(iterationResult);
118
+ tokensUsed += iterationResult.tokensUsed;
119
+ phase.tokenBudget.used = tokensUsed;
120
+ phase.tokenBudget.remaining = maxTokens - tokensUsed;
121
+ // Update phase discoveries
122
+ this.mergeDiscoveries(phase, iterationResult);
123
+ this.onProgress(`Iteration ${iteration} complete`, {
124
+ newPKs: iterationResult.discoveries.newPKs.length,
125
+ newFKs: iterationResult.discoveries.newFKs.length,
126
+ tokensUsed: iterationResult.tokensUsed
127
+ });
128
+ // Check if we should continue
129
+ const shouldContinue = this.shouldContinueDiscovery(iterationResult, phase, iteration);
130
+ if (!shouldContinue) {
131
+ this.onProgress('Discovery converged - no significant changes detected');
132
+ break;
133
+ }
134
+ iteration++;
135
+ }
136
+ // Finalize phase
137
+ phase.completedAt = new Date().toISOString();
138
+ this.calculateSummary(phase);
139
+ this.onProgress('Discovery complete', {
140
+ iterations: phase.iterations.length,
141
+ tokensUsed,
142
+ pksDiscovered: phase.discovered.primaryKeys.length,
143
+ fksDiscovered: phase.discovered.foreignKeys.length
144
+ });
145
+ return {
146
+ phase,
147
+ guardrailsReached,
148
+ guardrailReason,
149
+ statsCache: this.statsCache
150
+ };
151
+ }
152
+ /**
153
+ * Initialize discovery phase
154
+ */
155
+ initializePhase(maxTokens, triggerAnalysis) {
156
+ const triggerReason = this.determineTriggerReason(triggerAnalysis);
157
+ return {
158
+ triggered: true,
159
+ triggerReason,
160
+ triggerDetails: {
161
+ tablesWithoutPK: triggerAnalysis.details.tablesWithoutPK,
162
+ expectedFKs: triggerAnalysis.details.expectedMinFKs,
163
+ actualFKs: triggerAnalysis.details.totalFKs,
164
+ fkDeficitPercentage: triggerAnalysis.details.fkDeficitPercentage
165
+ },
166
+ startedAt: new Date().toISOString(),
167
+ tokenBudget: {
168
+ allocated: maxTokens,
169
+ used: 0,
170
+ remaining: maxTokens
171
+ },
172
+ iterations: [],
173
+ discovered: {
174
+ primaryKeys: [],
175
+ foreignKeys: []
176
+ },
177
+ schemaEnhancements: {
178
+ pkeysAdded: 0,
179
+ fkeysAdded: 0,
180
+ overallConfidence: 0
181
+ },
182
+ feedbackFromAnalysis: [],
183
+ summary: {
184
+ totalTablesAnalyzed: 0,
185
+ tablesWithDiscoveredPKs: 0,
186
+ relationshipsDiscovered: 0,
187
+ averageConfidence: 0,
188
+ highConfidenceCount: 0,
189
+ mediumConfidenceCount: 0,
190
+ lowConfidenceCount: 0,
191
+ rejectedCount: 0
192
+ }
193
+ };
194
+ }
195
+ /**
196
+ * Determine trigger reason from analysis
197
+ */
198
+ determineTriggerReason(analysis) {
199
+ const hasMissingPKs = analysis.details.tablesWithoutPK > 0;
200
+ const hasInsufficientFKs = analysis.details.fkDeficitPercentage >= this.config.triggers.fkDeficitThreshold;
201
+ if (hasMissingPKs && hasInsufficientFKs) {
202
+ return 'both';
203
+ }
204
+ else if (hasMissingPKs) {
205
+ return 'missing_pks';
206
+ }
207
+ else if (hasInsufficientFKs) {
208
+ return 'insufficient_fks';
209
+ }
210
+ else {
211
+ return 'manual';
212
+ }
213
+ }
214
+ /**
215
+ * Execute a single discovery iteration
216
+ */
217
+ async executeIteration(iteration, phase, remainingTokens) {
218
+ const startedAt = new Date().toISOString();
219
+ const iterationResult = {
220
+ iteration,
221
+ phase: 'sampling',
222
+ startedAt,
223
+ completedAt: '',
224
+ tokensUsed: 0,
225
+ discoveries: {
226
+ newPKs: [],
227
+ newFKs: [],
228
+ validated: [],
229
+ rejected: [],
230
+ confidenceChanges: []
231
+ },
232
+ backpropTriggered: false
233
+ };
234
+ // Phase 1: PK Detection
235
+ iterationResult.phase = 'pk_detection';
236
+ let newPKs = await this.detectPrimaryKeys(iteration, phase);
237
+ iterationResult.discoveries.newPKs = newPKs;
238
+ this.onProgress(`Detected ${newPKs.length} PK candidates`, {
239
+ iteration,
240
+ candidates: newPKs.map(pk => `${pk.schemaName}.${pk.tableName}.${pk.columnNames.join('+')}`).slice(0, 5)
241
+ });
242
+ // Phase 2: FK Detection
243
+ iterationResult.phase = 'fk_detection';
244
+ const allPKs = [...phase.discovered.primaryKeys, ...newPKs];
245
+ let newFKs = await this.detectForeignKeys(iteration, phase, allPKs);
246
+ this.onProgress(`Detected ${newFKs.length} FK candidates (pre-sanity)`, {
247
+ iteration,
248
+ candidates: newFKs.map(fk => `${fk.schemaName}.${fk.sourceTable}.${fk.sourceColumn} -> ${fk.targetSchema}.${fk.targetTable}.${fk.targetColumn}`).slice(0, 5)
249
+ });
250
+ // Phase 2.5: LLM Sanity Check (FIRST TIME ONLY - reject obviously wrong candidates)
251
+ if (this.sanityChecker && iteration === 1 && (newPKs.length > 0 || newFKs.length > 0)) {
252
+ iterationResult.phase = 'sanity_check';
253
+ this.onProgress('Running LLM sanity check for obvious errors', {
254
+ pkCandidates: newPKs.length,
255
+ fkCandidates: newFKs.length
256
+ });
257
+ const sanityResult = await this.sanityChecker.reviewCandidates(newPKs, newFKs);
258
+ iterationResult.tokensUsed += sanityResult.tokensUsed;
259
+ // Remove invalid PKs
260
+ if (sanityResult.invalidPKs.length > 0) {
261
+ console.log(`[DiscoveryEngine] Sanity check rejecting ${sanityResult.invalidPKs.length} PKs:`);
262
+ for (const invalid of sanityResult.invalidPKs) {
263
+ console.log(` - ${invalid.schema}.${invalid.table}.${invalid.column}: ${invalid.reason}`);
264
+ }
265
+ newPKs = newPKs.filter(pk => {
266
+ return !sanityResult.invalidPKs.some(invalid => invalid.schema === pk.schemaName &&
267
+ invalid.table === pk.tableName &&
268
+ pk.columnNames.includes(invalid.column));
269
+ });
270
+ }
271
+ // Remove invalid FKs
272
+ if (sanityResult.invalidFKs.length > 0) {
273
+ console.log(`[DiscoveryEngine] Sanity check rejecting ${sanityResult.invalidFKs.length} FKs:`);
274
+ for (const invalid of sanityResult.invalidFKs) {
275
+ console.log(` - ${invalid.schema}.${invalid.table}.${invalid.column}: ${invalid.reason}`);
276
+ }
277
+ newFKs = newFKs.filter(fk => {
278
+ return !sanityResult.invalidFKs.some(invalid => invalid.schema === fk.schemaName &&
279
+ invalid.table === fk.sourceTable &&
280
+ invalid.column === fk.sourceColumn);
281
+ });
282
+ }
283
+ // Log suggestions
284
+ if (sanityResult.suggestions.length > 0) {
285
+ console.log(`[DiscoveryEngine] Sanity check suggestions:`);
286
+ for (const suggestion of sanityResult.suggestions) {
287
+ console.log(` - ${suggestion}`);
288
+ }
289
+ }
290
+ this.onProgress(`Sanity check complete`, {
291
+ invalidPKs: sanityResult.invalidPKs.length,
292
+ invalidFKs: sanityResult.invalidFKs.length,
293
+ finalPKs: newPKs.length,
294
+ finalFKs: newFKs.length,
295
+ tokensUsed: sanityResult.tokensUsed
296
+ });
297
+ // Update discoveries
298
+ iterationResult.discoveries.newPKs = newPKs;
299
+ iterationResult.discoveries.newFKs = newFKs;
300
+ }
301
+ // Phase 3: LLM Validation (if enabled and we have candidates or need validation)
302
+ if (this.llmValidator && (newPKs.length > 0 || newFKs.length > 0)) {
303
+ iterationResult.phase = 'llm_validation';
304
+ this.onProgress('Starting LLM validation', {
305
+ pkCandidates: newPKs.length,
306
+ fkCandidates: newFKs.length,
307
+ tokensRemaining: remainingTokens - iterationResult.tokensUsed
308
+ });
309
+ const validationResults = await this.performLLMValidation(newPKs, newFKs, allPKs, iterationResult, remainingTokens - iterationResult.tokensUsed);
310
+ // Update candidates based on LLM feedback
311
+ newPKs = validationResults.updatedPKs;
312
+ newFKs = validationResults.updatedFKs;
313
+ iterationResult.tokensUsed += validationResults.tokensUsed;
314
+ this.onProgress(`LLM validation complete`, {
315
+ tokensUsed: validationResults.tokensUsed,
316
+ validated: validationResults.validated.length,
317
+ rejected: validationResults.rejected.length,
318
+ finalPKs: newPKs.length,
319
+ finalFKs: newFKs.length
320
+ });
321
+ iterationResult.discoveries.validated = validationResults.validated;
322
+ iterationResult.discoveries.rejected = validationResults.rejected;
323
+ iterationResult.discoveries.newPKs = newPKs;
324
+ iterationResult.discoveries.newFKs = newFKs;
325
+ }
326
+ // Phase 4: Backpropagation check
327
+ iterationResult.phase = 'backprop';
328
+ const backpropCheck = this.shouldTriggerBackprop(iterationResult, phase);
329
+ iterationResult.backpropTriggered = backpropCheck.shouldTrigger;
330
+ iterationResult.backpropReason = backpropCheck.reason;
331
+ if (backpropCheck.shouldTrigger && this.config.backpropagation.enabled) {
332
+ this.onProgress('Backpropagation triggered', { reason: backpropCheck.reason });
333
+ // Re-analyze affected tables with new relationship context
334
+ const confidenceChanges = await this.performBackpropagation(phase, newPKs, newFKs);
335
+ iterationResult.discoveries.confidenceChanges = confidenceChanges;
336
+ }
337
+ iterationResult.completedAt = new Date().toISOString();
338
+ return iterationResult;
339
+ }
340
+ /**
341
+ * Detect primary keys in all tables without PKs
342
+ */
343
+ async detectPrimaryKeys(iteration, phase) {
344
+ const allPKs = [];
345
+ const tablesAnalyzed = new Set();
346
+ for (const schema of this.schemas) {
347
+ for (const table of schema.tables) {
348
+ const tableKey = `${schema.name}.${table.name}`;
349
+ // Skip if already has PK or already analyzed
350
+ const hasExistingPK = table.columns.some(col => col.isPrimaryKey);
351
+ const hasDiscoveredPK = phase.discovered.primaryKeys.some(pk => pk.schemaName === schema.name && pk.tableName === table.name);
352
+ if (hasExistingPK || hasDiscoveredPK || tablesAnalyzed.has(tableKey)) {
353
+ continue;
354
+ }
355
+ // Detect PK candidates for this table
356
+ const candidates = await this.pkDetector.detectPKCandidates(schema.name, table, iteration);
357
+ allPKs.push(...candidates);
358
+ tablesAnalyzed.add(tableKey);
359
+ }
360
+ }
361
+ return allPKs;
362
+ }
363
+ /**
364
+ * Detect foreign keys across all schemas
365
+ */
366
+ async detectForeignKeys(iteration, phase, discoveredPKs) {
367
+ const allFKs = [];
368
+ for (const schema of this.schemas) {
369
+ for (const table of schema.tables) {
370
+ // Detect FK candidates for this table
371
+ const candidates = await this.fkDetector.detectFKCandidates(this.schemas, schema.name, table, discoveredPKs, iteration);
372
+ // Filter out duplicates (already discovered FKs)
373
+ const newCandidates = candidates.filter(newFK => !phase.discovered.foreignKeys.some(existingFK => existingFK.schemaName === newFK.schemaName &&
374
+ existingFK.sourceTable === newFK.sourceTable &&
375
+ existingFK.sourceColumn === newFK.sourceColumn &&
376
+ existingFK.targetSchema === newFK.targetSchema &&
377
+ existingFK.targetTable === newFK.targetTable &&
378
+ existingFK.targetColumn === newFK.targetColumn));
379
+ allFKs.push(...newCandidates);
380
+ }
381
+ }
382
+ return allFKs;
383
+ }
384
+ /**
385
+ * Perform LLM validation on PK/FK candidates
386
+ */
387
+ async performLLMValidation(pkCandidates, fkCandidates, allPKs, iteration, remainingTokens) {
388
+ if (!this.llmValidator) {
389
+ return {
390
+ updatedPKs: pkCandidates,
391
+ updatedFKs: fkCandidates,
392
+ validated: [],
393
+ rejected: [],
394
+ tokensUsed: 0
395
+ };
396
+ }
397
+ const updatedPKs = [...pkCandidates];
398
+ const updatedFKs = [...fkCandidates];
399
+ const validated = [];
400
+ const rejected = [];
401
+ let tokensUsed = 0;
402
+ // Group candidates by table for efficient validation
403
+ const tableMap = new Map();
404
+ for (const pk of pkCandidates) {
405
+ const key = `${pk.schemaName}.${pk.tableName}`;
406
+ if (!tableMap.has(key)) {
407
+ tableMap.set(key, { pks: [], fks: [] });
408
+ }
409
+ tableMap.get(key).pks.push(pk);
410
+ }
411
+ for (const fk of fkCandidates) {
412
+ const key = `${fk.schemaName}.${fk.sourceTable}`;
413
+ if (!tableMap.has(key)) {
414
+ tableMap.set(key, { pks: [], fks: [] });
415
+ }
416
+ tableMap.get(key).fks.push(fk);
417
+ }
418
+ // Validate each table's candidates
419
+ for (const [tableKey, candidates] of tableMap.entries()) {
420
+ if (tokensUsed >= remainingTokens) {
421
+ this.onProgress('Token budget exhausted during LLM validation');
422
+ break;
423
+ }
424
+ const [schemaName, tableName] = tableKey.split('.');
425
+ this.onProgress(`Validating ${tableKey}`, {
426
+ pks: candidates.pks.length,
427
+ fks: candidates.fks.length
428
+ });
429
+ try {
430
+ const result = await this.llmValidator.validateTableRelationships(schemaName, tableName, candidates.pks, candidates.fks);
431
+ tokensUsed += result.tokensUsed;
432
+ if (!result.validated) {
433
+ this.onProgress(`LLM validation failed for ${tableKey}: ${result.reasoning}`);
434
+ continue;
435
+ }
436
+ // Process LLM recommendations
437
+ for (const rec of result.recommendations) {
438
+ const recId = `${rec.target}:${rec.schemaName}.${rec.tableName}.${rec.columnName}`;
439
+ if (rec.type === 'confirm') {
440
+ validated.push(recId);
441
+ // Mark as validated and potentially boost confidence
442
+ if (rec.target === 'pk') {
443
+ const pk = updatedPKs.find(p => p.schemaName === rec.schemaName &&
444
+ p.tableName === rec.tableName &&
445
+ p.columnNames.includes(rec.columnName));
446
+ if (pk) {
447
+ pk.validatedByLLM = true;
448
+ pk.confidence = Math.min(pk.confidence + 15, 100); // Boost confidence
449
+ }
450
+ }
451
+ else if (rec.target === 'fk') {
452
+ const fk = updatedFKs.find(f => f.schemaName === rec.schemaName &&
453
+ f.sourceTable === rec.tableName &&
454
+ f.sourceColumn === rec.columnName);
455
+ if (fk) {
456
+ fk.validatedByLLM = true;
457
+ fk.confidence = Math.min(fk.confidence + 20, 100); // Bigger boost for FKs
458
+ }
459
+ }
460
+ }
461
+ else if (rec.type === 'reject') {
462
+ rejected.push(recId);
463
+ // Remove or downgrade confidence
464
+ if (rec.target === 'pk') {
465
+ const pkIndex = updatedPKs.findIndex(p => p.schemaName === rec.schemaName &&
466
+ p.tableName === rec.tableName &&
467
+ p.columnNames.includes(rec.columnName));
468
+ if (pkIndex >= 0) {
469
+ updatedPKs[pkIndex].status = 'rejected';
470
+ updatedPKs.splice(pkIndex, 1); // Remove rejected
471
+ }
472
+ }
473
+ else if (rec.target === 'fk') {
474
+ const fkIndex = updatedFKs.findIndex(f => f.schemaName === rec.schemaName &&
475
+ f.sourceTable === rec.tableName &&
476
+ f.sourceColumn === rec.columnName);
477
+ if (fkIndex >= 0) {
478
+ updatedFKs[fkIndex].status = 'rejected';
479
+ updatedFKs.splice(fkIndex, 1); // Remove rejected
480
+ }
481
+ }
482
+ }
483
+ }
484
+ this.onProgress(`Validated ${tableKey}`, {
485
+ reasoning: result.reasoning.substring(0, 100) + '...'
486
+ });
487
+ }
488
+ catch (error) {
489
+ this.onProgress(`Error validating ${tableKey}: ${error.message}`);
490
+ }
491
+ }
492
+ return {
493
+ updatedPKs,
494
+ updatedFKs,
495
+ validated,
496
+ rejected,
497
+ tokensUsed
498
+ };
499
+ }
500
+ /**
501
+ * Check if backpropagation should be triggered
502
+ */
503
+ shouldTriggerBackprop(iteration, phase) {
504
+ // Don't trigger on first iteration
505
+ if (iteration.iteration === 1) {
506
+ return { shouldTrigger: false };
507
+ }
508
+ // Check if significant new discoveries were made
509
+ const significantPKs = iteration.discoveries.newPKs.filter(pk => pk.confidence >= 80).length;
510
+ const significantFKs = iteration.discoveries.newFKs.filter(fk => fk.confidence >= 80).length;
511
+ if (significantPKs > 0 || significantFKs > 0) {
512
+ return {
513
+ shouldTrigger: true,
514
+ reason: `Discovered ${significantPKs} high-confidence PKs and ${significantFKs} high-confidence FKs`
515
+ };
516
+ }
517
+ return { shouldTrigger: false };
518
+ }
519
+ /**
520
+ * Perform backpropagation to update confidence scores
521
+ */
522
+ async performBackpropagation(phase, newPKs, newFKs) {
523
+ const changes = [];
524
+ // Update FK confidence if their target is now a confirmed PK
525
+ for (const fk of phase.discovered.foreignKeys) {
526
+ const targetIsNowPK = newPKs.some(pk => pk.schemaName === fk.targetSchema &&
527
+ pk.tableName === fk.targetTable &&
528
+ pk.columnNames.includes(fk.targetColumn) &&
529
+ pk.confidence >= 80);
530
+ if (targetIsNowPK && fk.confidence < 90) {
531
+ const oldConfidence = fk.confidence;
532
+ // Boost FK confidence if pointing to a confirmed PK
533
+ fk.confidence = Math.min(fk.confidence + 15, 95);
534
+ changes.push({
535
+ id: `${fk.schemaName}.${fk.sourceTable}.${fk.sourceColumn}`,
536
+ oldConfidence,
537
+ newConfidence: fk.confidence,
538
+ reason: 'Target column confirmed as PK'
539
+ });
540
+ }
541
+ }
542
+ // Update PK confidence if columns have FKs pointing to them
543
+ for (const pk of phase.discovered.primaryKeys) {
544
+ const incomingFKs = newFKs.filter(fk => fk.targetSchema === pk.schemaName &&
545
+ fk.targetTable === pk.tableName &&
546
+ pk.columnNames.includes(fk.targetColumn)).length;
547
+ if (incomingFKs > 0 && pk.confidence < 95) {
548
+ const oldConfidence = pk.confidence;
549
+ // Boost PK confidence if other tables reference it
550
+ pk.confidence = Math.min(pk.confidence + (incomingFKs * 5), 95);
551
+ changes.push({
552
+ id: `${pk.schemaName}.${pk.tableName}.${pk.columnNames.join('+')}`,
553
+ oldConfidence,
554
+ newConfidence: pk.confidence,
555
+ reason: `${incomingFKs} FK(s) now reference this column`
556
+ });
557
+ }
558
+ }
559
+ return changes;
560
+ }
561
+ /**
562
+ * Merge iteration discoveries into phase
563
+ */
564
+ mergeDiscoveries(phase, iteration) {
565
+ // Add new PKs (no duplicates)
566
+ for (const newPK of iteration.discoveries.newPKs) {
567
+ const exists = phase.discovered.primaryKeys.some(pk => pk.schemaName === newPK.schemaName &&
568
+ pk.tableName === newPK.tableName &&
569
+ pk.columnNames.join(',') === newPK.columnNames.join(','));
570
+ if (!exists) {
571
+ phase.discovered.primaryKeys.push(newPK);
572
+ }
573
+ }
574
+ // Add new FKs (no duplicates)
575
+ for (const newFK of iteration.discoveries.newFKs) {
576
+ const exists = phase.discovered.foreignKeys.some(fk => fk.schemaName === newFK.schemaName &&
577
+ fk.sourceTable === newFK.sourceTable &&
578
+ fk.sourceColumn === newFK.sourceColumn &&
579
+ fk.targetSchema === newFK.targetSchema &&
580
+ fk.targetTable === newFK.targetTable &&
581
+ fk.targetColumn === newFK.targetColumn);
582
+ if (!exists) {
583
+ phase.discovered.foreignKeys.push(newFK);
584
+ }
585
+ }
586
+ }
587
+ /**
588
+ * Check if discovery should continue
589
+ */
590
+ shouldContinueDiscovery(iteration, phase, iterationNumber) {
591
+ // Always run at least 2 iterations
592
+ if (iterationNumber < 2) {
593
+ return true;
594
+ }
595
+ // Stop if no new discoveries
596
+ if (iteration.discoveries.newPKs.length === 0 &&
597
+ iteration.discoveries.newFKs.length === 0 &&
598
+ iteration.discoveries.confidenceChanges.length === 0) {
599
+ return false;
600
+ }
601
+ // Stop if only low-confidence discoveries
602
+ const hasHighConfidenceDiscoveries = iteration.discoveries.newPKs.some(pk => pk.confidence >= 70) ||
603
+ iteration.discoveries.newFKs.some(fk => fk.confidence >= 60);
604
+ if (!hasHighConfidenceDiscoveries) {
605
+ return false;
606
+ }
607
+ return true;
608
+ }
609
+ /**
610
+ * Check guardrails
611
+ */
612
+ checkGuardrails(tokensUsed, maxTokens, iteration) {
613
+ // Check token budget
614
+ if (tokensUsed >= maxTokens) {
615
+ return {
616
+ canContinue: false,
617
+ reason: `Token budget exhausted (${tokensUsed}/${maxTokens})`
618
+ };
619
+ }
620
+ // Check max iterations
621
+ if (iteration > this.config.backpropagation.maxIterations) {
622
+ return {
623
+ canContinue: false,
624
+ reason: `Max iterations reached (${this.config.backpropagation.maxIterations})`
625
+ };
626
+ }
627
+ // Check if we're within warning threshold (80%)
628
+ const tokenPercentage = tokensUsed / maxTokens;
629
+ if (tokenPercentage > 0.8) {
630
+ this.onProgress('Warning: Token budget 80% consumed', {
631
+ used: tokensUsed,
632
+ total: maxTokens
633
+ });
634
+ }
635
+ return { canContinue: true };
636
+ }
637
+ /**
638
+ * Calculate final summary statistics
639
+ */
640
+ calculateSummary(phase) {
641
+ const allPKs = phase.discovered.primaryKeys;
642
+ const allFKs = phase.discovered.foreignKeys;
643
+ const allDiscoveries = [...allPKs, ...allFKs];
644
+ // Count by confidence level
645
+ const highConfidence = allDiscoveries.filter(d => d.confidence >= 80).length;
646
+ const mediumConfidence = allDiscoveries.filter(d => d.confidence >= 50 && d.confidence < 80).length;
647
+ const lowConfidence = allDiscoveries.filter(d => d.confidence < 50).length;
648
+ const rejected = allDiscoveries.filter(d => d.status === 'rejected').length;
649
+ // Calculate average confidence
650
+ const totalConfidence = allDiscoveries.reduce((sum, d) => sum + d.confidence, 0);
651
+ const averageConfidence = allDiscoveries.length > 0
652
+ ? totalConfidence / allDiscoveries.length
653
+ : 0;
654
+ // Count unique tables with discovered PKs
655
+ const tablesWithPKs = new Set(allPKs.map(pk => `${pk.schemaName}.${pk.tableName}`)).size;
656
+ // Count total tables analyzed
657
+ const tablesAnalyzed = new Set();
658
+ for (const schema of this.schemas) {
659
+ for (const table of schema.tables) {
660
+ tablesAnalyzed.add(`${schema.name}.${table.name}`);
661
+ }
662
+ }
663
+ phase.summary = {
664
+ totalTablesAnalyzed: tablesAnalyzed.size,
665
+ tablesWithDiscoveredPKs: tablesWithPKs,
666
+ relationshipsDiscovered: allFKs.length,
667
+ averageConfidence: Math.round(averageConfidence),
668
+ highConfidenceCount: highConfidence,
669
+ mediumConfidenceCount: mediumConfidence,
670
+ lowConfidenceCount: lowConfidence,
671
+ rejectedCount: rejected
672
+ };
673
+ phase.schemaEnhancements = {
674
+ pkeysAdded: allPKs.filter(pk => pk.status === 'confirmed').length,
675
+ fkeysAdded: allFKs.filter(fk => fk.status === 'confirmed').length,
676
+ overallConfidence: Math.round(averageConfidence)
677
+ };
678
+ }
679
+ /**
680
+ * Apply discovered relationships to state
681
+ */
682
+ applyDiscoveriesToState(state, phase) {
683
+ // Apply high-confidence PKs
684
+ for (const pk of phase.discovered.primaryKeys) {
685
+ if (pk.confidence >= this.config.confidence.primaryKeyMinimum * 100) {
686
+ const schema = state.schemas.find(s => s.name === pk.schemaName);
687
+ if (!schema)
688
+ continue;
689
+ const table = schema.tables.find(t => t.name === pk.tableName);
690
+ if (!table)
691
+ continue;
692
+ for (const columnName of pk.columnNames) {
693
+ const column = table.columns.find(c => c.name === columnName);
694
+ if (column) {
695
+ column.isPrimaryKey = true;
696
+ }
697
+ }
698
+ }
699
+ }
700
+ // Apply high-confidence FKs
701
+ for (const fk of phase.discovered.foreignKeys) {
702
+ if (fk.confidence >= this.config.confidence.foreignKeyMinimum * 100) {
703
+ const schema = state.schemas.find(s => s.name === fk.schemaName);
704
+ if (!schema)
705
+ continue;
706
+ const table = schema.tables.find(t => t.name === fk.sourceTable);
707
+ if (!table)
708
+ continue;
709
+ const column = table.columns.find(c => c.name === fk.sourceColumn);
710
+ if (column) {
711
+ column.isForeignKey = true;
712
+ column.foreignKeyReferences = {
713
+ schema: fk.targetSchema,
714
+ table: fk.targetTable,
715
+ column: fk.targetColumn,
716
+ referencedColumn: fk.targetColumn
717
+ };
718
+ }
719
+ }
720
+ }
721
+ // Store discovery phase in state (new phases structure)
722
+ state.phases.keyDetection = phase;
723
+ }
724
+ }
725
+ exports.DiscoveryEngine = DiscoveryEngine;
726
+ //# sourceMappingURL=DiscoveryEngine.js.map