amsd-pipeline-shared 0.5.1 → 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.
|
@@ -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
|
|
@@ -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
|
+
}
|
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": {
|