rails-informant 0.5.3 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87e632d9075cba7b12f2a9d4f37654dbf5c38503d4d037916f70956db5831da6
4
- data.tar.gz: a004d0304531b5f591b1c7cf55cf57c188edb1fa0969ab3c1b86dd340f9a1c69
3
+ metadata.gz: 905f71f9e86e6c982710d2c1dbd8471218798e1cfa80b8baca78124180a1fef3
4
+ data.tar.gz: 9cc699035012351e2bfe4b0423c2b7fe0bffd7880f05250d2d54f758bf2d50ea
5
5
  SHA512:
6
- metadata.gz: 938408a0098cc497a2498bd6ac34ff70b1b69740d81647f5a32b997c227d5e2bee260d6f7d7737d26ae92dff5aeb17ba70c78fc530d4b146732b3f0bd251c137
7
- data.tar.gz: 2e8a5894b1c1c9f3e4129c813c3717d78c2ecf0c31a338bbf15843044684123d614f465ebf1fe389bf0d433b6ae453d83c4fb1927e8a46f594c790f1270c035b
6
+ metadata.gz: b103c2f10d32404883a51e7d9ef82daf0dda368bd7ec50165d32f88b9b9d53924aeb6264af9c4e7f10468d6abe891228a62e7e09c2584a74a0d469a619cf9dd5
7
+ data.tar.gz: fad95e73c07500480d703c2018e5eeeef77656f0cd16aced9b8e778d90c700a450b1f2963c54f093b76fc2b501a013148b2afb18bef81b8eeed399db1bd5d96c
data/README.md CHANGED
@@ -63,6 +63,8 @@ Install Claude Code integration:
63
63
  bin/rails generate rails_informant:skill
64
64
  ```
65
65
 
66
+ With [Rails Hyperdrive](https://github.com/rails-hyperdrive/rails-hyperdrive), the `/informant` skill arrives through `bin/rails hyperdrive:sync` instead, and the generator installs the rest (see [With Rails Hyperdrive](#with-rails-hyperdrive)).
67
+
66
68
  Errors are captured automatically. To capture manually:
67
69
 
68
70
  ```ruby
@@ -222,6 +224,19 @@ bin/rails informant:doctor # reports current / stale / not installed; exits no
222
224
 
223
225
  Apps that use the gem only for error capture (no Claude Code integration) are never affected.
224
226
 
227
+ #### With Rails Hyperdrive
228
+
229
+ rails-informant is a Hyperdrive companion gem. Once an app has `.hyperdrive/config.yml`, Hyperdrive owns the skill (`.claude/skills/informant/SKILL.md`) and updates it on `bin/rails hyperdrive:sync`, keeping your local edits safe. The generator still owns the hook script, its `.claude/settings.json` entry and `.mcp.json`, and no longer writes the skill. The drift checks ignore the skill in these apps.
230
+
231
+ If the generator installed the skill before Hyperdrive arrived, hand it over once:
232
+
233
+ ```sh
234
+ rm -f .claude/skills/informant/SKILL.md .claude/skills/informant/SKILL.md.new
235
+ bin/rails hyperdrive:sync
236
+ ```
237
+
238
+ Hyperdrive treats a file it did not install as a local edit and never overwrites it, so the old copy has to go first. Avoid `hyperdrive:sync --overwrite` for this: it rewrites every locally edited Hyperdrive file, from every companion gem. `bin/rails informant:doctor` prints these steps while the handover is pending.
239
+
225
240
  ### Tools
226
241
 
227
242
  | Tool | Description |
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require "hyperdrive/skill_tasks"
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
6
  t.libs << "test"
@@ -9,7 +9,9 @@ module RailsInformant
9
9
  source_root File.expand_path("skill/templates", __dir__)
10
10
 
11
11
  def copy_skill_file
12
- copy_file "SKILL.md", ".claude/skills/informant/SKILL.md"
12
+ return if hyperdrive_managed?
13
+
14
+ create_file Content::SKILL_PATH, Content.skill_markdown
13
15
  end
14
16
 
15
17
  def copy_hook_script
@@ -63,7 +65,11 @@ module RailsInformant
63
65
  say "Claude Code integration installed!", :green
64
66
  say ""
65
67
  say " Created .mcp.json"
66
- say " Created .claude/skills/informant/SKILL.md"
68
+ if hyperdrive_managed?
69
+ say " Skipped #{Content::SKILL_PATH} (Rails Hyperdrive installs it: bin/rails hyperdrive:sync)"
70
+ else
71
+ say " Created #{Content::SKILL_PATH}"
72
+ end
67
73
  say " Created .claude/hooks/informant-alerts.sh"
