Class: RailsInformant::Integration

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_informant/integration.rb

Overview

Classifies the host app's committed Claude Code integration by comparing its live .claude/ files against what the installed gem would generate now. The detection primitive behind the three drift channels (boot warning, doctor, hook nudge); Rails-boot-independent and unit-testable in isolation.

Internals may raise (missing gem spec, unreadable templates); each channel wraps a single top-level rescue so a drift check never breaks boot or CI. The one exception is #write_drift_flag, which is best-effort by nature and swallows its own IO failures.

Constant Summary collapse

Content =
ClaudeIntegrationContent
DRIFT_FLAG =
"rails-informant-drift"
COMPONENTS =

Fixed order so the digest is deterministic across runs.

%w[hook mcp settings skill].freeze

Instance Method Summary collapse

Constructor Details

#initialize(app_root: Rails.root) ⇒ Integration

Returns a new instance of Integration.



25
26
27
# File 'lib/rails_informant/integration.rb', line 25

def initialize(app_root: Rails.root)
  @app_root = Pathname(app_root)
end

Instance Method Details

#drift_flag_pathObject



57
58
59
# File 'lib/rails_informant/integration.rb', line 57

def drift_flag_path
  @app_root.join "tmp", DRIFT_FLAG
end

#gem_versionObject

Display only — the drift decision uses the digest, not the version. Harmless that local path/git installs report 0.0.0.dev here; only published installs show a real number.



53
54
55
# File 'lib/rails_informant/integration.rb', line 53

def gem_version
  Gem.loaded_specs["rails-informant"]&.version&.to_s
end

#installed?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/rails_informant/integration.rb', line 42

def installed?
  hook_script_present? || settings_informant_present? || mcp_informant_present?
end

#stale?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/rails_informant/integration.rb', line 46

def stale?
  status == :stale
end

#statusObject

:not_installed | :current | :stale | :error

not_installed wins first so apps that use the gem only for error capture are never nagged. error (a present-but-unparseable settings.json/.mcp.json) is distinct from stale because re-running the generator skips unparseable files — it could never clear a stale reported on one.



35
36
37
38
39
40
# File 'lib/rails_informant/integration.rb', line 35

def status
  return :not_installed unless installed?
  return :error if json_error?

  live_digest == expected_digest ? :current : :stale
end

#write_drift_flag(stale:) ⇒ Object

Best-effort: the Ruby channels refresh this flag so the bash hook can read drift without loading Ruby. Never raises out — a read-only tmp/ must not break a dev boot or a doctor run.



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rails_informant/integration.rb', line 64

def write_drift_flag(stale:)
  if stale
    FileUtils.mkdir_p drift_flag_path.dirname
    drift_flag_path.write "The Claude Code integration is out of date. " \
      "Run `bin/rails g rails_informant:skill` to update it.\n"
  elsif drift_flag_path.exist?
    drift_flag_path.delete
  end
rescue SystemCallError
  nil
end