devmemory-cli 0.1.0.dev0__tar.gz → 0.1.0.dev1__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 (185) hide show
  1. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/PKG-INFO +1 -1
  2. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/__about__.py +1 -1
  3. devmemory_cli-0.1.0.dev1/src/devmemory/adapters/genie.py +161 -0
  4. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/api/app.py +27 -0
  5. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/api/schemas.py +15 -0
  6. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/config.py +5 -0
  7. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/App.tsx +2 -0
  8. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/api/client.ts +18 -0
  9. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/api/types.ts +20 -0
  10. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/Sidebar.tsx +1 -0
  11. devmemory_cli-0.1.0.dev1/src/devmemory/web/frontend/src/routes/Ask.tsx +235 -0
  12. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/theme/components.css +90 -0
  13. devmemory_cli-0.1.0.dev0/src/devmemory/web/static/assets/index-CbV5njRH.js → devmemory_cli-0.1.0.dev1/src/devmemory/web/static/assets/index-B9obAJEW.js +20 -15
  14. devmemory_cli-0.1.0.dev0/src/devmemory/web/static/assets/index-DD-7ceZx.css → devmemory_cli-0.1.0.dev1/src/devmemory/web/static/assets/index-C81gevra.css +1 -1
  15. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/static/index.html +2 -2
  16. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_api.py +40 -0
  17. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/.gitignore +0 -0
  18. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/CHANGELOG.md +0 -0
  19. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/DEMO.md +0 -0
  20. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/LICENSE +0 -0
  21. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/README.md +0 -0
  22. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/ARCHITECTURE.md +0 -0
  23. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/CONFIGURATION.md +0 -0
  24. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/DATA_AND_API_SPEC.md +0 -0
  25. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/ENTIRE_INTEGRATION.md +0 -0
  26. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/ENTIRE_QUICKSTART.md +0 -0
  27. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/IMPLEMENTATION_STRATEGY.md +0 -0
  28. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/MCP.md +0 -0
  29. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/PROJECT_SPEC.md +0 -0
  30. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/docs/STATE_LOOP.md +0 -0
  31. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/examples/demo/seed.py +0 -0
  32. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/pyproject.toml +0 -0
  33. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/__init__.py +0 -0
  34. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/__main__.py +0 -0
  35. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/__init__.py +0 -0
  36. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/databricks.py +0 -0
  37. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/entire.py +0 -0
  38. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/git.py +0 -0
  39. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/graph.py +0 -0
  40. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/metrics.py +0 -0
  41. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/adapters/tests.py +0 -0
  42. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/analysis/__init__.py +0 -0
  43. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/analysis/base.py +0 -0
  44. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/analysis/chain.py +0 -0
  45. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/analysis/llm.py +0 -0
  46. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/analysis/rules.py +0 -0
  47. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/api/__init__.py +0 -0
  48. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/api/mappers.py +0 -0
  49. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/__init__.py +0 -0
  50. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/_errors.py +0 -0
  51. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/_render.py +0 -0
  52. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/analytics.py +0 -0
  53. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/analyze.py +0 -0
  54. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/app.py +0 -0
  55. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/checkpoint.py +0 -0
  56. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/compare.py +0 -0
  57. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/doctor.py +0 -0
  58. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/history.py +0 -0
  59. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/impact.py +0 -0
  60. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/init.py +0 -0
  61. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/mcp.py +0 -0
  62. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/memory.py +0 -0
  63. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/restore.py +0 -0
  64. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/search.py +0 -0
  65. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/serve.py +0 -0
  66. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/show.py +0 -0
  67. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/status.py +0 -0
  68. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/cli/task.py +0 -0
  69. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/domain/__init__.py +0 -0
  70. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/domain/enums.py +0 -0
  71. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/domain/errors.py +0 -0
  72. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/domain/models.py +0 -0
  73. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/domain/taskloop.py +0 -0
  74. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/environment.py +0 -0
  75. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/logging.py +0 -0
  76. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/mcp/__init__.py +0 -0
  77. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/mcp/server.py +0 -0
  78. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/paths.py +0 -0
  79. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/__init__.py +0 -0
  80. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/checkpoint.py +0 -0
  81. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/feature_detect.py +0 -0
  82. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/regression.py +0 -0
  83. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/runlog.py +0 -0
  84. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/pipeline/status_rules.py +0 -0
  85. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/py.typed +0 -0
  86. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/__init__.py +0 -0
  87. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/agent_context.py +0 -0
  88. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/analysis.py +0 -0
  89. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/analytics.py +0 -0
  90. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/brief.py +0 -0
  91. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/context.py +0 -0
  92. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/databricks_sync.py +0 -0
  93. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/features.py +0 -0
  94. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/impact.py +0 -0
  95. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/memory.py +0 -0
  96. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/projects.py +0 -0
  97. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/restore.py +0 -0
  98. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/taskloop/__init__.py +0 -0
  99. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/taskloop/collectors.py +0 -0
  100. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/taskloop/engine.py +0 -0
  101. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/taskloop/requirements.py +0 -0
  102. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/trace.py +0 -0
  103. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/services/versions.py +0 -0
  104. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/__init__.py +0 -0
  105. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/artifacts.py +0 -0
  106. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/db.py +0 -0
  107. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/graph_impacts.py +0 -0
  108. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/migrations/0001_init.sql +0 -0
  109. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/migrations/0002_versions.sql +0 -0
  110. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/migrations/0003_graph.sql +0 -0
  111. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/migrations/0004_taskloop.sql +0 -0
  112. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/migrations/0005_project_brief.sql +0 -0
  113. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/repositories.py +0 -0
  114. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/tasks.py +0 -0
  115. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/storage/versions.py +0 -0
  116. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/README.md +0 -0
  117. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/index.html +0 -0
  118. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/package-lock.json +0 -0
  119. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/package.json +0 -0
  120. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/Async.tsx +0 -0
  121. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/AttemptCard.tsx +0 -0
  122. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/CommandPalette.tsx +0 -0
  123. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/DiffView.tsx +0 -0
  124. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/ErrorBoundary.tsx +0 -0
  125. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/Icon.tsx +0 -0
  126. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/Logo.tsx +0 -0
  127. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/Sparkline.tsx +0 -0
  128. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/TopBar.tsx +0 -0
  129. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/bits.tsx +0 -0
  130. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/charts.tsx +0 -0
  131. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/components/primitives.tsx +0 -0
  132. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/lib/format.ts +0 -0
  133. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/lib/status.ts +0 -0
  134. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/lib/useDebounced.ts +0 -0
  135. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/main.tsx +0 -0
  136. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Compare.tsx +0 -0
  137. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/FeatureDetail.tsx +0 -0
  138. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Features.tsx +0 -0
  139. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Intelligence.tsx +0 -0
  140. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Memory.tsx +0 -0
  141. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/NotFound.tsx +0 -0
  142. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Overview.tsx +0 -0
  143. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/SafeToChange.tsx +0 -0
  144. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Search.tsx +0 -0
  145. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/TaskDetail.tsx +0 -0
  146. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Tasks.tsx +0 -0
  147. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/Timeline.tsx +0 -0
  148. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/routes/VersionDetail.tsx +0 -0
  149. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/theme/ThemeProvider.tsx +0 -0
  150. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/src/theme/theme.css +0 -0
  151. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/tsconfig.json +0 -0
  152. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/src/devmemory/web/frontend/vite.config.ts +0 -0
  153. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/__init__.py +0 -0
  154. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/conftest.py +0 -0
  155. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_agent_context.py +0 -0
  156. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_analysis.py +0 -0
  157. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_analytics.py +0 -0
  158. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_api_tasks.py +0 -0
  159. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_artifacts_restore.py +0 -0
  160. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_checkpoint_phase6.py +0 -0
  161. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_checkpoint_pipeline.py +0 -0
  162. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_cli.py +0 -0
  163. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_cli_checkpoint.py +0 -0
  164. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_cli_compare_search.py +0 -0
  165. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_cli_init_status.py +0 -0
  166. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_collectors.py +0 -0
  167. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_config.py +0 -0
  168. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_databricks.py +0 -0
  169. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_doctor.py +0 -0
  170. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_dotenv.py +0 -0
  171. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_e2e.py +0 -0
  172. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_entire_adapter.py +0 -0
  173. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_entire_checkpoints.py +0 -0
  174. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_entire_real.py +0 -0
  175. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_errors.py +0 -0
  176. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_git_adapter.py +0 -0
  177. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_graph_impact.py +0 -0
  178. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_init_service.py +0 -0
  179. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_logging.py +0 -0
  180. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_mcp.py +0 -0
  181. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_memory.py +0 -0
  182. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_migrations.py +0 -0
  183. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_paths.py +0 -0
  184. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_taskloop.py +0 -0
  185. {devmemory_cli-0.1.0.dev0 → devmemory_cli-0.1.0.dev1}/tests/test_versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devmemory-cli
