dbagent-cli 0.9.1__tar.gz → 0.9.3__tar.gz

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 (84) hide show
  1. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/PKG-INFO +1 -1
  2. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/conversation.py +5 -1
  4. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/generator.py +45 -25
  5. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/pipeline.py +40 -5
  6. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/connectors/base.py +32 -4
  7. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/PKG-INFO +1 -1
  8. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/pyproject.toml +1 -1
  9. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/README.md +0 -0
  10. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/confidence.py +0 -0
  11. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/doctor.py +0 -0
  12. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/error_classifier.py +0 -0
  13. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/recommender.py +0 -0
  14. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/agent/validator.py +0 -0
  15. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/cli.py +0 -0
  16. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/config.py +0 -0
  17. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/connectors/factory.py +0 -0
  18. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/connectors/mongo.py +0 -0
  19. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/connectors/relational.py +0 -0
  20. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/__init__.py +0 -0
  21. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/cache.py +0 -0
  22. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/changeset.py +0 -0
  23. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/cost.py +0 -0
  24. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/recovery.py +0 -0
  25. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/sandbox.py +0 -0
  26. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/execution/streaming.py +0 -0
  27. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/anthropic_provider.py +0 -0
  28. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/auto_setup.py +0 -0
  29. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/base.py +0 -0
  30. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/deepseek_provider.py +0 -0
  31. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/factory.py +0 -0
  32. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/gemini_provider.py +0 -0
  33. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/groq_provider.py +0 -0
  34. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/mistral_provider.py +0 -0
  35. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/mock_provider.py +0 -0
  36. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/ollama_provider.py +0 -0
  37. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/openai_provider.py +0 -0
  38. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/llm/openrouter_provider.py +0 -0
  39. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/cache.py +0 -0
  40. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/formatter.py +0 -0
  41. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/graph.py +0 -0
  42. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/ground_truth.py +0 -0
  43. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/models.py +0 -0
  44. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/retriever.py +0 -0
  45. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/schema/selector.py +0 -0
  46. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/__init__.py +0 -0
  47. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/audit.py +0 -0
  48. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/environment.py +0 -0
  49. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/masking.py +0 -0
  50. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/modes.py +0 -0
  51. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/security/rbac.py +0 -0
  52. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/semantic/__init__.py +0 -0
  53. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/semantic/dictionary.py +0 -0
  54. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/semantic/knowledge.py +0 -0
  55. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/semantic/templates.py +0 -0
  56. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/sql/__init__.py +0 -0
  57. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/sql/optimizer.py +0 -0
  58. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/sql/validator.py +0 -0
  59. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/ui/chat_screen.py +0 -0
  60. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/ui/console.py +0 -0
  61. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/ui/grid_window.py +0 -0
  62. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/ui/viewer.py +0 -0
  63. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent/ui/visualizer.py +0 -0
  64. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/SOURCES.txt +0 -0
  65. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  66. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/entry_points.txt +0 -0
  67. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/requires.txt +0 -0
  68. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/dbagent_cli.egg-info/top_level.txt +0 -0
  69. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/setup.cfg +0 -0
  70. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_cache.py +0 -0
  71. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_cli.py +0 -0
  72. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_connectors.py +0 -0
  73. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_direct_sql.py +0 -0
  74. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_doctor_and_viz.py +0 -0
  75. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_generator.py +0 -0
  76. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_graph.py +0 -0
  77. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_knowledge.py +0 -0
  78. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_providers.py +0 -0
  79. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_recovery.py +0 -0
  80. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_safety.py +0 -0
  81. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_sandbox.py +0 -0
  82. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_schema.py +0 -0
  83. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_security.py +0 -0
  84. {dbagent_cli-0.9.1 → dbagent_cli-0.9.3}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -2,5 +2,5 @@
2
2
  DB-Agent: Universal Database Introspection and Script Generation AI Agent (CLI).
