deployangel 0.1.17 → 0.1.19

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: a288e6edf81d6e4119526b5ce13d87e033a9bc54d5a02679bf5d5585771e67fb
4
- data.tar.gz: 0530abbe1a6dd018fbb528697e0a86fd7c10377ba09271b1bfd576ac2cca2e3e
3
+ metadata.gz: bf16d5f1c1214f77e3b72e911693d7e52efd852da3dcaa21308957d8789bbb63
4
+ data.tar.gz: 6957cf6b461965450428d49b7d12631bcd2431e7a71bdfd9d995de9b63bd8f0a
5
5
  SHA512:
6
- metadata.gz: 12968f549c284c8c96f5151455341f6966f1c4dc5ee2e3a8a634cbe0d751235c8868ec254a6e40a49376ab260497262107da682ab16f20f0a53c5446ebde6989
7
- data.tar.gz: '01408cbd6f6fcb74ef21eb0b3f2db04ebf9c24981c7d798f897edca04487018ad84383216cb6744102b5e6eba1a51f6be5a6204b7757daab328176bca1f76f63'
6
+ metadata.gz: f8eb73de68335aec60421c1e550c4983e1a1ffd0e93e27726ce75028d13245a4cc41ec3ea9193d4dbadb48a30eb6953e1bd41cac7837634cefea05bb9b2820f8
7
+ data.tar.gz: 2c910d0882a1c2bed0b0e4bf24dc8b238121702d1d77ae760d74e207050757795b60d054a7f5fa3a6a0b113ee10d736da344ed49db29dfb3f9f6cf76f1a9f766
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.19 (2026-10-09)
4
+
5
+ - `deployangel install agents` sets up Claude Code, Cursor, and Codex in a
6
+ project: the MCP server in `.mcp.json`, `.cursor/mcp.json`, and
7
+ `.codex/config.toml` (forwarding `DEPLOYANGEL_API_TOKEN`), and instructions
8
+ to wait for a verdict after deploying in `AGENTS.md`, with a `CLAUDE.md`
9
+ that imports it. It never replaces existing entries, and re-running it only
10
+ updates its own instructions.
11
+
12
+ ## 0.1.18 (2026-10-08)
13
+
14
+ - In Rails, the agent gets its file digests when it starts, so the reporter
15
+ works out the code fingerprint right away rather than at its first send, a
16
+ minute or two later. A file changed in between, as by an in-place
17
+ `git pull`, no longer ends up in the fingerprint of code that isn't running.
18
+
3
19
  ## 0.1.17 (2026-10-08)
4
20
 
5
21
  - The agent reports from every Rails environment but `development` and `test`,
data/README.md CHANGED
@@ -413,6 +413,22 @@ choose with `--format=text|json`. Exit codes:
413
413
 
414
414
  ### MCP server
415
415
 
416
+ To set up Claude Code, Cursor, and Codex for a project in one step, run this
417
+ in the project:
418
+
419
+ ```bash
420
+ bundle exec deployangel install agents
421
+ ```
422
+
423
+ It adds the MCP server to `.mcp.json` (Claude Code), `.cursor/mcp.json`
424
+ (Cursor), and `.codex/config.toml` (Codex, which reads it only in projects you
425
+ trust), and the instructions below to `AGENTS.md`, with a `CLAUDE.md` that
426
+ imports it. It never replaces an existing entry, and running it again updates
427
+ only its own instructions. Commit the files; the token stays in your
428
+ environment.
429
+
430
+ Or add the server yourself:
431
+
416
432
  ```bash
417
433
  claude mcp add deployangel -- bundle exec deployangel mcp # Claude Code
418
434
  ```
@@ -450,7 +466,8 @@ so its plan lists nothing.
450
466
 
451
467
  ### Suggested instructions for your coding agent
452
468
 
453
- Add this to your `CLAUDE.md` or `AGENTS.md`:
469
+ `deployangel install agents` adds instructions like these. To add them
470
+ yourself, put this in your `CLAUDE.md` or `AGENTS.md`:
454
471
 