3
- Version: 0.1.0.dev0
3
+ Version: 0.1.0.dev1
4
4
  Summary: Development-memory and version-intelligence for AI-assisted software development
5
5
  Project-URL: Documentation, https://github.com/KYadavPR/devmemory
6
6
  Project-URL: Source, https://github.com/KYadavPR/devmemory
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "0.1.0.dev0"
3
+ __version__ = "0.1.0.dev1"
@@ -0,0 +1,161 @@
1
+ """Databricks Genie - a natural-language chat layer over the published telemetry.
2
+
3
+ Genie answers questions in plain English against a *Genie space* that sits on
4
+ top of the ``devmemory.analytics`` Delta tables (see
5
+ :mod:`devmemory.adapters.databricks`). This adapter is a thin wrapper over the
6
+ Databricks SDK's Genie Conversation API: one call per turn, returning the
7
+ answer text, the SQL Genie ran, and a small preview of the rows.
8
+
9
+ Everything degrades gracefully (§21): with no credentials, no ``databricks``
10
+ extra, or no space id, :attr:`GenieAdapter.is_configured` is ``False`` and the
11
+ dashboard simply hides the chat.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ from pydantic import BaseModel
20
+
21
+ from devmemory.config import DevMemoryConfig, resolve_databricks_credentials
22
+ from devmemory.domain.errors import DatabricksError
23
+ from devmemory.logging import get_logger
24
+
25
+ if TYPE_CHECKING:
26
+ from databricks.sdk import WorkspaceClient
27
+
28
+ _log = get_logger(__name__)
29
+
30
+ _MAX_ROWS = 50
31
+
32
+
33
+ class GenieUnavailableError(DatabricksError):
34
+ """Genie is not configured, the SDK is missing, or the space is unreachable."""
35
+
36
+
37
+ class GenieAnswer(BaseModel):
38
+ conversation_id: str | None = None
39
+ message_id: str | None = None
40
+ question: str
41
+ text: str | None = None
42
+ sql: str | None = None
43
+ sql_description: str | None = None
44
+ columns: list[str] = []
45
+ rows: list[list[Any]] = []
46
+ row_count: int | None = None
47
+ truncated: bool = False
48
+ error: str | None = None
49
+
50
+
51
+ class GenieAdapter:
52
+ def __init__(self, config: DevMemoryConfig) -> None:
53
+ self._config = config
54
+ self._space_id = (
55
+ os.environ.get("DATABRICKS_GENIE_SPACE_ID") or config.databricks.genie_space_id or ""
56
+ ).strip()
57
+ self._client: WorkspaceClient | None = None
58
+
59
+ @property
60
+ def space_id(self) -> str:
61
+ return self._space_id
62
+
63
+ @property
64
+ def is_configured(self) -> bool:
65
+ return bool(self._space_id) and resolve_databricks_credentials() is not None
66
+
67
+ def unavailable_reason(self) -> str | None:
68
+ if not self._space_id:
69
+ return "no Genie space configured (set databricks.genie_space_id or DATABRICKS_GENIE_SPACE_ID)"
70
+ if resolve_databricks_credentials() is None:
71
+ return "Databricks credentials are not set (DATABRICKS_HOST / DATABRICKS_TOKEN)"
72
+ return None
73
+
74
+ # -- connection ---------------------------------------------------
75
+
76
+ def _workspace(self) -> WorkspaceClient:
77
+ if self._client is not None:
78
+ return self._client
79
+ creds = resolve_databricks_credentials()
80
+ if creds is None:
81
+ raise GenieUnavailableError("Databricks credentials are not set.")
82
+ try:
83
+ from databricks.sdk import WorkspaceClient
84
+ except ImportError as exc: # pragma: no cover - extra not installed
85
+ raise GenieUnavailableError(
86
+ "the databricks extra is not installed",
87
+ hint="pip install 'devmemory-cli[databricks]'",
88
+ ) from exc
89
+ self._client = WorkspaceClient(host=creds.host, token=creds.token)
90
+ return self._client
91
+
92
+ # -- one chat turn ----------------------------------------------
93
+
94
+ def ask(self, question: str, *, conversation_id: str | None = None) -> GenieAnswer:
95
+ """Send one question and wait for the answer. Pass ``conversation_id`` to
96
+ continue an existing thread (Genie keeps the earlier context)."""
97
+ if not self._space_id:
98
+ raise GenieUnavailableError("no Genie space configured")
99
+ w = self._workspace()
100
+ q = question.strip()
101
+ try:
102
+ if conversation_id:
103
+ msg = w.genie.create_message_and_wait(self._space_id, conversation_id, q)
104
+ else:
105
+ msg = w.genie.start_conversation_and_wait(self._space_id, q)
106
+ except Exception as exc:
107
+ raise GenieUnavailableError(f"Genie request failed: {exc}") from exc
108
+
109
+ answer = GenieAnswer(
110
+ question=q,
111
+ conversation_id=msg.conversation_id,
112
+ message_id=msg.message_id,
113
+ error=msg.error.error if msg.error is not None else None,
114
+ )
115
+ self._fill_from_attachments(w, msg, answer)
116
+ _log.info(
117
+ "genie.answer",
118
+ space=self._space_id,
119
+ conversation=answer.conversation_id,
120
+ has_sql=answer.sql is not None,
121
+ rows=len(answer.rows),
122
+ )
123
+ return answer
124
+
125
+ def _fill_from_attachments(
126
+ self, w: WorkspaceClient, msg: Any, answer: GenieAnswer
127
+ ) -> None:
128
+ for att in msg.attachments or []:
129
+ if getattr(att, "text", None) and att.text.content:
130
+ answer.text = (answer.text or "") + att.text.content
131
+ query = getattr(att, "query", None)
132
+ if query is None:
133
+ continue
134
+ answer.sql = query.query
135
+ answer.sql_description = query.description
136
+ if not answer.text and query.description:
137
+ answer.text = query.description
138
+ try:
139
+ self._attach_rows(w, msg, att.attachment_id, answer)
140
+ except Exception as exc: # results are best-effort
141
+ _log.warning("genie.results_failed", error=str(exc))
142
+
143
+ def _attach_rows(
144
+ self, w: WorkspaceClient, msg: Any, attachment_id: str, answer: GenieAnswer
145
+ ) -> None:
146
+ res = w.genie.get_message_query_result_by_attachment(
147
+ self._space_id, msg.conversation_id, msg.message_id, attachment_id
148
+ )
149
+ sr = res.statement_response
150
+ if sr is None:
151
+ return
152
+ schema = sr.manifest.schema if sr.manifest else None
153
+ cols = (schema.columns or []) if schema else []
154
+ answer.columns = [c.name for c in cols if c.name]
155
+ data = (sr.result.data_array if sr.result else None) or []
156
+ answer.row_count = len(data)
157
+ answer.truncated = len(data) > _MAX_ROWS
158
+ answer.rows = [list(r) for r in data[:_MAX_ROWS]]
159
+
160
+
161
+ __all__ = ["GenieAdapter", "GenieAnswer", "GenieUnavailableError"]
@@ -17,12 +17,15 @@ from fastapi.responses import FileResponse, PlainTextResponse
17
17
  from fastapi.staticfiles import StaticFiles
18
18
 
19
19
  from devmemory.__about__ import __version__
20
+ from devmemory.adapters.genie import GenieAdapter, GenieAnswer, GenieUnavailableError
20
21
  from devmemory.adapters.graph import GraphImpact
21
22
  from devmemory.api import mappers
22
23
  from devmemory.api.schemas import (
23
24
  AgentCheckRequest,
24
25
  ComparisonResponse,
25
26
  FeatureDetail,
27
+ GenieAskRequest,
28
+ GenieStatus,
26
29
  IssueRequest,
27
30
  ProjectBriefDoc,
28
31
  ProjectBriefRequest,
@@ -153,6 +156,30 @@ def create_app(repo_path: Path | str | None = None, *, enable_restore: bool = Fa
153
156
  doc = briefsvc.set_brief(ctx, body.content)
154
157
  return ProjectBriefDoc(content=doc.content, updated_at=doc.updated_at)
155
158
 
159
+ # -- Genie chat (Databricks) -------------------------------------
160
+
161
+ @app.get("/api/genie/status", response_model=GenieStatus)
162
+ def genie_status(ctx: Ctx) -> GenieStatus:
163
+ g = GenieAdapter(ctx.config)
164
+ return GenieStatus(
165
+ configured=g.is_configured,
166
+ space_id=g.space_id or None,
167
+ reason=g.unavailable_reason(),
168
+ )
169
+
170
+ @app.post("/api/genie/ask", response_model=GenieAnswer)
171
+ def genie_ask(ctx: Ctx, body: GenieAskRequest) -> GenieAnswer:
172
+ question = body.question.strip()
173
+ if not question:
174
+ raise HTTPException(status_code=422, detail="question is empty")
175
+ g = GenieAdapter(ctx.config)
176
+ if not g.is_configured:
177
+ raise HTTPException(status_code=503, detail=g.unavailable_reason() or "Genie unavailable")
178
+ try:
179
+ return g.ask(question, conversation_id=body.conversation_id)
180
+ except GenieUnavailableError as exc:
181
+ raise HTTPException(status_code=502, detail=exc.message) from exc
182
+
156
183
  # -- versions ------------------------------------------------------
157
184
 
158
185
  @app.get("/api/versions", response_model=list[VersionListItem])
@@ -181,11 +181,26 @@ class ProjectBriefRequest(BaseModel):
181
181
  content: str
182
182
 
183
183
 
184
+ class GenieAskRequest(BaseModel):
185
+ """One turn in the dashboard's Genie chat."""
186
+
187
+ question: str
188
+ conversation_id: str | None = None
189
+
190
+
191
+ class GenieStatus(BaseModel):
192
+ configured: bool
193
+ space_id: str | None = None
194
+ reason: str | None = None
195
+
196
+
184
197
  __all__ = [
185
198
  "AgentCheckRequest",
186
199
  "ComparisonResponse",
187
200
  "FeatureDetail",
188
201
  "FeatureHistoryPoint",
202
+ "GenieAskRequest",
203
+ "GenieStatus",
189
204
  "IssueRequest",
190
205
  "MetricChange",
191
206
  "ProjectBriefDoc",
@@ -101,6 +101,11 @@ class DatabricksSettings(_Section):
101
101
  enabled: bool = False
102
102
  catalog: str = "devmemory"
103
103
  schema_name: str = Field(default="analytics", alias="schema")
104
+ genie_space_id: str | None = Field(
105
+ default=None,
106
+ description="Genie space that backs the dashboard chat. "
107
+ "Overridden by DATABRICKS_GENIE_SPACE_ID.",
108
+ )
104
109
 
105
110
 
106
111
  class RegressionSettings(_Section):
@@ -5,6 +5,7 @@ import { TopBar } from "@/components/TopBar";
5
5
  import { CommandPalette } from "@/components/CommandPalette";
6
6
  import { ErrorBoundary } from "@/components/ErrorBoundary";
7
7
  import { Overview } from "@/routes/Overview";
8
+ import { Ask } from "@/routes/Ask";
8
9
  import { Tasks } from "@/routes/Tasks";
9
10
  import { TaskDetail } from "@/routes/TaskDetail";
10
11
  import { Timeline } from "@/routes/Timeline";
@@ -54,6 +55,7 @@ export function App() {
54
55
  <div className="route-fade" key={location.pathname}>
55
56
  <Routes>
56
57
  <Route path="/" element={<Overview />} />
58
+ <Route path="/ask" element={<Ask />} />
57
59
  <Route path="/tasks" element={<Tasks />} />
58
60
  <Route path="/task/:id" element={<TaskDetail />} />
59
61
  <Route path="/timeline" element={<Timeline />} />
@@ -16,6 +16,8 @@ import type {
16
16
  TaskSummary,
17
17
  SnapshotSummary,
18
18
  ProjectBriefDoc,
19
+ GenieStatus,
20
+ GenieAnswer,
19
21
  } from "./types";
20
22
 
21
23
  const BASE = "/api";
@@ -157,6 +159,22 @@ export const useSearch = (q: string) =>
157
159
  export const checkChange = (files: string[], intent: string, feature?: string) =>
158
160
  apiPost<ChangeGuidance>("/agent/check", { files, intent: intent || null, feature: feature || null });
159
161
 
162
+ // --- Genie chat (Databricks) ----------------------------------------------
163
+
164
+ export const useGenieStatus = (opts?: QOpts<GenieStatus>) =>
165
+ useQuery({
166
+ queryKey: ["genie-status"],
167
+ queryFn: () => apiGet<GenieStatus>("/genie/status"),
168
+ staleTime: 60_000,
169
+ ...opts,
170
+ });
171
+
172
+ export const askGenie = (question: string, conversationId: string | null) =>
173
+ apiPost<GenieAnswer>("/genie/ask", {
174
+ question,
175
+ conversation_id: conversationId,
176
+ });
177
+
160
178
  // --- project brief (single source of truth) --------------------------------
161
179
 
162
180
  export const useProjectBrief = (opts?: QOpts<ProjectBriefDoc>) =>
@@ -520,3 +520,23 @@ export interface ProjectBriefDoc {
520
520
  content: string;
521
521
  updated_at: string | null;
522
522
  }
523
+
524
+ export interface GenieStatus {
525
+ configured: boolean;
526
+ space_id: string | null;
527
+ reason: string | null;
528
+ }
529
+
530
+ export interface GenieAnswer {
531
+ conversation_id: string | null;
532
+ message_id: string | null;
533
+ question: string;
534
+ text: string | null;
535
+ sql: string | null;
536
+ sql_description: string | null;
537
+ columns: string[];
538
+ rows: unknown[][];
539
+ row_count: number | null;
540
+ truncated: boolean;
541
+ error: string | null;
542
+ }
@@ -5,6 +5,7 @@ import { Logo } from "./Logo";
5
5
 
6
6
  const NAV: { to: string; label: string; icon: IconName }[] = [
7
7
  { to: "/", label: "Overview", icon: "overview" },
8
+ { to: "/ask", label: "Ask", icon: "sparkles" },
8
9
  { to: "/tasks", label: "Tasks", icon: "target" },
9
10
  { to: "/timeline", label: "Timeline", icon: "timeline" },
10
11
  { to: "/features", label: "Features", icon: "features" },
@@ -0,0 +1,235 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useMutation } from "@tanstack/react-query";
3
+ import { useGenieStatus, askGenie } from "@/api/client";
4
+ import { Card, PageHead, EmptyState } from "@/components/primitives";
5
+ import { Icon } from "@/components/Icon";
6
+ import type { GenieAnswer } from "@/api/types";
7
+
8
+ type Turn =
9
+ | { role: "user"; text: string }
10
+ | { role: "genie"; answer: GenieAnswer }
11
+ | { role: "error"; text: string };
12
+
13
+ const SUGGESTIONS = [
14
+ "Which features regressed the most?",
15
+ "Show every version that failed tests, newest first",
16
+ "What files change together most often?",
17
+ "How did quote_latency_ms move over time?",
18
+ ];
19
+
20
+ function errorDetail(message: string): string {
21
+ try {
22
+ const parsed = JSON.parse(message);
23
+ if (parsed && typeof parsed.detail === "string") return parsed.detail;
24
+ } catch {
25
+ /* not json */
26
+ }
27
+ return message;
28
+ }
29
+
30
+ export function Ask() {
31
+ const status = useGenieStatus();
32
+ const [turns, setTurns] = useState<Turn[]>([]);
33
+ const [draft, setDraft] = useState("");
34
+ const [conversationId, setConversationId] = useState<string | null>(null);
35
+ const scrollerRef = useRef<HTMLDivElement>(null);
36
+
37
+ const ask = useMutation({
38
+ mutationFn: (q: string) => askGenie(q, conversationId),
39
+ onSuccess: (answer) => {
40
+ if (answer.conversation_id) setConversationId(answer.conversation_id);
41
+ setTurns((t) => [...t, { role: "genie", answer }]);
42
+ },
43
+ onError: (err: Error) => {
44
+ setTurns((t) => [...t, { role: "error", text: errorDetail(err.message) }]);
45
+ },
46
+ });
47
+
48
+ useEffect(() => {
49
+ scrollerRef.current?.scrollTo({ top: scrollerRef.current.scrollHeight, behavior: "smooth" });
50
+ }, [turns, ask.isPending]);
51
+
52
+ const send = (q: string) => {
53
+ const question = q.trim();
54
+ if (!question || ask.isPending) return;
55
+ setTurns((t) => [...t, { role: "user", text: question }]);
56
+ setDraft("");
57
+ ask.mutate(question);
58
+ };
59
+
60
+ const newChat = () => {
61
+ setTurns([]);
62
+ setConversationId(null);
63
+ };
64
+
65
+ return (
66
+ <>
67
+ <PageHead
68
+ title="Ask"
69
+ subtitle="Natural-language questions over your development history, answered by Databricks Genie against the devmemory.analytics tables."
70
+ actions={
71
+ turns.length > 0 ? (
72
+ <button className="btn" onClick={newChat}>
73
+ <Icon name="plus" size={14} /> New chat
74
+ </button>
75
+ ) : undefined
76
+ }
77
+ />
78
+
79
+ {status.data && !status.data.configured ? (
80
+ <Card title="Genie is not configured" pad>
81
+ <p className="text-sm">{status.data.reason}</p>
82
+ <p className="muted text-sm">
83
+ Set a Genie space that reads the <code>devmemory.analytics</code> schema, then point
84
+ DevMemory at it:
85
+ </p>
86
+ <pre className="loop-prompt">{`# .devmemory/config.json
87
+ "databricks": { "enabled": true, "genie_space_id": "01ef..." }
88
+
89
+ # or the environment
90
+ DATABRICKS_HOST=... DATABRICKS_TOKEN=...
91
+ DATABRICKS_GENIE_SPACE_ID=01ef...`}</pre>
92
+ <p className="muted text-xs">
93
+ Run <code>devmemory databricks sync</code> first so the tables have data for Genie to
94
+ query.
95
+ </p>
96
+ </Card>
97
+ ) : (
98
+ <Card flush>
99
+ <div className="chat" ref={scrollerRef}>
100
+ {turns.length === 0 && (
101
+ <div className="chat__empty">
102
+ <EmptyState
103
+ icon="sparkles"
104
+ title="Ask about your project"
105
+ sub="Genie writes the SQL, runs it on Databricks, and explains the result."
106
+ />
107
+ <div className="chat__suggest">
108
+ {SUGGESTIONS.map((s) => (
109
+ <button key={s} className="chip" onClick={() => send(s)}>
110
+ {s}
111
+ </button>
112
+ ))}
113
+ </div>
114
+ </div>
115
+ )}
116
+
117
+ {turns.map((turn, i) => (
118
+ <TurnView key={i} turn={turn} />
119
+ ))}
120
+
121
+ {ask.isPending && (
122
+ <div className="chat__msg chat__msg--genie">
123
+ <div className="chat__role">Genie</div>
124
+ <div className="chat__body row" style={{ gap: 8 }}>
125
+ <span className="spinner" /> thinking…
126
+ </div>
127
+ </div>
128
+ )}
129
+ </div>
130
+
131
+ <form
132
+ className="chat__input"
133
+ onSubmit={(e) => {
134
+ e.preventDefault();
135
+ send(draft);
136
+ }}
137
+ >
138
+ <input
139
+ className="input"
140
+ placeholder="Ask a question about this project…"
141
+ value={draft}
142
+ onChange={(e) => setDraft(e.target.value)}
143
+ disabled={ask.isPending || !status.data?.configured}
144
+ />
145
+ <button
146
+ className="btn btn--primary"
147
+ type="submit"
148
+ disabled={ask.isPending || !draft.trim() || !status.data?.configured}
149
+ >
150
+ <Icon name="arrowRight" size={15} />
151
+ </button>
152
+ </form>
153
+ </Card>
154
+ )}
155
+ </>
156
+ );
157
+ }
158
+
159
+ function TurnView({ turn }: { turn: Turn }) {
160
+ if (turn.role === "user") {
161
+ return (
162
+ <div className="chat__msg chat__msg--user">
163
+ <div className="chat__body">{turn.text}</div>
164
+ </div>
165
+ );
166
+ }
167
+ if (turn.role === "error") {
168
+ return (
169
+ <div className="chat__msg chat__msg--genie">
170
+ <div className="chat__role">Genie</div>
171
+ <div className="chat__body callout callout--bad" style={{ padding: "8px 10px" }}>
172
+ <Icon name="alert" size={13} /> {turn.text}
173
+ </div>
174
+ </div>
175
+ );
176
+ }
177
+
178
+ const a = turn.answer;
179
+ return (
180
+ <div className="chat__msg chat__msg--genie">
181
+ <div className="chat__role">Genie</div>
182
+ <div className="chat__body stack" style={{ gap: 10 }}>
183
+ {a.error && (
184
+ <div className="callout callout--bad" style={{ padding: "8px 10px" }}>
185
+ <Icon name="alert" size={13} /> {a.error}
186
+ </div>
187
+ )}
188
+ {a.text && <div style={{ whiteSpace: "pre-wrap" }}>{a.text}</div>}
189
+ {a.rows.length > 0 && <ResultTable columns={a.columns} rows={a.rows} />}
190
+ {(a.row_count != null || a.truncated) && (
191
+ <div className="muted text-xs">
192
+ {a.row_count ?? a.rows.length} row{(a.row_count ?? a.rows.length) === 1 ? "" : "s"}
193
+ {a.truncated && ` · showing first ${a.rows.length}`}
194
+ </div>
195
+ )}
196
+ {a.sql && (
197
+ <details className="chat__sql">
198
+ <summary>SQL</summary>
199
+ <pre className="loop-prompt">{a.sql}</pre>
200
+ </details>
201
+ )}
202
+ {!a.text && !a.rows.length && !a.sql && !a.error && (
203
+ <div className="muted">No answer returned.</div>
204
+ )}
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+
210
+ function ResultTable({ columns, rows }: { columns: string[]; rows: unknown[][] }) {
211
+ return (
212
+ <div className="dm-table__scroll">
213
+ <table className="dm-table">
214
+ <thead>
215
+ <tr>
216
+ {columns.map((c) => (
217
+ <th key={c}>{c}</th>
218
+ ))}
219
+ </tr>
220
+ </thead>
221
+ <tbody>
222
+ {rows.map((r, i) => (
223
+ <tr key={i}>
224
+ {r.map((cell, j) => (
225
+ <td key={j} className="mono text-xs">
226
+ {cell == null ? <span className="muted">null</span> : String(cell)}
227
+ </td>
228
+ ))}
229
+ </tr>
230
+ ))}
231
+ </tbody>
232
+ </table>
233
+ </div>
234
+ );
235
+ }
@@ -965,3 +965,93 @@ textarea.input {
965
965
  max-height: 320px;
966
966
  overflow-y: auto;
967
967
  }
968
+
969
+ /* Genie chat ("Ask") */
970
+ .chat {
971
+ display: flex;
972
+ flex-direction: column;
973
+ gap: 14px;
974
+ padding: 18px;
975
+ min-height: 340px;
976
+ max-height: calc(100vh - 300px);
977
+ overflow-y: auto;
978
+ }
979
+ .chat__empty {
980
+ margin: auto;
981
+ display: flex;
982
+ flex-direction: column;
983
+ align-items: center;
984
+ gap: 16px;
985
+ }
986
+ .chat__suggest {
987
+ display: flex;
988
+ flex-wrap: wrap;
989
+ gap: 8px;
990
+ justify-content: center;
991
+ max-width: 560px;
992
+ }
993
+ .chip {
994
+ border: 1px solid var(--border);
995
+ background: var(--surface);
996
+ color: var(--text);
997
+ border-radius: 999px;
998
+ padding: 6px 12px;
999
+ font-size: 0.8125rem;
1000
+ cursor: pointer;
1001
+ }
1002
+ .chip:hover {
1003
+ border-color: var(--accent);
1004
+ color: var(--accent);
1005
+ }
1006
+ .chat__msg {
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ gap: 4px;
1010
+ max-width: 82%;
1011
+ }
1012
+ .chat__msg--user {
1013
+ align-self: flex-end;
1014
+ align-items: flex-end;
1015
+ }
1016
+ .chat__msg--genie {
1017
+ align-self: flex-start;
1018
+ max-width: 100%;
1019
+ width: 100%;
1020
+ }
1021
+ .chat__role {
1022
+ font-size: 0.6875rem;
1023
+ text-transform: uppercase;
1024
+ letter-spacing: 0.06em;
1025
+ color: var(--text-muted);
1026
+ }
1027
+ .chat__msg--user .chat__body {
1028
+ background: var(--accent);
1029
+ color: #fff;
1030
+ border-radius: var(--r-2);
1031
+ padding: 8px 12px;
1032
+ white-space: pre-wrap;
1033
+ }
1034
+ .chat__msg--genie .chat__body {
1035
+ background: var(--surface-2, var(--surface));
1036
+ border: 1px solid var(--border);
1037
+ border-radius: var(--r-2);
1038
+ padding: 12px 14px;
1039
+ font-size: 0.9375rem;
1040
+ }
1041
+ .chat__sql summary {
1042
+ cursor: pointer;
1043
+ font-size: 0.8125rem;
1044
+ color: var(--text-muted);
1045
+ }
1046
+ .chat__sql[open] summary {
1047
+ margin-bottom: 8px;
1048
+ }
1049
+ .chat__input {
1050
+ display: flex;
1051
+ gap: 8px;
1052
+ padding: 14px 18px;
1053
+ border-top: 1px solid var(--border);
1054
+ }
1055
+ .chat__input .input {
1056
+ flex: 1;
1057
+ }