fosm-rails-coding-agent 0.0.1

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 (30) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +77 -0
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +127 -0
  5. data/README.md +135 -0
  6. data/bin/fosm-coding-agent +14 -0
  7. data/lib/fosm_rails_coding_agent/acp_agent.rb +132 -0
  8. data/lib/fosm_rails_coding_agent/configuration.rb +27 -0
  9. data/lib/fosm_rails_coding_agent/exceptions_middleware.rb +67 -0
  10. data/lib/fosm_rails_coding_agent/middleware.rb +115 -0
  11. data/lib/fosm_rails_coding_agent/quiet_middleware.rb +18 -0
  12. data/lib/fosm_rails_coding_agent/railtie.rb +51 -0
  13. data/lib/fosm_rails_coding_agent/tools/base.rb +10 -0
  14. data/lib/fosm_rails_coding_agent/tools/execute_sql.rb +44 -0
  15. data/lib/fosm_rails_coding_agent/tools/fosm/available_events.rb +46 -0
  16. data/lib/fosm_rails_coding_agent/tools/fosm/fire_event.rb +46 -0
  17. data/lib/fosm_rails_coding_agent/tools/fosm/inspect_lifecycle.rb +44 -0
  18. data/lib/fosm_rails_coding_agent/tools/fosm/list_lifecycles.rb +45 -0
  19. data/lib/fosm_rails_coding_agent/tools/fosm/model_resolver.rb +25 -0
  20. data/lib/fosm_rails_coding_agent/tools/fosm/transition_history.rb +56 -0
  21. data/lib/fosm_rails_coding_agent/tools/fosm/why_blocked.rb +36 -0
  22. data/lib/fosm_rails_coding_agent/tools/get_docs.rb +58 -0
  23. data/lib/fosm_rails_coding_agent/tools/get_logs.rb +66 -0
  24. data/lib/fosm_rails_coding_agent/tools/get_models.rb +35 -0
  25. data/lib/fosm_rails_coding_agent/tools/get_source_location.rb +69 -0
  26. data/lib/fosm_rails_coding_agent/tools/project_eval.rb +76 -0
  27. data/lib/fosm_rails_coding_agent/transport.rb +84 -0
  28. data/lib/fosm_rails_coding_agent/version.rb +5 -0
  29. data/lib/fosm_rails_coding_agent.rb +26 -0
  30. metadata +139 -0
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FosmRailsCodingAgent
4
+ module Tools
5
+ # Lists all ActiveRecord models in the application with their source locations.
6
+ class GetModels < Base
7
+ tool_name "get_models"
8
+ description <<~DESC
9
+ Returns a list of all ActiveRecord model classes in the application
10
+ with their source file locations.
11
+ DESC
12
+
13
+ def call
14
+ Rails.application.eager_load!
15
+
16
+ ActiveRecord::Base.descendants.reject(&:abstract_class?).sort_by(&:name).map do |model|
17
+ location = source_location_for(model.name)
18
+ location ? "* #{model.name} at #{location}" : "* #{model.name}"
19
+ end.join("\n")
20
+ end
21
+
22
+ private
23
+
24
+ def source_location_for(class_name)
25
+ file_path, line_number = Object.const_source_location(class_name)
26
+ return nil unless file_path
27
+
28
+ relative = Pathname.new(file_path).relative_path_from(Rails.root)
29
+ "#{relative}:#{line_number}"
30
+ rescue ArgumentError
31
+ "#{file_path}:#{line_number}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FosmRailsCodingAgent
4
+ module Tools
5
+ # Finds the source file and line number for a Ruby constant or method.
6
+ # Supports constants (String), instance methods (String#gsub),
7
+ # class methods (File.executable?), and gem roots (dep:rails).
8
+ class GetSourceLocation < Base
9
+ tool_name "get_source_location"
10
+ description <<~DESC
11
+ Returns the source location for a Ruby constant or method reference.
12
+
13
+ Examples: `String`, `String#gsub`, `File.executable?`, `dep:rails`
14
+
15
+ Works across the current project and all loaded gems.
16
+ Prefer this over grepping when you know the exact reference.
17
+ DESC
18
+
19
+ arguments do
20
+ required(:reference).filled(:string).description("Constant, method, or dep:PACKAGE to look up")
21
+ end
22
+
23
+ def call(reference:)
24
+ if reference.start_with?("dep:")
25
+ return package_location(reference.delete_prefix("dep:"))
26
+ end
27
+
28
+ file_path, line_number = self.class.resolve(reference)
29
+ raise NameError, "could not find source location for #{reference}" unless file_path
30
+
31
+ relative_path(file_path, line_number)
32
+ end
33
+
34
+ # Resolve a reference to [file_path, line_number].
35
+ # Used by GetDocs to share resolution logic.
36
+ def self.resolve(reference)
37
+ constant_path, selector, method_name = reference.rpartition(/\.|#/)
38
+ return Object.const_source_location(method_name) if selector.empty?
39
+
40
+ mod = Object.const_get(constant_path)
41
+ raise "#{constant_path} is not a class/module" unless mod.is_a?(Module)
42
+
43
+ if selector == "#"
44
+ mod.instance_method(method_name).source_location
45
+ else
46
+ mod.method(method_name).source_location
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def package_location(package)
53
+ raise "dep: prefix requires Bundler" unless defined?(Bundler)
54
+
55
+ spec = Bundler.load.specs.find { |s| s.name == package }
56
+ raise "Package #{package} not found in Gemfile" unless spec
57
+
58
+ spec.full_gem_path
59
+ end
60
+
61
+ def relative_path(file_path, line_number)
62
+ relative = Pathname.new(file_path).relative_path_from(Rails.root)
63
+ "#{relative}:#{line_number}"
64
+ rescue ArgumentError
65
+ "#{file_path}:#{line_number}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+ require "json"
5
+
6
+ module FosmRailsCodingAgent
7
+ module Tools
8
+ # Evaluates Ruby code in the running Rails application context.
9
+ # Captures stdout/stderr and enforces a configurable timeout.
10
+ class ProjectEval < Base
11
+ tool_name "project_eval"
12
+ description <<~DESC
13
+ Evaluates Ruby code in the context of the running Rails application.
14
+ Current Ruby version: #{RUBY_VERSION}
15
+
16
+ Use this to test behavior, inspect objects, or debug issues.
17
+ Returns the result plus any stdout/stderr output.
18
+ DESC
19
+
20
+ arguments do
21
+ required(:code).filled(:string).description("The Ruby code to evaluate")
22
+ optional(:arguments).value(:array).description("Arguments available as `arguments` in the evaluated code")
23
+ optional(:timeout).filled(:integer).description("Timeout in milliseconds (default: 30000)")
24
+ end
25
+
26
+ def @input_schema.json_schema
27
+ schema = super
28
+ schema[:properties][:arguments][:items] = {}
29
+ schema
30
+ end
31
+
32
+ def call(code:, arguments: [], timeout: nil)
33
+ timeout ||= FosmRailsCodingAgent.config.eval_timeout_ms
34
+ original_stdout = $stdout
35
+ original_stderr = $stderr
36
+
37
+ stdout_capture = StringIO.new
38
+ stderr_capture = StringIO.new
39
+ $stdout = stdout_capture
40
+ $stderr = stderr_capture
41
+
42
+ begin
43
+ timeout_seconds = timeout / 1000.0
44
+
45
+ success, result = begin
46
+ Timeout.timeout(timeout_seconds) do
47
+ [true, eval(code, eval_binding(arguments))] # rubocop:disable Security/Eval
48
+ end
49
+ rescue Timeout::Error
50
+ [false, "Timeout::Error: Evaluation timed out after #{timeout}ms"]
51
+ rescue => e
52
+ [false, e.full_message]
53
+ end
54
+
55
+ stdout = stdout_capture.string
56
+ stderr = stderr_capture.string
57
+
58
+ if stdout.empty? && stderr.empty?
59
+ result.to_s
60
+ else
61
+ "STDOUT:\n#{stdout}\n\nSTDERR:\n#{stderr}\n\nResult:\n#{result}"
62
+ end
63
+ ensure
64
+ $stdout = original_stdout
65
+ $stderr = original_stderr
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def eval_binding(arguments)
72
+ binding
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack"
5
+ require "fast_mcp"
6
+
7
+ module FosmRailsCodingAgent
8
+ # Streamable HTTP transport for MCP — POST-only, no SSE.
9
+ # Implements the MCP Streamable HTTP protocol for synchronous
10
+ # JSON-RPC message exchange between coding agents and FosmRailsCodingAgent.
11
+ class Transport < FastMcp::Transports::BaseTransport
12
+ attr_reader :app, :path
13
+
14
+ def initialize(app, server, options = {})
15
+ super(server, logger: options[:logger])
16
+ @app = app
17
+ @path = options[:path_prefix] || "/mcp"
18
+ @running = false
19
+ end
20
+
21
+ def start
22
+ @logger.debug("[FosmRailsCodingAgent] MCP transport started at #{@path}")
23
+ @running = true
24
+ end
25
+
26
+ def stop
27
+ @logger.debug("[FosmRailsCodingAgent] MCP transport stopped")
28
+ @running = false
29
+ end
30
+
31
+ def send_message(message)
32
+ @captured_response = message
33
+ end
34
+
35
+ def call(env)
36
+ request = Rack::Request.new(env)
37
+
38
+ if request.path == @path
39
+ @server.transport = self
40
+ handle_request(request)
41
+ else
42
+ @app.call(env)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def handle_request(request)
49
+ return method_not_allowed unless request.post?
50
+
51
+ body = request.body.read
52
+ message = JSON.parse(body)
53
+
54
+ return json_error(400, -32600, "Invalid Request") unless valid_jsonrpc?(message)
55
+
56
+ @captured_response = nil
57
+ @server.handle_json_request(message)
58
+
59
+ if @captured_response
60
+ [200, { "Content-Type" => "application/json" }, [JSON.generate(@captured_response)]]
61
+ else
62
+ [202, {}, []]
63
+ end
64
+ rescue JSON::ParserError
65
+ json_error(400, -32700, "Parse error")
66
+ rescue => e
67
+ @logger.error("[FosmRailsCodingAgent] #{e.message}")
68
+ json_error(500, -32603, "Internal error")
69
+ end
70
+
71
+ def valid_jsonrpc?(msg)
72
+ msg.is_a?(Hash) && msg["jsonrpc"] == "2.0" && (msg.key?("method") || msg.key?("result") || msg.key?("error"))
73
+ end
74
+
75
+ def method_not_allowed
76
+ json_error(405, -32601, "Only POST requests are supported")
77
+ end
78
+
79
+ def json_error(http_status, code, message)
80
+ [http_status, { "Content-Type" => "application/json" },
81
+ [JSON.generate({ jsonrpc: "2.0", error: { code: code, message: message }, id: nil })]]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FosmRailsCodingAgent
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fosm_rails_coding_agent/version"
4
+ require "fosm_rails_coding_agent/configuration"
5
+
6
+ module FosmRailsCodingAgent
7
+ class << self
8
+ # Returns the global FosmRailsCodingAgent configuration.
9
+ def config
10
+ @config ||= Configuration.new
11
+ end
12
+
13
+ # Yields the configuration for block-style setup.
14
+ def configure
15
+ yield config
16
+ end
17
+
18
+ # Returns true when fosm-rails is loaded and the Fosm::Lifecycle constant
19
+ # is defined. FOSM tools are only registered when this returns true.
20
+ def fosm_available?
21
+ defined?(::Fosm::Lifecycle) && defined?(::Fosm::Registry)
22
+ end
23
+ end
24
+ end
25
+
26
+ require "fosm_rails_coding_agent/railtie" if defined?(Rails::Railtie)
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fosm-rails-coding-agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Abhishek Parolkar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '8.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '8.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fast-mcp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: acp_ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ description: |
70
+ Embeds a FOSM-aware MCP server and ACP agent into your Rails development
71
+ environment, giving coding agents (Claude Code, Codex, Copilot) runtime intelligence:
72
+ database queries, logs, code evaluation, and deep introspection of FOSM lifecycle
73
+ definitions, state machines, transitions, guards, and audit trails.
74
+
75
+ Built on the FOSM (Finite Object State Machine) paradigm — declarative lifecycles
76
+ for business objects where AI agents operate within bounded, auditable state machines.
77
+ email:
78
+ - abhishek@parolkar.com
79
+ executables:
80
+ - fosm-coding-agent
81
+ extensions: []
82
+ extra_rdoc_files: []
83
+ files:
84
+ - AGENTS.md
85
+ - CHANGELOG.md
86
+ - LICENSE
87
+ - README.md
88
+ - bin/fosm-coding-agent
89
+ - lib/fosm_rails_coding_agent.rb
90
+ - lib/fosm_rails_coding_agent/acp_agent.rb
91
+ - lib/fosm_rails_coding_agent/configuration.rb
92
+ - lib/fosm_rails_coding_agent/exceptions_middleware.rb
93
+ - lib/fosm_rails_coding_agent/middleware.rb
94
+ - lib/fosm_rails_coding_agent/quiet_middleware.rb
95
+ - lib/fosm_rails_coding_agent/railtie.rb
96
+ - lib/fosm_rails_coding_agent/tools/base.rb
97
+ - lib/fosm_rails_coding_agent/tools/execute_sql.rb
98
+ - lib/fosm_rails_coding_agent/tools/fosm/available_events.rb
99
+ - lib/fosm_rails_coding_agent/tools/fosm/fire_event.rb
100
+ - lib/fosm_rails_coding_agent/tools/fosm/inspect_lifecycle.rb
101
+ - lib/fosm_rails_coding_agent/tools/fosm/list_lifecycles.rb
102
+ - lib/fosm_rails_coding_agent/tools/fosm/model_resolver.rb
103
+ - lib/fosm_rails_coding_agent/tools/fosm/transition_history.rb
104
+ - lib/fosm_rails_coding_agent/tools/fosm/why_blocked.rb
105
+ - lib/fosm_rails_coding_agent/tools/get_docs.rb
106
+ - lib/fosm_rails_coding_agent/tools/get_logs.rb
107
+ - lib/fosm_rails_coding_agent/tools/get_models.rb
108
+ - lib/fosm_rails_coding_agent/tools/get_source_location.rb
109
+ - lib/fosm_rails_coding_agent/tools/project_eval.rb
110
+ - lib/fosm_rails_coding_agent/transport.rb
111
+ - lib/fosm_rails_coding_agent/version.rb
112
+ homepage: https://github.com/inloopstudio/fosm-rails-coding-agent
113
+ licenses:
114
+ - FSL-1.1-Apache-2.0
115
+ metadata:
116
+ homepage_uri: https://github.com/inloopstudio/fosm-rails-coding-agent
117
+ source_code_uri: https://github.com/inloopstudio/fosm-rails-coding-agent
118
+ changelog_uri: https://github.com/inloopstudio/fosm-rails-coding-agent/blob/main/CHANGELOG.md
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '3.1'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.5.22
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: FOSM-aware runtime intelligence for Rails — MCP server + ACP agent for coding
138
+ agents
139
+ test_files: []