68
74
  say " Created .claude/settings.json (UserPromptSubmit hook)"
69
75
  say ""
@@ -81,6 +87,8 @@ module RailsInformant
81
87
 
82
88
  private
83
89
 
90
+ def hyperdrive_managed? = Content.hyperdrive_managed?(destination_root)
91
+
84
92
  # Remove every prior informant registration (matched by script path) from all
85
93
  # event keys and drop keys left empty, so a re-run migrates a stale
86
94
  # registration (e.g. a leftover SessionStart from an earlier gem version)
@@ -14,6 +14,13 @@ module RailsInformant
14
14
  SETTINGS_PATH = ".claude/settings.json"
15
15
  MCP_PATH = ".mcp.json"
16
16
 
17
+ # `hyperdrive:init` writes this before the first sync, so the skill is left
18
+ # to Hyperdrive from the start. Keying on the lock instead would let the
19
+ # generator write the skill in that window, and Hyperdrive treats an
20
+ # unlocked file as a local edit it never overwrites.
21
+ HYPERDRIVE_CONFIG_PATH = ".hyperdrive/config.yml"
22
+ HYPERDRIVE_LOCK_PATH = ".hyperdrive/lock.yml"
23
+
17
24
  # The command string a settings.json hook entry uses to invoke the script.
18
25
  # Absolute via Claude Code's $CLAUDE_PROJECT_DIR because the hook's cwd is
19
26
  # not guaranteed to be the app root: a bare relative path fails on every
@@ -48,9 +55,11 @@ module RailsInformant
48
55
  end
49
56
 
50
57
  def skill_markdown
51
- File.read File.join(templates_dir, "SKILL.md")
58
+ File.read File.join(gem_dir, "skills", "informant", "SKILL.md")
52
59
  end
53
60
 
61
+ def hyperdrive_managed?(app_root) = File.exist?(File.join(app_root, HYPERDRIVE_CONFIG_PATH))
62
+
54
63
  # The informant server entry inside .mcp.json's "mcpServers".
55
64
  def mcp_entry
56
65
  { "command" => "informant-mcp" }
@@ -6,6 +6,13 @@ module RailsInformant
6
6
  # or error) so it doubles as a CI signal. A thin wrapper over Integration that
7
7
  # never raises out — a diagnostic must not crash the run it reports on.
8
8
  class Doctor
9
+ HYPERDRIVE_ADOPTION = <<~REPORT.chomp
10
+ [Informant] Rails Hyperdrive manages the informant skill, but #{ClaudeIntegrationContent::SKILL_PATH} is not a Hyperdrive install.
11
+ Hand it over (not --overwrite, which rewrites every edited Hyperdrive file):
12
+ rm -f #{ClaudeIntegrationContent::SKILL_PATH} #{ClaudeIntegrationContent::SKILL_PATH}.new
13
+ bin/rails hyperdrive:sync
14
+ REPORT
15
+
9
16
  def initialize(integration: Integration.new, io: $stdout)
10
17
  @integration = integration
11
18
  @io = io
@@ -15,6 +22,7 @@ module RailsInformant
15
22
  status = @integration.status
16
23
  @integration.write_drift_flag stale: status == :stale
17
24
  @io.puts report_for(status)
25
+ @io.puts HYPERDRIVE_ADOPTION if @integration.skill_awaiting_hyperdrive?
18
26
  exit_code_for status
19
27
  rescue StandardError => e
20
28
  # An unexpected failure must exit nonzero, not 0 — a doctor wired into CI
@@ -2,6 +2,7 @@ require "digest"
2
2
  require "fileutils"
3
3
  require "json"
4
4
  require "pathname"
5
+ require "yaml"
5
6
  require "rails_informant/claude_integration_content"
6
7
 
7
8
  module RailsInformant
@@ -24,6 +25,7 @@ module RailsInformant
24
25
 
25
26
  def initialize(app_root: Rails.root)
26
27
  @app_root = Pathname(app_root)
28
+ @hyperdrive_managed = Content.hyperdrive_managed?(@app_root)
27
29
  end
28
30
 
29
31
  # :not_installed | :current | :stale | :error
@@ -47,6 +49,17 @@ module RailsInformant
47
49
  status == :stale
48
50
  end
49
51
 
52
+ # Never true for a skill Hyperdrive already owns: a lock-SHA mismatch is a
53
+ # local edit, and a sidecar with a recorded ancestor is Hyperdrive's own
54
+ # upgrade of that edit (hyperdrive:sync --resolve). The handover would
55
+ # discard the edit in both cases.
56
+ def skill_awaiting_hyperdrive?
57
+ return false unless @hyperdrive_managed && skill_path.exist?
58
+
59
+ entry = informant_skill_entry or return true
60
+ sidecar_path.exist? && entry["ancestor_sha"].nil?
61
+ end
62
+
50
63
  # Display only — the drift decision uses the digest, not the version. Harmless
