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.
- package/docs/gateway-baseline-2.0.80.md5 +47 -0
- package/docs/gateway-files.txt +47 -0
- package/orchestrations/config/repo-artifacts.json +23 -0
- package/orchestrations/config/resume-preserves.json +25 -0
- package/orchestrations/config/run-modes.json +47 -0
- package/orchestrations/scripts/lib/checkpoints.sh +175 -0
- package/orchestrations/scripts/lib/codeline-lanes.sh +1664 -0
- package/orchestrations/scripts/lib/codeline-packages.js +67 -0
- package/orchestrations/scripts/lib/codeline-write-perimeter.sh +318 -0
- package/orchestrations/scripts/lib/common.sh +66 -0
- package/orchestrations/scripts/lib/control-plane.sh +506 -0
- package/orchestrations/scripts/lib/ecosystem-registry.js +148 -0
- package/orchestrations/scripts/lib/engine-paths.sh +52 -0
- package/orchestrations/scripts/lib/flags.sh +38 -0
- package/orchestrations/scripts/lib/git-ops.sh +975 -0
- package/orchestrations/scripts/lib/halt-recovery.sh +56 -0
- package/orchestrations/scripts/lib/handlers/_read-input.js +88 -0
- package/orchestrations/scripts/lib/handlers/carry-plan.js +25 -0
- package/orchestrations/scripts/lib/handlers/cl-entries.js +10 -0
- package/orchestrations/scripts/lib/handlers/codeline-ecosystem.js +164 -0
- package/orchestrations/scripts/lib/handlers/codeline-manifests.js +148 -0
- package/orchestrations/scripts/lib/handlers/filtered-prd.js +85 -0
- package/orchestrations/scripts/lib/handlers/merge-lane-into-canonical.js +39 -0
- package/orchestrations/scripts/lib/handlers/package-engines-node.js +25 -0
- package/orchestrations/scripts/lib/handlers/prd-phases.js +46 -0
- package/orchestrations/scripts/lib/handlers/rebuild-cross-contract.js +7 -0
- package/orchestrations/scripts/lib/handlers/record-declared-pass.js +23 -0
- package/orchestrations/scripts/lib/handlers/release-line-prd.js +31 -0
- package/orchestrations/scripts/lib/handlers/release-lines.js +31 -0
- package/orchestrations/scripts/lib/handlers/repo-exclude-patterns.js +108 -0
- package/orchestrations/scripts/lib/handlers/resume-spec-output-present.js +16 -0
- package/orchestrations/scripts/lib/handlers/sibling-providers.js +43 -0
- package/orchestrations/scripts/lib/handlers/spanning-stories-incomplete.js +50 -0
- package/orchestrations/scripts/lib/handlers/spec-inputs-digest.js +48 -0
- package/orchestrations/scripts/lib/handlers/spec-predates-plan.js +40 -0
- package/orchestrations/scripts/lib/handlers/stories-lacking-spec.js +64 -0
- package/orchestrations/scripts/lib/handlers/write-missing-plans.js +92 -0
- package/orchestrations/scripts/lib/orchestration-resume.sh +74 -0
- package/orchestrations/scripts/lib/phase-exit.sh +90 -0
- package/orchestrations/scripts/lib/pipeline-commit.sh +19 -0
- package/orchestrations/scripts/lib/plan-hash.js +19 -0
- package/orchestrations/scripts/lib/plan-scope.js +73 -0
- package/orchestrations/scripts/lib/qa-gate-evidence.sh +235 -0
- package/orchestrations/scripts/lib/release-lines.sh +166 -0
- package/orchestrations/scripts/lib/resume-semantics.sh +31 -0
- package/orchestrations/scripts/lib/run-checkpoint.sh +842 -0
- package/orchestrations/scripts/lib/run-modes.sh +93 -0
- package/orchestrations/scripts/lib/story-outputs.sh +322 -0
- package/orchestrations/scripts/lib/story-retry-state.sh +355 -0
- 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.
|
|
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": {
|