amsd-pipeline-shared 0.5.2 → 0.5.3

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.
@@ -603,25 +603,36 @@ archive_prior_lane_state() {
603
603
  local _lanes="${LOG_DIR:-}/lanes"
604
604
  [ -d "$_lanes" ] || return 0
605
605
  local _run="${EPAM_RESUME_RUN:-${ORCH_RUN_ID:-}}"
606
- [ -n "$_run" ] || return 0
606
+ if [ -z "$_run" ]; then
607
+ warning "[orch] the earlier lane state is not archived: there is no run id (EPAM_RESUME_RUN / ORCH_RUN_ID) — it stays where it is" || true
608
+ return 0
609
+ fi
607
610
  local _marker=""
608
611
  if [ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ]; then
609
612
  _marker="${EPAM_PROJECT_CONFIG_DIR}/runs/${_run}/lane-state-archived"
610
613
  [ -f "$_marker" ] && return 0
611
614
  fi
612
- # The run's start is in its id (YYYYMMDDTHHMMSSZ, UTC).
613
- local _start
615
+ # B80 (RS-7): the run's start is its id when the id is a timestamp (as before); for ANY other id (every Claude workflows id is LIVE-…) it is NOW — the
616
+ # first time this function runs for the run — which is only safe with the marker that stops a resume from archiving the run's own state
617
+ local _start _how=""
614
618
  _start=$(printf '%s' "$_run" | sed -nE 's/^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?$/\1-\2-\3 \4:\5:\6 UTC/p')
615
- [ -n "$_start" ] || return 0
619
+ if [ -z "$_start" ]; then
620
+ if [ -z "$_marker" ]; then
621
+ warning "[orch] the earlier lane state is not archived: run id '${_run}' carries no start time and there is no EPAM_PROJECT_CONFIG_DIR to record that it was archived (a resume would archive the run's own state)" || true
622
+ return 0
623
+ fi
624
+ _start=$(date -u '+%Y-%m-%d %H:%M:%S.%N UTC'); _how=" (run '${_run}' carries no start time: it starts now)"
625
+ fi
616
626
  local _ref; _ref=$(mktemp "${TMPDIR:-/tmp}/run-start-XXXXXX") || return 0
617
627
  touch -d "$_start" "$_ref" 2>/dev/null || { rm -f "$_ref"; return 0; }
618
- local _dest="${LOG_DIR}/archive/lanes-before-${_run}" _n=0 _f _rel
628
+ local _dest="${LOG_DIR}/archive/lanes-before-${_run}" _n=0 _failed=0 _f _rel
619
629
  while IFS= read -r -d '' _f; do
620
630
  _rel="${_f#"$_lanes"/}"
621
- mkdir -p "$_dest/$(dirname "$_rel")" 2>/dev/null && mv "$_f" "$_dest/$_rel" 2>/dev/null && _n=$((_n + 1))
631
+ if mkdir -p "$_dest/$(dirname "$_rel")" 2>/dev/null && mv "$_f" "$_dest/$_rel" 2>/dev/null; then _n=$((_n + 1)); else _failed=$((_failed + 1)); fi
622
632
  done < <(find "$_lanes" -type f ! -newer "$_ref" -print0 2>/dev/null)
623
633
  rm -f "$_ref"
624
- [ "$_n" -gt 0 ] && log "[orch] archived ${_n} file(s) of earlier runs' lane state → ${_dest} (this run starts with none of it)"
634
+ [ "$_n" -gt 0 ] && { log "[orch] archived ${_n} file(s) of earlier runs' lane state → ${_dest} (this run starts with none of it)${_how}" || true; }
635
+ [ "$_failed" -gt 0 ] && { warning "[orch] ${_failed} file(s) of earlier runs' lane state could not be archived — they stay in ${_lanes}" || true; }
625
636
  [ -n "$_marker" ] && { mkdir -p "${_marker%/*}" 2>/dev/null; date -u +%Y-%m-%dT%H:%M:%SZ > "$_marker"; }
626
637
  return 0
627
638
  }
