Module: ReactOnRails::AgentGuardrails

Defined in:
lib/react_on_rails/agent_guardrails.rb

Overview

Installs (and idempotently updates) the RSC "agent guardrail" assets into a host app: a Claude Code skill and an advisory PostToolUse hook that steer AI agents away from the React Server Components API footguns (unauthenticated payload route, trusting props, exposing the Node renderer, leaking secrets). Invoked by rake react_on_rails:install_rsc_agent_guardrails and by the RSC generator.

Defined Under Namespace

Classes: Installer

Constant Summary collapse

Error =
Class.new(StandardError)
TEMPLATES_DIR =
File.expand_path("agent_guardrails/templates", __dir__)
FILES =

source template (under TEMPLATES_DIR) => destination path relative to the app root

{
  "rsc_app_safety_skill.md" => ".claude/skills/rsc-app-safety/SKILL.md",
  "rsc_app_safety_check.rb" => ".claude/hooks/rsc-app-safety-check.rb"
}.freeze
HOOK_COMMAND =
"ruby"
HOOK_ARGS =
["${CLAUDE_PROJECT_DIR}/.claude/hooks/rsc-app-safety-check.rb"].freeze
LEGACY_HOOK_COMMAND =
"${CLAUDE_PROJECT_DIR}/.claude/hooks/rsc-app-safety-check.sh"
LEGACY_HOOK_REL =
".claude/hooks/rsc-app-safety-check.sh"
HOOK_REL =
".claude/hooks/rsc-app-safety-check.rb"
HOOK_MATCHER =
"Edit|Write"
SETTINGS_REL =
".claude/settings.json"

Class Method Summary collapse

Class Method Details

.default_destination_root(explicit = nil) ⇒ Object

Where guardrails get installed when no explicit destination is given. Prefers the Rails application root so the task installs into the app's .claude/ even when rake is invoked from a subdirectory; falls back to the working directory outside a Rails app.



45
46
47
48
49
50
51
# File 'lib/react_on_rails/agent_guardrails.rb', line 45

def self.default_destination_root(explicit = nil)
  explicit = explicit.to_s
  return explicit unless explicit.empty?

  rails_root = defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : nil
  rails_root ? rails_root.to_s : Dir.pwd
end

.install(destination_root, skip_existing: false) ⇒ Object

Copies the guardrail files and registers the advisory hook. Idempotent: re-running only writes what changed. Returns an array of human-readable action strings.



34
35
36
# File 'lib/react_on_rails/agent_guardrails.rb', line 34

def self.install(destination_root, skip_existing: false)
  new_installer(destination_root, skip_existing:).install
end

.new_installer(destination_root, skip_existing: false) ⇒ Object



38
39
40
# File 'lib/react_on_rails/agent_guardrails.rb', line 38

def self.new_installer(destination_root, skip_existing: false)
  Installer.new(destination_root, skip_existing:)
end