3
3
  """
4
4
 
5
- __version__ = "0.9.1"
5
+ __version__ = "0.9.3"
6
6
  __author__ = "Santhosh Gupta"
@@ -54,9 +54,13 @@ class ConversationContext:
54
54
  if len(self.turns) > self.max_history:
55
55
  self.turns.pop(0)
56
56
 
57
- if tables and not is_chat:
57
+ if tables:
58
58
  self.active_tables.update(t.lower() for t in tables)
59
59
 
60
+ def get_active_tables(self) -> List[str]:
61
+ """Returns currently active database table names discussed in this session."""
62
+ return list(self.active_tables)
63
+
60
64
  def get_last_turn(self) -> Optional[ConversationTurn]:
61
65
  if self.turns:
62
66
  return self.turns[-1]
@@ -106,6 +106,11 @@ Provide ready-to-run, dialect-accurate SQL JOIN queries answering the user's spe
106
106
 
107
107
  ### 4. 💡 Architectural Insights & Best Practices
108
108
  Mention indexing recommendations, cascade rules, or performance considerations for these join paths.
109
+
110
+ ### 🛑 Critical Operational Directives:
111
+ 1. NEVER ask passive questions like "Would you like me to inspect tables?" or "Should I run a query?". Always proactively inspect and deliver the full architectural blueprint immediately.
112
+ 2. NEVER give generic textbook definitions. Always ground every explanation in the real database schema provided above.
113
+ 3. Always include ALL 4 sections (Diagram, Theoretical Breakdown, SQL Queries, and Insights).
109
114
  """
110
115
 
111
116
 
@@ -142,37 +147,56 @@ class ScriptGenerator:
142
147
  prompt_lower = user_prompt.lower().strip()
143
148
  clean_prompt = prompt_lower.rstrip(".!?,:;~ ")
144
149
 
145
- # Chat / Conversational indicators (greetings, feedback, praise, acknowledgements)
146
- chat_triggers = [
147
- "hello", "hi", "hey", "howdy", "greetings", "good morning", "good evening", "good afternoon",
148
- "who are you", "what can you do", "what are you", "help me", "how to use",
149
- "what is this", "tell me about this database", "how does this work",
150
- "can you help", "thanks", "thank you", "thx", "bye", "goodbye", "see ya",
151
- "good", "great", "nice", "cool", "awesome", "perfect", "ok", "okay",
152
- "yes", "no", "sure", "got it", "understood", "nice work", "well done",
153
- "sounds good", "looks good", "alright", "super", "neat", "excellent",
154
- "amazing", "wonderful", "cool thanks", "great thanks", "thanks a lot",
155
- ]
156
- for trig in chat_triggers:
157
- if clean_prompt == trig or prompt_lower.startswith(f"{trig} ") or prompt_lower.startswith(f"{trig},") or prompt_lower.startswith(f"{trig}!"):
158
- return "chat"
159
-
160
- # Architecture / Relationship / Linkage / Analysis indicators
150
+ # 1. Architecture / Relationship / Linkage / Analysis indicators (Highest Priority)
161
151
  analysis_keywords = [
162
152
  "linkage architecture", "linkage", "architecture", "relationship between",
163
153
  "how are", "how is", "how were", "how they were related", "how were they related",
164
- "how they were mapped", "how to map", "mapped to", "er diagram", "diagram of",
154
+ "how they were mapped", "how to map", "where they mapped", "where is it mapped",
155
+ "how they were assigned", "how are they assigned", "how will be linked", "how is it linked",
156
+ "mapped to", "mapped with", "er diagram", "diagram of",
165
157
  "data model", "explain relationship", "explain schema", "schema analysis",
166
158
  "analyze the database", "analyze database", "table relationship",
167
159
  "foreign key relationship", "how are tables related", "which sites in",
168
160
  "how to know that which", "how to know which", "mapping architecture",
169
161
  "entity relationship", "theoritical information", "theoretical information",
162
+ "check with my previous context", "check with previous context", "previous context",
163
+ "complete architecture", "scope architecture", "tenant level to user level",
164
+ "tenant level", "user level",
165
+ "tables that were linked", "tables linked with", "linked with", "links of",
166
+ "related tables", "related tebales", "table mappings", "related tables mappings",
167
+ "related tebales mappings", "not correct", "not right", "check all the tables",
168
+ "mappings also", "find all mappings", "check links", "check mappings",
170
169
  ]
171
170
  for kw in analysis_keywords:
172
171
  if kw in prompt_lower:
173
172
  return "analysis"
174
173
 