@@ -223,13 +223,32 @@ qa_phase_baseline_sha() {
223
223
  # phase-baseline-sha.txt: every pre-existing failure was charged to its writer, eight attempts in a
224
224
  # row (£0 multi-lane run, 2026-10-04). Nothing resolvable is said out loud, never written as a guess.
225
225
  qa_record_phase_baseline() {
226
- local _root="${1:-}" _branch="${2:-}" _log_dir="${3:-${LOG_DIR:-}}" _sha
226
+ local _root="${1:-}" _branch="${2:-}" _log_dir="${3:-${LOG_DIR:-}}" _sha _have _key
227
227
  [ -n "$_root" ] && [ -n "$_log_dir" ] || return 1
228
+ local _bf="$_log_dir/phase-baseline-sha.txt" _kf="$_log_dir/phase-baseline-key.txt"
229
+ # B77 (RS-4, = B73): THE BASELINE IS RECORDED ONCE for a run and phase. A resume starts the phase again; the baseline it had must not move to wherever HEAD
230
+ # is by then (that made "committed since the baseline" empty). A new phase, or a new run in the same log folder, has a different key and records its own.
231
+ _key="${PHASE:-main}|${ORCH_RUN_ID:-}"
232
+ if [ -f "$_bf" ] && [ -f "$_kf" ] && [ "$(cat "$_kf" 2>/dev/null)" = "$_key" ]; then
233
+ _have=$(tr -d '[:space:]' < "$_bf" 2>/dev/null)
234
+ if [ -n "$_have" ] && git -C "$_root" cat-file -e "${_have}^{commit}" 2>/dev/null; then
235
+ printf '%s' "$_have"
236
+ return 0
237
+ fi
238
+ warning "[orch] the recorded phase baseline (${_have:-empty}) is not a commit of $_root — recording it again" || true
239
+ fi
228
240
  _sha="$(qa_phase_baseline_sha "$_root" "$_branch" 2>/dev/null || echo "")"
229
241
  if [ -z "$_sha" ]; then
230
- warning "[orch] could not resolve a baseline commit in $_root — every diff-based gate will compare against nothing"
242
+ warning "[orch] could not resolve a baseline commit in $_root — every diff-based gate will compare against nothing" || true
231
243
  return 1
232
244
  fi
233
- printf '%s\n' "$_sha" > "$_log_dir/phase-baseline-sha.txt"
245
+ # the fallback is HEAD (the codeline has no such baseline branch — cx-shared has no develop): fine at the FIRST call, and said, because it is not a fork point
246
+ if [ -n "$_branch" ] && ! git -C "$_root" rev-parse --verify --quiet "origin/${_branch}^{commit}" >/dev/null 2>&1 \
247
+ && ! git -C "$_root" rev-parse --verify --quiet "${_branch}^{commit}" >/dev/null 2>&1; then
248
+ warning "[orch] baseline '${_branch}' did not resolve in $_root — the phase baseline is HEAD (${_sha:0:9}) as of now; it is recorded once and not moved" || true
249
+ fi
250
+ printf '%s\n' "$_sha" > "$_bf"
251
+ printf '%s\n' "$_key" > "$_kf"
234
252
  printf '%s' "$_sha"
253
+ return 0
235
254
  }
@@ -32,19 +32,35 @@ _run_release_lines() {
32
32
 
33
33
  # _return_from_release_lines <codeline-root> [name] — back on the branch the record names; the record is removed.
34
34
  _return_from_release_lines() {
35
- local _gd _home
35
+ local _gd _home _cur
36
36
  _gd=$(git -C "$1" rev-parse --absolute-git-dir 2>/dev/null) || return 0
37
37
  [ -f "$_gd/epam-release-home" ] || return 0
38
38
  _home=$(cat "$_gd/epam-release-home")
39
- if [ -n "$_home" ] && [ "$(git -C "$1" symbolic-ref -q --short HEAD 2>/dev/null)" != "$_home" ]; then
39
+ _cur=$(git -C "$1" symbolic-ref -q --short HEAD 2>/dev/null)
40
+ if [ -n "$_home" ] && [ "$_cur" != "$_home" ]; then
40
41
  if git -C "$1" checkout -q "$_home" 2>/dev/null; then
41
- log "[orch] Codeline '${2:-$1}': release lines done — back on ${_home}"
42
+ log "[orch] Codeline '${2:-$1}': release lines done — back on ${_home}" || true
42
43
  else
43
- warning "[orch] Codeline '${2:-$1}': could not return to ${_home} after its release lines"
44
- return 0
44
+ # B81 (RS-8): the usual reason is a dirty tree (a killed session). Whatever it holds is PRESERVED first (a rescue ref, git-ops.sh), then the copy is
45
+ # forced home; if it still cannot go home, that is an error (rc 1, the marker kept) — never a warning that leaves the copy on the line branch.
46
+ if [ -n "$(git -C "$1" status --porcelain --untracked-files=all 2>/dev/null)" ]; then
47
+ if declare -F _preserve_uncommitted >/dev/null 2>&1; then
48
+ _preserve_uncommitted "$1" "${_cur//\//-}" || { error "[orch] Codeline '${2:-$1}': could not return to ${_home} after its release lines — its uncommitted work could not be preserved, so nothing was forced"; return 1; }
49
+ else
50
+ error "[orch] Codeline '${2:-$1}': could not return to ${_home} after its release lines — the tree is dirty and this install cannot preserve it (needs amsd-pipeline-shared >= 0.5.2)"
51
+ return 1
52
+ fi
53
+ fi
54
+ if git -C "$1" checkout -q -f "$_home" 2>/dev/null; then
55
+ warning "[orch] Codeline '${2:-$1}': release lines done — back on ${_home} (its tree was not clean; the work it held is preserved)" || true
56
+ else
57
+ error "[orch] Codeline '${2:-$1}': could not return to ${_home} after its release lines (it is still on ${_cur:-a detached HEAD}; any uncommitted work is preserved on a rescue ref)"
58
+ return 1
59
+ fi
45
60
  fi
46
61
  fi
47
62
  rm -f "$_gd/epam-release-home"
63
+ return 0
48
64
  }
49
65
 
50
66
  # _return_codelines_from_release_lines <prd> — every codeline of the PRD a line pass left is put back before the
@@ -220,12 +220,16 @@ story_outputs_tests_for() {
220
220
  # the writer skips it and the review judges the committed change. A newer rejection leaves it for the writer.
221
221
  story_resume_completed_by_commit() {
222
222
  local project_root="$1" log_dir="$2" prd="$3"
223
- [ -n "$project_root" ] && [ -e "$project_root/.git" ] && [ -f "$prd" ] || return 0
224
- [ -f "$log_dir/phase-baseline-sha.txt" ] || return 0
223
+ [ -n "$project_root" ] && [ -e "$project_root/.git" ] && [ -f "$prd" ] || { echo "[resume] no story is completed by its commit: ${project_root:-no project root} is not a repository, or there is no PRD at ${prd:-none}" >&2; return 0; }
224
+ # B78 (RS-5): a precondition it cannot meet is SAID — the story is then left exactly as it was (the writer is invoked), and the operator can see why
225
+ [ -f "$log_dir/phase-baseline-sha.txt" ] || { echo "[resume] no phase baseline is recorded in ${log_dir} — cannot tell which commits are this run's; no story is completed by its commit" >&2; return 0; }
225
226
  local _base; _base=$(tr -d '[:space:]' < "$log_dir/phase-baseline-sha.txt" 2>/dev/null)
226
- [ -n "$_base" ] && git -C "$project_root" rev-parse --verify --quiet "$_base" >/dev/null 2>&1 || return 0
227
+ if [ -z "$_base" ] || ! git -C "$project_root" rev-parse --verify --quiet "${_base}^{commit}" >/dev/null 2>&1; then
228
+ echo "[resume] the recorded phase baseline (${_base:-empty}) is not a commit of ${project_root} — no story is completed by its commit" >&2
229
+ return 0
230
+ fi
227
231
  local _id _ct _fb _fbt
228
- for _id in $(jq -r '.stories[]? | select(.completed != true) | .id' "$prd" 2>/dev/null); do
232
+ for _id in $(jq -r '.stories[]? | select(.completed != true and .status != "deprecated") | .id' "$prd" 2>/dev/null); do
229
233
  _ct=$(git -C "$project_root" log "${_base}..HEAD" --grep="^${_id}:" -1 --format=%ct 2>/dev/null)
230
234
  [ -n "$_ct" ] || continue
231
235
  _fb="$log_dir/review-feedback-${_id}.json"
@@ -234,9 +238,9 @@ story_resume_completed_by_commit() {
234
238
  [ "${_fbt:-0}" -gt "$_ct" ] && continue
235
239
  fi
236
240
  local _tmp; _tmp=$(mktemp)
237
- jq --arg id "$_id" '(.stories[] | select(.id == $id)) |= (. + {completed: true})' "$prd" > "$_tmp" \
241
+ jq --arg id "$_id" '(.stories[] | select(.id == $id)) |= (. + {completed: true, status: "completed"})' "$prd" > "$_tmp" \
238
242
  && mv "$_tmp" "$prd" || rm -f "$_tmp"
239
- echo "[resume] ${_id}: its work is committed in this run ($(git -C "$project_root" log "${_base}..HEAD" --grep="^${_id}:" -1 --format=%h 2>/dev/null)) and no review rejection is newer — the writer is not invoked again; the review judges the committed change"
243
+ echo "[resume] ${_id}: its work is committed in this run ($(git -C "$project_root" log "${_base}..HEAD" --grep="^${_id}:" -1 --format=%h 2>/dev/null)) and no review rejection is newer — the writer is not invoked again; the review judges the committed change" >&2
240
244
  done
241
245
  return 0
242
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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": {