amsd-pipeline-shared 0.5.0 → 0.5.2

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.
Files changed (50) hide show
  1. package/docs/gateway-baseline-2.0.80.md5 +47 -0
  2. package/docs/gateway-files.txt +47 -0
  3. package/orchestrations/config/repo-artifacts.json +23 -0
  4. package/orchestrations/config/resume-preserves.json +25 -0
  5. package/orchestrations/config/run-modes.json +47 -0
  6. package/orchestrations/scripts/lib/checkpoints.sh +175 -0
  7. package/orchestrations/scripts/lib/codeline-lanes.sh +1664 -0
  8. package/orchestrations/scripts/lib/codeline-packages.js +67 -0
  9. package/orchestrations/scripts/lib/codeline-write-perimeter.sh +318 -0
  10. package/orchestrations/scripts/lib/common.sh +66 -0
  11. package/orchestrations/scripts/lib/control-plane.sh +506 -0
  12. package/orchestrations/scripts/lib/ecosystem-registry.js +148 -0
  13. package/orchestrations/scripts/lib/engine-paths.sh +52 -0
  14. package/orchestrations/scripts/lib/flags.sh +38 -0
  15. package/orchestrations/scripts/lib/git-ops.sh +975 -0
  16. package/orchestrations/scripts/lib/halt-recovery.sh +56 -0
  17. package/orchestrations/scripts/lib/handlers/_read-input.js +88 -0
  18. package/orchestrations/scripts/lib/handlers/carry-plan.js +25 -0
  19. package/orchestrations/scripts/lib/handlers/cl-entries.js +10 -0
  20. package/orchestrations/scripts/lib/handlers/codeline-ecosystem.js +164 -0
  21. package/orchestrations/scripts/lib/handlers/codeline-manifests.js +148 -0
  22. package/orchestrations/scripts/lib/handlers/filtered-prd.js +85 -0
  23. package/orchestrations/scripts/lib/handlers/merge-lane-into-canonical.js +39 -0
  24. package/orchestrations/scripts/lib/handlers/package-engines-node.js +25 -0
  25. package/orchestrations/scripts/lib/handlers/prd-phases.js +46 -0
  26. package/orchestrations/scripts/lib/handlers/rebuild-cross-contract.js +7 -0
  27. package/orchestrations/scripts/lib/handlers/record-declared-pass.js +23 -0
  28. package/orchestrations/scripts/lib/handlers/release-line-prd.js +31 -0
  29. package/orchestrations/scripts/lib/handlers/release-lines.js +31 -0
  30. package/orchestrations/scripts/lib/handlers/repo-exclude-patterns.js +108 -0
  31. package/orchestrations/scripts/lib/handlers/resume-spec-output-present.js +16 -0
  32. package/orchestrations/scripts/lib/handlers/sibling-providers.js +43 -0
  33. package/orchestrations/scripts/lib/handlers/spanning-stories-incomplete.js +50 -0
  34. package/orchestrations/scripts/lib/handlers/spec-inputs-digest.js +48 -0
  35. package/orchestrations/scripts/lib/handlers/spec-predates-plan.js +40 -0
  36. package/orchestrations/scripts/lib/handlers/stories-lacking-spec.js +64 -0
  37. package/orchestrations/scripts/lib/handlers/write-missing-plans.js +92 -0
  38. package/orchestrations/scripts/lib/orchestration-resume.sh +74 -0
  39. package/orchestrations/scripts/lib/phase-exit.sh +90 -0
  40. package/orchestrations/scripts/lib/pipeline-commit.sh +19 -0
  41. package/orchestrations/scripts/lib/plan-hash.js +19 -0
  42. package/orchestrations/scripts/lib/plan-scope.js +73 -0
  43. package/orchestrations/scripts/lib/qa-gate-evidence.sh +235 -0
  44. package/orchestrations/scripts/lib/release-lines.sh +166 -0
  45. package/orchestrations/scripts/lib/resume-semantics.sh +31 -0
  46. package/orchestrations/scripts/lib/run-checkpoint.sh +842 -0
  47. package/orchestrations/scripts/lib/run-modes.sh +93 -0
  48. package/orchestrations/scripts/lib/story-outputs.sh +322 -0
  49. package/orchestrations/scripts/lib/story-retry-state.sh +355 -0
  50. package/package.json +1 -1
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env bash
2
+ # Per-story inference-ladder rung state — survives across SEPARATE claude.sh
3
+ # subprocess invocations for the same story within one run.
4
+ #
5
+ # Root cause this fixes (found live, run 20260806T021820Z): retry_count (the
6
+ # ladder's rung counter) was a `local` inside implement_story() in claude.sh.
7
+ # Step 3.6's review->re-implement loop re-invokes claude.sh as a brand-new
8
+ # subprocess on every review-rejection cycle, so retry_count silently reset
9
+ # to 0 every cycle — the ladder never climbed past rung 0 before Step 3.6's
10
+ # fixed REVIEW_MAX_CYCLES cap hard-escalated. Explicit standing requirement:
11
+ # "Retries MUST proceed up the rungs — nothing is allowed to intercede."
12
+ #
13
+ # State lives under LOG_DIR (already lane-scoped — see
14
+ # project_parallel_lanes_shared_state), one file per story.
15
+ #
16
+ # CORRECTED 2026-08-07: this used to claim the state was "wiped for free by the
17
+ # existing teardown/clean-slate reset at the start of every run. No separate
18
+ # reset logic is needed or wanted here." That was never true. pre-run-reset.sh's
19
+ # sweep matches *.log, story-outputs-*.txt and eslint-baseline-*.json, and never
20
+ # matched *.count. AMSD-2041.count held 6 across every run of 2026-08-06/07, so a
21
+ # fresh writer attempt started at rung 3 of 4: one review rejection exhausted the
22
+ # ladder, Step 3.6 escalated without a single re-implementation cycle, and the
23
+ # phase halted. Left alone, a story that exhausts its ladder once is escalated
24
+ # after one rejection on EVERY later run, forever.
25
+ #
26
+ # pre-run-reset.sh now clears these counters explicitly. Within a run they must
27
+ # still persist across claude.sh subprocesses — that is the requirement above.
28
+ #
29
+ # Sourced by both claude.sh (reads/writes retry_count) and
30
+ # run-agent-orchestration.sh (Step 3.6 advances the rung + checks exhaustion).
31
+
32
+ _story_retry_state_dir() {
33
+ local log_dir="$1"
34
+ echo "${log_dir}/story-retry-state"
35
+ }
36
+
37
+ _story_retry_state_file() {
38
+ local log_dir="$1" story_id="$2"
39
+ echo "$(_story_retry_state_dir "$log_dir")/${story_id}.count"
40
+ }
41
+
42
+ # Echoes the persisted retry_count for a story, or 0 if none exists yet.
43
+ read_story_retry_count() {
44
+ local log_dir="$1" story_id="$2"
45
+ local f
46
+ f="$(_story_retry_state_file "$log_dir" "$story_id")"
47
+ if [ -f "$f" ]; then
48
+ local v
49
+ v="$(cat "$f" 2>/dev/null)"
50
+ case "$v" in
51
+ ''|*[!0-9]*) echo 0 ;;
52
+ *) echo "$v" ;;
53
+ esac
54
+ else
55
+ echo 0
56
+ fi
57
+ }
58
+
59
+ write_story_retry_count() {
60
+ local log_dir="$1" story_id="$2" count="$3"
61
+ local dir f
62
+ dir="$(_story_retry_state_dir "$log_dir")"
63
+ mkdir -p "$dir" 2>/dev/null || true
64
+ f="$(_story_retry_state_file "$log_dir" "$story_id")"
65
+ echo "$count" > "$f" 2>/dev/null || true
66
+ }
67
+
68
+ _story_retry_model_file() {
69
+ local log_dir="$1" story_id="$2"
70
+ echo "$(_story_retry_state_dir "$log_dir")/${story_id}.model"
71
+ }
72
+
73
+ # read_story_retry_model <log_dir> <story_id>
74
+ # The model the ladder had escalated to when the last process exited, or "" if none.
75
+ #
76
+ # retry_count alone was persisted until 2026-08-10, and the counter is only a PROXY for the
77
+ # thing the ladder exists to control. STORY_MODEL is re-derived from the PRD on every
78
+ # invocation (claude.sh:1028), so a story re-entering after a Step 3.6 review rejection or a
79
+ # watchdog retry reset to the PRD model and took ONE step from there — it never resumed the
80
+ # climb. Live 2026-08-10: `InferenceLadder[Rung3/R8]: model 'MiniMax-M3' -> 'z-ai/glm-5.2'`,
81
+ # the rung-1 model at rung 3, burning rung 3's retry budget and its largest iteration budget.
82
+ #
83
+ # Empty is returned for a missing/blank file so the caller keeps the PRD model — a first
84
+ # attempt has nothing to resume, and that must stay distinguishable from a persisted value.
85
+ read_story_retry_model() {
86
+ local log_dir="$1" story_id="$2"
87
+ local f
88
+ f="$(_story_retry_model_file "$log_dir" "$story_id")"
89
+ if [ -f "$f" ]; then
90
+ local v
91
+ v="$(tr -d '\n\r' < "$f" 2>/dev/null)"
92
+ printf '%s' "$v"
93
+ else
94
+ printf ''
95
+ fi
96
+ }
97
+
98
+ write_story_retry_model() {
99
+ local log_dir="$1" story_id="$2" model="$3"
100
+ # Never persist an empty model: it would read as "no state" on resume and silently restart
101
+ # the climb, which is the defect this exists to fix.
102
+ [ -n "$model" ] || return 0
103
+ local dir f
104
+ dir="$(_story_retry_state_dir "$log_dir")"
105
+ mkdir -p "$dir" 2>/dev/null || true
106
+ f="$(_story_retry_model_file "$log_dir" "$story_id")"
107
+ printf '%s' "$model" > "$f" 2>/dev/null || true
108
+ }
109
+
110
+ _story_retry_provider_set_file() {
111
+ local log_dir="$1" story_id="$2"
112
+ echo "$(_story_retry_state_dir "$log_dir")/${story_id}.provider-set"
113
+ }
114
+
115
+ # read_story_retry_provider_set <log_dir> <story_id>
116
+ # WHICH EPAM_PROVIDER_SET was active when the persisted model was chosen, or "" if none recorded.
117
+ #
118
+ # read_story_retry_model() persists a model NAME with no notion of which provider set chose it.
119
+ # "MiniMax-M3" is meaningless once EPAM_PROVIDER_SET has moved from openrouter to claude between
120
+ # two invocations — the operator swapped because a provider ran out of tokens, exactly the
121
+ # scenario hot-swap exists for — but nothing recorded that the set had changed, so a resume
122
+ # trusted a rung from a vendor namespace the CURRENT set has never heard of. The caller compares
123
+ # this against the CURRENT EPAM_PROVIDER_SET before trusting the persisted model at all.
124
+ read_story_retry_provider_set() {
125
+ local log_dir="$1" story_id="$2"
126
+ local f
127
+ f="$(_story_retry_provider_set_file "$log_dir" "$story_id")"
128
+ if [ -f "$f" ]; then
129
+ local v
130
+ v="$(tr -d '\n\r' < "$f" 2>/dev/null)"
131
+ printf '%s' "$v"
132
+ else
133
+ printf ''
134
+ fi
135
+ }
136
+
137
+ write_story_retry_provider_set() {
138
+ local log_dir="$1" story_id="$2" set_name="$3"
139
+ # Never persist an empty set: a run with no declared set has expressed no preference, and a
140
+ # blank marker read back later must not be mistaken for "recorded, and empty" — it must read
141
+ # exactly as "nothing recorded", matching write_story_retry_model's own rule.
142
+ [ -n "$set_name" ] || return 0
143
+ local dir f
144
+ dir="$(_story_retry_state_dir "$log_dir")"
145
+ mkdir -p "$dir" 2>/dev/null || true
146
+ f="$(_story_retry_provider_set_file "$log_dir" "$story_id")"
147
+ printf '%s' "$set_name" > "$f" 2>/dev/null || true
148
+ }
149
+
150
+ _story_retry_bump_file() {
151
+ local log_dir="$1" story_id="$2"
152
+ echo "$(_story_retry_state_dir "$log_dir")/${story_id}.iterbump"
153
+ }
154
+
155
+ # The accumulated rung iteration bump. Same reason as the model: it is computed per rung and
156
+ # lives in the process, so a re-invocation restarted the story at the base budget. Observed
157
+ # live 2026-08-10 — maxIter went 185 at attempt 3, then back to 120 on the next invocation,
158
+ # discarding every rung's escalation exactly as the model did.
159
+ read_story_iteration_bump() {
160
+ local f; f="$(_story_retry_bump_file "$1" "$2")"
161
+ if [ -f "$f" ]; then
162
+ local v; v="$(tr -dc '0-9' < "$f" 2>/dev/null)"
163
+ echo "${v:-0}"
164
+ else
165
+ echo 0
166
+ fi
167
+ }
168
+
169
+ write_story_iteration_bump() {
170
+ local log_dir="$1" story_id="$2" bump="$3"
171
+ case "$bump" in ''|*[!0-9]*) return 0 ;; esac
172
+ mkdir -p "$(_story_retry_state_dir "$log_dir")" 2>/dev/null || true
173
+ printf '%s' "$bump" > "$(_story_retry_bump_file "$log_dir" "$story_id")" 2>/dev/null || true
174
+ }
175
+
176
+ _story_effective_iters_file() {
177
+ local log_dir="$1" story_id="$2"
178
+ echo "$(_story_retry_state_dir "$log_dir")/${story_id}.effiter"
179
+ }
180
+
181
+ # THE WALL MUST BE DERIVED FROM THE BUDGET IT POLICES, AND ONLY THE CHILD KNOWS THAT BUDGET.
182
+ #
183
+ # run-agent-orchestration.sh sizes each story's watchdog timeout from
184
+ # secondsPerIteration x iterations, capped at storyTimeoutMaxSecs. It reads iterations from
185
+ # EPAM_MAX_ITERATIONS — which is UNSET in the parent, because claude.sh computes
186
+ # _effective_max_iterations per model, per attempt, several minutes AFTER the parent has
187
+ # already fixed its wall. The value flows DOWNWARD to the LLM invocation and never UPWARD to
188
+ # the watchdog policing it, so the derivation branch has never once executed and the wall has
189
+ # always silently stayed at the configured FLOOR.
190
+ #
191
+ # Measured live 2026-08-11: MiniMax-M3 was granted 185 iterations at 12s each = 2,220s of
192
+ # AUTHORISED work, policed by an 1,800s wall, with a 5,400s cap never approached. The story
193
+ # was authorised to do 37 minutes of work and killed at 30. The kill was scheduled at
194
+ # authorisation time, and the same 1800s timeout fired again on the 23:34 run.
195
+ #
196
+ # So the child writes what it actually granted, and the parent reads it on EVERY attempt.
197
+ read_story_effective_iterations() {
198
+ local f; f="$(_story_effective_iters_file "$1" "$2")"
199
+ if [ -f "$f" ]; then
200
+ local v; v="$(tr -dc '0-9' < "$f" 2>/dev/null)"
201
+ echo "${v:-0}"
202
+ else
203
+ echo 0
204
+ fi
205
+ }
206
+
207
+ write_story_effective_iterations() {
208
+ local log_dir="$1" story_id="$2" iters="$3"
209
+ case "$iters" in ''|*[!0-9]*) return 0 ;; esac
210
+ mkdir -p "$(_story_retry_state_dir "$log_dir")" 2>/dev/null || true
211
+ printf '%s' "$iters" > "$(_story_effective_iters_file "$log_dir" "$story_id")" 2>/dev/null || true
212
+ }
213
+
214
+ # rung = retry_count / 2 (matches claude.sh's own `_rung=$(( retry_count / 2 ))`)
215
+ story_ladder_rung() {
216
+ local retry_count="$1"
217
+ echo $(( retry_count / 2 ))
218
+ }
219
+
220
+ story_max_rung() {
221
+ local max_retries="$1"
222
+ echo $(( max_retries / 2 ))
223
+ }
224
+
225
+ # True (exit 0) once the story's persisted rung has reached the top rung —
226
+ # i.e. there is no further model/effort escalation the ladder can offer.
227
+ story_ladder_exhausted() {
228
+ local log_dir="$1" story_id="$2" max_retries="$3"
229
+ local cur rung max_rung
230
+ cur="$(read_story_retry_count "$log_dir" "$story_id")"
231
+ rung="$(story_ladder_rung "$cur")"
232
+ max_rung="$(story_max_rung "$max_retries")"
233
+ [ "$rung" -ge "$max_rung" ]
234
+ }
235
+
236
+ # Advances a story's persisted retry_count to the START of the NEXT rung
237
+ # (e.g. rung0 -> retry_count 2, rung1 -> retry_count 4), clamped to
238
+ # max_retries so an already-top-rung story stays at the top rung rather than
239
+ # being pushed past MAX_RETRIES (which would skip its final attempt
240
+ # entirely). A review rejection is itself evidence this attempt did not
241
+ # succeed, even when the code built/tested fine internally — so review-
242
+ # driven re-implementation must count as ladder progression too, not just
243
+ # internal build/test failures.
244
+ # The shared key convention for ai-run.sh's ladder state — ANY caller that
245
+ # needs to advance an agent's ladder position from OUTSIDE ai-run.sh (e.g. a
246
+ # reviewer rejecting a technically-successful call) must derive the same key
247
+ # this way, or its write lands on a different file than ai-run.sh reads.
248
+ # Sanitized because EPAM_AGENT_NAME routinely contains ':' (e.g.
249
+ # "code-graph-detective:plan"), which is not a safe filename component.
250
+ ai_ladder_state_key() {
251
+ local agent_name="${1:-agent}" story_id="${2:-global}"
252
+ local key="${agent_name}__${story_id}"
253
+ # printf (not echo) — echo's trailing newline would itself get mangled by
254
+ # tr into a trailing '_', corrupting every key it touches.
255
+ printf '%s' "$key" | tr -c 'A-Za-z0-9_.-' '_'
256
+ }
257
+
258
+ advance_story_retry_rung() {
259
+ local log_dir="$1" story_id="$2" max_retries="$3"
260
+ local cur cur_rung next
261
+ cur="$(read_story_retry_count "$log_dir" "$story_id")"
262
+ cur_rung="$(story_ladder_rung "$cur")"
263
+ next=$(( (cur_rung + 1) * 2 ))
264
+ [ "$next" -gt "$max_retries" ] && next="$max_retries"
265
+ write_story_retry_count "$log_dir" "$story_id" "$next"
266
+ }
267
+
268
+ # grant_remediated_attempt <log_dir> <story_id> <max_retries>
269
+ #
270
+ # A REMEDIATED GATE GIVES THE WRITER AN ATTEMPT. When a gate's self-heal adds requirements to a story whose ladder is
271
+ # already spent, the story gets one attempt on its top rung to act on them — as review remediation already does
272
+ # (lib/review-cycle.sh). AMSD-2974 run 9 resume 13: the mutant-hunter remediation added 2 ACs, the lane retried, and
273
+ # the writer stopped at "retry_count=12 … Failed after 12 attempts" without calling the model. A story with attempts
274
+ # left is not touched.
275
+ grant_remediated_attempt() {
276
+ local log_dir="$1" story_id="$2" max_retries="$3" cur
277
+ cur="$(read_story_retry_count "$log_dir" "$story_id")"
278
+ case "$cur$max_retries" in *[!0-9]*|'') return 0 ;; esac
279
+ [ "$cur" -gt "$max_retries" ] && write_story_retry_count "$log_dir" "$story_id" "$max_retries"
280
+ return 0
281
+ }
282
+
283
+ # advance_story_retry_on_review_rejection <log_dir> <story_id> <max_retries>
284
+ #
285
+ # A REVIEW REJECTION RETRIES THE SAME MODEL BEFORE CLIMBING. One attempt, not one rung: each rung allows two attempts,
286
+ # and a rejection on a rung's first attempt is retried on that rung — with the requirements the review remediation
287
+ # derived from it — before the ladder climbs. AMSD-2974 run 9 (2026-10-10): the writer's first attempt on Sonnet 5 was
288
+ # rejected and the next ran on Opus 5.5; the run never came back down. Operator, 2026-10-11 ("Option 2").
289
+ advance_story_retry_on_review_rejection() {
290
+ local log_dir="$1" story_id="$2" max_retries="$3"
291
+ local next
292
+ next=$(( $(read_story_retry_count "$log_dir" "$story_id") + 1 ))
293
+ [ "$next" -gt "$max_retries" ] && next="$max_retries"
294
+ write_story_retry_count "$log_dir" "$story_id" "$next"
295
+ }
296
+
297
+ # ai-run.sh's ladder is flat (one escalation per attempt, no 2-attempts-per-
298
+ # rung grouping — that grouping is specific to claude.sh's writer loop). ANY
299
+ # caller that re-invokes an ai-run.sh-based agent after an explicit
300
+ # REJECTION (not a transport failure ai-run.sh already retries on its own)
301
+ # must call this — using the SAME key ai_ladder_state_key() derives — before
302
+ # re-invoking, or the next ai-run.sh process resumes at rung 0 regardless of
303
+ # how far a prior invocation actually climbed.
304
+ advance_ladder_escalation() {
305
+ local log_dir="$1" key="$2"
306
+ local cur
307
+ cur="$(read_story_retry_count "$log_dir" "$key")"
308
+ cur=$((cur + 1))
309
+ write_story_retry_count "$log_dir" "$key" "$cur"
310
+ echo "$cur"
311
+ }
312
+
313
+ # escalation_budget_allows <attempts_made_so_far>
314
+ #
315
+ # AN ESCALATED FIX CLIMBS THE OWNER'S LADDER (found live, 2026-09-20, regintel
316
+ # 20260919T224649Z resume 7). resolve_escalation used to run the owning story with
317
+ # MAX_RETRIES=1: one attempt at rung 0, then "HealingBroken: at max rung" — a
318
+ # one-rung ladder has nothing to climb to, every re-escalation ran the same
319
+ # model with the same amendment, and once the persisted count passed 1 the loop
320
+ # never entered at all. Three stories burned their ladders re-escalating into a
321
+ # fix that could not change.
322
+ #
323
+ # Now the owner keeps its FULL ladder and its persisted rung; what an escalation
324
+ # bounds is how many attempts it may make THIS time. The budget is declared per
325
+ # project (llm-settings.json retries.escalationAttempts → EPAM_ESCALATION_ATTEMPTS)
326
+ # and set by resolve_escalation for the duration of the owner's call; an
327
+ # ordinary story (no budget) is unaffected. The count persists between
328
+ # escalations, so the next one resumes at the rung this one reached.
329
+ # escalation_start_retry_count <persisted_count> <max_retries>
330
+ #
331
+ # WHERE AN ESCALATED FIX STARTS ON THE OWNER'S LADDER. Live 2026-09-24 (regintel, v2.0.65): the
332
+ # codeline's real defect was diagnosed in REGI-005-A's file and escalated to it, but REGI-005-A's
333
+ # count was 4 against a ladder of 2 -- spent in EARLIER runs and carried in by the resume. The
334
+ # attempt loop runs while retry_count <= MAX_RETRIES, so the owner could never make one attempt,
335
+ # and the one bug failing every story could never be fixed. An escalation brings NEW evidence and
336
+ # its own bounded budget (escalation_budget_allows); an owner already past its ladder takes it on
337
+ # its TOP rung. Without a budget (an ordinary story) the count is returned unchanged.
338
+ escalation_start_retry_count() {
339
+ local count="${1:-0}" max="${2:-0}"
340
+ case "$count" in ''|*[!0-9]*) count=0 ;; esac
341
+ case "$max" in ''|*[!0-9]*) max=0 ;; esac
342
+ if [ -n "${EPAM_ESCALATION_ATTEMPT_BUDGET:-}" ] && [ "$count" -gt "$max" ]; then
343
+ echo "$max"
344
+ else
345
+ echo "$count"
346
+ fi
347
+ }
348
+
349
+ escalation_budget_allows() {
350
+ local attempts_made="${1:-0}"
351
+ local budget="${EPAM_ESCALATION_ATTEMPT_BUDGET:-}"
352
+ [ -z "$budget" ] && return 0
353
+ case "$budget" in ''|*[!0-9]*) return 0 ;; esac
354
+ [ "$attempts_made" -lt "$budget" ]
355
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "The AMSD pipeline's shared components, one source for the bash pipeline and its Claude workflows: the model-call gateway (llm-handler.sh — retry, ladder, self-heal, cost) and the libraries it runs.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {