rails-ai-context 5.32.2 → 5.34.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 +4 -4
- data/CHANGELOG.md +1491 -0
- data/CONTEXT.md +27 -3
- data/README.md +16 -12
- data/Rakefile +6 -0
- data/app/controllers/rails_ai_context/mcp_controller.rb +112 -84
- data/config/routes.rb +2 -3
- data/docs/ARCHITECTURE.md +7 -5
- data/docs/CLI.md +79 -6
- data/docs/COMPATIBILITY.md +75 -1
- data/docs/CONFIGURATION.md +30 -11
- data/docs/CUSTOM_TOOLS.md +2 -1
- data/docs/FAQ.md +16 -6
- data/docs/GUIDE.md +117 -71
- data/docs/INTROSPECTORS.md +10 -8
- data/docs/QUICKSTART.md +3 -3
- data/docs/RECIPES.md +1 -1
- data/docs/SECURITY.md +109 -25
- data/docs/SETUP.md +30 -6
- data/docs/STANDALONE.md +10 -3
- data/docs/TOOLS.md +152 -34
- data/docs/TROUBLESHOOTING.md +90 -12
- data/docs/adr/0003-shared-tool-cache-semantics.md +8 -0
- data/docs/adr/0005-workspace-mcp-entries.md +39 -0
- data/exe/rails-ai-context +733 -104
- data/lib/generators/rails_ai_context/install/install_generator.rb +341 -125
- data/lib/rails_ai_context/app_kind.rb +18 -5
- data/lib/rails_ai_context/ast_cache.rb +7 -0
- data/lib/rails_ai_context/base_mixins.rb +8 -8
- data/lib/rails_ai_context/boot_manager.rb +22 -3
- data/lib/rails_ai_context/change_watch.rb +14 -11
- data/lib/rails_ai_context/cli/app_root.rb +401 -0
- data/lib/rails_ai_context/cli/entry_boot.rb +58 -22
- data/lib/rails_ai_context/cli/tool_runner.rb +47 -21
- data/lib/rails_ai_context/code_reloader.rb +115 -1
- data/lib/rails_ai_context/concern_membership.rb +3 -1
- data/lib/rails_ai_context/concern_paths.rb +8 -3
- data/lib/rails_ai_context/configuration.rb +41 -9
- data/lib/rails_ai_context/context_file_report.rb +19 -2
- data/lib/rails_ai_context/database_file.rb +26 -0
- data/lib/rails_ai_context/doctor.rb +1122 -184
- data/lib/rails_ai_context/engine.rb +18 -1
- data/lib/rails_ai_context/erb_source.rb +19 -0
- data/lib/rails_ai_context/executable.rb +21 -0
- data/lib/rails_ai_context/facts_formatter.rb +5 -2
- data/lib/rails_ai_context/file_walk.rb +61 -10
- data/lib/rails_ai_context/fingerprinter/snapshot.rb +234 -0
- data/lib/rails_ai_context/fingerprinter.rb +40 -12
- data/lib/rails_ai_context/gem_lock.rb +186 -27
- data/lib/rails_ai_context/git_ignore.rb +22 -0
- data/lib/rails_ai_context/install/ai_tool.rb +31 -5
- data/lib/rails_ai_context/install/cleanup.rb +21 -0
- data/lib/rails_ai_context/install/program.rb +209 -35
- data/lib/rails_ai_context/install/selection_record.rb +258 -97
- data/lib/rails_ai_context/install/surface.rb +6 -1
- data/lib/rails_ai_context/install/validation_hook.rb +282 -0
- data/lib/rails_ai_context/install/workspace.rb +166 -0
- data/lib/rails_ai_context/install_mode.rb +71 -13
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -6
- data/lib/rails_ai_context/introspectors/action_source.rb +182 -0
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +1 -2
- data/lib/rails_ai_context/introspectors/active_record_settings.rb +1 -1
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -3
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/admin_resources.rb +1 -1
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +1 -1
- data/lib/rails_ai_context/introspectors/api_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +15 -10
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +24 -1
- data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -2
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +387 -60
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +15 -3
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/eager_load.rb +201 -9
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +10 -1
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +23 -10
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +1 -1
- data/lib/rails_ai_context/introspectors/habtm_join_tables.rb +1 -1
- data/lib/rails_ai_context/introspectors/helper_names.rb +1 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/job_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +12 -8
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +78 -4
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +1 -0
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/foreign_evals_listener.rb +61 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +13 -4
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +3 -2
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +29 -8
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +88 -12
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +27 -0
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +1 -1
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +83 -27
- data/lib/rails_ai_context/introspectors/migration_replay.rb +15 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +231 -23
- data/lib/rails_ai_context/introspectors/n_plus_one_scan.rb +620 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +11 -174
- data/lib/rails_ai_context/introspectors/route_introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +23 -3
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +66 -16
- data/lib/rails_ai_context/introspectors/schema_reader.rb +4 -3
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +39 -9
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +15 -6
- data/lib/rails_ai_context/introspectors/test_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +131 -12
- data/lib/rails_ai_context/introspectors/view_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +15 -1
- data/lib/rails_ai_context/legacy_cleanup.rb +17 -4
- data/lib/rails_ai_context/live_reload.rb +20 -20
- data/lib/rails_ai_context/mcp_config_generator.rb +916 -109
- data/lib/rails_ai_context/mcp_edge.rb +182 -32
- data/lib/rails_ai_context/middleware.rb +5 -1
- data/lib/rails_ai_context/migration_status.rb +88 -25
- data/lib/rails_ai_context/output_guard.rb +48 -2
- data/lib/rails_ai_context/package_json.rb +1 -1
- data/lib/rails_ai_context/path_resolver.rb +46 -13
- data/lib/rails_ai_context/payload.rb +92 -8
- data/lib/rails_ai_context/pending_migrations.rb +5 -1
- data/lib/rails_ai_context/portable_path.rb +3 -2
- data/lib/rails_ai_context/presets.rb +15 -11
- data/lib/rails_ai_context/redaction.rb +262 -51
- data/lib/rails_ai_context/resources.rb +55 -20
- data/lib/rails_ai_context/route_coverage.rb +8 -0
- data/lib/rails_ai_context/safe_file.rb +14 -1
- data/lib/rails_ai_context/safe_path.rb +101 -4
- data/lib/rails_ai_context/schema_adapter.rb +8 -0
- data/lib/rails_ai_context/schema_version.rb +9 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +88 -33
- data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +36 -34
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +21 -29
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +8 -6
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +2 -7
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +19 -16
- data/lib/rails_ai_context/serializers/guide_examples.rb +142 -0
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +146 -30
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +38 -15
- data/lib/rails_ai_context/serializers/section_facts.rb +44 -1
- data/lib/rails_ai_context/serializers/section_marker_writer.rb +94 -12
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +24 -12
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +244 -129
- data/lib/rails_ai_context/server.rb +304 -71
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +447 -372
- data/lib/rails_ai_context/tools/analyze_feature.rb +72 -18
- data/lib/rails_ai_context/tools/argument_schema.rb +49 -0
- data/lib/rails_ai_context/tools/base_tool.rb +367 -46
- data/lib/rails_ai_context/tools/brakeman_guard.rb +24 -0
- data/lib/rails_ai_context/tools/dependency_graph.rb +63 -28
- data/lib/rails_ai_context/tools/diagnose.rb +218 -11
- data/lib/rails_ai_context/tools/generate_test.rb +339 -39
- data/lib/rails_ai_context/tools/get_callbacks.rb +24 -13
- data/lib/rails_ai_context/tools/get_component_catalog.rb +1 -1
- data/lib/rails_ai_context/tools/get_concern.rb +91 -27
- data/lib/rails_ai_context/tools/get_config.rb +13 -4
- data/lib/rails_ai_context/tools/get_context.rb +71 -14
- data/lib/rails_ai_context/tools/get_controllers.rb +179 -44
- data/lib/rails_ai_context/tools/get_conventions.rb +28 -6
- data/lib/rails_ai_context/tools/get_edit_context.rb +27 -14
- data/lib/rails_ai_context/tools/get_env.rb +18 -9
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +3 -1
- data/lib/rails_ai_context/tools/get_gems.rb +1 -1
- data/lib/rails_ai_context/tools/get_helper_methods.rb +10 -6
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
- data/lib/rails_ai_context/tools/get_job_pattern.rb +3 -2
- data/lib/rails_ai_context/tools/get_mailers.rb +3 -3
- data/lib/rails_ai_context/tools/get_model_details.rb +30 -10
- data/lib/rails_ai_context/tools/get_partial_interface.rb +242 -23
- data/lib/rails_ai_context/tools/get_routes.rb +29 -8
- data/lib/rails_ai_context/tools/get_schema.rb +52 -19
- data/lib/rails_ai_context/tools/get_service_pattern.rb +24 -10
- data/lib/rails_ai_context/tools/get_stimulus.rb +37 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +18 -4
- data/lib/rails_ai_context/tools/get_turbo_map.rb +221 -58
- data/lib/rails_ai_context/tools/get_view.rb +14 -14
- data/lib/rails_ai_context/tools/migration_advisor.rb +355 -48
- data/lib/rails_ai_context/tools/onboard.rb +52 -8
- data/lib/rails_ai_context/tools/performance_check.rb +5 -3
- data/lib/rails_ai_context/tools/query.rb +914 -86
- data/lib/rails_ai_context/tools/read_logs.rb +69 -15
- data/lib/rails_ai_context/tools/review_changes.rb +215 -53
- data/lib/rails_ai_context/tools/runtime_info.rb +51 -1
- data/lib/rails_ai_context/tools/safe_call.rb +35 -14
- data/lib/rails_ai_context/tools/search_code.rb +243 -67
- data/lib/rails_ai_context/tools/search_docs.rb +4 -4
- data/lib/rails_ai_context/tools/security_scan.rb +143 -33
- data/lib/rails_ai_context/tools/session_context.rb +39 -5
- data/lib/rails_ai_context/tools/validate.rb +259 -40
- data/lib/rails_ai_context/tools/validate_semantics.rb +130 -56
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +67 -40
- data/lib/rails_ai_context/view_file.rb +41 -3
- data/lib/rails_ai_context/watcher.rb +51 -13
- data/lib/rails_ai_context.rb +21 -0
- data/server.json +1 -1
- metadata +14 -1
|
@@ -66,6 +66,10 @@ module RailsAiContext
|
|
|
66
66
|
|
|
67
67
|
stat = ActiveRecord::Base.connection_pool.stat
|
|
68
68
|
lines = [ "## Connection Pool", "" ]
|
|
69
|
+
# Read in-process, so the counts are the MCP server's own. 0 connections
|
|
70
|
+
# said nothing about an app server running beside it.
|
|
71
|
+
lines << "_This MCP server process's pool: the counts are its own connections. Each app server process keeps a pool of its own, of the size configured here._"
|
|
72
|
+
lines << ""
|
|
69
73
|
lines << "| Metric | Value |"
|
|
70
74
|
lines << "|--------|-------|"
|
|
71
75
|
lines << "| Pool size | #{stat[:size]} |"
|
|
@@ -233,10 +237,49 @@ module RailsAiContext
|
|
|
233
237
|
value.is_a?(Hash) ? value.map { |k, v| "#{k}: #{v}" }.join(", ") : value.to_s
|
|
234
238
|
end
|
|
235
239
|
|
|
240
|
+
# Whose numbers these are depends on where the store keeps its entries.
|
|
241
|
+
def cache_scope(cache)
|
|
242
|
+
case cache.class.name.demodulize
|
|
243
|
+
when "MemoryStore"
|
|
244
|
+
"_A MemoryStore lives inside one process, so these numbers are this MCP server's own. " \
|
|
245
|
+
"Each app server process holds entries of its own that no other process sees._"
|
|
246
|
+
when "NullStore" then "_A NullStore keeps nothing, in this process or any other._"
|
|
247
|
+
when "FileStore"
|
|
248
|
+
"_The app's processes on this machine share these files#{" under `#{relative_path(cache.cache_path)}`" if cache.respond_to?(:cache_path)}._"
|
|
249
|
+
else
|
|
250
|
+
"_The app's processes in this environment share this store, so these numbers are theirs too._"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def relative_path(path)
|
|
255
|
+
path.to_s.delete_prefix("#{rails_app.root}/")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# A store no other process can read is nobody's real cache.
|
|
259
|
+
PROCESS_STORES = /\A:(memory|null)_store\b/
|
|
260
|
+
|
|
261
|
+
# This process runs one environment's store; another environment that
|
|
262
|
+
# configures a shared one (Redis, Memcached, Solid Cache) keeps the
|
|
263
|
+
# app's real cache there.
|
|
264
|
+
def configured_elsewhere
|
|
265
|
+
config = cached_context[:env_config]
|
|
266
|
+
return [] unless config.is_a?(Hash)
|
|
267
|
+
|
|
268
|
+
Array(config[:environments]).filter_map do |env|
|
|
269
|
+
next if env[:name].to_s == rails_env_name.to_s
|
|
270
|
+
|
|
271
|
+
store = (env[:notable] || {})["cache_store"]
|
|
272
|
+
next unless store && !store.match?(PROCESS_STORES)
|
|
273
|
+
|
|
274
|
+
# The store's name; its options are rails_get_env_config's to show, unshortened.
|
|
275
|
+
"- #{env[:name]}: `#{store[/\A:\w+/] || store}` (`#{env[:file]}`)"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
236
279
|
def gather_cache
|
|
237
280
|
cache = Rails.cache
|
|
238
281
|
lines = [ "## Cache", "" ]
|
|
239
|
-
lines << "**Store:** #{cache.class.name.demodulize}"
|
|
282
|
+
lines << "**Store:** #{cache.class.name.demodulize}, this MCP server process's `Rails.cache` (#{rails_env_name})"
|
|
240
283
|
|
|
241
284
|
if cache.respond_to?(:stats)
|
|
242
285
|
cache_stat_lines(cache.stats).each { |line| lines << line }
|
|
@@ -263,6 +306,13 @@ module RailsAiContext
|
|
|
263
306
|
lines << "_Stats not available for #{cache.class.name}. Supported: Redis, MemoryStore._"
|
|
264
307
|
end
|
|
265
308
|
|
|
309
|
+
lines << "" << cache_scope(cache)
|
|
310
|
+
elsewhere = configured_elsewhere
|
|
311
|
+
if elsewhere.any?
|
|
312
|
+
lines.push("", "**The app's cache in other environments:**", *elsewhere,
|
|
313
|
+
"_`rails_get_env_config(environment:\"...\")` shows each one's options._")
|
|
314
|
+
end
|
|
315
|
+
|
|
266
316
|
lines << ""
|
|
267
317
|
lines
|
|
268
318
|
rescue => e
|
|
@@ -27,10 +27,31 @@ module RailsAiContext
|
|
|
27
27
|
# have its note consumed by the inner one.
|
|
28
28
|
discarded = discarded_detail(kwargs)
|
|
29
29
|
kwargs = listify_arrays(normalize_detail(kwargs))
|
|
30
|
-
|
|
30
|
+
# Before the call reads anything: an edit since the last call drops
|
|
31
|
+
# the caches and reloads the app's code (see the method).
|
|
32
|
+
RailsAiContext::Tools::BaseTool.refresh_if_files_changed!
|
|
33
|
+
|
|
34
|
+
answer = -> { answer_call(discarded) { super(**kwargs) } }
|
|
35
|
+
|
|
36
|
+
# Only the call the client made is its query. A composite tool answers
|
|
37
|
+
# by calling others, and each of those recorded itself, so one
|
|
38
|
+
# get_context read as four calls the client never made - and the
|
|
39
|
+
# first inner call took the outer one's params with it.
|
|
40
|
+
return answer.call if Thread.current[:rails_ai_context_call]
|
|
41
|
+
|
|
42
|
+
Thread.current[:rails_ai_context_call] = { tool: self, params: session_params(kwargs) }
|
|
43
|
+
begin
|
|
44
|
+
answer.call
|
|
45
|
+
ensure
|
|
46
|
+
Thread.current[:rails_ai_context_call] = nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
#
|
|
52
|
+
def answer_call(discarded)
|
|
53
|
+
# Held across the tool body: a reload another call runs must not
|
|
54
|
+
# unload constants while this call is reading them.
|
|
34
55
|
#
|
|
35
56
|
# The rescue sits INSIDE the block on purpose. `executor.wrap` reports
|
|
36
57
|
# anything that crosses it to the host app's error_reporter as
|
|
@@ -40,18 +61,16 @@ module RailsAiContext
|
|
|
40
61
|
RailsAiContext::CodeReloader.with_app_code do
|
|
41
62
|
begin
|
|
42
63
|
# One call is one run: what it reads per controller or per route is read once.
|
|
43
|
-
append_note(RailsAiContext::RunCache.around {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
append_note(RailsAiContext::RunCache.around { yield }, invalid_detail_note(discarded))
|
|
65
|
+
# ScriptError too: a SyntaxError or LoadError out of an app file the
|
|
66
|
+
# tool reached must cost that call, not the stdio server and every
|
|
67
|
+
# tool with it.
|
|
68
|
+
rescue StandardError, ScriptError => e
|
|
48
69
|
failure_response(e)
|
|
49
70
|
end
|
|
50
71
|
end
|
|
51
72
|
end
|
|
52
73
|
|
|
53
|
-
private
|
|
54
|
-
|
|
55
74
|
# The declared params are the contract, so a tool whose method takes
|
|
56
75
|
# **kwargs refuses the same keys as one that names them.
|
|
57
76
|
def unknown_params(kwargs)
|
|
@@ -93,7 +112,7 @@ module RailsAiContext
|
|
|
93
112
|
text = +"Tool #{label} failed: #{error.class}: #{error.message.to_s.lines.first&.strip}\n"
|
|
94
113
|
text << "At: #{origin}\n" unless origin.empty?
|
|
95
114
|
text << "Recovery: retry with a narrower query (a single table, model, or " \
|
|
96
|
-
"controller), or run `
|
|
115
|
+
"controller), or run `#{RailsAiContext.doctor_command}` to check app health."
|
|
97
116
|
MCP::Tool::Response.new([ { type: "text", text: text } ], error: true)
|
|
98
117
|
end
|
|
99
118
|
|
|
@@ -107,9 +126,11 @@ module RailsAiContext
|
|
|
107
126
|
return "(eval at #{portable_origin(eval_frame[1])})#{eval_frame[2]}"
|
|
108
127
|
end
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
# FILE:LINE, split at the line number: a Windows path has a colon of
|
|
130
|
+
# its own after the drive letter (D:/a/app/x.rb:1).
|
|
131
|
+
path, rest = frame.match(/\A(.+?):(\d+(?::.*)?)\z/m)&.captures || [ frame, nil ]
|
|
111
132
|
# A relative path (`(eval)`, `-e`) names nothing on this machine.
|
|
112
|
-
return frame unless
|
|
133
|
+
return frame unless RailsAiContext::SafePath.absolute?(path)
|
|
113
134
|
|
|
114
135
|
# This runs inside the rescue that answers a tool failure, so a raise
|
|
115
136
|
# here would leave the net and reach the client as a protocol error.
|
|
@@ -124,7 +145,7 @@ module RailsAiContext
|
|
|
124
145
|
rescue StandardError
|
|
125
146
|
path
|
|
126
147
|
end
|
|
127
|
-
relative = File.basename(relative) if
|
|
148
|
+
relative = File.basename(relative) if RailsAiContext::SafePath.absolute?(relative)
|
|
128
149
|
[ relative, rest ].compact.join(":")
|
|
129
150
|
end
|
|
130
151
|
|