muse-crew 0.14.11 → 0.14.12
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/lib/worktree-lifecycle.sh +89 -34
- package/package.json +1 -1
|
@@ -988,7 +988,8 @@ cmd_integration_target() {
|
|
|
988
988
|
integration_target
|
|
989
989
|
}
|
|
990
990
|
|
|
991
|
-
# Branch-state classification for Review entry (blocker 34, 2026-09-21
|
|
991
|
+
# Branch-state classification for Review entry (blocker 34, 2026-09-21;
|
|
992
|
+
# no-branch path blocker 50, 2026-09-23).
|
|
992
993
|
# Usage: classify-branch <task_id>
|
|
993
994
|
# Stdout: exactly one successful line —
|
|
994
995
|
# BRANCH_STATE: has-work | already-merged:<40-hex> | empty-no-work
|
|
@@ -1005,17 +1006,72 @@ cmd_integration_target() {
|
|
|
1005
1006
|
# 1. current-tip identity — the live merge carrying the current branch
|
|
1006
1007
|
# tip (current_branch_merge, blocker 35's identity primitive). If the
|
|
1007
1008
|
# tip is on the live line via a merge, that merge IS the deliverable.
|
|
1009
|
+
# Branch-present path only — a gone branch has no tip to identify.
|
|
1008
1010
|
# 2. the task's own durable merge records, newest first (at most 20
|
|
1009
1011
|
# candidates scanned; garbage/unresolvable entries skipped). The
|
|
1010
1012
|
# first record that resolves to a MERGE commit and is an ancestor of
|
|
1011
1013
|
# the live target wins; the SHA is canonicalized to full 40-hex.
|
|
1012
1014
|
# Poison rule: the newest RESOLVABLE non-ancestor record poisons the
|
|
1013
1015
|
# fallback — an older record is never accepted beneath a newer known-
|
|
1014
|
-
# stale one.
|
|
1015
|
-
# never a fabricated already-merged.
|
|
1016
|
+
# stale one. On the branch-present path the answer is empty-no-work
|
|
1017
|
+
# (stderr notes the poison), never a fabricated already-merged. On the
|
|
1018
|
+
# no-branch path the poison fails closed (exit 1): without a branch,
|
|
1019
|
+
# "empty" is indistinguishable from evidence loss.
|
|
1016
1020
|
# The branch is empty here, so the blocker-35 identity/ancestry
|
|
1017
1021
|
# distinction does not apply: there is no moved branch to go stale
|
|
1018
1022
|
# against, and every candidate is this task's own record.
|
|
1023
|
+
#
|
|
1024
|
+
# No-branch path (blocker 50): a missing branch is the normal
|
|
1025
|
+
# post-integrate state — Integrate merges, the worktree lifecycle reclaims
|
|
1026
|
+
# the branch, and QA/rework run after. The classifier resolves the
|
|
1027
|
+
# integration target independently of the branch and runs the durable
|
|
1028
|
+
# merge-record scan (step 2 below). A hit prints already-merged:<sha> with
|
|
1029
|
+
# the real DIAG counts, exit 0. No hit — no records, none resolvable, none
|
|
1030
|
+
# an ancestor, or poisoned — exits 1 with diagnostics, never empty-no-work.
|
|
1031
|
+
|
|
1032
|
+
# Durable merge-record scan, shared by classify-branch's branch-present
|
|
1033
|
+
# and no-branch paths (blocker 50). Newest first, at most 20 candidates:
|
|
1034
|
+
# valid-hex merge_commit= lines only; a candidate must resolve to a MERGE
|
|
1035
|
+
# commit (^2 exists) to count as resolvable. The first resolvable record
|
|
1036
|
+
# that is an ancestor of $2 wins. Results in SCAN_MERGED (full 40-hex or
|
|
1037
|
+
# empty) and SCAN_POISONED (1 when the newest resolvable record is a
|
|
1038
|
+
# non-ancestor — an older ancestor beneath it is refused, stderr-noted).
|
|
1039
|
+
# Updates the caller's diag_records/diag_resolvable (dynamic scope; the
|
|
1040
|
+
# defaults keep set -u safe). No stdout of its own.
|
|
1041
|
+
scan_merge_records() {
|
|
1042
|
+
local task_id="$1" target="$2"
|
|
1043
|
+
SCAN_MERGED=""; SCAN_POISONED=0
|
|
1044
|
+
: "${diag_records:=0}" "${diag_resolvable:=0}"
|
|
1045
|
+
local rec="$MERGE_RECORDS_DIR/$task_id"
|
|
1046
|
+
[ -f "$rec" ] || return 0
|
|
1047
|
+
local count=0 sha full poisoned=""
|
|
1048
|
+
while IFS= read -r line && [ "$count" -lt 20 ]; do
|
|
1049
|
+
case "$line" in
|
|
1050
|
+
merge_commit=*) sha="${line#merge_commit=}" ;;
|
|
1051
|
+
*) continue ;;
|
|
1052
|
+
esac
|
|
1053
|
+
case "$sha" in ""|*[!0-9a-f]*) continue ;; esac
|
|
1054
|
+
count=$((count + 1))
|
|
1055
|
+
diag_records=$((diag_records + 1))
|
|
1056
|
+
full=$(git rev-parse --verify --quiet "$sha" 2>/dev/null) || continue
|
|
1057
|
+
# A merge commit: the frozen first-parent diff the workflow reviews
|
|
1058
|
+
# only exists for merges. A non-merge ancestor is never the task's
|
|
1059
|
+
# merge — skipped, not poison (garbage must not veto delivery).
|
|
1060
|
+
git rev-parse --verify --quiet "$full^2" >/dev/null 2>&1 || continue
|
|
1061
|
+
diag_resolvable=$((diag_resolvable + 1))
|
|
1062
|
+
if git merge-base --is-ancestor "$full" "$target" 2>/dev/null; then
|
|
1063
|
+
if [ -n "$poisoned" ]; then
|
|
1064
|
+
echo "classify-branch: newest resolvable record $poisoned is stale (not an ancestor of $target); refusing older record $full" >&2
|
|
1065
|
+
SCAN_POISONED=1
|
|
1066
|
+
return 0
|
|
1067
|
+
fi
|
|
1068
|
+
SCAN_MERGED="$full"
|
|
1069
|
+
return 0
|
|
1070
|
+
fi
|
|
1071
|
+
[ -z "$poisoned" ] && poisoned="$full"
|
|
1072
|
+
done < <(tac "$rec" 2>/dev/null)
|
|
1073
|
+
return 0
|
|
1074
|
+
}
|
|
1019
1075
|
cmd_classify_branch() {
|
|
1020
1076
|
local task_id="$1"
|
|
1021
1077
|
validate_task_id "$task_id"
|
|
@@ -1035,7 +1091,29 @@ cmd_classify_branch() {
|
|
|
1035
1091
|
branch=$(resolve_branch "$task_id") || { echo "classify-branch: cannot resolve task branch" >&2; diag; return 1; }
|
|
1036
1092
|
|
|
1037
1093
|
cd "$REPO"
|
|
1038
|
-
|
|
1094
|
+
|
|
1095
|
+
# Blocker 50: a missing branch is the normal post-integrate state, not a
|
|
1096
|
+
# classification failure — Integrate merges, the worktree lifecycle
|
|
1097
|
+
# reclaims the branch, and QA/rework run after. Resolve the target
|
|
1098
|
+
# independently of the branch and let the durable merge records plus
|
|
1099
|
+
# trunk ancestry decide. Never empty-no-work here: without a branch,
|
|
1100
|
+
# "empty" is indistinguishable from evidence loss.
|
|
1101
|
+
if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then
|
|
1102
|
+
echo "classify-branch: branch $branch does not exist; scanning durable merge records" >&2
|
|
1103
|
+
local nb_target
|
|
1104
|
+
nb_target=$(integration_target) || { echo "classify-branch: cannot resolve integration target" >&2; diag; return 1; }
|
|
1105
|
+
scan_merge_records "$task_id" "$nb_target"
|
|
1106
|
+
if [ -n "$SCAN_MERGED" ]; then
|
|
1107
|
+
diag
|
|
1108
|
+
echo "BRANCH_STATE: already-merged:$SCAN_MERGED"
|
|
1109
|
+
return 0
|
|
1110
|
+
fi
|
|
1111
|
+
if [ "$SCAN_POISONED" != "1" ]; then
|
|
1112
|
+
echo "classify-branch: no merge record for $task_id is an ancestor of $nb_target" >&2
|
|
1113
|
+
fi
|
|
1114
|
+
diag
|
|
1115
|
+
return 1
|
|
1116
|
+
fi
|
|
1039
1117
|
|
|
1040
1118
|
local target
|
|
1041
1119
|
target=$(integration_target) || { echo "classify-branch: cannot resolve integration target" >&2; diag; return 1; }
|
|
@@ -1059,36 +1137,13 @@ cmd_classify_branch() {
|
|
|
1059
1137
|
fi
|
|
1060
1138
|
fi
|
|
1061
1139
|
|
|
1062
|
-
# 2. Durable merge records, newest first
|
|
1063
|
-
#
|
|
1064
|
-
|
|
1065
|
-
if [ -
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
merge_commit=*) sha="${line#merge_commit=}" ;;
|
|
1070
|
-
*) continue ;;
|
|
1071
|
-
esac
|
|
1072
|
-
case "$sha" in ""|*[!0-9a-f]*) continue ;; esac
|
|
1073
|
-
count=$((count + 1))
|
|
1074
|
-
diag_records=$((diag_records + 1))
|
|
1075
|
-
full=$(git rev-parse --verify --quiet "$sha" 2>/dev/null) || continue
|
|
1076
|
-
# A merge commit: the frozen first-parent diff the workflow reviews
|
|
1077
|
-
# only exists for merges. A non-merge ancestor is never the task's
|
|
1078
|
-
# merge — skipped, not poison (garbage must not veto delivery).
|
|
1079
|
-
git rev-parse --verify --quiet "$full^2" >/dev/null 2>&1 || continue
|
|
1080
|
-
diag_resolvable=$((diag_resolvable + 1))
|
|
1081
|
-
if git merge-base --is-ancestor "$full" "$target" 2>/dev/null; then
|
|
1082
|
-
if [ -n "$poisoned" ]; then
|
|
1083
|
-
echo "classify-branch: newest resolvable record $poisoned is stale (not an ancestor of $target); refusing older record $full" >&2
|
|
1084
|
-
break
|
|
1085
|
-
fi
|
|
1086
|
-
diag
|
|
1087
|
-
echo "BRANCH_STATE: already-merged:$full"
|
|
1088
|
-
return 0
|
|
1089
|
-
fi
|
|
1090
|
-
[ -z "$poisoned" ] && poisoned="$full"
|
|
1091
|
-
done < <(tac "$rec" 2>/dev/null)
|
|
1140
|
+
# 2. Durable merge records, newest first (scan_merge_records; blocker 50
|
|
1141
|
+
# shares this scan with the no-branch path).
|
|
1142
|
+
scan_merge_records "$task_id" "$target"
|
|
1143
|
+
if [ -n "$SCAN_MERGED" ]; then
|
|
1144
|
+
diag
|
|
1145
|
+
echo "BRANCH_STATE: already-merged:$SCAN_MERGED"
|
|
1146
|
+
return 0
|
|
1092
1147
|
fi
|
|
1093
1148
|
|
|
1094
1149
|
diag
|
package/package.json
CHANGED