deployangel 0.1.43 → 0.1.44
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +18 -2
- data/lib/deployangel/cli/formatter.rb +34 -0
- data/lib/deployangel/cli.rb +32 -1
- data/lib/deployangel/client.rb +7 -0
- data/lib/deployangel/mcp.rb +25 -1
- data/lib/deployangel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6808537b2a27128d5b5c8770015db33cdf62b708776649485f2113ecd5a013d9
|
|
4
|
+
data.tar.gz: 001e46db33f806a94016b93f6af3dfa7b5709eb923f4918197d6ba2d444120ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d08170a8aeb9620b47db7630b95e016c415c11afb04a6c7bc7ea79cac1703e7c1a34c86dddcf73c8385cb258e31f39ca651f3e1339068df544248f507533c9e5
|
|
7
|
+
data.tar.gz: cdf1c1fe799b63e653abe0309332798d90607d6a3631527acb74669a8689897662870627062e95bd466ed3c70959e28bbe8c87cc8f81a4984f7c458d5726af6a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.44 (2026-11-14)
|
|
4
|
+
|
|
5
|
+
- `deployangel context <files>` and the MCP server's `get_production_context`
|
|
6
|
+
say what production does with code before you change it: the routes and
|
|
7
|
+
jobs the files serve, how often production ran them in the last week and
|
|
8
|
+
how many failed, whether a release's clearance would cover a change to them
|
|
9
|
+
at this time of day, their schedules and critical flows, and failed
|
|
10
|
+
releases and late regressions on them in the last 90 days. Files it can't
|
|
11
|
+
map are listed as unmapped, never as unused. The MCP server's instructions,
|
|
12
|
+
and the block `deployangel install agents` writes, tell coding agents to
|
|
13
|
+
call it before changing code that serves requests or runs as a job
|
|
14
|
+
(servers from 2026-11-14).
|
|
15
|
+
|
|
3
16
|
## 0.1.43 (2026-11-05)
|
|
4
17
|
|
|
5
18
|
- The MCP server's instructions, and the block `deployangel install agents`
|
data/README.md
CHANGED
|
@@ -517,6 +517,7 @@ bundle exec deployangel exercise --url=https://example.com # send the plan's re
|
|
|
517
517
|
bundle exec deployangel release --commit=$SHA # register a deploy (manual or CI)
|
|
518
518
|
bundle exec deployangel check --name="smoke: signup" --status=pass --covers=registration
|
|
519
519
|
bundle exec deployangel exception <fingerprint>
|
|
520
|
+
bundle exec deployangel context app/jobs/refund_job.rb # what production does with code, before changing it
|
|
520
521
|
bundle exec deployangel doctor # check the setup
|
|
521
522
|
```
|
|
522
523
|
|
|
@@ -580,8 +581,23 @@ server needs `DEPLOYANGEL_API_TOKEN` in the environment it starts in.
|
|
|
580
581
|
|
|
581
582
|
Tools: `get_verification`, `wait_for_verification` (up to 5 minutes per call),
|
|
582
583
|
`get_exercise_plan`, `list_deployments`, `get_exception`,
|
|
583
|
-
`list_late_regressions`, and `register_deployment`
|
|
584
|
-
The tools are read-only with respect to production.
|
|
584
|
+
`list_late_regressions`, `get_production_context`, and `register_deployment`
|
|
585
|
+
when the token allows it. The tools are read-only with respect to production.
|
|
586
|
+
|
|
587
|
+
### Before changing code
|
|
588
|
+
|
|
589
|
+
`deployangel context <files>` (or the `get_production_context` MCP tool)
|
|
590
|
+
says what production does with code before you or your agent change it: the
|
|
591
|
+
routes and jobs the files serve, how often production ran them in the last
|
|
592
|
+
week and how many failed, whether a release's clearance would cover a
|
|
593
|
+
change to them at this time of day, their schedules and critical flows, and
|
|
594
|
+
failed releases and late regressions on them in the last 90 days. A rarely
|
|
595
|
+
used route won't be covered by a clearance, so that's when to plan a smoke
|
|
596
|
+
test or `deployangel exercise`. Files map through the routes and jobs the
|
|
597
|
+
agent reported and through stack traces; models and libraries often map
|
|
598
|
+
only through the exceptions raised in them, and a file DeployAngel can't map
|
|
599
|
+
is listed as unmapped, never as unused. Pass `--route` or `--job` to ask
|
|
600
|
+
about a route or job class by name.
|
|
585
601
|
|
|
586
602
|
### Clearing quiet releases sooner
|
|
587
603
|
|
|
@@ -229,6 +229,40 @@ module DeployAngel
|
|
|
229
229
|
end
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
+
OUTLOOKS = { "normal_traffic" => "normal traffic covers it", "later_today" => "runs later today",
|
|
233
|
+
"rarely_used" => "rarely used: a clearance won't cover it", "unknown" => "outlook unknown" }.freeze
|
|
234
|
+
|
|
235
|
+
# Production context, one block per route or job.
|
|
236
|
+
def production_context(details)
|
|
237
|
+
lines = []
|
|
238
|
+
Array(details["items"]).each do |item|
|
|
239
|
+
uses = "#{item["uses_last_7_days"]} #{item["kind"] == "route" ? "requests" : "runs"} in 7 days"
|
|
240
|
+
failures = item["failures_last_7_days"].to_i.positive? ? ", #{item["failures_last_7_days"]} failed" : ""
|
|
241
|
+
lines << "#{item["kind"] == "route" ? "Route" : "Job"} #{item["key"]}: #{uses}#{failures} · " \
|
|
242
|
+
"#{OUTLOOKS.fetch(item.dig("clearance", "outlook"), item.dig("clearance", "outlook"))}"
|
|
243
|
+
lines << " #{item.dig("clearance", "note")}" if item.dig("clearance", "outlook") != "normal_traffic"
|
|
244
|
+
lines << " Pending first use since an earlier release" if item["pending_first_use"]
|
|
245
|
+
if (schedule = item["schedule"])
|
|
246
|
+
lines << " Scheduled #{schedule["schedule"]}#{", next #{schedule["next_at"]}" if schedule["next_at"]} (#{schedule["status"]})"
|
|
247
|
+
end
|
|
248
|
+
lines << " Critical flows: #{item["critical_flows"].join(", ")}" if Array(item["critical_flows"]).any?
|
|
249
|
+
Array(item["failed_releases"]).each { |failed| lines << " Failed #{failed["release"]}: #{failed["label"]}" }
|
|
250
|
+
Array(item["late_regressions"]).each do |late|
|
|
251
|
+
lines << " Late regression in #{late["release"]}#{" (dismissed)" if late["dismissed"]}: #{late["summary"]}"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
Array(details["files"]).each do |file|
|
|
255
|
+
Array(file["exceptions"]).each do |exception|
|
|
256
|
+
lines << "#{file["path"]}: #{exception["exception_class"]} in #{exception["top_frame"]}, " \
|
|
257
|
+
"#{exception["occurrences_last_7_days"]} in 7 days (fingerprint #{exception["fingerprint"]})"
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
unmapped = Array(details["unmapped_files"])
|
|
261
|
+
lines << "Unmapped (not known to be unused): #{unmapped.join(", ")}" if unmapped.any?
|
|
262
|
+
lines << "Nothing found for these." if lines.empty?
|
|
263
|
+
(lines + Array(details["notes"]).map { |note| "Note: #{note}" }).join("\n")
|
|
264
|
+
end
|
|
265
|
+
|
|
232
266
|
def exception(details)
|
|
233
267
|
[ "#{details["exception_class"]}: #{details["message"]}",
|
|
234
268
|
"Fingerprint #{details["fingerprint"]} · first seen #{details["first_seen_at"]} (#{details["first_seen_release"]})",
|
data/lib/deployangel/cli.rb
CHANGED
|
@@ -20,7 +20,7 @@ module DeployAngel
|
|
|
20
20
|
# Runs without booting Rails.
|
|
21
21
|
class CLI
|
|
22
22
|
USAGE_ERROR = 5
|
|
23
|
-
COMMANDS = %w[release verify status plan exercise exception check install doctor setup token mcp version help].freeze
|
|
23
|
+
COMMANDS = %w[release verify status plan exercise exception context check install doctor setup token mcp version help].freeze
|
|
24
24
|
|
|
25
25
|
HELP = <<~TEXT
|
|
26
26
|
Usage: deployangel <command> [options]
|
|
@@ -37,6 +37,8 @@ module DeployAngel
|
|
|
37
37
|
requests to production [--param=NAME=VALUE] [--header="Name: value"]
|
|
38
38
|
[--max-requests=200] [--dry-run]
|
|
39
39
|
exception Details for a fingerprint deployangel exception FINGERPRINT
|
|
40
|
+
context What production does with deployangel context FILE... [--route="POST /x"] [--job=Job]
|
|
41
|
+
code, before you change it [--format=text|json]
|
|
40
42
|
check Report a smoke test result --name=NAME --status=pass|fail [--covers=a,b]
|
|
41
43
|
[--commit=SHA | --deployment=ID] [--details-url=URL]
|
|
42
44
|
install Add a Kamal deploy hook, deployangel install kamal
|
|
@@ -136,6 +138,12 @@ module DeployAngel
|
|
|
136
138
|
and `list_late_regressions` (or run `bundle exec deployangel status`). If a
|
|
137
139
|
recent release failed, isn't cleared yet, or has a late regression, mention
|
|
138
140
|
it before other work.
|
|
141
|
+
- Before changing code that serves requests or runs as a job: call
|
|
142
|
+
`get_production_context` with the files you plan to change (or run
|
|
143
|
+
`bundle exec deployangel context <files>`). If an item's clearance outlook
|
|
144
|
+
is `rarely_used` or `later_today`, a clearance won't cover your change soon
|
|
145
|
+
after deploying: say so, and plan to exercise it or report a check that
|
|
146
|
+
covers it. Files in `unmapped_files` aren't known to be unused.
|
|
139
147
|
#{AGENTS_END}
|
|
140
148
|
MARKDOWN
|
|
141
149
|
|
|
@@ -185,6 +193,7 @@ module DeployAngel
|
|
|
185
193
|
when "plan" then plan
|
|
186
194
|
when "exercise" then exercise
|
|
187
195
|
when "exception" then exception
|
|
196
|
+
when "context" then context
|
|
188
197
|
when "check" then check
|
|
189
198
|
when "install" then install
|
|
190
199
|
when "doctor" then doctor
|
|
@@ -384,6 +393,28 @@ module DeployAngel
|
|
|
384
393
|
0
|
|
385
394
|
end
|
|
386
395
|
|
|
396
|
+
# What production does with some code, before changing it (spec §27,
|
|
397
|
+
# Production context).
|
|
398
|
+
def context
|
|
399
|
+
options = parse(routes: [], job_classes: [], format: nil) do |o, opts|
|
|
400
|
+
o.on("--route=ROUTE") { |v| opts[:routes] << v }
|
|
401
|
+
o.on("--job=JOB_CLASS") { |v| opts[:job_classes] << v }
|
|
402
|
+
o.on("--format=FORMAT", %w[text json]) { |v| opts[:format] = v }
|
|
403
|
+
end
|
|
404
|
+
files = @argv.dup
|
|
405
|
+
@argv.clear
|
|
406
|
+
if files.empty? && options[:routes].empty? && options[:job_classes].empty?
|
|
407
|
+
return usage_error("context needs files, --route, or --job, like `deployangel context app/jobs/refund_job.rb`")
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
details = client.production_context(files: files, routes: options[:routes], job_classes: options[:job_classes])
|
|
411
|
+
output(details, options[:format]) { Formatter.production_context(details) }
|
|
412
|
+
0
|
|
413
|
+
rescue Client::NotFound
|
|
414
|
+
@stderr.puts("deployangel: this DeployAngel server doesn't offer production context yet")
|
|
415
|
+
USAGE_ERROR
|
|
416
|
+
end
|
|
417
|
+
|
|
387
418
|
def check
|
|
388
419
|
options = parse(name: nil, status: nil, covers: [], details_url: nil) do |o, opts|
|
|
389
420
|
o.on("--name=NAME") { |v| opts[:name] = v }
|
data/lib/deployangel/client.rb
CHANGED
|
@@ -40,6 +40,13 @@ module DeployAngel
|
|
|
40
40
|
def exception(fingerprint) = get("/api/v1/exceptions/#{URI.encode_www_form_component(fingerprint)}")
|
|
41
41
|
def late_regressions(**filters) = get("/api/v1/late_regressions", filters.compact)["late_regressions"]
|
|
42
42
|
|
|
43
|
+
# What production does with some code, before a coding agent changes it
|
|
44
|
+
# (spec §27, Production context).
|
|
45
|
+
def production_context(files: [], routes: [], job_classes: [])
|
|
46
|
+
get("/api/v1/production_context",
|
|
47
|
+
{ "files[]" => files, "routes[]" => routes, "job_classes[]" => job_classes }.reject { |_, values| values.empty? })
|
|
48
|
+
end
|
|
49
|
+
|
|
43
50
|
def verification(deployment_id, all_findings: false)
|
|
44
51
|
get("/api/v1/deployments/#{deployment_id}/verification", all_findings ? { findings: "all" } : {})
|
|
45
52
|
end
|
data/lib/deployangel/mcp.rb
CHANGED
|
@@ -37,6 +37,11 @@ module DeployAngel
|
|
|
37
37
|
evidence, and give them the accept_url so a person decides. Until someone accepts it, the release
|
|
38
38
|
is failed: never report it as success. A finding with status
|
|
39
39
|
accepted was accepted as intended by the person in accepted_as_intended.
|
|
40
|
+
Before changing code that serves requests or runs as a job, call get_production_context with the files
|
|
41
|
+
you plan to change. It says which routes and jobs they serve and how often production runs them. If an
|
|
42
|
+
item's clearance outlook is rarely_used or later_today, a clearance won't cover your change soon after
|
|
43
|
+
deploying: tell the user, and plan to exercise it or report a check that covers it. unmapped_files are
|
|
44
|
+
files DeployAngel can't map to routes or jobs: never take that to mean they're unused.
|
|
40
45
|
TEXT
|
|
41
46
|
|
|
42
47
|
TARGET_PROPERTIES = {
|
|
@@ -128,7 +133,16 @@ module DeployAngel
|
|
|
128
133
|
tool("list_late_regressions", "Failures found after a release was cleared, on paths the clearance did not cover. " \
|
|
129
134
|
"One with dismissed set was dismissed by a person as a false alarm, with their reason: it needs no action.",
|
|
130
135
|
{ "since" => { "type" => "string", "description" => "ISO 8601 time" },
|
|
131
|
-
"limit" => { "type" => "integer", "minimum" => 1, "maximum" => 50, "default" => 10 } })
|
|
136
|
+
"limit" => { "type" => "integer", "minimum" => 1, "maximum" => 50, "default" => 10 } }),
|
|
137
|
+
tool("get_production_context", "What production does with code before you change it: the routes and jobs the given " \
|
|
138
|
+
"files serve, how often production ran them in the last 7 days and how many failed, whether a release's clearance " \
|
|
139
|
+
"would cover a change to them (clearance.outlook: normal_traffic, later_today, rarely_used, or unknown), their " \
|
|
140
|
+
"schedules and critical flows, and failed releases and late regressions on them in the last 90 days. " \
|
|
141
|
+
"Files it can't map are listed in unmapped_files; that doesn't mean they're unused.",
|
|
142
|
+
{ "files" => { "type" => "array", "items" => { "type" => "string" },
|
|
143
|
+
"description" => "Repository-relative paths, e.g. app/jobs/refund_job.rb (up to 50)." },
|
|
144
|
+
"routes" => { "type" => "array", "items" => { "type" => "string" }, "description" => "Route keys, e.g. \"POST /refunds\"." },
|
|
145
|
+
"job_classes" => { "type" => "array", "items" => { "type" => "string" }, "description" => "Job class names, e.g. RefundJob." } })
|
|
132
146
|
]
|
|
133
147
|
if scopes.include?("deployments")
|
|
134
148
|
list << tool("register_deployment", "Register a deployment so it is verified (manual and CI deploys).",
|
|
@@ -161,6 +175,7 @@ module DeployAngel
|
|
|
161
175
|
when "get_exception" then @client.exception(arguments.fetch("fingerprint") { raise ArgumentError, "fingerprint is required" })
|
|
162
176
|
when "list_late_regressions"
|
|
163
177
|
{ "late_regressions" => @client.late_regressions(since: arguments["since"], limit: arguments.fetch("limit", 10)) }
|
|
178
|
+
when "get_production_context" then production_context(arguments)
|
|
164
179
|
when "register_deployment"
|
|
165
180
|
raise ArgumentError, "register_deployment is not available for this token" unless scopes.include?("deployments")
|
|
166
181
|
|
|
@@ -188,6 +203,15 @@ module DeployAngel
|
|
|
188
203
|
"in_progress" => outcome.exit_code == VerificationWaiter::TIMED_OUT, "verification" => outcome.document }
|
|
189
204
|
end
|
|
190
205
|
|
|
206
|
+
def production_context(arguments)
|
|
207
|
+
files, routes, job_classes = %w[files routes job_classes].map { |key| Array(arguments[key]).map(&:to_s).reject(&:empty?) }
|
|
208
|
+
raise ArgumentError, "give files, routes, or job_classes" if [ files, routes, job_classes ].all?(&:empty?)
|
|
209
|
+
|
|
210
|
+
@client.production_context(files: files, routes: routes, job_classes: job_classes)
|
|
211
|
+
rescue Client::NotFound
|
|
212
|
+
{ "note" => "This DeployAngel server doesn't offer production context yet." }
|
|
213
|
+
end
|
|
214
|
+
|
|
191
215
|
def plan_content(outcome)
|
|
192
216
|
return { "found" => false, "note" => "No matching deployment is registered yet." } if outcome.not_found
|
|
193
217
|
|
data/lib/deployangel/version.rb
CHANGED