175
- # DDL indicators
174
+ # Contextual clarification for analysis (e.g. "i mean ivis_scope", "i mean table_x")
175
+ if re.search(r'\bi mean\b', prompt_lower) and not re.search(r'\b(insert|update|delete|drop)\b', prompt_lower):
176
+ return "analysis"
177
+
178
+ # 2. Pure Greeting / Feedback / Help triggers (Only when not asking a technical question)
179
+ pure_chat_greetings = ["hello", "hi", "hey", "howdy", "greetings", "good morning", "good evening", "good afternoon"]
180
+ pure_chat_feedback = [
181
+ "thanks", "thank you", "thx", "bye", "goodbye", "see ya",
182
+ "good", "great", "nice", "cool", "awesome", "perfect", "ok", "okay",
183
+ "yes", "no", "sure", "got it", "understood", "nice work", "well done",
184
+ "sounds good", "looks good", "alright", "super", "neat", "excellent",
185
+ "amazing", "wonderful", "cool thanks", "great thanks", "thanks a lot",
186
+ ]
187
+ pure_chat_help = [
188
+ "who are you", "what can you do", "what are you", "help me", "how to use",
189
+ "what is this", "tell me about this database", "how does this work", "can you help",
190
+ ]
191
+
192
+ if clean_prompt in pure_chat_greetings or clean_prompt in pure_chat_feedback or clean_prompt in pure_chat_help:
193
+ return "chat"
194
+
195
+ for trig in pure_chat_greetings + pure_chat_help:
196
+ if prompt_lower.startswith(f"{trig} ") or prompt_lower.startswith(f"{trig},") or prompt_lower.startswith(f"{trig}!"):
197
+ return "chat"
198
+
199
+ # 3. DDL indicators
176
200
  ddl_keywords = [
177
201
  "create table", "alter table", "drop table", "add column",
178
202
  "remove column", "rename column", "rename table", "modify column",
@@ -182,7 +206,7 @@ class ScriptGenerator:
182
206
  if kw in prompt_lower:
183
207
  return "ddl"
184
208
 
185
- # Write indicators (word-boundary matching to avoid false positives like "show dataset")
209
+ # 4. Write indicators (word-boundary matching to avoid false positives like "show dataset")
186
210
  write_phrases = [
187
211
  "insert", "add record", "add row", "add new",
188
212
  "update", "delete", "drop record",
@@ -191,14 +215,10 @@ class ScriptGenerator:
191
215
  for kw in write_phrases:
192
216
  if kw in prompt_lower:
193
217
  return "write"
194
- # Single-word write verbs need word boundary to avoid false positives
195
- # e.g. "set" should not match "dataset", "change" should not match "show changes"
196
- import re as _re
197
218
  write_verbs = ["set", "change", "modify", "remove"]
198
219
  for verb in write_verbs:
199
- if _re.search(r'\b' + verb + r'\b', prompt_lower):
200
- # Exclude when preceded by 'show', 'list', 'display', 'view', 'get'
201
- read_context = _re.search(r'\b(?:show|list|display|view|get|count)\b.*\b' + verb, prompt_lower)
220
+ if re.search(r'\b' + verb + r'\b', prompt_lower):
221
+ read_context = re.search(r'\b(?:show|list|display|view|get|count)\b.*\b' + verb, prompt_lower)
202
222
  if not read_context:
203
223
  return "write"
204
224
 
@@ -157,17 +157,40 @@ class QueryPipeline:
157
157
  max_tables=8,
158
158
  )
159
159
 
160
- # 2. Targeted on-demand table introspection (~50ms instead of 5000ms for full scan)
160
+ # 2. Targeted on-demand table introspection with automatic junction expansion
161
161
  target_names = list(set(exact + fuzzy))
162
162
  if target_names:
163
+ try:
164
+ all_table_names = self.connector.get_table_names()
165
+ stems = set()
166
+ for t in target_names:
167
+ unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t.lower())
168
+ unprefixed = re.sub(r'(?:s|es)$', '', unprefixed)
169
+ if len(unprefixed) >= 3:
170
+ stems.add(unprefixed)
171
+
172
+ for t in all_table_names:
173
+ t_lower = t.lower()
174
+ if t in target_names:
175
+ continue
176
+ is_map = any(m in t_lower for m in ["_map", "mapping", "_rel", "_junction", "_link", "_access", "_config"])
177
+ matches_stem = any(s in t_lower for s in stems)
178
+ if (is_map and matches_stem) or sum(1 for s in stems if s in t_lower) >= 2:
179
+ if t not in target_names:
180
+ target_names.append(t)
181
+ if len(target_names) >= 12:
182
+ break
183
+ except Exception:
184
+ pass
185
+
163
186
  targeted = self.connector.inspect_schema(table_names=target_names, include_samples=True, max_samples=2)