51
64
  # that local path/git installs report 0.0.0.dev here; only published installs
52
65
  # show a real number.
@@ -79,6 +92,8 @@ module RailsInformant
79
92
  def skill_path = @app_root.join(Content::SKILL_PATH)
80
93
  def settings_path = @app_root.join(Content::SETTINGS_PATH)
81
94
  def mcp_path = @app_root.join(Content::MCP_PATH)
95
+ def sidecar_path = @app_root.join("#{Content::SKILL_PATH}.new")
96
+ def lock_path = @app_root.join(Content::HYPERDRIVE_LOCK_PATH)
82
97
 
83
98
  def hook_script_present? = hook_path.exist?
84
99
 
@@ -100,7 +115,7 @@ module RailsInformant
100
115
  def expected_digest
101
116
  digest_of(
102
117
  "hook" => normalize_text(Content.hook_script),
103
- "skill" => normalize_text(Content.skill_markdown),
118
+ "skill" => @hyperdrive_managed ? "" : normalize_text(Content.skill_markdown),
104
119
  "settings" => canonical_json(Content.expected_registrations),
105
120
  "mcp" => canonical_json(Content.mcp_entry)
106
121
  )
@@ -109,7 +124,7 @@ module RailsInformant
109
124
  def live_digest
110
125
  digest_of(
111
126
  "hook" => normalize_text(read_or_empty(hook_path)),
112
- "skill" => normalize_text(read_or_empty(skill_path)),
127
+ "skill" => @hyperdrive_managed ? "" : normalize_text(read_or_empty(skill_path)),
113
128
  "settings" => canonical_json(live_settings_registrations || {}),
114
129
  "mcp" => canonical_json(live_mcp_entry || {})
115
130
  )
@@ -143,6 +158,21 @@ module RailsInformant
143
158
  mcp.dig "mcpServers", "informant"
144
159
  end
145
160
 
161
+ def informant_skill_entry
162
+ Array(lock_files).find do |entry|
163
+ entry["artifact"] == "skill" && entry["source"].to_s.start_with?("rails-informant@")
164
+ end
165
+ end
166
+
167
+ def lock_files
168
+ return unless lock_path.exist?
169
+
170
+ lock = YAML.safe_load(lock_path.read)
171
+ lock["files"] if lock.is_a?(Hash)
172
+ rescue Psych::Exception
173
+ nil
174
+ end
175
+
146
176
  # --- json / text helpers ------------------------------------------------
147
177
 
148
178
  # Read and parse each JSON file at most once per instance. Instances are
@@ -1,4 +1,6 @@
1
1
  ---
2
+ name: informant
3
+ description: Triage and fix production errors captured by Rails Informant, using its MCP tools.
2
4
  disable-model-invocation: true
3
5
  allowed-tools:
4
6
  - mcp__informant__*
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-informant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel López Prat
@@ -127,7 +127,6 @@ files:
127
127
  - db/migrate/20260227000000_create_informant_tables.rb
128
128
  - exe/informant-mcp
129
129
  - lib/generators/rails_informant/install_generator.rb
130
- - lib/generators/rails_informant/skill/templates/SKILL.md
131
130
  - lib/generators/rails_informant/skill/templates/informant-alerts.sh
132
131
  - lib/generators/rails_informant/skill_generator.rb
133
132
  - lib/generators/rails_informant/templates/create_informant_tables.rb.erb
@@ -176,12 +175,14 @@ files:
176
175
  - lib/rails_informant/structured_event_subscriber.rb
177
176
  - lib/rails_informant/version.rb
178
177
  - lib/tasks/rails_informant.rake
178
+ - skills/informant/SKILL.md
179
179
  homepage: https://github.com/6temes/rails-informant
180
180
  licenses:
181
181
  - MIT
182
182
  metadata:
183
183
  bug_tracker_uri: https://github.com/6temes/rails-informant/issues
184
184
  changelog_uri: https://github.com/6temes/rails-informant/releases
185
+ hyperdrive_targets: rails-informant
185
186
  rubygems_mfa_required: 'true'
186
187
  source_code_uri: https://github.com/6temes/rails-informant
187
188
  rdoc_options: []
@@ -198,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
199
  - !ruby/object:Gem::Version
199
200
  version: '0'
200
201
  requirements: []
201
- rubygems_version: 4.0.16
202
+ rubygems_version: 4.0.20
202
203
  specification_version: 4
203
204
  summary: Self-hosted error monitoring for Rails with MCP server for agentic workflows
204
205
  test_files: []