amsd-pipeline-shared 0.5.1 → 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.
- package/docs/gateway-baseline-2.0.80.md5 +2 -0
- package/docs/gateway-files.txt +2 -0
- package/orchestrations/scripts/lib/codeline-lanes.sh +18 -7
- package/orchestrations/scripts/lib/engine-paths.sh +52 -0
- package/orchestrations/scripts/lib/git-ops.sh +53 -6
- package/orchestrations/scripts/lib/pipeline-commit.sh +19 -0
- package/orchestrations/scripts/lib/qa-gate-evidence.sh +22 -3
- package/orchestrations/scripts/lib/release-lines.sh +21 -5
- package/orchestrations/scripts/lib/story-outputs.sh +10 -6
- package/package.json +1 -1
|
@@ -86,3 +86,5 @@ ac166207d8a1d93a4c50a88568ef6e5c scripts/lib/handlers/write-missing-plans.js
|
|
|
86
86
|
8a1bb4df2d1b6e7fd135f757423fb2f2 config/repo-artifacts.json
|
|
87
87
|
91cec3c50040ebc9050c3d95f037bce2 config/run-modes.json
|
|
88
88
|
ee4366721c38cbc8cd72ee0f779d22a8 scripts/lib/plan-scope.js
|
|
89
|
+
aab7063cf68a9ecd54f6ef735adf895e scripts/lib/engine-paths.sh
|
|
90
|
+
1d0346da09c13150e511809034396949 scripts/lib/pipeline-commit.sh
|
package/docs/gateway-files.txt
CHANGED
|
@@ -32,6 +32,7 @@ scripts/lib/doctor/repair.js
|
|
|
32
32
|
scripts/lib/doctor/restarter.js
|
|
33
33
|
scripts/lib/doctor/state.js
|
|
34
34
|
scripts/lib/ecosystem-registry.js
|
|
35
|
+
scripts/lib/engine-paths.sh
|
|
35
36
|
scripts/lib/engine-prompt.js
|
|
36
37
|
scripts/lib/env-file.sh
|
|
37
38
|
scripts/lib/evidence-windows.js
|
|
@@ -73,6 +74,7 @@ scripts/lib/model-ladder.sh
|
|
|
73
74
|
scripts/lib/model-ladders.sh
|
|
74
75
|
scripts/lib/orchestration-resume.sh
|
|
75
76
|
scripts/lib/phase-exit.sh
|
|
77
|
+
scripts/lib/pipeline-commit.sh
|
|
76
78
|
scripts/lib/pipeline-tools.js
|
|
77
79
|
scripts/lib/plan-hash.js
|
|
78
80
|
scripts/lib/plan-scope.js
|
|
@@ -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
|
-
[ -
|
|
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
|
-
#
|
|
613
|
-
|
|
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
|
-
[ -
|
|
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
|
|
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
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# What the ENGINE owns — the bash half of src/config/enginePaths.ts.
|
|
3
|
+
#
|
|
4
|
+
# These directories hold epam-cli's own state: knowledge base, agent profiles, run logs,
|
|
5
|
+
# code indexes, telemetry, contracts. None of it is client content, none of it is writer
|
|
6
|
+
# output, and none of it may be committed into a client codeline.
|
|
7
|
+
#
|
|
8
|
+
# ONE definition, because this rule has already drifted three ways. lib/git-ops.sh carried
|
|
9
|
+
# the full list, worktree-health-check.sh excluded only orchestrations/logs/* (so
|
|
10
|
+
# orchestrations/agents/KB.md passed straight through), Step 9's auto-commit excluded
|
|
11
|
+
# nothing, and lib/story-outputs.sh had its own separate regex covering only two of the
|
|
12
|
+
# five. Live metrolinx 20260804T225443Z: the engine's KB.md was written into the upexpress
|
|
13
|
+
# client repo and recorded as writer output in that lane's manifest.
|
|
14
|
+
#
|
|
15
|
+
# The real perimeter is the WRITE seam (src/config/enginePaths.ts, enforced in
|
|
16
|
+
# WriteFileTool). Everything here is defence in depth.
|
|
17
|
+
|
|
18
|
+
_ENGINE_OWNED_DIRS=( 'orchestrations' '.epam' '.codegraph' '.deepeval' '.contracts' )
|
|
19
|
+
|
|
20
|
+
# engine_paths_filter — stdin: repo-relative paths. stdout: those that are NOT engine-owned.
|
|
21
|
+
#
|
|
22
|
+
# Matches whole path SEGMENTS, never a substring: `src/orchestrations-ui/App.tsx` is client
|
|
23
|
+
# code and must survive, while `orchestrations/agents/KB.md` and
|
|
24
|
+
# `packages/web/.epam/settings.json` must both be dropped.
|
|
25
|
+
engine_paths_filter() {
|
|
26
|
+
local _line
|
|
27
|
+
local _owned_list
|
|
28
|
+
_owned_list=$(printf '%s\n' "${_ENGINE_OWNED_DIRS[@]}")
|
|
29
|
+
while IFS= read -r _line; do
|
|
30
|
+
[ -n "$_line" ] || continue
|
|
31
|
+
# -x -F: exact, literal, whole-segment. No pattern matching.
|
|
32
|
+
if printf '%s\n' "$_line" | tr '/' '\n' | grep -qxF "$_owned_list" 2>/dev/null; then
|
|
33
|
+
continue
|
|
34
|
+
fi
|
|
35
|
+
printf '%s\n' "$_line"
|
|
36
|
+
done
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# engine_paths_pathspec — the same owned set, as git pathspec exclusions.
|
|
40
|
+
#
|
|
41
|
+
# engine_paths_filter above filters a LIST of paths. Some callers need the exclusion applied by
|
|
42
|
+
# git itself, because their output is a patch rather than a list and there are no paths to filter.
|
|
43
|
+
# Both read _ENGINE_OWNED_DIRS, so the set is defined once: restating it in pathspec form at a
|
|
44
|
+
# call site is how this rule has already drifted three ways.
|
|
45
|
+
#
|
|
46
|
+
# Usage: mapfile -t _ex < <(engine_paths_pathspec); git diff <ref> -- . "${_ex[@]}"
|
|
47
|
+
engine_paths_pathspec() {
|
|
48
|
+
local _d
|
|
49
|
+
for _d in "${_ENGINE_OWNED_DIRS[@]}"; do
|
|
50
|
+
printf ':!%s/*\n:!*/%s/*\n' "$_d" "$_d"
|
|
51
|
+
done
|
|
52
|
+
}
|
|
@@ -370,22 +370,66 @@ git_add_client_outputs() {
|
|
|
370
370
|
return $_rc
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
#
|
|
374
|
-
#
|
|
373
|
+
# story_branch_name <story_id> [suffix] — THE ONE RULE for a story's branch name, for every pipeline that works a story branch (B75, RS-10): the project's
|
|
374
|
+
# prefix (EPAM_BRANCH_PREFIX), "AI-<story_id>", and a release line's suffix (the argument, else EPAM_BRANCH_SUFFIX; an explicit empty suffix means none).
|
|
375
|
+
story_branch_name() { printf '%s' "${EPAM_BRANCH_PREFIX:-}AI-${1}${2-${EPAM_BRANCH_SUFFIX:-}}"; }
|
|
376
|
+
|
|
377
|
+
# _story_local_base_ref <root> <baseline> — the baseline as the checkout already has it, WITHOUT the network: the tag, then origin's branch, then the local
|
|
378
|
+
# branch. Empty when none resolves.
|
|
379
|
+
_story_local_base_ref() {
|
|
380
|
+
local _root="$1" _b="${2#refs/tags/}" _ref
|
|
381
|
+
_b="${_b#refs/heads/}"
|
|
382
|
+
[ -n "$_b" ] || return 0
|
|
383
|
+
for _ref in "refs/tags/${_b}" "refs/remotes/origin/${_b}" "refs/heads/${_b}"; do
|
|
384
|
+
git -C "$_root" rev-parse -q --verify "${_ref}^{commit}" >/dev/null 2>&1 && { printf '%s' "$_ref"; return 0; }
|
|
385
|
+
done
|
|
386
|
+
return 0
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# _story_branch_carries_own_work <root> <branch> <story_id> [baseline] — true when <branch> exists and carries the story's own work. B75 (RS-1): the evidence
|
|
390
|
+
# is GIT — a commit of the story ("<story_id>: …", the convention every commit of the story follows) between the baseline and the branch — so it holds for
|
|
391
|
+
# any pipeline, wherever it keeps its records; a client's own history using the same ticket prefix does not count (only commits since the baseline). The bash
|
|
392
|
+
# pipeline's own record (LOG_DIR/story-changes.jsonl) is evidence too, as before. Never on a resume sent back to an earlier stage.
|
|
375
393
|
_story_branch_carries_own_work() {
|
|
376
|
-
local _root="$1" _br="$2" _id="$3" _f="${LOG_DIR:-}/story-changes.jsonl" _sha
|
|
377
|
-
[ -n "$_id" ]
|
|
394
|
+
local _root="$1" _br="$2" _id="$3" _base_name="${4:-}" _f="${LOG_DIR:-}/story-changes.jsonl" _sha _base _re
|
|
395
|
+
[ -n "$_id" ] || return 1
|
|
378
396
|
git -C "$_root" rev-parse -q --verify "refs/heads/${_br}" >/dev/null 2>&1 || return 1
|
|
379
397
|
if [ -n "${EPAM_RESUME_FROM_STAGE:-}" ] && declare -F _stage_rank >/dev/null 2>&1 \
|
|
380
398
|
&& [ "$(_stage_rank "$EPAM_RESUME_FROM_STAGE")" -lt "$(_stage_rank pre-writer)" ]; then
|
|
381
399
|
return 1
|
|
382
400
|
fi
|
|
401
|
+
_base=$(_story_local_base_ref "$_root" "$_base_name")
|
|
402
|
+
if [ -n "$_base" ]; then
|
|
403
|
+
_re=$(printf '%s' "$_id" | sed 's/[][\.*^$/]/\\&/g')
|
|
404
|
+
[ -n "$(git -C "$_root" log -1 --format=%h --grep="^${_re}:" "${_base}..refs/heads/${_br}" 2>/dev/null)" ] && return 0
|
|
405
|
+
fi
|
|
406
|
+
[ -f "$_f" ] || return 1
|
|
383
407
|
while IFS= read -r _sha; do
|
|
384
408
|
[ -n "$_sha" ] && git -C "$_root" merge-base --is-ancestor "$_sha" "refs/heads/${_br}" 2>/dev/null && return 0
|
|
385
409
|
done < <(jq -r --arg id "$_id" 'select(.storyId == $id) | .sha // empty' "$_f" 2>/dev/null)
|
|
386
410
|
return 1
|
|
387
411
|
}
|
|
388
412
|
|
|
413
|
+
# _preserve_uncommitted <root> <story_id> — B75 (RS-3): before ANY reset, whatever the working tree holds that no commit holds (tracked edits and untracked
|
|
414
|
+
# files, the pipeline's own .epam/ aside) is committed to a rescue ref, epam-rescue/<story>-wip-<sha>, without touching the branch, the index or the tree.
|
|
415
|
+
# Nothing to preserve: nothing is done. Fails (non-zero) when it cannot — the caller then refuses to reset, as it does when a commit cannot be pinned.
|
|
416
|
+
_preserve_uncommitted() {
|
|
417
|
+
local _root="$1" _id="$2" _idx _tree _head _commit _ref
|
|
418
|
+
[ -n "$(git -C "$_root" status --porcelain --untracked-files=all -- . ':(exclude).epam' 2>/dev/null)" ] || return 0
|
|
419
|
+
_head=$(git -C "$_root" rev-parse -q --verify HEAD 2>/dev/null) || _head=""
|
|
420
|
+
_idx=$(mktemp -u "${TMPDIR:-/tmp}/epam-wip-index-XXXXXX") || return 1
|
|
421
|
+
GIT_INDEX_FILE="$_idx" git -C "$_root" add -A -- . ':(exclude).epam' >/dev/null 2>&1 || { rm -f "$_idx"; return 1; }
|
|
422
|
+
_tree=$(GIT_INDEX_FILE="$_idx" git -C "$_root" write-tree 2>/dev/null) || { rm -f "$_idx"; return 1; }
|
|
423
|
+
rm -f "$_idx"
|
|
424
|
+
_commit=$(git -C "$_root" -c user.name="${GIT_COMMITTER_NAME:-epam-rescue}" -c user.email="${GIT_COMMITTER_EMAIL:-epam-rescue@local}" \
|
|
425
|
+
commit-tree "$_tree" ${_head:+-p "$_head"} -m "epam-rescue: uncommitted work of ${_id}" 2>/dev/null) || return 1
|
|
426
|
+
_ref="epam-rescue/${_id}-wip-$(git -C "$_root" rev-parse --short "$_commit" 2>/dev/null || echo unknown)"
|
|
427
|
+
git -C "$_root" branch -f "$_ref" "$_commit" --quiet 2>/dev/null || return 1
|
|
428
|
+
warning " [story-branch] ${_id}: uncommitted work preserved on branch '${_ref}'"
|
|
429
|
+
warning " [story-branch] recover with: git -C '${_root}' checkout ${_ref} -- <path>"
|
|
430
|
+
return 0 # the work IS preserved; a logger that cannot write (it returns its own status) must not turn that into a failure
|
|
431
|
+
}
|
|
432
|
+
|
|
389
433
|
ensure_story_branch() {
|
|
390
434
|
local codeline_root="$1"
|
|
391
435
|
local story_id="$2"
|
|
@@ -405,15 +449,16 @@ ensure_story_branch() {
|
|
|
405
449
|
# constraint, so the default preserves today's plain "AI-<story_id>" shape.
|
|
406
450
|
# ONE STORY BRANCH PER RELEASE LINE (lib/release-lines.sh): a line other than the codeline's own carries a
|
|
407
451
|
# suffix, so the codeline's own story branch is never reset by another line's work.
|
|
408
|
-
local _branch
|
|
452
|
+
local _branch; _branch=$(story_branch_name "$story_id")
|
|
409
453
|
|
|
410
454
|
# A STORY THAT ALREADY HAS ITS OWN WORK ON ITS BRANCH CONTINUES FROM IT (AMSD-2974 run 9, 2026-10-14). A self-heal retry
|
|
411
455
|
# sets the story pending again and comes back here; the hard reset below moved the 7.x line's branch to v7.11.0 twice
|
|
412
456
|
# in one run, discarding the approved full port (c25e819), and the writer re-wrote it from nothing — paid twice, weaker.
|
|
413
457
|
# This run's own record of the story's commits (story-changes.jsonl) is what says the work is its own: a fresh run has
|
|
414
458
|
# none, and a resume sent back to an earlier stage (EPAM_RESUME_FROM_STAGE) starts over as asked.
|
|
415
|
-
if _story_branch_carries_own_work "$codeline_root" "$_branch" "$story_id"; then
|
|
459
|
+
if _story_branch_carries_own_work "$codeline_root" "$_branch" "$story_id" "$baseline_branch"; then
|
|
416
460
|
if git -C "$codeline_root" checkout -q "$_branch" 2>/dev/null; then
|
|
461
|
+
_preserve_uncommitted "$codeline_root" "$story_id" || { error " [story-branch] $story_id: uncommitted work could not be preserved — refusing to reset it away"; return 1; }
|
|
417
462
|
git -C "$codeline_root" reset --hard -q "$_branch" 2>/dev/null || true
|
|
418
463
|
git -C "$codeline_root" clean -fd -e .epam -q 2>/dev/null || true
|
|
419
464
|
_provision_epam_plugin_config "$codeline_root"
|
|
@@ -509,6 +554,8 @@ ensure_story_branch() {
|
|
|
509
554
|
fi
|
|
510
555
|
done
|
|
511
556
|
|
|
557
|
+
_preserve_uncommitted "$codeline_root" "$story_id" || { error " [story-branch] $story_id: uncommitted work could not be preserved — refusing to reset it away"; return 1; }
|
|
558
|
+
|
|
512
559
|
if git -C "$codeline_root" checkout -B "$_branch" "$_base_ref" --quiet 2>/dev/null; then
|
|
513
560
|
# checkout -B only forces tracked files that DIFFER between the old
|
|
514
561
|
# branch tip and the new start-point — a tracked file whose content
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pipeline_commit_flags — the flags every commit the PIPELINE makes passes to `git commit`, one per line.
|
|
3
|
+
#
|
|
4
|
+
# A codeline's hooks are written for its team's own checkout and machine. Operator decisions,
|
|
5
|
+
# 2026-10-05 (AMSD-2974): the pipeline's commits skip them — first inside worktrees, then in the
|
|
6
|
+
# checkout too. Proven on a copy of next.gotransit.com: its pre-commit hook (husky + lint-staged)
|
|
7
|
+
# type-checks the WHOLE project, which fails on the untouched checkout with 6 errors because the
|
|
8
|
+
# team's node_modules is older than its own develop branch expects (it needs an `npm install`, which
|
|
9
|
+
# the pipeline may not run) — so the hook refused every pipeline commit there, whatever it held, and
|
|
10
|
+
# the reproducing test of a correct fix was never committed. In worktrees the same hook also refused
|
|
11
|
+
# the branch name and sourced a helper git does not track.
|
|
12
|
+
#
|
|
13
|
+
# What the hook checks is still checked by the pipeline: the story's own verification, Step 19/20's
|
|
14
|
+
# type check judged against the phase baseline, the lint gate, review and the QA gates. The Step 17
|
|
15
|
+
# merge onto the story branch still runs the codeline's commit-msg hook (git merge has no --no-verify
|
|
16
|
+
# here), and its message is written to pass it.
|
|
17
|
+
pipeline_commit_flags() {
|
|
18
|
+
echo "--no-verify"
|
|
19
|
+
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
[ -
|
|
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.
|
|
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": {
|