455
472
  ```markdown
456
473
  ## Production verification
@@ -20,7 +20,7 @@ module DeployAngel
20
20
  attr_writer :metadata
21
21
 
22
22
  def initialize(config:, environment:, root: nil, framework: nil, framework_version: nil,
23
- env: ENV, transport: nil, clock: -> { Time.now.utc }, eager: false)
23
+ env: ENV, transport: nil, clock: -> { Time.now.utc }, eager: false, metadata: nil)
24
24
  @config = config
25
25
  @active = config.active?(environment)
26
26
  @release = Release.resolve(config: config, env: env, root: root)
@@ -35,6 +35,7 @@ module DeployAngel
35
35
  @root = root
36
36
  @clock = clock
37
37
  @eager = eager
38
+ @metadata = metadata
38
39
  @thread_mutex = Mutex.new
39
40
  @warned = {}
40
41
  reset_process_state
@@ -33,8 +33,9 @@ module DeployAngel
33
33
  exception Details for a fingerprint deployangel exception FINGERPRINT
34
34
  check Report a smoke test result --name=NAME --status=pass|fail [--covers=a,b]
35
35
  [--commit=SHA | --deployment=ID] [--details-url=URL]
36
- install Add a Kamal deploy hook, or deployangel install kamal
37
- bake the commit into an image deployangel install docker
36
+ install Add a Kamal deploy hook, deployangel install kamal
37
+ bake the commit into an image, deployangel install docker
38
+ or set up coding agents deployangel install agents
38
39
  mcp Run the MCP server over stdio (for coding agents)
39
40
 
40
41
  With no target, verify, plan, and check use the current git HEAD commit.
@@ -71,6 +72,46 @@ module DeployAngel
71
72
  Kamal apps don't need this: DeployAngel reads Kamal's KAMAL_VERSION.
72
73
  HELP
73
74
 
75
+ # The block `deployangel install agents` keeps in AGENTS.md (and CLAUDE.md
76
+ # when it doesn't import AGENTS.md), replaced whole on each run.
77
+ AGENTS_START = "<!-- deployangel:start -->"
78
+ AGENTS_END = "<!-- deployangel:end -->"
79
+ AGENT_INSTRUCTIONS = <<~MARKDOWN
80
+ #{AGENTS_START}
81
+ ## Production verification (DeployAngel)
82
+
83
+ After deploying, or pushing a commit that deploys, wait for DeployAngel's
84
+ verdict: call the `wait_for_verification` MCP tool with the commit and
85
+ `until: "initial"`, calling again while it's in progress, or run
86
+ `bundle exec deployangel verify --commit=<sha> --wait --until=initial`.
87
+
88
+ - Exit 0 / verified: the release is cleared. Report the clearance line and
89
+ anything DeployAngel is still watching, then move on.
90
+ - Exit 6: no problems so far, but NOT cleared. Report "no problems so far, not
91
+ yet cleared" and the expected clearance time. DeployAngel keeps verifying and
92
+ alerts on failure.
93
+ - Exit 7: warnings at the initial check. Report them and review the findings.
94
+ The release is NOT cleared.
95
+ - Exit 2 / inconclusive: the release is NOT verified. Do not claim success.
96
+ - Exit 1 / failed: read the findings and exceptions (`get_exception`),
97
+ investigate the likely cause, and propose a fix. Do not roll back or change
98
+ production without explicit approval.
99
+ - Exit 3: still verifying; run the command again.
100
+ - Not cleared yet: call `get_exercise_plan` (or `bundle exec deployangel plan`).
101
+ If its status is "exercisable" or "waiting_for_activity", say what it lists
102
+ and offer to exercise it: read-only routes freely, routes marked mutating
103
+ only with a test account or after asking. If the status is "warm_up",
104
+ nothing run can clear it.
105
+ #{AGENTS_END}
106
+ MARKDOWN
107
+
108
+ AGENTS_TOKEN_HELP = <<~HELP.chomp
109
+ The MCP server and CLI need DEPLOYANGEL_API_TOKEN, a "CLI & coding agents" token
110
+ from the app's Settings, in the environment your agent runs in (for example in
111
+ an .envrc with direnv). Never put it in these files: they're meant to be committed.
112
+ Codex reads .codex/config.toml only in projects you've marked as trusted.
113
+ HELP
114
+
74
115
  DOCKERFILE_LINES = <<~DOCKERFILE
75
116
  # The commit this image runs, for DeployAngel. Build with --build-arg GIT_SHA=$(git rev-parse HEAD).
76
117
  ARG GIT_SHA
@@ -230,7 +271,8 @@ module DeployAngel
230
271
  case (target = @argv.shift)
231
272
  when "kamal" then install_kamal
232
273
  when "docker" then install_docker
233
- else usage_error("install needs a target: deployangel install kamal, or deployangel install docker")
274
+ when "agents" then install_agents
275
+ else usage_error("install needs a target: deployangel install kamal, docker, or agents")
234
276
  end
235
277
  end
236
278
 
@@ -256,6 +298,93 @@ module DeployAngel
256
298
  0
257
299
  end
258
300
 
301
+ # Sets up Claude Code, Cursor, and Codex in this project: the MCP server
302
+ # in each one's project config, and instructions to wait for a verdict
303
+ # after deploying. Never overwrites what's there: an existing entry is
304
+ # kept, and a file it can't read is left for you to edit.
305
+ def install_agents
306
+ command = %w[bundle exec deployangel mcp]
307
+ lines = [
308
+ install_json_mcp(".mcp.json", command, "Claude Code"),
309
+ install_json_mcp(File.join(".cursor", "mcp.json"), command, "Cursor"),
310
+ install_codex_mcp(command),
311
+ *install_agent_instructions
312
+ ]
313
+ @stdout.puts(*lines, "", AGENTS_TOKEN_HELP)
314
+ 0
315
+ end
316
+
317
+ def install_json_mcp(relative, command, agent)
318
+ path = File.join(@root, relative)
319
+ config = File.exist?(path) ? JSON.parse(File.read(path)) : {}
320
+ raise JSON::ParserError, "not a JSON object" unless config.is_a?(Hash)
321
+
322
+ servers = (config["mcpServers"] ||= {})
323
+ return "#{relative} already has a deployangel MCP server (#{agent})." if servers.key?("deployangel")
324
+
325
+ created = !File.exist?(path)
326
+ servers["deployangel"] = { "command" => command.first, "args" => command.drop(1) }
327
+ FileUtils.mkdir_p(File.dirname(path))
328
+ File.write(path, "#{JSON.pretty_generate(config)}\n")
329
+ "#{created ? "Created" : "Updated"} #{relative}: the DeployAngel MCP server for #{agent}."
330
+ rescue JSON::ParserError
331
+ "Couldn't read #{relative}, so it's unchanged. Add a \"deployangel\" server to its mcpServers: " \
332
+ "command \"#{command.first}\", args #{command.drop(1).to_json}."
333
+ end
334
+
335
+ def install_codex_mcp(command)
336
+ relative = File.join(".codex", "config.toml")
337
+ path = File.join(@root, relative)
338
+ existing = File.exist?(path) ? File.read(path) : nil
339
+ return "#{relative} already has a deployangel MCP server (Codex)." if existing&.include?("[mcp_servers.deployangel]")
340
+
341
+ table = <<~TOML
342
+ [mcp_servers.deployangel]
343
+ command = #{command.first.to_json}
344
+ args = #{command.drop(1).to_json.gsub(",", ", ")}
345
+ env_vars = ["DEPLOYANGEL_API_TOKEN", "DEPLOYANGEL_URL"]
346
+ TOML
347
+ FileUtils.mkdir_p(File.dirname(path))
348
+ File.write(path, existing ? "#{existing.sub(/\n*\z/, "\n")}\n#{table}" : table)
349
+ "#{existing ? "Updated" : "Created"} #{relative}: the DeployAngel MCP server for Codex."
350
+ end
351
+
352
+ # AGENTS.md serves Codex and Cursor. Claude Code reads CLAUDE.md, so it
353
+ # gets the block too, unless it already imports AGENTS.md; a project
354
+ # without one gets a CLAUDE.md that does.
355
+ def install_agent_instructions
356
+ lines = [ upsert_instructions("AGENTS.md") ]
357
+ claude = File.join(@root, "CLAUDE.md")
358
+ if !File.exist?(claude)
359
+ File.write(claude, "@AGENTS.md\n")
360
+ lines << "Created CLAUDE.md, which imports AGENTS.md for Claude Code."
361
+ elsif !File.read(claude).match?(/^@AGENTS\.md\s*$/)
362
+ lines << upsert_instructions("CLAUDE.md")
363
+ end
364
+ lines
365
+ end
366
+
367
+ def upsert_instructions(relative)
368
+ path = File.join(@root, relative)
369
+ unless File.exist?(path)
370
+ File.write(path, AGENT_INSTRUCTIONS)
371
+ return "Created #{relative} with instructions to wait for DeployAngel's verdict after deploying."
372
+ end
373
+
374
+ content = File.read(path)
375
+ block = /#{Regexp.escape(AGENTS_START)}.*?#{Regexp.escape(AGENTS_END)}\n?/m
376
+ if content.match?(block)
377
+ updated = content.sub(block, AGENT_INSTRUCTIONS)
378
+ return "#{relative} already has DeployAngel's instructions." if updated == content
379
+
380
+ File.write(path, updated)
381
+ "Updated DeployAngel's instructions in #{relative}."
382
+ else
383
+ File.write(path, "#{content.sub(/\n*\z/, "\n")}\n#{AGENT_INSTRUCTIONS}")
384
+ "Added instructions to wait for DeployAngel's verdict after deploying to #{relative}."
385
+ end
386
+ end
387
+
259
388
  # Adds DEPLOYANGEL_REVISION, from a GIT_SHA build arg, to the Dockerfile.
260
389
  # It never edits CI workflows; it says what to pass instead.
261
390
  def install_docker
@@ -22,14 +22,16 @@ module DeployAngel
22
22
 
23
23
  config.after_initialize do |app|
24
24
  DeployAngel::Apartment.install if DeployAngel.configuration.exception_messages
25
- agent = DeployAngel.start(
25
+ # The metadata goes in at start, before the reporter thread can run:
26
+ # the code fingerprint is built from its file digests.
27
+ DeployAngel.start(
26
28
  environment: ::Rails.env,
27
29
  root: ::Rails.root.to_s,
28
30
  framework: "rails",
29
31
  framework_version: ::Rails.version,
30
- logger: ::Rails.logger
32
+ logger: ::Rails.logger,
33
+ metadata: DeployAngel::Rails::Metadata.new(app: app, config: DeployAngel.configuration, root: ::Rails.root.to_s, environment: ::Rails.env)
31
34
  )
32
- agent&.metadata = DeployAngel::Rails::Metadata.new(app: app, config: DeployAngel.configuration, root: ::Rails.root.to_s, environment: ::Rails.env)
33
35
  end
34
36
  end
35
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployAngel
4
- VERSION = "0.1.17"
4
+ VERSION = "0.1.19"
5
5
  end
data/lib/deployangel.rb CHANGED
@@ -34,10 +34,10 @@ module DeployAngel
34
34
  # Called by the Railtie after the app boots. Web server processes start
35
35
  # reporting immediately, so idle processes still send heartbeats; other
36
36
  # processes (console, rake) only report after recording a request.
37
- def start(environment:, root: nil, framework: nil, framework_version: nil, logger: nil)
37
+ def start(environment:, root: nil, framework: nil, framework_version: nil, logger: nil, metadata: nil)
38
38
  configuration.logger = logger if logger
39
39
  @agent = Agent.new(config: configuration, environment: environment, root: root,
40
- framework: framework, framework_version: framework_version, eager: server_process?)
40
+ framework: framework, framework_version: framework_version, eager: server_process?, metadata: metadata)
41
41
  install_exit_hook
42
42
  @agent
43
43
  rescue StandardError => e
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deployangel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17
4
+ version: 0.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Owens
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
84
  requirements: []
85
- rubygems_version: 4.0.0
85
+ rubygems_version: 4.0.10
86
86
  specification_version: 4
87
87
  summary: 'DeployAngel agent for Rails: production verification for every deployment.'
88
88
  test_files: []