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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +96 -0
  3. data/.rubocop.yml +54 -0
  4. data/CHANGELOG.md +88 -0
  5. data/CLAUDE.md +115 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/README.md +354 -0
  8. data/app/assets/config/glancer_manifest.js +1 -0
  9. data/app/assets/javascripts/glancer/application.js +15 -0
  10. data/app/assets/javascripts/glancer/controllers/chat_controller.js +101 -0
  11. data/app/assets/javascripts/glancer/controllers/message_controller.js +1052 -0
  12. data/app/assets/javascripts/glancer/controllers/toast_controller.js +63 -0
  13. data/app/assets/stylesheets/glancer/application.css +350 -0
  14. data/app/assets/stylesheets/glancer/code-blocks.css +6 -0
  15. data/app/assets/stylesheets/glancer/list.css +31 -0
  16. data/app/assets/stylesheets/glancer/scrollbar.css +16 -0
  17. data/app/assets/stylesheets/glancer/table.css +97 -0
  18. data/app/controllers/glancer/application_controller.rb +33 -0
  19. data/app/controllers/glancer/chats_controller.rb +49 -0
  20. data/app/controllers/glancer/messages_controller.rb +144 -0
  21. data/app/controllers/glancer/schema_controller.rb +29 -0
  22. data/app/controllers/glancer/settings_controller.rb +23 -0
  23. data/app/helpers/glancer/application_helper.rb +17 -0
  24. data/app/jobs/glancer/application_job.rb +6 -0
  25. data/app/jobs/glancer/process_message_job.rb +38 -0
  26. data/app/models/glancer/audit.rb +12 -0
  27. data/app/models/glancer/chat.rb +8 -0
  28. data/app/models/glancer/code_version.rb +12 -0
  29. data/app/models/glancer/embedding.rb +6 -0
  30. data/app/models/glancer/message.rb +25 -0
  31. data/app/models/glancer/setting.rb +23 -0
  32. data/app/models/glancer/sql_version.rb +6 -0
  33. data/app/views/glancer/_data/_importmap.json.erb +7 -0
  34. data/app/views/glancer/chats/_chat_sidebar.html.erb +2 -0
  35. data/app/views/glancer/chats/_show.html.erb +52 -0
  36. data/app/views/glancer/chats/_sidebar_chat_list.html.erb +30 -0
  37. data/app/views/glancer/chats/index.html.erb +10 -0
  38. data/app/views/glancer/chats/show.html.erb +1 -0
  39. data/app/views/glancer/messages/_data_table.html.erb +268 -0
  40. data/app/views/glancer/messages/_execution_error.html.erb +26 -0
  41. data/app/views/glancer/messages/_form.html.erb +93 -0
  42. data/app/views/glancer/messages/_message.html.erb +206 -0
  43. data/app/views/glancer/messages/_message_info.html.erb +176 -0
  44. data/app/views/glancer/messages/_temp_form.html.erb +100 -0
  45. data/app/views/glancer/messages/create.turbo_stream.erb +25 -0
  46. data/app/views/glancer/schema/show.html.erb +123 -0
  47. data/app/views/glancer/settings/show.html.erb +306 -0
  48. data/app/views/glancer/shared/_icons.html.erb +126 -0
  49. data/app/views/layouts/glancer/application.html.erb +234 -0
  50. data/config/locales/glancer.en.yml +90 -0
  51. data/config/locales/glancer.es.yml +90 -0
  52. data/config/locales/glancer.pt-BR.yml +90 -0
  53. data/config/routes.rb +20 -0
  54. data/db/migrate/20250629212642_create_glancer_audits.rb +19 -0
  55. data/db/migrate/20250629212643_create_glancer_chats.rb +10 -0
  56. data/db/migrate/20250629212645_create_glancer_embeddings.rb +17 -0
  57. data/db/migrate/20250629212647_create_glancer_messages.rb +29 -0
  58. data/db/migrate/20260513204129_add_user_edited_sql_to_glancer_messages.rb +11 -0
  59. data/db/migrate/20260513210647_create_glancer_sql_versions.rb +18 -0
  60. data/db/migrate/20260513210648_add_message_id_to_glancer_audits.rb +8 -0
  61. data/db/migrate/20260513220000_create_glancer_settings.rb +12 -0
  62. data/db/migrate/20260514083509_add_llm_model_to_glancer_messages.rb +9 -0
  63. data/db/migrate/20260523120000_rename_code_columns_in_glancer_messages.rb +8 -0
  64. data/db/migrate/20260523120001_rename_code_column_in_glancer_audits.rb +7 -0
  65. data/db/migrate/20260523120002_add_code_type_to_glancer_tables.rb +10 -0
  66. data/db/migrate/20260523120003_rename_glancer_sql_versions_to_code_versions.rb +8 -0
  67. data/db/migrate/20260523130000_add_enriched_question_to_glancer_messages.rb +7 -0
  68. data/db/migrate/20260524100000_add_status_to_glancer_messages.rb +9 -0
  69. data/lib/generators/glancer/install/install_generator.rb +74 -0
  70. data/lib/generators/glancer/install/templates/glancer.rb +227 -0
  71. data/lib/generators/glancer/install/templates/llm_context.glancer.md +51 -0
  72. data/lib/glancer/async_runner.rb +50 -0
  73. data/lib/glancer/chart_analyzer.rb +230 -0
  74. data/lib/glancer/configuration.rb +372 -0
  75. data/lib/glancer/engine.rb +90 -0
  76. data/lib/glancer/indexer/context_indexer.rb +58 -0
  77. data/lib/glancer/indexer/model_indexer.rb +64 -0
  78. data/lib/glancer/indexer/schema_indexer.rb +171 -0
  79. data/lib/glancer/indexer.rb +50 -0
  80. data/lib/glancer/retriever.rb +114 -0
  81. data/lib/glancer/utils/logger.rb +83 -0
  82. data/lib/glancer/utils/markdown_helper.rb +56 -0
  83. data/lib/glancer/utils/result_formatter.rb +25 -0
  84. data/lib/glancer/utils/table_stats.rb +18 -0
  85. data/lib/glancer/utils/transaction.rb +59 -0
  86. data/lib/glancer/version.rb +5 -0
  87. data/lib/glancer/workflow/ar_executor.rb +104 -0
  88. data/lib/glancer/workflow/ar_extractor.rb +25 -0
  89. data/lib/glancer/workflow/ar_prompt_builder.rb +64 -0
  90. data/lib/glancer/workflow/ar_sanitizer.rb +88 -0
  91. data/lib/glancer/workflow/builder.rb +129 -0
  92. data/lib/glancer/workflow/cache.rb +55 -0
  93. data/lib/glancer/workflow/executor.rb +72 -0
  94. data/lib/glancer/workflow/llm.rb +123 -0
  95. data/lib/glancer/workflow/prompt_builder.rb +143 -0
  96. data/lib/glancer/workflow/query_enricher.rb +117 -0
  97. data/lib/glancer/workflow/sql_extractor.rb +42 -0
  98. data/lib/glancer/workflow/sql_sanitizer.rb +42 -0
  99. data/lib/glancer/workflow/sql_validator.rb +67 -0
  100. data/lib/glancer/workflow.rb +158 -0
  101. data/lib/glancer.rb +50 -0
  102. data/lib/tasks/glancer/tailwind.rake +8 -0
  103. data/lib/tasks/glancer.rake +99 -0
  104. data/spec/glancer_spec.rb +62 -0
  105. data/spec/lib/glancer/async_runner_spec.rb +133 -0
  106. data/spec/lib/glancer/chart_analyzer_spec.rb +296 -0
  107. data/spec/lib/glancer/configuration_spec.rb +858 -0
  108. data/spec/lib/glancer/engine_spec.rb +209 -0
  109. data/spec/lib/glancer/indexer/context_indexer_spec.rb +96 -0
  110. data/spec/lib/glancer/indexer/model_indexer_spec.rb +103 -0
  111. data/spec/lib/glancer/indexer/schema_indexer_spec.rb +382 -0
  112. data/spec/lib/glancer/indexer_spec.rb +95 -0
  113. data/spec/lib/glancer/retriever_spec.rb +179 -0
  114. data/spec/lib/glancer/utils/logger_spec.rb +85 -0
  115. data/spec/lib/glancer/utils/markdown_helper_spec.rb +92 -0
  116. data/spec/lib/glancer/utils/result_formatter_spec.rb +73 -0
  117. data/spec/lib/glancer/utils/table_stats_spec.rb +34 -0
  118. data/spec/lib/glancer/utils/transaction_spec.rb +73 -0
  119. data/spec/lib/glancer/workflow/ar_executor_spec.rb +155 -0
  120. data/spec/lib/glancer/workflow/ar_extractor_spec.rb +50 -0
  121. data/spec/lib/glancer/workflow/ar_prompt_builder_spec.rb +79 -0
  122. data/spec/lib/glancer/workflow/ar_sanitizer_spec.rb +175 -0
  123. data/spec/lib/glancer/workflow/builder_spec.rb +204 -0
  124. data/spec/lib/glancer/workflow/cache_spec.rb +142 -0
  125. data/spec/lib/glancer/workflow/executor_spec.rb +149 -0
  126. data/spec/lib/glancer/workflow/llm_spec.rb +124 -0
  127. data/spec/lib/glancer/workflow/prompt_builder_spec.rb +196 -0
  128. data/spec/lib/glancer/workflow/query_enricher_spec.rb +184 -0
  129. data/spec/lib/glancer/workflow/sql_extractor_spec.rb +82 -0
  130. data/spec/lib/glancer/workflow/sql_sanitizer_spec.rb +98 -0
  131. data/spec/lib/glancer/workflow/sql_validator_spec.rb +166 -0
  132. data/spec/lib/glancer/workflow_spec.rb +308 -0
  133. data/spec/models/glancer/audit_spec.rb +82 -0
  134. data/spec/models/glancer/chat_spec.rb +60 -0
  135. data/spec/models/glancer/code_version_spec.rb +71 -0
  136. data/spec/models/glancer/embedding_spec.rb +73 -0
  137. data/spec/models/glancer/message_spec.rb +144 -0
  138. data/spec/models/glancer/setting_spec.rb +88 -0
  139. data/spec/models/glancer/sql_version_spec.rb +4 -0
  140. data/spec/spec_helper.rb +128 -0
  141. data/spec/support/schema.rb +55 -0
  142. 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'`.
@@ -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