#!/usr/bin/env bash set -euo pipefail format="human" cli_family="" work_classification="" pull_request_target="" spec_path="" branch_name="" repo_scope="" explicit_ref="" usage() { cat <<'EOF' Usage: scripts/resolve-feature-base [options] Options: --branch-family=platform|website|repository-wide --work-classification=platform|website|cross-stream|promotion|repository-governance --pull-request-target= --spec= --branch-name= --repo-scope=platform|website|repository --ref= --format=human|kv EOF } fail_resolution() { local reason="$1" shift if [[ "${format}" == "human" ]]; then echo "Feature base resolution failed." >&2 fi echo "status=failed" >&2 echo "reason=${reason}" >&2 for detail in "$@"; do echo "${detail}" >&2 done echo "guidance=Declare a consistent branch family, work classification, PR target, active spec, and family-matching ref." >&2 echo "diff_ran=false" >&2 exit 2 } while [[ $# -gt 0 ]]; do case "$1" in --branch-family=*) cli_family="${1#*=}" shift ;; --branch-family) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--branch-family" cli_family="$2" shift 2 ;; --work-classification=*) work_classification="${1#*=}" shift ;; --work-classification) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--work-classification" work_classification="$2" shift 2 ;; --pull-request-target=*) pull_request_target="${1#*=}" shift ;; --pull-request-target) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--pull-request-target" pull_request_target="$2" shift 2 ;; --spec=*) spec_path="${1#*=}" shift ;; --spec) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--spec" spec_path="$2" shift 2 ;; --branch-name=*) branch_name="${1#*=}" shift ;; --branch-name) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--branch-name" branch_name="$2" shift 2 ;; --repo-scope=*) repo_scope="${1#*=}" shift ;; --repo-scope) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--repo-scope" repo_scope="$2" shift 2 ;; --ref=*) explicit_ref="${1#*=}" shift ;; --ref) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--ref" explicit_ref="$2" shift 2 ;; --format=*) format="${1#*=}" shift ;; --format) [[ $# -ge 2 ]] || fail_resolution "missing-option-value" "option=--format" format="$2" shift 2 ;; --help|-h) usage exit 0 ;; *) fail_resolution "unknown-option" "option=$1" ;; esac done if [[ "${format}" != "human" && "${format}" != "kv" ]]; then fail_resolution "invalid-format" "format=${format}" fi normalize_family() { case "$1" in platform) echo "platform" ;; website) echo "website" ;; repository-wide) echo "repository-wide" ;; *) return 1 ;; esac } family_for_branch() { case "$1" in platform-dev|origin/platform-dev|refs/heads/platform-dev|refs/remotes/origin/platform-dev) echo "platform" ;; website-dev|origin/website-dev|refs/heads/website-dev|refs/remotes/origin/website-dev) echo "website" ;; dev|origin/dev|refs/heads/dev|refs/remotes/origin/dev) echo "repository-wide" ;; *) return 1 ;; esac } expected_branch_for_family() { case "$1" in platform) echo "platform-dev" ;; website) echo "website-dev" ;; repository-wide) echo "dev" ;; esac } trim_value() { local value="$1" value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" value="${value//\`/}" printf '%s\n' "${value}" } metadata_value() { local file="$1" local label="$2" local line line="$(awk -v prefix="**${label}**:" 'index($0, prefix) == 1 { print substr($0, length(prefix) + 1); exit }' "${file}")" trim_value "${line}" } repository_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" if [[ -z "${repository_root}" ]]; then fail_resolution "not-a-git-repository" fi if [[ -z "${branch_name}" ]]; then branch_name="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)" fi environment_family="${TENANTATLAS_BRANCH_FAMILY:-}" if [[ -n "${cli_family}" && -n "${environment_family}" && "${cli_family}" != "${environment_family}" ]]; then fail_resolution "branch-family-conflict" "cli_family=${cli_family}" "workflow_family=${environment_family}" fi explicit_family_raw="${cli_family:-${environment_family}}" explicit_family="" if [[ -n "${explicit_family_raw}" ]]; then explicit_family="$(normalize_family "${explicit_family_raw}" || true)" if [[ -z "${explicit_family}" ]]; then fail_resolution "invalid-branch-family" "explicit=${explicit_family_raw}" fi fi environment_classification="${TENANTATLAS_WORK_CLASSIFICATION:-}" if [[ -n "${work_classification}" && -n "${environment_classification}" && "${work_classification}" != "${environment_classification}" ]]; then fail_resolution "work-classification-conflict" "cli_classification=${work_classification}" "workflow_classification=${environment_classification}" fi work_classification="${work_classification:-${environment_classification}}" case "${work_classification}" in ""|platform|website|cross-stream|promotion|repository-governance) ;; *) fail_resolution "invalid-work-classification" "work_classification=${work_classification}" ;; esac environment_ref="${TENANTATLAS_BASE_REF:-}" if [[ -n "${explicit_ref}" && -n "${environment_ref}" && "${explicit_ref}" != "${environment_ref}" ]]; then fail_resolution "explicit-ref-conflict" "cli_ref=${explicit_ref}" "workflow_ref=${environment_ref}" fi explicit_ref="${explicit_ref:-${environment_ref}}" resolved_target="" target_family="" for target_signal in \ "${pull_request_target}" \ "${TENANTATLAS_PR_TARGET:-}" \ "${GITHUB_BASE_REF:-}" \ "${GITEA_BASE_REF:-}"; do [[ -z "${target_signal}" ]] && continue signal_family="$(family_for_branch "${target_signal}" || true)" if [[ -z "${signal_family}" ]]; then fail_resolution "unknown-pull-request-target" "pull_request_target=${target_signal}" fi if [[ -z "${resolved_target}" ]]; then resolved_target="${target_signal}" target_family="${signal_family}" elif [[ "${target_family}" != "${signal_family}" ]]; then fail_resolution "pull-request-target-conflict" "pull_request_targets=${resolved_target},${target_signal}" fi done explicit_spec_requested=false if [[ -n "${spec_path}" ]]; then explicit_spec_requested=true elif [[ -n "${TENANTATLAS_ACTIVE_SPEC:-}" ]]; then spec_path="${TENANTATLAS_ACTIVE_SPEC}" explicit_spec_requested=true elif [[ "${branch_name}" == feat/* ]]; then candidate_spec="${repository_root}/specs/${branch_name#feat/}/spec.md" if [[ -f "${candidate_spec}" ]]; then spec_path="${candidate_spec}" fi fi if [[ -n "${spec_path}" && "${spec_path}" != /* ]]; then spec_path="${repository_root}/${spec_path}" fi if [[ "${explicit_spec_requested}" == true && ! -f "${spec_path}" ]]; then fail_resolution "spec-not-readable" "spec_path=${spec_path}" fi spec_family="" spec_classification="" spec_declared_target="" if [[ -n "${spec_path}" && -f "${spec_path}" ]]; then feature_base="$(metadata_value "${spec_path}" "Feature Base")" merge_target="$(metadata_value "${spec_path}" "Normal Merge Target")" spec_declared_target="${merge_target}" development_stream="$(metadata_value "${spec_path}" "Development Stream")" for metadata_branch in "${feature_base}" "${merge_target}"; do [[ -z "${metadata_branch}" ]] && continue metadata_family="$(family_for_branch "${metadata_branch}" || true)" if [[ -z "${metadata_family}" ]]; then fail_resolution "unknown-spec-branch" "spec_branch=${metadata_branch}" "spec_path=${spec_path}" fi if [[ -z "${spec_family}" ]]; then spec_family="${metadata_family}" elif [[ "${spec_family}" != "${metadata_family}" ]]; then fail_resolution "spec-metadata-conflict" "feature_base=${feature_base}" "normal_merge_target=${merge_target}" "spec_path=${spec_path}" fi done case "${development_stream}" in platform*) spec_classification="platform" ;; website*) spec_classification="website" ;; cross-stream*) spec_classification="cross-stream" ;; promotion*) spec_classification="promotion" ;; repository-governance*) spec_classification="repository-governance" ;; esac fi if [[ -n "${work_classification}" && -n "${spec_classification}" && "${work_classification}" != "${spec_classification}" ]]; then fail_resolution "work-classification-conflict" "explicit_classification=${work_classification}" "spec_classification=${spec_classification}" fi work_classification="${work_classification:-${spec_classification}}" branch_hint="" lower_branch="$(printf '%s' "${branch_name}" | tr '[:upper:]' '[:lower:]')" if [[ "${lower_branch}" == *platform* && "${lower_branch}" == *website* ]]; then branch_hint="ambiguous" elif [[ "${lower_branch}" == *platform* ]]; then branch_hint="platform" elif [[ "${lower_branch}" == *website* ]]; then branch_hint="website" fi scope_hint="" repo_scope="${repo_scope:-${TENANTATLAS_REPO_SCOPE:-}}" case "${repo_scope}" in ""|repository) ;; platform|website) scope_hint="${repo_scope}" ;; *) fail_resolution "invalid-repository-scope" "repo_scope=${repo_scope}" ;; esac hint_family="" hint_ambiguous=false if [[ "${branch_hint}" == "ambiguous" ]]; then hint_ambiguous=true elif [[ -n "${branch_hint}" && -n "${scope_hint}" ]]; then if [[ "${branch_hint}" != "${scope_hint}" ]]; then hint_ambiguous=true else hint_family="${branch_hint}" fi fi selected_family="" family_source="" if [[ -n "${explicit_family}" ]]; then selected_family="${explicit_family}" family_source="explicit" fi if [[ -n "${target_family}" ]]; then if [[ -n "${selected_family}" && "${selected_family}" != "${target_family}" ]]; then fail_resolution "branch-family-conflict" "explicit=${selected_family}" "pull_request_target=${target_family}" fi if [[ -z "${selected_family}" ]]; then selected_family="${target_family}" family_source="pull-request-target" fi fi if [[ -n "${spec_family}" ]]; then if [[ -n "${selected_family}" && "${selected_family}" != "${spec_family}" ]]; then fail_resolution "branch-family-conflict" "selected=${selected_family}" "spec_metadata=${spec_family}" "spec_path=${spec_path}" fi if [[ -z "${selected_family}" ]]; then selected_family="${spec_family}" family_source="spec-metadata" fi fi if [[ -n "${hint_family}" ]]; then if [[ -z "${selected_family}" ]]; then selected_family="${hint_family}" family_source="branch-and-scope" fi fi if [[ -z "${selected_family}" ]]; then if [[ "${hint_ambiguous}" == true ]]; then fail_resolution "ambiguous-branch-family" "branch_name=${branch_name}" "repo_scope=${repo_scope:-unset}" "spec_path=${spec_path:-unset}" fi fail_resolution "ambiguous-branch-family" "branch_name=${branch_name:-detached}" "repo_scope=${repo_scope:-unset}" "spec_path=${spec_path:-unset}" fi if [[ -z "${work_classification}" ]]; then if [[ "${selected_family}" == "repository-wide" ]]; then fail_resolution "explicit-promotion-classification-required" "pull_request_target=${resolved_target:-unset}" fi work_classification="${selected_family}" fi case "${work_classification}:${selected_family}" in platform:platform|website:website|promotion:repository-wide|cross-stream:*|repository-governance:*) ;; *) fail_resolution "work-classification-family-conflict" "work_classification=${work_classification}" "branch_family=${selected_family}" ;; esac if [[ "${selected_family}" == "repository-wide" && "${work_classification}" != "promotion" && "${work_classification}" != "cross-stream" && "${work_classification}" != "repository-governance" ]]; then fail_resolution "explicit-promotion-classification-required" "work_classification=${work_classification}" fi if [[ "${work_classification}" == "promotion" || "${work_classification}" == "cross-stream" || "${work_classification}" == "repository-governance" ]]; then if [[ -z "${resolved_target}" && -z "${spec_declared_target}" ]]; then fail_resolution "explicit-target-required" "work_classification=${work_classification}" "branch_family=${selected_family}" fi fi expected_branch="$(expected_branch_for_family "${selected_family}")" selected_ref="" if [[ -n "${explicit_ref}" ]]; then explicit_ref_family="$(family_for_branch "${explicit_ref}" || true)" if [[ -z "${explicit_ref_family}" || "${explicit_ref_family}" != "${selected_family}" ]]; then fail_resolution "wrong-family-ref" "expected_branch=${expected_branch}" "explicit_ref=${explicit_ref}" fi if ! git rev-parse --verify "${explicit_ref}^{commit}" >/dev/null 2>&1; then fail_resolution "explicit-ref-unavailable" "expected_branch=${expected_branch}" "explicit_ref=${explicit_ref}" fi selected_ref="${explicit_ref}" elif git rev-parse --verify "refs/remotes/origin/${expected_branch}^{commit}" >/dev/null 2>&1; then selected_ref="origin/${expected_branch}" elif git rev-parse --verify "refs/heads/${expected_branch}^{commit}" >/dev/null 2>&1; then selected_ref="${expected_branch}" else fail_resolution "missing-family-ref" "branch_family=${selected_family}" "expected_branch=${expected_branch}" "attempted_refs=origin/${expected_branch},${expected_branch}" fi target_commit="$(git rev-parse --verify "${selected_ref}^{commit}" 2>/dev/null || true)" if [[ -z "${target_commit}" ]]; then fail_resolution "selected-ref-unavailable" "selected_ref=${selected_ref}" fi merge_base="$(git merge-base HEAD "${selected_ref}" 2>/dev/null || true)" if [[ -z "${merge_base}" ]]; then fail_resolution "no-common-merge-base" "selected_ref=${selected_ref}" "target_commit=${target_commit}" fi base_range="${merge_base}...HEAD" if [[ "${format}" == "human" ]]; then cat <