164
187
  retriever = ContextRetriever(targeted)
165
- return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=8)
188
+ return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=12)
166
189
 
167
190
  # 3. Fallback: inspect targeted
168
- targeted = self.connector.inspect_targeted(user_prompt, max_tables=8, include_samples=True)
191
+ targeted = self.connector.inspect_targeted(user_prompt, max_tables=12, include_samples=True)
169
192
  retriever = ContextRetriever(targeted)
170
- return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=8)
193
+ return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=12)
171
194
 
172
195
  def run(self, user_prompt: str, force: bool = False, bypass_cache: bool = False) -> PipelineResult:
173
196
  """
@@ -214,6 +237,12 @@ class QueryPipeline:
214
237
  except Exception:
215
238
  exact, fuzzy = [], []
216
239
 
240
+ # Inherit active tables from conversation context if follow-up prompt doesn't name tables explicitly
241
+ if not exact and not fuzzy:
242
+ inherited = self.conversation.get_active_tables()
243
+ if inherited:
244
+ exact = inherited[:4]
245
+
217
246
  sub_schema, join_steps = self._get_fast_schema_context(user_prompt, exact, fuzzy)
218
247
  result.schema = sub_schema
219
248
  result.join_steps = join_steps
@@ -244,7 +273,13 @@ class QueryPipeline:
244
273
  result.was_executed = True
245
274
  result.query_type = "analysis"
246
275
  result.execution_time_ms = (time.time() - t_start) * 1000
247
- self.conversation.add_turn(user_prompt=user_prompt, sql="", assistant_response=result.message, is_chat=True)
276
+ self.conversation.add_turn(
277
+ user_prompt=user_prompt,
278
+ sql="",
279
+ assistant_response=result.message,
280
+ tables=exact + fuzzy,
281
+ is_chat=True,
282
+ )
248
283
  return result
249
284
  except Exception as e:
250
285
  result.error = f"Architecture analysis failed: {str(e)}"
@@ -44,14 +44,42 @@ class BaseConnector(ABC):
44
44
 
45
45
  def resolve_tables(self, user_prompt: str) -> Tuple[List[str], List[str]]:
46
46
  """
47
- Resolve which tables the user is referring to.
47
+ Resolve which tables the user is referring to with prefix stripping, pluralization handling, and fuzzy matching.
48
48
  Returns (exact_matches, fuzzy_matches).
49
- Default implementation uses get_table_names with simple substring matching.
50
49
  """
50
+ import re
51
51
  all_tables = self.get_table_names()
52
52
  prompt_lower = user_prompt.lower()
53
- exact = [t for t in all_tables if t.lower() in prompt_lower]
54
- return exact, []
53
+ exact = []
54
+ fuzzy = []
55
+
56
+ tokens = set(re.findall(r'\b[a-zA-Z0-9_]+\b', prompt_lower))
57
+
58
+ for t in all_tables:
59
+ t_lower = t.lower()
60
+ # 1. Exact full table name in prompt
61
+ if re.search(r'\b' + re.escape(t_lower) + r'\b', prompt_lower):
62
+ exact.append(t)
63
+ continue
64
+
65
+ # 2. Match without common prefixes (e.g. "ivis_scope" -> "scope", "scopes")
66
+ unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t_lower)
67
+ if unprefixed and len(unprefixed) >= 3:
68
+ if (unprefixed in tokens or
69
+ f"{unprefixed}s" in tokens or
70
+ f"{unprefixed}es" in tokens or
71
+ re.search(r'\b' + re.escape(unprefixed) + r'(?:s|es)?\b', prompt_lower)):
72
+ if t not in exact:
73
+ exact.append(t)
74
+ continue
75
+
76
+ # 3. Fuzzy partial match
77
+ for tok in tokens:
78
+ if len(tok) >= 4 and (tok in t_lower or t_lower in tok):
79
+ if t not in exact and t not in fuzzy:
80
+ fuzzy.append(t)
81
+
82
+ return exact, fuzzy
55
83
 
56
84
  def refresh_cache(self) -> None:
57
85
  """Invalidate any cached metadata. Override in subclasses."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbagent-cli"
7
- version = "0.9.1"
7
+ version = "0.9.3"
8
8
  description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes