codebase_index 0.1.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/CHANGELOG.md +29 -0
- data/CODE_OF_CONDUCT.md +83 -0
- data/CONTRIBUTING.md +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +481 -0
- data/exe/codebase-console-mcp +22 -0
- data/exe/codebase-index-mcp +61 -0
- data/exe/codebase-index-mcp-http +64 -0
- data/exe/codebase-index-mcp-start +58 -0
- data/lib/codebase_index/ast/call_site_extractor.rb +106 -0
- data/lib/codebase_index/ast/method_extractor.rb +76 -0
- data/lib/codebase_index/ast/node.rb +88 -0
- data/lib/codebase_index/ast/parser.rb +653 -0
- data/lib/codebase_index/ast.rb +6 -0
- data/lib/codebase_index/builder.rb +137 -0
- data/lib/codebase_index/chunking/chunk.rb +84 -0
- data/lib/codebase_index/chunking/semantic_chunker.rb +290 -0
- data/lib/codebase_index/console/adapters/cache_adapter.rb +58 -0
- data/lib/codebase_index/console/adapters/good_job_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +66 -0
- data/lib/codebase_index/console/audit_logger.rb +75 -0
- data/lib/codebase_index/console/bridge.rb +170 -0
- data/lib/codebase_index/console/confirmation.rb +90 -0
- data/lib/codebase_index/console/connection_manager.rb +173 -0
- data/lib/codebase_index/console/console_response_renderer.rb +78 -0
- data/lib/codebase_index/console/model_validator.rb +81 -0
- data/lib/codebase_index/console/safe_context.rb +82 -0
- data/lib/codebase_index/console/server.rb +557 -0
- data/lib/codebase_index/console/sql_validator.rb +172 -0
- data/lib/codebase_index/console/tools/tier1.rb +118 -0
- data/lib/codebase_index/console/tools/tier2.rb +117 -0
- data/lib/codebase_index/console/tools/tier3.rb +110 -0
- data/lib/codebase_index/console/tools/tier4.rb +79 -0
- data/lib/codebase_index/coordination/pipeline_lock.rb +109 -0
- data/lib/codebase_index/cost_model/embedding_cost.rb +88 -0
- data/lib/codebase_index/cost_model/estimator.rb +128 -0
- data/lib/codebase_index/cost_model/provider_pricing.rb +67 -0
- data/lib/codebase_index/cost_model/storage_cost.rb +52 -0
- data/lib/codebase_index/cost_model.rb +22 -0
- data/lib/codebase_index/db/migrations/001_create_units.rb +38 -0
- data/lib/codebase_index/db/migrations/002_create_edges.rb +35 -0
- data/lib/codebase_index/db/migrations/003_create_embeddings.rb +37 -0
- data/lib/codebase_index/db/migrations/004_create_snapshots.rb +45 -0
- data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +40 -0
- data/lib/codebase_index/db/migrator.rb +71 -0
- data/lib/codebase_index/db/schema_version.rb +73 -0
- data/lib/codebase_index/dependency_graph.rb +227 -0
- data/lib/codebase_index/embedding/indexer.rb +130 -0
- data/lib/codebase_index/embedding/openai.rb +105 -0
- data/lib/codebase_index/embedding/provider.rb +135 -0
- data/lib/codebase_index/embedding/text_preparer.rb +112 -0
- data/lib/codebase_index/evaluation/baseline_runner.rb +115 -0
- data/lib/codebase_index/evaluation/evaluator.rb +146 -0
- data/lib/codebase_index/evaluation/metrics.rb +79 -0
- data/lib/codebase_index/evaluation/query_set.rb +148 -0
- data/lib/codebase_index/evaluation/report_generator.rb +90 -0
- data/lib/codebase_index/extracted_unit.rb +145 -0
- data/lib/codebase_index/extractor.rb +956 -0
- data/lib/codebase_index/extractors/action_cable_extractor.rb +228 -0
- data/lib/codebase_index/extractors/ast_source_extraction.rb +46 -0
- data/lib/codebase_index/extractors/behavioral_profile.rb +309 -0
- data/lib/codebase_index/extractors/caching_extractor.rb +261 -0
- data/lib/codebase_index/extractors/callback_analyzer.rb +232 -0
- data/lib/codebase_index/extractors/concern_extractor.rb +253 -0
- data/lib/codebase_index/extractors/configuration_extractor.rb +219 -0
- data/lib/codebase_index/extractors/controller_extractor.rb +494 -0
- data/lib/codebase_index/extractors/database_view_extractor.rb +278 -0
- data/lib/codebase_index/extractors/decorator_extractor.rb +260 -0
- data/lib/codebase_index/extractors/engine_extractor.rb +204 -0
- data/lib/codebase_index/extractors/event_extractor.rb +211 -0
- data/lib/codebase_index/extractors/factory_extractor.rb +289 -0
- data/lib/codebase_index/extractors/graphql_extractor.rb +917 -0
- data/lib/codebase_index/extractors/i18n_extractor.rb +117 -0
- data/lib/codebase_index/extractors/job_extractor.rb +369 -0
- data/lib/codebase_index/extractors/lib_extractor.rb +249 -0
- data/lib/codebase_index/extractors/mailer_extractor.rb +339 -0
- data/lib/codebase_index/extractors/manager_extractor.rb +202 -0
- data/lib/codebase_index/extractors/middleware_extractor.rb +133 -0
- data/lib/codebase_index/extractors/migration_extractor.rb +469 -0
- data/lib/codebase_index/extractors/model_extractor.rb +960 -0
- data/lib/codebase_index/extractors/phlex_extractor.rb +252 -0
- data/lib/codebase_index/extractors/policy_extractor.rb +214 -0
- data/lib/codebase_index/extractors/poro_extractor.rb +246 -0
- data/lib/codebase_index/extractors/pundit_extractor.rb +223 -0
- data/lib/codebase_index/extractors/rails_source_extractor.rb +473 -0
- data/lib/codebase_index/extractors/rake_task_extractor.rb +343 -0
- data/lib/codebase_index/extractors/route_extractor.rb +181 -0
- data/lib/codebase_index/extractors/scheduled_job_extractor.rb +331 -0
- data/lib/codebase_index/extractors/serializer_extractor.rb +334 -0
- data/lib/codebase_index/extractors/service_extractor.rb +254 -0
- data/lib/codebase_index/extractors/shared_dependency_scanner.rb +91 -0
- data/lib/codebase_index/extractors/shared_utility_methods.rb +99 -0
- data/lib/codebase_index/extractors/state_machine_extractor.rb +398 -0
- data/lib/codebase_index/extractors/test_mapping_extractor.rb +225 -0
- data/lib/codebase_index/extractors/validator_extractor.rb +225 -0
- data/lib/codebase_index/extractors/view_component_extractor.rb +310 -0
- data/lib/codebase_index/extractors/view_template_extractor.rb +261 -0
- data/lib/codebase_index/feedback/gap_detector.rb +89 -0
- data/lib/codebase_index/feedback/store.rb +119 -0
- data/lib/codebase_index/flow_analysis/operation_extractor.rb +209 -0
- data/lib/codebase_index/flow_analysis/response_code_mapper.rb +154 -0
- data/lib/codebase_index/flow_assembler.rb +290 -0
- data/lib/codebase_index/flow_document.rb +191 -0
- data/lib/codebase_index/flow_precomputer.rb +102 -0
- data/lib/codebase_index/formatting/base.rb +40 -0
- data/lib/codebase_index/formatting/claude_adapter.rb +98 -0
- data/lib/codebase_index/formatting/generic_adapter.rb +56 -0
- data/lib/codebase_index/formatting/gpt_adapter.rb +64 -0
- data/lib/codebase_index/formatting/human_adapter.rb +78 -0
- data/lib/codebase_index/graph_analyzer.rb +374 -0
- data/lib/codebase_index/mcp/index_reader.rb +394 -0
- data/lib/codebase_index/mcp/renderers/claude_renderer.rb +81 -0
- data/lib/codebase_index/mcp/renderers/json_renderer.rb +17 -0
- data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +352 -0
- data/lib/codebase_index/mcp/renderers/plain_renderer.rb +240 -0
- data/lib/codebase_index/mcp/server.rb +935 -0
- data/lib/codebase_index/mcp/tool_response_renderer.rb +62 -0
- data/lib/codebase_index/model_name_cache.rb +51 -0
- data/lib/codebase_index/notion/client.rb +217 -0
- data/lib/codebase_index/notion/exporter.rb +219 -0
- data/lib/codebase_index/notion/mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/column_mapper.rb +65 -0
- data/lib/codebase_index/notion/mappers/migration_mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/model_mapper.rb +164 -0
- data/lib/codebase_index/notion/rate_limiter.rb +68 -0
- data/lib/codebase_index/observability/health_check.rb +81 -0
- data/lib/codebase_index/observability/instrumentation.rb +34 -0
- data/lib/codebase_index/observability/structured_logger.rb +75 -0
- data/lib/codebase_index/operator/error_escalator.rb +81 -0
- data/lib/codebase_index/operator/pipeline_guard.rb +99 -0
- data/lib/codebase_index/operator/status_reporter.rb +80 -0
- data/lib/codebase_index/railtie.rb +26 -0
- data/lib/codebase_index/resilience/circuit_breaker.rb +99 -0
- data/lib/codebase_index/resilience/index_validator.rb +185 -0
- data/lib/codebase_index/resilience/retryable_provider.rb +108 -0
- data/lib/codebase_index/retrieval/context_assembler.rb +249 -0
- data/lib/codebase_index/retrieval/query_classifier.rb +131 -0
- data/lib/codebase_index/retrieval/ranker.rb +273 -0
- data/lib/codebase_index/retrieval/search_executor.rb +327 -0
- data/lib/codebase_index/retriever.rb +160 -0
- data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +190 -0
- data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +78 -0
- data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +18 -0
- data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +275 -0
- data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +143 -0
- data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +139 -0
- data/lib/codebase_index/ruby_analyzer.rb +87 -0
- data/lib/codebase_index/session_tracer/file_store.rb +111 -0
- data/lib/codebase_index/session_tracer/middleware.rb +143 -0
- data/lib/codebase_index/session_tracer/redis_store.rb +112 -0
- data/lib/codebase_index/session_tracer/session_flow_assembler.rb +263 -0
- data/lib/codebase_index/session_tracer/session_flow_document.rb +223 -0
- data/lib/codebase_index/session_tracer/solid_cache_store.rb +145 -0
- data/lib/codebase_index/session_tracer/store.rb +67 -0
- data/lib/codebase_index/storage/graph_store.rb +120 -0
- data/lib/codebase_index/storage/metadata_store.rb +169 -0
- data/lib/codebase_index/storage/pgvector.rb +163 -0
- data/lib/codebase_index/storage/qdrant.rb +172 -0
- data/lib/codebase_index/storage/vector_store.rb +156 -0
- data/lib/codebase_index/temporal/snapshot_store.rb +341 -0
- data/lib/codebase_index/version.rb +5 -0
- data/lib/codebase_index.rb +223 -0
- data/lib/generators/codebase_index/install_generator.rb +32 -0
- data/lib/generators/codebase_index/pgvector_generator.rb +37 -0
- data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +15 -0
- data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +43 -0
- data/lib/tasks/codebase_index.rake +583 -0
- data/lib/tasks/codebase_index_evaluation.rake +115 -0
- metadata +252 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# @see CodebaseIndex
|
|
4
|
+
module CodebaseIndex
|
|
5
|
+
class Error < StandardError; end unless defined?(CodebaseIndex::Error)
|
|
6
|
+
|
|
7
|
+
module Console
|
|
8
|
+
class SqlValidationError < CodebaseIndex::Error; end
|
|
9
|
+
|
|
10
|
+
# Validates SQL strings for read-only safety.
|
|
11
|
+
#
|
|
12
|
+
# Allows only SELECT and WITH...SELECT statements. Rejects DML (INSERT,
|
|
13
|
+
# UPDATE, DELETE), DDL (CREATE, DROP, ALTER, TRUNCATE), and administrative
|
|
14
|
+
# commands (GRANT, REVOKE). Also rejects multiple statements (semicolons).
|
|
15
|
+
#
|
|
16
|
+
# Uses pattern-based validation, not full SQL parsing.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# validator = SqlValidator.new
|
|
20
|
+
# validator.validate!('SELECT * FROM users') # => true
|
|
21
|
+
# validator.validate!('DELETE FROM users') # => raises SqlValidationError
|
|
22
|
+
# validator.valid?('SELECT 1') # => true
|
|
23
|
+
#
|
|
24
|
+
class SqlValidator
|
|
25
|
+
# Forbidden statement prefixes (case-insensitive).
|
|
26
|
+
FORBIDDEN_KEYWORDS = %w[
|
|
27
|
+
INSERT UPDATE DELETE DROP ALTER TRUNCATE CREATE GRANT REVOKE
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
# Keywords that are forbidden anywhere in the SQL (not just at start).
|
|
31
|
+
BODY_FORBIDDEN_KEYWORDS = %w[UNION INTO COPY].freeze
|
|
32
|
+
|
|
33
|
+
# Dangerous functions that can be used for DoS or file access.
|
|
34
|
+
DANGEROUS_FUNCTIONS = %w[
|
|
35
|
+
pg_sleep lo_import lo_export pg_read_file pg_write_file
|
|
36
|
+
load_file sleep benchmark
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
# Allowed statement prefixes (case-insensitive).
|
|
40
|
+
ALLOWED_PREFIXES = /\A\s*(SELECT|WITH|EXPLAIN)\b/i
|
|
41
|
+
|
|
42
|
+
# @return [true]
|
|
43
|
+
# @raise [SqlValidationError] if the SQL is not a safe read-only statement
|
|
44
|
+
def validate!(sql) # rubocop:disable Naming/PredicateMethod
|
|
45
|
+
raise SqlValidationError, 'SQL is empty' if sql.nil? || sql.strip.empty?
|
|
46
|
+
|
|
47
|
+
normalized = sql.strip
|
|
48
|
+
|
|
49
|
+
# Reject multiple statements (semicolons not inside string literals)
|
|
50
|
+
if contains_multiple_statements?(normalized)
|
|
51
|
+
raise SqlValidationError, 'Rejected: multiple statements are not allowed'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check for forbidden keywords at statement start
|
|
55
|
+
check_forbidden_keywords!(normalized)
|
|
56
|
+
|
|
57
|
+
# Check for writable CTEs (before body keywords to give better error messages)
|
|
58
|
+
check_writable_ctes!(normalized)
|
|
59
|
+
|
|
60
|
+
# Check for forbidden keywords anywhere in the SQL body
|
|
61
|
+
check_body_forbidden_keywords!(normalized)
|
|
62
|
+
|
|
63
|
+
# Check for dangerous functions
|
|
64
|
+
check_dangerous_functions!(normalized)
|
|
65
|
+
|
|
66
|
+
# After stripping comments, check again for forbidden keywords that might have been hidden
|
|
67
|
+
check_forbidden_keywords_in_body!(normalized)
|
|
68
|
+
|
|
69
|
+
# Must start with an allowed prefix
|
|
70
|
+
unless normalized.match?(ALLOWED_PREFIXES)
|
|
71
|
+
raise SqlValidationError, 'Rejected: SQL must start with SELECT, WITH, or EXPLAIN'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if SQL is valid without raising.
|
|
78
|
+
#
|
|
79
|
+
# @param sql [String] SQL string to validate
|
|
80
|
+
# @return [Boolean]
|
|
81
|
+
def valid?(sql)
|
|
82
|
+
validate!(sql)
|
|
83
|
+
true
|
|
84
|
+
rescue SqlValidationError
|
|
85
|
+
false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Check if the SQL contains multiple statements separated by semicolons.
|
|
91
|
+
# Strips SQL comments and string literals before checking.
|
|
92
|
+
#
|
|
93
|
+
# @param sql [String]
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
def contains_multiple_statements?(sql)
|
|
96
|
+
# Strip SQL comments before checking
|
|
97
|
+
stripped = sql.gsub(/--[^\n]*/, '') # line comments
|
|
98
|
+
stripped = stripped.gsub(%r{/\*.*?\*/}m, '') # block comments
|
|
99
|
+
# Strip single-quoted strings to avoid false positives
|
|
100
|
+
stripped = stripped.gsub(/'[^']*'/, '')
|
|
101
|
+
stripped.include?(';')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Check if the SQL starts with a forbidden keyword.
|
|
105
|
+
#
|
|
106
|
+
# @param sql [String]
|
|
107
|
+
# @raise [SqlValidationError] if a forbidden keyword is found
|
|
108
|
+
def check_forbidden_keywords!(sql)
|
|
109
|
+
FORBIDDEN_KEYWORDS.each do |keyword|
|
|
110
|
+
if sql.match?(/\A\s*#{keyword}\b/i)
|
|
111
|
+
raise SqlValidationError, "Rejected: #{keyword} statements are not allowed"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Check if the SQL contains forbidden keywords anywhere in the body.
|
|
117
|
+
#
|
|
118
|
+
# @param sql [String]
|
|
119
|
+
# @raise [SqlValidationError] if a forbidden keyword is found
|
|
120
|
+
def check_body_forbidden_keywords!(sql)
|
|
121
|
+
BODY_FORBIDDEN_KEYWORDS.each do |keyword|
|
|
122
|
+
raise SqlValidationError, "Rejected: #{keyword} is not allowed" if sql.match?(/\b#{keyword}\b/i)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Check if the SQL contains writable CTEs (WITH...DELETE/UPDATE/INSERT).
|
|
127
|
+
#
|
|
128
|
+
# @param sql [String]
|
|
129
|
+
# @raise [SqlValidationError] if a writable CTE is found
|
|
130
|
+
def check_writable_ctes!(sql)
|
|
131
|
+
return unless sql.match?(/WITH\s+\w+\s+AS\s*\(\s*(DELETE|UPDATE|INSERT)\b/i)
|
|
132
|
+
|
|
133
|
+
raise SqlValidationError, 'Rejected: writable CTEs are not allowed'
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Check if the SQL calls dangerous functions.
|
|
137
|
+
#
|
|
138
|
+
# @param sql [String]
|
|
139
|
+
# @raise [SqlValidationError] if a dangerous function is found
|
|
140
|
+
def check_dangerous_functions!(sql)
|
|
141
|
+
DANGEROUS_FUNCTIONS.each do |func|
|
|
142
|
+
if sql.match?(/\b#{func}\s*\(/i)
|
|
143
|
+
raise SqlValidationError, "Rejected: dangerous function #{func} is not allowed"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Check if the SQL contains forbidden keywords anywhere in the body after stripping comments.
|
|
149
|
+
# This catches comment-hidden injections like "SELECT 1 --;\nDELETE FROM users".
|
|
150
|
+
#
|
|
151
|
+
# @param sql [String]
|
|
152
|
+
# @raise [SqlValidationError] if a forbidden keyword is found
|
|
153
|
+
def check_forbidden_keywords_in_body!(sql)
|
|
154
|
+
# Strip comments to reveal hidden statements
|
|
155
|
+
stripped = sql.gsub(/--[^\n]*/, '') # line comments
|
|
156
|
+
stripped = stripped.gsub(%r{/\*.*?\*/}m, '') # block comments
|
|
157
|
+
|
|
158
|
+
# Check if any forbidden keyword appears anywhere (not just at start)
|
|
159
|
+
FORBIDDEN_KEYWORDS.each do |keyword|
|
|
160
|
+
# Look for keyword as a whole word anywhere in the stripped SQL
|
|
161
|
+
next unless stripped.match?(/\b#{keyword}\b/i)
|
|
162
|
+
|
|
163
|
+
# Make sure it's not at the very start (already checked)
|
|
164
|
+
unless stripped.match?(/\A\s*#{keyword}\b/i)
|
|
165
|
+
raise SqlValidationError,
|
|
166
|
+
"Rejected: #{keyword} statements are not allowed (found in SQL body)"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Console
|
|
5
|
+
module Tools
|
|
6
|
+
# Tier 1: MVP read-only tools for querying live Rails data.
|
|
7
|
+
#
|
|
8
|
+
# Each method builds a bridge request hash from validated parameters.
|
|
9
|
+
# The bridge executes the query against the Rails database.
|
|
10
|
+
#
|
|
11
|
+
module Tier1
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Count records matching scope conditions.
|
|
15
|
+
#
|
|
16
|
+
# @param model [String] Model name
|
|
17
|
+
# @param scope [Hash, nil] Filter conditions
|
|
18
|
+
# @return [Hash] Bridge request
|
|
19
|
+
def console_count(model:, scope: nil)
|
|
20
|
+
{ tool: 'count', params: { model: model, scope: scope }.compact }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Random sample of records.
|
|
24
|
+
#
|
|
25
|
+
# @param model [String] Model name
|
|
26
|
+
# @param scope [Hash, nil] Filter conditions
|
|
27
|
+
# @param limit [Integer] Max records (default: 5, max: 25)
|
|
28
|
+
# @param columns [Array<String>, nil] Columns to include
|
|
29
|
+
# @return [Hash] Bridge request
|
|
30
|
+
def console_sample(model:, scope: nil, limit: 5, columns: nil)
|
|
31
|
+
limit = [limit, 25].min
|
|
32
|
+
{ tool: 'sample', params: { model: model, scope: scope, limit: limit, columns: columns }.compact }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Find a single record by primary key or unique column.
|
|
36
|
+
#
|
|
37
|
+
# @param model [String] Model name
|
|
38
|
+
# @param id [Integer, nil] Primary key value
|
|
39
|
+
# @param by [Hash, nil] Unique column lookup (e.g., { email: "x@y.com" })
|
|
40
|
+
# @param columns [Array<String>, nil] Columns to include
|
|
41
|
+
# @return [Hash] Bridge request
|
|
42
|
+
def console_find(model:, id: nil, by: nil, columns: nil)
|
|
43
|
+
{ tool: 'find', params: { model: model, id: id, by: by, columns: columns }.compact }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Extract column values.
|
|
47
|
+
#
|
|
48
|
+
# @param model [String] Model name
|
|
49
|
+
# @param columns [Array<String>] Column names to pluck
|
|
50
|
+
# @param scope [Hash, nil] Filter conditions
|
|
51
|
+
# @param limit [Integer] Max records (default: 100, max: 1000)
|
|
52
|
+
# @param distinct [Boolean] Return unique values only
|
|
53
|
+
# @return [Hash] Bridge request
|
|
54
|
+
def console_pluck(model:, columns:, scope: nil, limit: 100, distinct: false)
|
|
55
|
+
limit = [limit, 1000].min
|
|
56
|
+
{ tool: 'pluck', params: { model: model, columns: columns, scope: scope,
|
|
57
|
+
limit: limit, distinct: distinct }.compact }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Run aggregate function on a column.
|
|
61
|
+
#
|
|
62
|
+
# @param model [String] Model name
|
|
63
|
+
# @param function [String] One of: sum, avg, minimum, maximum
|
|
64
|
+
# @param column [String] Column to aggregate
|
|
65
|
+
# @param scope [Hash, nil] Filter conditions
|
|
66
|
+
# @return [Hash] Bridge request
|
|
67
|
+
def console_aggregate(model:, function:, column:, scope: nil)
|
|
68
|
+
{ tool: 'aggregate', params: { model: model, function: function, column: column, scope: scope }.compact }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Count associated records.
|
|
72
|
+
#
|
|
73
|
+
# @param model [String] Model name
|
|
74
|
+
# @param id [Integer] Record primary key
|
|
75
|
+
# @param association [String] Association name
|
|
76
|
+
# @param scope [Hash, nil] Filter on the association
|
|
77
|
+
# @return [Hash] Bridge request
|
|
78
|
+
def console_association_count(model:, id:, association:, scope: nil)
|
|
79
|
+
{ tool: 'association_count',
|
|
80
|
+
params: { model: model, id: id, association: association, scope: scope }.compact }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get database schema for a model.
|
|
84
|
+
#
|
|
85
|
+
# @param model [String] Model name
|
|
86
|
+
# @param include_indexes [Boolean] Include index information
|
|
87
|
+
# @return [Hash] Bridge request
|
|
88
|
+
def console_schema(model:, include_indexes: false)
|
|
89
|
+
{ tool: 'schema', params: { model: model, include_indexes: include_indexes } }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Recently created/updated records.
|
|
93
|
+
#
|
|
94
|
+
# @param model [String] Model name
|
|
95
|
+
# @param order_by [String] Column to sort by (default: created_at)
|
|
96
|
+
# @param direction [String] Sort direction (default: desc)
|
|
97
|
+
# @param limit [Integer] Max records (default: 10, max: 50)
|
|
98
|
+
# @param scope [Hash, nil] Filter conditions
|
|
99
|
+
# @param columns [Array<String>, nil] Columns to include
|
|
100
|
+
# @return [Hash] Bridge request
|
|
101
|
+
# rubocop:disable Metrics/ParameterLists
|
|
102
|
+
def console_recent(model:, order_by: 'created_at', direction: 'desc', limit: 10, scope: nil, columns: nil)
|
|
103
|
+
limit = [limit, 50].min
|
|
104
|
+
{ tool: 'recent', params: { model: model, order_by: order_by, direction: direction,
|
|
105
|
+
limit: limit, scope: scope, columns: columns }.compact }
|
|
106
|
+
end
|
|
107
|
+
# rubocop:enable Metrics/ParameterLists
|
|
108
|
+
|
|
109
|
+
# System health check.
|
|
110
|
+
#
|
|
111
|
+
# @return [Hash] Bridge request
|
|
112
|
+
def console_status
|
|
113
|
+
{ tool: 'status', params: {} }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Console
|
|
5
|
+
module Tools
|
|
6
|
+
# Tier 2: Domain-aware tools for querying live Rails data.
|
|
7
|
+
#
|
|
8
|
+
# These tools build on Tier 1 primitives to provide higher-level
|
|
9
|
+
# domain operations: model diagnostics, data snapshots, validation,
|
|
10
|
+
# settings management, policy checks, and decorator invocation.
|
|
11
|
+
#
|
|
12
|
+
# Each method builds a bridge request hash from validated parameters.
|
|
13
|
+
# The bridge executes the operation against the Rails environment.
|
|
14
|
+
#
|
|
15
|
+
module Tier2
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Diagnose a model by composing multiple queries: count, recent records, and aggregates.
|
|
19
|
+
#
|
|
20
|
+
# @param model [String] Model name
|
|
21
|
+
# @param scope [Hash, nil] Filter conditions
|
|
22
|
+
# @param sample_size [Integer] Number of sample records (default: 5, max: 25)
|
|
23
|
+
# @return [Hash] Bridge request
|
|
24
|
+
def console_diagnose_model(model:, scope: nil, sample_size: 5)
|
|
25
|
+
sample_size = [sample_size, 25].min
|
|
26
|
+
{ tool: 'diagnose_model', params: { model: model, scope: scope, sample_size: sample_size }.compact }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Snapshot a record with its associations for debugging.
|
|
30
|
+
#
|
|
31
|
+
# @param model [String] Model name
|
|
32
|
+
# @param id [Integer] Record primary key
|
|
33
|
+
# @param associations [Array<String>, nil] Association names to include
|
|
34
|
+
# @param depth [Integer] Association traversal depth (default: 1, max: 3)
|
|
35
|
+
# @return [Hash] Bridge request
|
|
36
|
+
def console_data_snapshot(model:, id:, associations: nil, depth: 1)
|
|
37
|
+
depth = [depth, 3].min
|
|
38
|
+
{ tool: 'data_snapshot',
|
|
39
|
+
params: { model: model, id: id, associations: associations, depth: depth }.compact }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Run validations on an existing record, optionally with changed attributes.
|
|
43
|
+
#
|
|
44
|
+
# @param model [String] Model name
|
|
45
|
+
# @param id [Integer] Record primary key
|
|
46
|
+
# @param attributes [Hash, nil] Attributes to set before validating
|
|
47
|
+
# @return [Hash] Bridge request
|
|
48
|
+
def console_validate_record(model:, id:, attributes: nil)
|
|
49
|
+
{ tool: 'validate_record', params: { model: model, id: id, attributes: attributes }.compact }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Check a configuration setting value.
|
|
53
|
+
#
|
|
54
|
+
# @param key [String] Setting key
|
|
55
|
+
# @param namespace [String, nil] Setting namespace
|
|
56
|
+
# @return [Hash] Bridge request
|
|
57
|
+
def console_check_setting(key:, namespace: nil)
|
|
58
|
+
{ tool: 'check_setting', params: { key: key, namespace: namespace }.compact }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Update a configuration setting (requires human confirmation).
|
|
62
|
+
#
|
|
63
|
+
# @param key [String] Setting key
|
|
64
|
+
# @param value [Object] New value
|
|
65
|
+
# @param namespace [String, nil] Setting namespace
|
|
66
|
+
# @return [Hash] Bridge request with requires_confirmation flag
|
|
67
|
+
def console_update_setting(key:, value:, namespace: nil)
|
|
68
|
+
{ tool: 'update_setting',
|
|
69
|
+
params: { key: key, value: value, namespace: namespace }.compact,
|
|
70
|
+
requires_confirmation: true }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check authorization policy for a record and user.
|
|
74
|
+
#
|
|
75
|
+
# @param model [String] Model name
|
|
76
|
+
# @param id [Integer] Record primary key
|
|
77
|
+
# @param user_id [Integer] User to check authorization for
|
|
78
|
+
# @param action [String] Policy action (e.g., "update", "destroy")
|
|
79
|
+
# @return [Hash] Bridge request
|
|
80
|
+
def console_check_policy(model:, id:, user_id:, action:)
|
|
81
|
+
{ tool: 'check_policy',
|
|
82
|
+
params: { model: model, id: id, user_id: user_id, action: action } }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validate attributes against a model without persisting.
|
|
86
|
+
#
|
|
87
|
+
# @param model [String] Model name
|
|
88
|
+
# @param attributes [Hash] Attributes to validate
|
|
89
|
+
# @param context [String, nil] Validation context (e.g., "create", "update")
|
|
90
|
+
# @return [Hash] Bridge request
|
|
91
|
+
def console_validate_with(model:, attributes:, context: nil)
|
|
92
|
+
{ tool: 'validate_with', params: { model: model, attributes: attributes, context: context }.compact }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check feature eligibility for a record.
|
|
96
|
+
#
|
|
97
|
+
# @param model [String] Model name
|
|
98
|
+
# @param id [Integer] Record primary key
|
|
99
|
+
# @param feature [String] Feature name to check
|
|
100
|
+
# @return [Hash] Bridge request
|
|
101
|
+
def console_check_eligibility(model:, id:, feature:)
|
|
102
|
+
{ tool: 'check_eligibility', params: { model: model, id: id, feature: feature } }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Invoke a decorator on a record and return computed attributes.
|
|
106
|
+
#
|
|
107
|
+
# @param model [String] Model name
|
|
108
|
+
# @param id [Integer] Record primary key
|
|
109
|
+
# @param methods [Array<String>, nil] Specific decorator methods to call
|
|
110
|
+
# @return [Hash] Bridge request
|
|
111
|
+
def console_decorate(model:, id:, methods: nil)
|
|
112
|
+
{ tool: 'decorate', params: { model: model, id: id, methods: methods }.compact }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Console
|
|
5
|
+
module Tools
|
|
6
|
+
# Tier 3: Analytics tools for monitoring live Rails application state.
|
|
7
|
+
#
|
|
8
|
+
# Provides request performance metrics, job queue monitoring,
|
|
9
|
+
# cache statistics, and ActionCable channel status. Job-related
|
|
10
|
+
# tools delegate to backend-specific adapters (Sidekiq, Solid Queue, GoodJob).
|
|
11
|
+
#
|
|
12
|
+
# Each method builds a bridge request hash from validated parameters.
|
|
13
|
+
# The bridge executes the query against the Rails environment.
|
|
14
|
+
#
|
|
15
|
+
module Tier3
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# List slowest endpoints by response time.
|
|
19
|
+
#
|
|
20
|
+
# @param limit [Integer] Max endpoints to return (default: 10, max: 100)
|
|
21
|
+
# @param period [String] Time period (default: "1h")
|
|
22
|
+
# @return [Hash] Bridge request
|
|
23
|
+
def console_slow_endpoints(limit: 10, period: '1h')
|
|
24
|
+
limit = [limit, 100].min
|
|
25
|
+
{ tool: 'slow_endpoints', params: { limit: limit, period: period } }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get error rates by controller or overall.
|
|
29
|
+
#
|
|
30
|
+
# @param period [String] Time period (default: "1h")
|
|
31
|
+
# @param controller [String, nil] Filter by controller name
|
|
32
|
+
# @return [Hash] Bridge request
|
|
33
|
+
def console_error_rates(period: '1h', controller: nil)
|
|
34
|
+
{ tool: 'error_rates', params: { period: period, controller: controller }.compact }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get request throughput over time.
|
|
38
|
+
#
|
|
39
|
+
# @param period [String] Time period (default: "1h")
|
|
40
|
+
# @param interval [String] Aggregation interval (default: "5m")
|
|
41
|
+
# @return [Hash] Bridge request
|
|
42
|
+
def console_throughput(period: '1h', interval: '5m')
|
|
43
|
+
{ tool: 'throughput', params: { period: period, interval: interval } }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get job queue statistics.
|
|
47
|
+
#
|
|
48
|
+
# @param queue [String, nil] Filter by queue name
|
|
49
|
+
# @return [Hash] Bridge request
|
|
50
|
+
def console_job_queues(queue: nil)
|
|
51
|
+
{ tool: 'job_queues', params: { queue: queue }.compact }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# List recent job failures.
|
|
55
|
+
#
|
|
56
|
+
# @param limit [Integer] Max failures to return (default: 10, max: 100)
|
|
57
|
+
# @param queue [String, nil] Filter by queue name
|
|
58
|
+
# @return [Hash] Bridge request
|
|
59
|
+
def console_job_failures(limit: 10, queue: nil)
|
|
60
|
+
limit = [limit, 100].min
|
|
61
|
+
{ tool: 'job_failures', params: { limit: limit, queue: queue }.compact }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Find a job by ID, optionally retry it (requires confirmation).
|
|
65
|
+
#
|
|
66
|
+
# @param job_id [String] Job identifier
|
|
67
|
+
# @param retry_job [Boolean, nil] Whether to retry the job
|
|
68
|
+
# @return [Hash] Bridge request, with requires_confirmation if retry requested
|
|
69
|
+
def console_job_find(job_id:, retry_job: nil)
|
|
70
|
+
result = { tool: 'job_find', params: { job_id: job_id, retry: retry_job }.compact }
|
|
71
|
+
result[:requires_confirmation] = true if retry_job
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# List scheduled/upcoming jobs.
|
|
76
|
+
#
|
|
77
|
+
# @param limit [Integer] Max jobs to return (default: 20, max: 100)
|
|
78
|
+
# @return [Hash] Bridge request
|
|
79
|
+
def console_job_schedule(limit: 20)
|
|
80
|
+
limit = [limit, 100].min
|
|
81
|
+
{ tool: 'job_schedule', params: { limit: limit } }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get Redis server information.
|
|
85
|
+
#
|
|
86
|
+
# @param section [String, nil] Redis INFO section filter (e.g., "memory", "stats")
|
|
87
|
+
# @return [Hash] Bridge request
|
|
88
|
+
def console_redis_info(section: nil)
|
|
89
|
+
{ tool: 'redis_info', params: { section: section }.compact }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get cache store statistics.
|
|
93
|
+
#
|
|
94
|
+
# @param namespace [String, nil] Cache namespace filter
|
|
95
|
+
# @return [Hash] Bridge request
|
|
96
|
+
def console_cache_stats(namespace: nil)
|
|
97
|
+
{ tool: 'cache_stats', params: { namespace: namespace }.compact }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get ActionCable channel status.
|
|
101
|
+
#
|
|
102
|
+
# @param channel [String, nil] Filter by channel name
|
|
103
|
+
# @return [Hash] Bridge request
|
|
104
|
+
def console_channel_status(channel: nil)
|
|
105
|
+
{ tool: 'channel_status', params: { channel: channel }.compact }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Console
|
|
5
|
+
module Tools
|
|
6
|
+
# Tier 4: Guarded tools requiring confirmation or SQL validation.
|
|
7
|
+
#
|
|
8
|
+
# - `console_eval` — Arbitrary Ruby execution with confirmation + timeout
|
|
9
|
+
# - `console_sql` — Read-only SQL (validated by SqlValidator)
|
|
10
|
+
# - `console_query` — Enhanced query builder with joins/grouping
|
|
11
|
+
#
|
|
12
|
+
# Each method builds a bridge request hash. The bridge executes against
|
|
13
|
+
# the live Rails environment.
|
|
14
|
+
#
|
|
15
|
+
module Tier4
|
|
16
|
+
MAX_EVAL_TIMEOUT = 30
|
|
17
|
+
MIN_EVAL_TIMEOUT = 1
|
|
18
|
+
DEFAULT_EVAL_TIMEOUT = 10
|
|
19
|
+
MAX_SQL_LIMIT = 10_000
|
|
20
|
+
MAX_QUERY_LIMIT = 10_000
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Arbitrary Ruby evaluation with timeout.
|
|
25
|
+
#
|
|
26
|
+
# @param code [String] Ruby code to execute
|
|
27
|
+
# @param timeout [Integer] Execution timeout in seconds (default 10, max 30)
|
|
28
|
+
# @return [Hash] Bridge request
|
|
29
|
+
def console_eval(code:, timeout: DEFAULT_EVAL_TIMEOUT)
|
|
30
|
+
timeout = timeout.clamp(MIN_EVAL_TIMEOUT, MAX_EVAL_TIMEOUT)
|
|
31
|
+
{ tool: 'eval', params: { code: code, timeout: timeout } }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Read-only SQL execution with validation.
|
|
35
|
+
#
|
|
36
|
+
# @param sql [String] SQL query (must be SELECT or WITH...SELECT)
|
|
37
|
+
# @param validator [SqlValidator] SQL validator instance
|
|
38
|
+
# @param limit [Integer, nil] Optional row limit (max 10000)
|
|
39
|
+
# @return [Hash] Bridge request
|
|
40
|
+
# @raise [SqlValidationError] if SQL is not read-only
|
|
41
|
+
def console_sql(sql:, validator:, limit: nil)
|
|
42
|
+
validator.validate!(sql)
|
|
43
|
+
limit = [limit, MAX_SQL_LIMIT].min if limit
|
|
44
|
+
{ tool: 'sql', params: { sql: sql, limit: limit }.compact }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Enhanced query builder with joins and grouping.
|
|
48
|
+
#
|
|
49
|
+
# @param model [String] Model name
|
|
50
|
+
# @param select [Array<String>] Columns to select
|
|
51
|
+
# @param joins [Array<String>, nil] Associations to join
|
|
52
|
+
# @param group_by [Array<String>, nil] Columns to group by
|
|
53
|
+
# @param having [String, nil] HAVING clause
|
|
54
|
+
# @param order [Hash, nil] Order specification (e.g., { id: :desc })
|
|
55
|
+
# @param scope [Hash, nil] Filter conditions
|
|
56
|
+
# @param limit [Integer, nil] Row limit (max 10000)
|
|
57
|
+
# @return [Hash] Bridge request
|
|
58
|
+
# rubocop:disable Metrics/ParameterLists
|
|
59
|
+
def console_query(model:, select:, joins: nil, group_by: nil, having: nil, order: nil, scope: nil, limit: nil)
|
|
60
|
+
limit = [limit, MAX_QUERY_LIMIT].min if limit
|
|
61
|
+
{
|
|
62
|
+
tool: 'query',
|
|
63
|
+
params: {
|
|
64
|
+
model: model,
|
|
65
|
+
select: select,
|
|
66
|
+
joins: joins,
|
|
67
|
+
group_by: group_by,
|
|
68
|
+
having: having,
|
|
69
|
+
order: order,
|
|
70
|
+
scope: scope,
|
|
71
|
+
limit: limit
|
|
72
|
+
}.compact
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/ParameterLists
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|