glancer 1.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +96 -0
- data/.rubocop.yml +54 -0
- data/CHANGELOG.md +88 -0
- data/CLAUDE.md +115 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.md +354 -0
- data/app/assets/config/glancer_manifest.js +1 -0
- data/app/assets/javascripts/glancer/application.js +15 -0
- data/app/assets/javascripts/glancer/controllers/chat_controller.js +101 -0
- data/app/assets/javascripts/glancer/controllers/message_controller.js +1052 -0
- data/app/assets/javascripts/glancer/controllers/toast_controller.js +63 -0
- data/app/assets/stylesheets/glancer/application.css +350 -0
- data/app/assets/stylesheets/glancer/code-blocks.css +6 -0
- data/app/assets/stylesheets/glancer/list.css +31 -0
- data/app/assets/stylesheets/glancer/scrollbar.css +16 -0
- data/app/assets/stylesheets/glancer/table.css +97 -0
- data/app/controllers/glancer/application_controller.rb +33 -0
- data/app/controllers/glancer/chats_controller.rb +49 -0
- data/app/controllers/glancer/messages_controller.rb +144 -0
- data/app/controllers/glancer/schema_controller.rb +29 -0
- data/app/controllers/glancer/settings_controller.rb +23 -0
- data/app/helpers/glancer/application_helper.rb +17 -0
- data/app/jobs/glancer/application_job.rb +6 -0
- data/app/jobs/glancer/process_message_job.rb +38 -0
- data/app/models/glancer/audit.rb +12 -0
- data/app/models/glancer/chat.rb +8 -0
- data/app/models/glancer/code_version.rb +12 -0
- data/app/models/glancer/embedding.rb +6 -0
- data/app/models/glancer/message.rb +25 -0
- data/app/models/glancer/setting.rb +23 -0
- data/app/models/glancer/sql_version.rb +6 -0
- data/app/views/glancer/_data/_importmap.json.erb +7 -0
- data/app/views/glancer/chats/_chat_sidebar.html.erb +2 -0
- data/app/views/glancer/chats/_show.html.erb +52 -0
- data/app/views/glancer/chats/_sidebar_chat_list.html.erb +30 -0
- data/app/views/glancer/chats/index.html.erb +10 -0
- data/app/views/glancer/chats/show.html.erb +1 -0
- data/app/views/glancer/messages/_data_table.html.erb +268 -0
- data/app/views/glancer/messages/_execution_error.html.erb +26 -0
- data/app/views/glancer/messages/_form.html.erb +93 -0
- data/app/views/glancer/messages/_message.html.erb +206 -0
- data/app/views/glancer/messages/_message_info.html.erb +176 -0
- data/app/views/glancer/messages/_temp_form.html.erb +100 -0
- data/app/views/glancer/messages/create.turbo_stream.erb +25 -0
- data/app/views/glancer/schema/show.html.erb +123 -0
- data/app/views/glancer/settings/show.html.erb +306 -0
- data/app/views/glancer/shared/_icons.html.erb +126 -0
- data/app/views/layouts/glancer/application.html.erb +234 -0
- data/config/locales/glancer.en.yml +90 -0
- data/config/locales/glancer.es.yml +90 -0
- data/config/locales/glancer.pt-BR.yml +90 -0
- data/config/routes.rb +20 -0
- data/db/migrate/20250629212642_create_glancer_audits.rb +19 -0
- data/db/migrate/20250629212643_create_glancer_chats.rb +10 -0
- data/db/migrate/20250629212645_create_glancer_embeddings.rb +17 -0
- data/db/migrate/20250629212647_create_glancer_messages.rb +29 -0
- data/db/migrate/20260513204129_add_user_edited_sql_to_glancer_messages.rb +11 -0
- data/db/migrate/20260513210647_create_glancer_sql_versions.rb +18 -0
- data/db/migrate/20260513210648_add_message_id_to_glancer_audits.rb +8 -0
- data/db/migrate/20260513220000_create_glancer_settings.rb +12 -0
- data/db/migrate/20260514083509_add_llm_model_to_glancer_messages.rb +9 -0
- data/db/migrate/20260523120000_rename_code_columns_in_glancer_messages.rb +8 -0
- data/db/migrate/20260523120001_rename_code_column_in_glancer_audits.rb +7 -0
- data/db/migrate/20260523120002_add_code_type_to_glancer_tables.rb +10 -0
- data/db/migrate/20260523120003_rename_glancer_sql_versions_to_code_versions.rb +8 -0
- data/db/migrate/20260523130000_add_enriched_question_to_glancer_messages.rb +7 -0
- data/db/migrate/20260524100000_add_status_to_glancer_messages.rb +9 -0
- data/lib/generators/glancer/install/install_generator.rb +74 -0
- data/lib/generators/glancer/install/templates/glancer.rb +227 -0
- data/lib/generators/glancer/install/templates/llm_context.glancer.md +51 -0
- data/lib/glancer/async_runner.rb +50 -0
- data/lib/glancer/chart_analyzer.rb +230 -0
- data/lib/glancer/configuration.rb +372 -0
- data/lib/glancer/engine.rb +90 -0
- data/lib/glancer/indexer/context_indexer.rb +58 -0
- data/lib/glancer/indexer/model_indexer.rb +64 -0
- data/lib/glancer/indexer/schema_indexer.rb +171 -0
- data/lib/glancer/indexer.rb +50 -0
- data/lib/glancer/retriever.rb +114 -0
- data/lib/glancer/utils/logger.rb +83 -0
- data/lib/glancer/utils/markdown_helper.rb +56 -0
- data/lib/glancer/utils/result_formatter.rb +25 -0
- data/lib/glancer/utils/table_stats.rb +18 -0
- data/lib/glancer/utils/transaction.rb +59 -0
- data/lib/glancer/version.rb +5 -0
- data/lib/glancer/workflow/ar_executor.rb +104 -0
- data/lib/glancer/workflow/ar_extractor.rb +25 -0
- data/lib/glancer/workflow/ar_prompt_builder.rb +64 -0
- data/lib/glancer/workflow/ar_sanitizer.rb +88 -0
- data/lib/glancer/workflow/builder.rb +129 -0
- data/lib/glancer/workflow/cache.rb +55 -0
- data/lib/glancer/workflow/executor.rb +72 -0
- data/lib/glancer/workflow/llm.rb +123 -0
- data/lib/glancer/workflow/prompt_builder.rb +143 -0
- data/lib/glancer/workflow/query_enricher.rb +117 -0
- data/lib/glancer/workflow/sql_extractor.rb +42 -0
- data/lib/glancer/workflow/sql_sanitizer.rb +42 -0
- data/lib/glancer/workflow/sql_validator.rb +67 -0
- data/lib/glancer/workflow.rb +158 -0
- data/lib/glancer.rb +50 -0
- data/lib/tasks/glancer/tailwind.rake +8 -0
- data/lib/tasks/glancer.rake +99 -0
- data/spec/glancer_spec.rb +62 -0
- data/spec/lib/glancer/async_runner_spec.rb +133 -0
- data/spec/lib/glancer/chart_analyzer_spec.rb +296 -0
- data/spec/lib/glancer/configuration_spec.rb +858 -0
- data/spec/lib/glancer/engine_spec.rb +209 -0
- data/spec/lib/glancer/indexer/context_indexer_spec.rb +96 -0
- data/spec/lib/glancer/indexer/model_indexer_spec.rb +103 -0
- data/spec/lib/glancer/indexer/schema_indexer_spec.rb +382 -0
- data/spec/lib/glancer/indexer_spec.rb +95 -0
- data/spec/lib/glancer/retriever_spec.rb +179 -0
- data/spec/lib/glancer/utils/logger_spec.rb +85 -0
- data/spec/lib/glancer/utils/markdown_helper_spec.rb +92 -0
- data/spec/lib/glancer/utils/result_formatter_spec.rb +73 -0
- data/spec/lib/glancer/utils/table_stats_spec.rb +34 -0
- data/spec/lib/glancer/utils/transaction_spec.rb +73 -0
- data/spec/lib/glancer/workflow/ar_executor_spec.rb +155 -0
- data/spec/lib/glancer/workflow/ar_extractor_spec.rb +50 -0
- data/spec/lib/glancer/workflow/ar_prompt_builder_spec.rb +79 -0
- data/spec/lib/glancer/workflow/ar_sanitizer_spec.rb +175 -0
- data/spec/lib/glancer/workflow/builder_spec.rb +204 -0
- data/spec/lib/glancer/workflow/cache_spec.rb +142 -0
- data/spec/lib/glancer/workflow/executor_spec.rb +149 -0
- data/spec/lib/glancer/workflow/llm_spec.rb +124 -0
- data/spec/lib/glancer/workflow/prompt_builder_spec.rb +196 -0
- data/spec/lib/glancer/workflow/query_enricher_spec.rb +184 -0
- data/spec/lib/glancer/workflow/sql_extractor_spec.rb +82 -0
- data/spec/lib/glancer/workflow/sql_sanitizer_spec.rb +98 -0
- data/spec/lib/glancer/workflow/sql_validator_spec.rb +166 -0
- data/spec/lib/glancer/workflow_spec.rb +308 -0
- data/spec/models/glancer/audit_spec.rb +82 -0
- data/spec/models/glancer/chat_spec.rb +60 -0
- data/spec/models/glancer/code_version_spec.rb +71 -0
- data/spec/models/glancer/embedding_spec.rb +73 -0
- data/spec/models/glancer/message_spec.rb +144 -0
- data/spec/models/glancer/setting_spec.rb +88 -0
- data/spec/models/glancer/sql_version_spec.rb +4 -0
- data/spec/spec_helper.rb +128 -0
- data/spec/support/schema.rb +55 -0
- metadata +255 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d4248cad39bdfcfd7cb3907a83c81170f49bf15b76c0af226d6333622479dd72
|
|
4
|
+
data.tar.gz: cd8b5588e4032750fa0f5be8eae5de5ded28a713a1223b948d4fd94b258ce012
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4cb913d4829f562ceaa8ebc9ff808c22037b540c0ecf75991cfb6f785434fc0fd8588d73d6a65e54a09f96d002a7e0359bc0e8884ba112a75fac215832b32340
|
|
7
|
+
data.tar.gz: 4b5f94c281b5161e83bbbbf48296d321344302ee53811bf0b8b4184cce385f831552fca3827ee26dae34897dd501ff8de74ad33f6714e8d7e3ed1b6c135779d7
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
# ── Lint ────────────────────────────────────────────────────────────────────
|
|
14
|
+
lint:
|
|
15
|
+
name: RuboCop
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6.0.2
|
|
20
|
+
|
|
21
|
+
- uses: ruby/setup-ruby@v1
|
|
22
|
+
with:
|
|
23
|
+
ruby-version: "4.0"
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Run RuboCop
|
|
27
|
+
run: bundle exec rubocop --format github
|
|
28
|
+
|
|
29
|
+
# ── Test ────────────────────────────────────────────────────────────────────
|
|
30
|
+
test:
|
|
31
|
+
name: RSpec / Ruby ${{ matrix.ruby }}
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
needs: lint
|
|
34
|
+
permissions:
|
|
35
|
+
contents: write
|
|
36
|
+
|
|
37
|
+
strategy:
|
|
38
|
+
fail-fast: false
|
|
39
|
+
matrix:
|
|
40
|
+
ruby: ["3.3", "3.4", "4.0"]
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v6.0.2
|
|
44
|
+
|
|
45
|
+
- uses: ruby/setup-ruby@v1
|
|
46
|
+
with:
|
|
47
|
+
ruby-version: ${{ matrix.ruby }}
|
|
48
|
+
bundler-cache: true
|
|
49
|
+
|
|
50
|
+
- name: Run RSpec with coverage
|
|
51
|
+
run: bundle exec rspec
|
|
52
|
+
env:
|
|
53
|
+
COVERAGE: "true"
|
|
54
|
+
CI: "true"
|
|
55
|
+
|
|
56
|
+
- name: Upload coverage report (Ruby 3.3 only)
|
|
57
|
+
if: matrix.ruby == '3.3'
|
|
58
|
+
uses: actions/upload-artifact@v7.0.1
|
|
59
|
+
with:
|
|
60
|
+
name: coverage-report
|
|
61
|
+
path: coverage/
|
|
62
|
+
retention-days: 7
|
|
63
|
+
|
|
64
|
+
- name: Check minimum coverage
|
|
65
|
+
if: matrix.ruby == '3.3'
|
|
66
|
+
run: |
|
|
67
|
+
COVERED=$(ruby -e "
|
|
68
|
+
require 'json'
|
|
69
|
+
data = JSON.parse(File.read('coverage/.last_run.json'))
|
|
70
|
+
pct = data.dig('result', 'line') || data.dig('result', 'covered_percent') || 0
|
|
71
|
+
puts pct.round(2)
|
|
72
|
+
")
|
|
73
|
+
echo "Coverage: ${COVERED}%"
|
|
74
|
+
ruby -e "exit(1) if ${COVERED}.to_f < 80" || {
|
|
75
|
+
echo "::warning::Coverage ${COVERED}% is below the 80% threshold."
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
- name: Extract coverage percentage for badge
|
|
79
|
+
if: matrix.ruby == '3.3'
|
|
80
|
+
id: coverage
|
|
81
|
+
run: |
|
|
82
|
+
PCT=$(ruby -e "
|
|
83
|
+
require 'json'
|
|
84
|
+
data = JSON.parse(File.read('coverage/coverage.json'))
|
|
85
|
+
puts data.dig('metrics', 'covered_percent').round(2)
|
|
86
|
+
")
|
|
87
|
+
echo "pct=${PCT}%" >> "$GITHUB_OUTPUT"
|
|
88
|
+
|
|
89
|
+
- name: Generate coverage badge
|
|
90
|
+
if: matrix.ruby == '3.3' && github.ref == 'refs/heads/main'
|
|
91
|
+
uses: ernanej/badge-generator@main
|
|
92
|
+
with:
|
|
93
|
+
name: ${{ steps.coverage.outputs.pct }}
|
|
94
|
+
prefix: coverage
|
|
95
|
+
path: .github/badges/coverage.svg
|
|
96
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.3
|
|
3
|
+
NewCops: disable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- ".ruby-lsp/**/*"
|
|
7
|
+
- "vendor/**/*"
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Style/StringLiteralsInInterpolation:
|
|
13
|
+
EnforcedStyle: double_quotes
|
|
14
|
+
|
|
15
|
+
# Internal Rails engine — no public API to document
|
|
16
|
+
Style/Documentation:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
# @@store in Workflow::Cache is intentional
|
|
20
|
+
Style/ClassVars:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
# Complex pipeline and configuration methods legitimately exceed 10 lines
|
|
24
|
+
Metrics/MethodLength:
|
|
25
|
+
Max: 80
|
|
26
|
+
|
|
27
|
+
Layout/ExtraSpacing:
|
|
28
|
+
AllowForAlignment: true
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 55
|
|
32
|
+
|
|
33
|
+
Metrics/ClassLength:
|
|
34
|
+
Max: 300
|
|
35
|
+
|
|
36
|
+
Metrics/ModuleLength:
|
|
37
|
+
Max: 200
|
|
38
|
+
|
|
39
|
+
Metrics/CyclomaticComplexity:
|
|
40
|
+
Max: 15
|
|
41
|
+
|
|
42
|
+
Metrics/PerceivedComplexity:
|
|
43
|
+
Max: 15
|
|
44
|
+
|
|
45
|
+
# Rake tasks and generator templates are necessarily long blocks
|
|
46
|
+
Metrics/BlockLength:
|
|
47
|
+
Max: 80
|
|
48
|
+
Exclude:
|
|
49
|
+
- "lib/tasks/**/*.rake"
|
|
50
|
+
- "lib/generators/**/*.rb"
|
|
51
|
+
- "spec/**/*.rb"
|
|
52
|
+
|
|
53
|
+
Layout/LineLength:
|
|
54
|
+
Max: 140
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] — 2026-05-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **SQL editing**: users can edit the generated SQL directly in the chat UI before
|
|
15
|
+
executing it. Edits are persisted with a `user_edited_sql` flag and surfaced with
|
|
16
|
+
a visible badge.
|
|
17
|
+
- **Pipeline status labels**: animated step-by-step labels (embedding → retrieval →
|
|
18
|
+
SQL generation → validation → execution → response) while the pipeline is running.
|
|
19
|
+
- **Accordion results**: running a new query collapses the previous results panel;
|
|
20
|
+
panels can be toggled independently.
|
|
21
|
+
- **Copy buttons**: one-click copy for both the raw SQL and the full assistant response.
|
|
22
|
+
- **Large-result alert**: a warning banner when a query returns ≥ 500 rows or has no
|
|
23
|
+
`LIMIT` clause.
|
|
24
|
+
- **Audio input**: microphone button powered by the Web Speech API; transcribed text
|
|
25
|
+
is appended to the question field.
|
|
26
|
+
- **Desktop sidebar toggle**: chevron button to collapse/expand the chat list on large
|
|
27
|
+
screens; state is persisted in `localStorage`.
|
|
28
|
+
- **Blazer integration**: "Open in Blazer" button auto-detected when the `blazer` gem
|
|
29
|
+
is present; configurable via `config.blazer_path`.
|
|
30
|
+
- **`:silent` log verbosity level**: suppresses all log output including `warn` and
|
|
31
|
+
`error` — intended for test environments.
|
|
32
|
+
- **100 % test coverage**: 552 RSpec examples covering every workflow path, edge case,
|
|
33
|
+
and rescue branch.
|
|
34
|
+
- **CI badge**: automatically generated coverage SVG committed to the `badge-generator`
|
|
35
|
+
branch on every push to `main`.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- LLM humanization prompt rewritten to never describe the query as "executed" — it now
|
|
40
|
+
explains the logic and why it answers the question.
|
|
41
|
+
- Improved self-correction: the executor retries up to 3 times, passing the database
|
|
42
|
+
error back to the LLM on each attempt.
|
|
43
|
+
- Immediate user-message rendering: the user's bubble appears in the chat instantly
|
|
44
|
+
(before the server responds) via a temporary DOM node that is replaced by the
|
|
45
|
+
Turbo Stream response.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Info panel never showed content because the controller was passing `message_for_info:`
|
|
50
|
+
but the partial expected `message_info:`.
|
|
51
|
+
|
|
52
|
+
## [0.1.0] — 2026-05-14
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **RAG pipeline**: embed → retrieve → generate SQL → execute → humanize, with automatic
|
|
57
|
+
retry (up to 3 attempts) on SQL errors using LLM self-correction.
|
|
58
|
+
- **Multi-provider LLM support** via [ruby_llm](https://github.com/crmne/ruby_llm):
|
|
59
|
+
Gemini, OpenAI, and OpenRouter. Each role (SQL generation, chat responses, embeddings)
|
|
60
|
+
can use a different provider and model.
|
|
61
|
+
- **Indexers** for `db/schema.rb`, `app/models/**/*.rb`, and a custom Markdown context
|
|
62
|
+
file. Rake tasks: `glancer:index:all`, `glancer:index:schema`, `glancer:index:models`,
|
|
63
|
+
`glancer:index:context`.
|
|
64
|
+
- **Cosine similarity retrieval** with per-source-type relevance weights (schema 1.3×,
|
|
65
|
+
context 1.2×, models 1.1×) and a configurable minimum score threshold.
|
|
66
|
+
- **Chunk overlap** to prevent context loss at document boundaries.
|
|
67
|
+
- **SQL safety layer**: `SQLSanitizer` (blocks destructive statements),
|
|
68
|
+
`SQLValidator` (verifies table references against indexed schema), and
|
|
69
|
+
mandatory read-only transaction with automatic rollback.
|
|
70
|
+
- **Audit trail**: every executed query is stored in `glancer_audits` with a unique
|
|
71
|
+
`run_id` UUID injected as an SQL comment (`/*glancer,run_id:UUID*/`).
|
|
72
|
+
- **In-memory response cache** (`workflow_cache_ttl`) to avoid redundant LLM calls for
|
|
73
|
+
repeated identical questions.
|
|
74
|
+
- **Chat UI**: Stimulus + Turbo Streams interface with dark mode, typewriter effect,
|
|
75
|
+
CSV export, SQL re-run, pipeline status labels, accordion results, copy-to-clipboard,
|
|
76
|
+
and audio input (Web Speech API).
|
|
77
|
+
- **Settings page** at `/glancer/settings` for runtime custom instructions.
|
|
78
|
+
- **Schema viewer** at `/glancer/db-schema` showing indexed tables and columns.
|
|
79
|
+
- **Install generator**: `rails generate glancer:install` scaffolds the initializer,
|
|
80
|
+
context file, and mounts the engine.
|
|
81
|
+
- Configurable `statement_timeout` enforced via adapter-native mechanisms
|
|
82
|
+
(PostgreSQL `SET statement_timeout`, MySQL `SET max_execution_time`).
|
|
83
|
+
- `config.history_limit` to control how many prior turns are included in the prompt.
|
|
84
|
+
- `config.read_only_db` to route queries to a replica connection string.
|
|
85
|
+
|
|
86
|
+
[Unreleased]: https://github.com/ErnaneJ/glancer/compare/v1.0.0...HEAD
|
|
87
|
+
[1.0.0]: https://github.com/ErnaneJ/glancer/compare/v0.1.0...v1.0.0
|
|
88
|
+
[0.1.0]: https://github.com/ErnaneJ/glancer/releases/tag/v0.1.0
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Glancer is a **Ruby on Rails engine (gem)** that adds natural language database querying to any Rails app via RAG (Retrieval-Augmented Generation) and LLMs. It indexes the host app's schema, models, and custom context into a vector store, then answers user questions with generated and executed SQL, returning humanized responses through a chat UI.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Development
|
|
13
|
+
bundle exec rake spec rubocop # Default CI: tests + linting (both must pass)
|
|
14
|
+
bundle exec rake spec # RSpec only
|
|
15
|
+
bundle exec rake rubocop # RuboCop only
|
|
16
|
+
|
|
17
|
+
# Rake tasks (run in host app after mounting)
|
|
18
|
+
rails glancer:index:all # Rebuild all embeddings (prompts confirmation)
|
|
19
|
+
rails glancer:index:schema # Index db/schema.rb only
|
|
20
|
+
rails glancer:index:models # Index app/models only
|
|
21
|
+
rails glancer:index:context # Index custom context Markdown file
|
|
22
|
+
rails glancer:version # Print gem version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
### RAG Query Pipeline
|
|
28
|
+
|
|
29
|
+
The core workflow runs in `lib/glancer/workflow.rb` and follows this sequence per user message:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
1. Embed question → cosine similarity search over glancer_embeddings
|
|
33
|
+
- Weights: schema 1.3x, context 1.2x, models 1.1x
|
|
34
|
+
- Filters: min_score (default 0.6), top-k (default 5)
|
|
35
|
+
|
|
36
|
+
2. Build prompt (PromptBuilder) with retrieved chunks + last 6 messages
|
|
37
|
+
- LLM receives adapter type for syntax-specific SQL generation
|
|
38
|
+
- Language directive: respond in the same language as the question
|
|
39
|
+
|
|
40
|
+
3. LLM generates SELECT-only SQL
|
|
41
|
+
- SQLExtractor: parses ```sql blocks from raw LLM output
|
|
42
|
+
- SQLSanitizer: blocks DELETE/UPDATE/INSERT/DROP/TRUNCATE/ALTER/CREATE/REPLACE
|
|
43
|
+
- SQLValidator: verifies referenced tables exist in indexed schema
|
|
44
|
+
|
|
45
|
+
4. Execute inside a transaction that always rolls back (read-only safety)
|
|
46
|
+
- Injects /*glancer,run_id:UUID*/ comment for audit trail
|
|
47
|
+
- Creates Glancer::Audit record
|
|
48
|
+
- Auto-retry up to 3 times via LLM error correction
|
|
49
|
+
|
|
50
|
+
5. LLM humanizes results; response cached in-memory (TTL configurable)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Database Tables
|
|
54
|
+
|
|
55
|
+
| Table | Purpose |
|
|
56
|
+
|-------|---------|
|
|
57
|
+
| `glancer_chats` | Conversation containers |
|
|
58
|
+
| `glancer_messages` | User/assistant turns; stores `sql`, `successful` flag, optional `user_message_id` FK linking reply to source |
|
|
59
|
+
| `glancer_embeddings` | Vector store: `content`, `embedding` (JSONB on PG / JSON elsewhere), `source_type`, `source_path` |
|
|
60
|
+
| `glancer_audits` | Immutable query log: `question`, `sql`, `adapter`, `run_id` (unique UUID) |
|
|
61
|
+
|
|
62
|
+
### Indexers (`lib/glancer/indexer/`)
|
|
63
|
+
|
|
64
|
+
- **SchemaIndexer**: Splits `db/schema.rb` into per-table chunks by `create_table` blocks
|
|
65
|
+
- **ModelIndexer**: Reads `app/models/**/*.rb`, chunks at 1000 chars
|
|
66
|
+
- **ContextIndexer**: Reads custom Markdown from `config_file_path`; skips files whose first line is `--glancer-ignore`
|
|
67
|
+
|
|
68
|
+
Embeddings are regenerated on each `glancer:index:*` call (full re-index, not incremental).
|
|
69
|
+
|
|
70
|
+
### Configuration (`lib/glancer/configuration.rb`)
|
|
71
|
+
|
|
72
|
+
Singleton via `Glancer.configure { |config| }`. All setters validate on assignment (raise `ArgumentError` on invalid). Key non-obvious settings:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
config.adapter # Auto-detected from ActiveRecord if nil
|
|
76
|
+
config.read_only_db # Optional replica URL; connection opened per transaction, reset after
|
|
77
|
+
config.k # Number of top embeddings retrieved (default 10)
|
|
78
|
+
config.min_score # Cosine similarity floor 0.0–1.0 (default 0.6)
|
|
79
|
+
config.workflow_cache_ttl # In-memory result cache TTL (NOT shared across processes)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Frontend (`app/assets/`)
|
|
83
|
+
|
|
84
|
+
Stimulus JS + Turbo Streams with Tailwind CSS (CDN, dark mode default). Controllers:
|
|
85
|
+
- **MessageController**: form submit, typewriter effect (12ms/char), CSV export (client-side DOM traversal), SQL re-run
|
|
86
|
+
- **ChatController**: chat CRUD, copy-to-clipboard
|
|
87
|
+
|
|
88
|
+
Both controllers respond to `.html` and `.turbo_stream` formats. CSV generation happens entirely in the browser by traversing the rendered `<table>` DOM — there is no backend CSV endpoint.
|
|
89
|
+
|
|
90
|
+
### Routes
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
resources :chats, only: [:index, :show, :create, :destroy] do
|
|
94
|
+
resources :messages, only: [:create]
|
|
95
|
+
end
|
|
96
|
+
get "/messages/:id/info" # Side-panel with SQL + sources
|
|
97
|
+
post "/messages/:id/run_sql" # Re-execute saved SQL without regenerating
|
|
98
|
+
root to: "chats#index"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Non-Obvious Constraints
|
|
102
|
+
|
|
103
|
+
- **Transactions always roll back** — even on successful reads. This is intentional for read-only safety; do not add `raise ActiveRecord::Rollback` — it's already unconditional.
|
|
104
|
+
- **SQL audit comment is mandatory** — every executed query has `/*glancer,run_id:UUID*/` injected by Executor; removing this breaks audit tracking.
|
|
105
|
+
- **In-memory cache is process-local** — `Glancer::Workflow::Cache` uses a class-level `@@store` hash. It will not invalidate across Puma workers or restarts.
|
|
106
|
+
- **Vector search is O(n) pure Ruby** — no pgvector or native DB indexing. Large embedding tables will degrade retrieval performance noticeably.
|
|
107
|
+
- **ModelIndexer hard-codes 1000-char chunk size** — there is no configuration option for this.
|
|
108
|
+
- **Prompt language detection is LLM-instructed, not programmatic** — the system prompt tells the LLM to match input language; no ICU/language detection library is used.
|
|
109
|
+
- **`user_message_id` is a self-referential FK on `glancer_messages`** — assistant messages point to the user message that triggered them; this is used for threading in the UI info panel.
|
|
110
|
+
|
|
111
|
+
## Gem Development Notes
|
|
112
|
+
|
|
113
|
+
The gemspec (`glancer.gemspec`) includes only `.rb`, `.erb`, `.md`, `.yml`, `.rake` files. Assets follow normal Rails engine conventions with `config.assets.precompile` declared in the engine initializer.
|
|
114
|
+
|
|
115
|
+
To test changes against a host Rails app, use a `Gemfile` path reference: `gem 'glancer', path: '../glancer'`.
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
ernane.junior25@gmail.com.
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|