## Development lane / 开发通道 - Lane: `direct-to-main` bootstrap - Base branch: `main` - Active train: `n/a` - Owning Agent/session: workflow coordinator ## Change / 改动 - Split authoritative CI into parallel static, contract, Rust lint/test shards, Swift interaction, stress, overlay, bundle, and stable `Required CI` jobs. - Make every ready PR to `main` a complete gate while keeping task PRs to `gd-ops/train/*` path-scoped. - Add direct/train worktree coordination, bounded changelog fragments, protected main/train ruleset automation, and safe same-repository squash auto-merge. - Let GitHub Release promote an exact successful main commit, reuse its source proof, and create a missing release tag only after proof validation. - Keep local pre-push validation bounded to basic syntax, formatting, contracts, and compilation; complete duplicate proof stays in GitHub CI. This is the one bootstrap PR: the auto-merge workflow and required rules do not exist on the base branch yet. After this PR passes and is merged once, the exact `main` push must pass `Required CI`; then the fail-closed applicator can activate both rulesets and all later ready `gd-ops/*` PRs follow the automated path. ## Validation / 验证 - `actionlint .github/workflows/ci.yml .github/workflows/release.yml .github/workflows/auto-merge.yml` - `./script/validate_source_syntax.sh` - `./script/validate_build_scripts_safety.sh --skip-source-syntax` - Exact staged diff was reapplied to a clean detached worktree before the final validation run. - 28 validation-tooling tests, 40 release-distribution tests, macOS build-contract tests, Release shell contracts, and test-isolation validation passed. ## Risk / 风险 - No product runtime or credential access is added. - `pull_request_target` never checks out or executes PR code and only enables auto-merge after API verification of active PR and `Required CI` rules. - Full ruleset activation fails closed until the exact remote main commit has a trusted successful GitHub Actions push run. - Reversible repository settings are already enabled: auto-merge, squash-only, update branch, and delete merged branches. --------- Co-authored-by: gd-ops <gd-ops@xmfunny.com>
159 lines
4.7 KiB
Bash
Executable File
159 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BASE_REF=""
|
|
PLAN_ONLY=0
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
usage: validate_pre_push.sh [--base REF] [--plan-only]
|
|
|
|
Runs the bounded local fast gate: diff hygiene, source syntax, lightweight
|
|
contracts, formatting, and compilation for touched languages. Complete tests,
|
|
App assembly, stress, and artifact validation are authoritative remote gates.
|
|
EOF
|
|
}
|
|
|
|
while (($# > 0)); do
|
|
case "$1" in
|
|
--base)
|
|
(($# >= 2)) || { usage >&2; exit 2; }
|
|
BASE_REF="$2"
|
|
shift 2
|
|
;;
|
|
--plan-only)
|
|
PLAN_ONLY=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$BASE_REF" ]]; then
|
|
if upstream="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null)"; then
|
|
BASE_REF="$(git -C "$ROOT_DIR" merge-base HEAD "$upstream")"
|
|
else
|
|
BASE_REF="HEAD"
|
|
fi
|
|
fi
|
|
git -C "$ROOT_DIR" rev-parse --verify "$BASE_REF^{commit}" >/dev/null
|
|
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/apc-pre-push.XXXXXX")"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
CHANGED_PATHS="$TMP_DIR/changed-paths"
|
|
UNTRACKED_PATHS="$TMP_DIR/untracked-paths"
|
|
git -C "$ROOT_DIR" diff --name-only -z "$BASE_REF" -- >"$CHANGED_PATHS"
|
|
git -C "$ROOT_DIR" ls-files --others --exclude-standard -z >"$UNTRACKED_PATHS"
|
|
cat "$UNTRACKED_PATHS" >>"$CHANGED_PATHS"
|
|
|
|
eval "$(
|
|
"$ROOT_DIR/script/validation_scope.py" \
|
|
--root "$ROOT_DIR" \
|
|
--paths-file "$CHANGED_PATHS" \
|
|
--format shell
|
|
)"
|
|
|
|
PLAN=("git diff --check against $BASE_REF" "source syntax and local Markdown links")
|
|
if [[ "$APC_CHANGED_LOCALIZATION" == "1" ]]; then
|
|
PLAN+=("String Catalog/.strings parity")
|
|
fi
|
|
if [[ "$APC_CHANGED_SCRIPTS" == "1" ]]; then
|
|
PLAN+=("static build and workflow contract safety")
|
|
fi
|
|
if [[ "$APC_CHANGED_PRODUCER" == "1" ]]; then
|
|
PLAN+=("pet Skill structure contracts")
|
|
fi
|
|
case "$APC_RUST_MODE" in
|
|
workspace) PLAN+=("Rust format plus workspace compile check") ;;
|
|
petcore) PLAN+=("Rust format plus petcore/petcore-cli compile check") ;;
|
|
cli) PLAN+=("Rust format plus petcore-cli compile check") ;;
|
|
esac
|
|
case "$APC_SWIFT_MODE" in
|
|
overlay|full) PLAN+=("Swift package compile check") ;;
|
|
esac
|
|
if [[ "$APC_CHANGED_PLUGIN_VERSION" == "1" ]]; then
|
|
PLAN+=("Codex plugin and bundled Skill version discipline")
|
|
fi
|
|
if [[ "$APC_COMPUTER_USE" == "recommended" ]]; then
|
|
PLAN+=("Computer Use recommended after automated checks; never run automatically")
|
|
else
|
|
PLAN+=("Computer Use not required for this change scope")
|
|
fi
|
|
|
|
echo "Pre-push plan for $APC_CHANGED_COUNT changed path(s):"
|
|
printf ' - %s\n' "${PLAN[@]}"
|
|
if [[ "$PLAN_ONLY" == "1" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
run() {
|
|
local label="$1"
|
|
shift
|
|
printf '\n== %s ==\n' "$label"
|
|
"$@"
|
|
}
|
|
|
|
run "Whitespace and conflict-marker check" git -C "$ROOT_DIR" diff --check "$BASE_REF" --
|
|
while IFS= read -r -d '' untracked_path; do
|
|
untracked_check=""
|
|
untracked_status=0
|
|
if untracked_check="$(
|
|
git -C "$ROOT_DIR" diff --no-index --check -- /dev/null "$ROOT_DIR/$untracked_path" 2>&1
|
|
)"; then
|
|
untracked_status=0
|
|
else
|
|
untracked_status=$?
|
|
fi
|
|
if ((untracked_status > 1)) || [[ -n "$untracked_check" ]]; then
|
|
printf '%s\n' "$untracked_check" >&2
|
|
echo "untracked file failed whitespace validation: $untracked_path" >&2
|
|
exit 1
|
|
fi
|
|
done <"$UNTRACKED_PATHS"
|
|
run "Source syntax" "$ROOT_DIR/script/validate_source_syntax.sh"
|
|
if [[ "$APC_CHANGED_LOCALIZATION" == "1" ]]; then
|
|
run "Localization parity" "$ROOT_DIR/script/validate_localizations.py"
|
|
fi
|
|
if [[ "$APC_CHANGED_SCRIPTS" == "1" ]]; then
|
|
run "Build and release-script safety" \
|
|
"$ROOT_DIR/script/validate_build_scripts_safety.sh" \
|
|
--skip-source-syntax --static-only
|
|
fi
|
|
if [[ "$APC_CHANGED_PLUGIN_VERSION" == "1" ]]; then
|
|
run "Codex plugin and Skill version discipline" \
|
|
"$ROOT_DIR/script/validate_codex_plugin_version.py" \
|
|
--base-ref "$BASE_REF"
|
|
fi
|
|
if [[ "$APC_CHANGED_PRODUCER" == "1" ]]; then
|
|
run "Pet Skill contracts" "$ROOT_DIR/script/validate_pet_skills.sh"
|
|
fi
|
|
|
|
RUST_PACKAGE_ARGS=()
|
|
case "$APC_RUST_MODE" in
|
|
workspace) RUST_PACKAGE_ARGS=(--workspace) ;;
|
|
petcore) RUST_PACKAGE_ARGS=(-p petcore -p petcore-cli) ;;
|
|
cli) RUST_PACKAGE_ARGS=(-p petcore-cli) ;;
|
|
esac
|
|
if ((${#RUST_PACKAGE_ARGS[@]} > 0)); then
|
|
run "Rust formatting" cargo fmt --all --manifest-path "$ROOT_DIR/Cargo.toml" -- --check
|
|
run "Scoped Rust compile check" cargo check \
|
|
--manifest-path "$ROOT_DIR/Cargo.toml" \
|
|
"${RUST_PACKAGE_ARGS[@]}" \
|
|
--locked
|
|
fi
|
|
case "$APC_SWIFT_MODE" in
|
|
overlay|full)
|
|
run "Swift compile check" swift build --package-path "$ROOT_DIR/apps/macos"
|
|
;;
|
|
esac
|
|
|
|
echo 'Local fast gate passed. GitHub CI remains authoritative for complete tests and App assembly.'
|