diff --git a/.github/scripts/verify-sha-consistency.sh b/.github/scripts/verify-sha-consistency.sh index 87a436d..4c9e4f2 100755 --- a/.github/scripts/verify-sha-consistency.sh +++ b/.github/scripts/verify-sha-consistency.sh @@ -28,6 +28,11 @@ REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}" MANIFEST="${MANIFEST:-$REPO_ROOT/manifest.yml}" PENDING="${PENDING:-$REPO_ROOT/manifest-pending.yml}" +# Self-referencing entries that need structural validation beyond SHA consistency. +# Each entry maps to the required path that must exist at the pinned SHA. +declare -A SELF_REFS +SELF_REFS["YiAgent/OpenCI"]=".github/workflows/reusable" + # SPEC Appendix B.2 — deprecated actions. Keep in sync with docs/SPEC.md. DEPRECATED_ACTIONS=( "semgrep/semgrep-action" @@ -205,6 +210,24 @@ main() { fi done < <(collect_uses) + # ── Structural validation for self-referencing entries ────────────────────── + # Checks that the pinned SHA actually contains the required directory. + # Catches the "SHA predates reusable/ reorganization" class of failures + # before they reach CI (where they manifest as silent "workflow file issue"). + local self_name self_required_path self_sha tree_output + for self_name in "${!SELF_REFS[@]}"; do + self_required_path="${SELF_REFS[$self_name]}" + self_sha="$(echo "$manifest_map" | awk -F'\t' -v key="$self_name" '$1 == key { print $2; exit }')" + [ -z "$self_sha" ] && continue + + # git ls-tree returns non-empty output when the path exists at that SHA. + tree_output="$(git ls-tree "$self_sha" "$self_required_path/" 2>/dev/null || true)" + if [ -z "$tree_output" ]; then + emit_error "SHA Missing Structure" \ + "manifest.yml: $self_name SHA $self_sha has no '$self_required_path/' directory. Run scripts/bump-self-sha.sh to update to a valid commit." + fi + done + emit_notice "verify-sha-consistency" "Checked $checked uses, $errors error(s)." if [ "$errors" -gt 0 ]; then diff --git a/.github/workflows/agent.yml b/.github/workflows/agent.yml index 723c0e7..f64eb7d 100644 --- a/.github/workflows/agent.yml +++ b/.github/workflows/agent.yml @@ -40,7 +40,7 @@ concurrency: jobs: agent: - uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: task: ${{ inputs.task }} prompt: ${{ inputs.prompt }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1462a3a..86cd14b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ concurrency: jobs: ci: - uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ github.sha }} registry: ghcr.io diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 482fe6b..57dd8b1 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -15,6 +15,6 @@ concurrency: jobs: deps: - uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5e59260..614560c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: && github.event.workflow_run.name == 'ci' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && inputs.mode == 'stg') - uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: app-name: ${{ vars.APP_NAME || github.event.repository.name }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} @@ -54,7 +54,7 @@ jobs: && github.event.workflow_run.name == 'release' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd') - uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: app-name: ${{ vars.APP_NAME || github.event.repository.name }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 72e859c..a5d70a0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,7 +22,7 @@ concurrency: jobs: docs: - uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: build-cmd: ${{ vars.DOCS_BUILD_CMD || '' }} docs-path: ${{ vars.DOCS_DIR || 'docs' }} diff --git a/.github/workflows/issue-ops.yml b/.github/workflows/issue-ops.yml index 66e448d..94eaab5 100644 --- a/.github/workflows/issue-ops.yml +++ b/.github/workflows/issue-ops.yml @@ -32,7 +32,7 @@ concurrency: jobs: lifecycle: if: github.event_name == 'issues' || github.event_name == 'issue_comment' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: lifecycle runner: blacksmith-32vcpu-ubuntu-2404 @@ -46,7 +46,7 @@ jobs: ingest: if: github.event_name == 'repository_dispatch' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: ingest runner: blacksmith-32vcpu-ubuntu-2404 @@ -60,7 +60,7 @@ jobs: maintenance: if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'maintenance') - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: maintenance runner: blacksmith-32vcpu-ubuntu-2404 @@ -74,7 +74,7 @@ jobs: manual: if: github.event_name == 'workflow_dispatch' && inputs.mode != 'maintenance' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: ${{ inputs.mode }} runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/observability.yml b/.github/workflows/observability.yml index 2e6dc0b..8b61485 100644 --- a/.github/workflows/observability.yml +++ b/.github/workflows/observability.yml @@ -30,7 +30,7 @@ concurrency: jobs: observe-canary: if: ${{ github.event_name == 'schedule' && github.event.schedule == '*/15 * * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: canary-watch runner: blacksmith-32vcpu-ubuntu-2404 @@ -38,7 +38,7 @@ jobs: observe-drift: if: ${{ github.event_name == 'schedule' && github.event.schedule == '0 4 * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: terraform-drift infra-dir: ${{ vars.INFRA_DIR || 'infrastructure' }} @@ -50,7 +50,7 @@ jobs: (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || github.event_name == 'repository_dispatch' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'verify-fix') - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: verify-fix runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/on-main-bump-sha.yml b/.github/workflows/on-main-bump-sha.yml new file mode 100644 index 0000000..354be72 --- /dev/null +++ b/.github/workflows/on-main-bump-sha.yml @@ -0,0 +1,98 @@ +# on-main-bump-sha.yml — Auto-bump the YiAgent/OpenCI self-reference SHA after +# every merge to main. Opens a follow-up PR when the SHA needs updating so the +# manifest never drifts out of sync. +# +# Why: the pinned SHA must point to a commit that contains +# .github/workflows/reusable/. After structural reorganizations (or simply +# as main moves forward) the SHA can become stale. This workflow detects +# that condition and creates a one-commit PR to fix it automatically. +name: Auto-bump self SHA + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + bump: + name: Bump YiAgent/OpenCI SHA if stale + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Check if SHA needs bumping + id: check + run: | + current_sha="$(yq -r '.deps["YiAgent/OpenCI"] // ""' manifest.yml)" + head_sha="$(git rev-parse HEAD)" + + if [ -z "$current_sha" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "::notice::YiAgent/OpenCI not in manifest.yml — skipping" + exit 0 + fi + + tree_out="$(git ls-tree "$current_sha" .github/workflows/reusable/ 2>/dev/null || true)" + if [ -n "$tree_out" ] && [ "$current_sha" = "$head_sha" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "::notice::SHA $current_sha is current and valid — nothing to do" + exit 0 + fi + + { + echo "skip=false" + echo "current_sha=$current_sha" + echo "new_sha=$head_sha" + } >> "$GITHUB_OUTPUT" + + - name: Run bump-self-sha.sh + if: steps.check.outputs.skip != 'true' + run: bash scripts/bump-self-sha.sh + + - name: Commit and open PR + if: steps.check.outputs.skip != 'true' + env: + GH_TOKEN: ${{ github.token }} + NEW_SHA: ${{ steps.check.outputs.new_sha }} + OLD_SHA: ${{ steps.check.outputs.current_sha }} + run: | + branch="chore/bump-self-sha-${NEW_SHA:0:8}" + short_old="${OLD_SHA:0:8}" + short_new="${NEW_SHA:0:8}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add manifest.yml .github/workflows/ + git commit -m "chore(manifest): bump YiAgent/OpenCI SHA to ${short_new}" \ + -m "Automated update from on-main-bump-sha workflow. old=${OLD_SHA} new=${NEW_SHA}" + + git push origin "$branch" + + # shellcheck disable=SC2016 + printf 'Automated SHA bump: `%s` to `%s`\n\nThe YiAgent/OpenCI self-reference SHA in manifest.yml was stale. Updated to the latest main HEAD so all reusable workflow calls resolve correctly.\n\n> Generated by the on-main-bump-sha workflow.' \ + "$short_old" "$short_new" > /tmp/pr-body.md + + gh pr create \ + --title "chore(manifest): bump YiAgent/OpenCI SHA to ${short_new}" \ + --body-file /tmp/pr-body.md \ + --base main \ + --head "$branch" \ + --label "chore" 2>/dev/null || true diff --git a/.github/workflows/on-maintenance.yml b/.github/workflows/on-maintenance.yml index d363da2..76a3e8d 100644 --- a/.github/workflows/on-maintenance.yml +++ b/.github/workflows/on-maintenance.yml @@ -115,7 +115,7 @@ jobs: if: | !contains(fromJSON('["pr-review","flag-audit"]'), needs.resolve-mode.outputs.mode) - uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: ${{ needs.resolve-mode.outputs.mode }} openci-ref: ${{ needs.resolve-mode.outputs.openci-ref }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 90fc0ab..ea2004e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -28,7 +28,7 @@ concurrency: jobs: checks: - uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: enable-ai-review: true enable-eval: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5be3dab..1986939 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ concurrency: jobs: release: - uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@ebe8fca3260dce68d34d51b74703169e776bc72d with: mode: ${{ inputs.mode || 'both' }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} diff --git a/.github/workflows/reusable/ci.yml b/.github/workflows/reusable/ci.yml index e4456e5..e19d51f 100644 --- a/.github/workflows/reusable/ci.yml +++ b/.github/workflows/reusable/ci.yml @@ -127,7 +127,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - name: Probe secrets @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - id: detect @@ -183,7 +183,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - id: build @@ -212,7 +212,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - id: scan @@ -235,7 +235,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 @@ -282,7 +282,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - uses: ./.openci/actions/ci/check-migration @@ -305,7 +305,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - uses: ./.openci/actions/ci/eval-smoke @@ -485,7 +485,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@d280a64a392b7f1a3e906246286ca983e610f920 + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d with: openci-ref: ${{ inputs.openci-ref }} - name: Download ci-context artifact diff --git a/.github/workflows/reusable/pr.yml b/.github/workflows/reusable/pr.yml index c45af03..b9d7044 100644 --- a/.github/workflows/reusable/pr.yml +++ b/.github/workflows/reusable/pr.yml @@ -348,10 +348,33 @@ jobs: with: sonar-token: ${{ secrets.sonar-token }} + verify-sha: + name: Verify SHA Consistency + needs: preflight + runs-on: ${{ inputs.runner }} + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Install yq + run: | + if ! command -v yq >/dev/null 2>&1; then + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + fi + - name: Run verify-sha-consistency.sh + run: bash .github/scripts/verify-sha-consistency.sh + lint: name: Lint needs: detect-language runs-on: ${{ inputs.runner }} + timeout-minutes: 10 permissions: contents: read @@ -672,14 +695,15 @@ jobs: # ─────────────────────────────────────────────────────────────────────────── enrich: name: Stage 2 · Enrich - needs: [lint, test, validate-pr-title, scan-deps, scan-secrets] + needs: [lint, test, validate-pr-title, scan-deps, scan-secrets, verify-sha] if: >- always() && github.event.pull_request != null && needs.lint.result == 'success' && needs.test.result == 'success' && needs.validate-pr-title.result == 'success' && - needs.scan-deps.result == 'success' + needs.scan-deps.result == 'success' && + needs.verify-sha.result == 'success' runs-on: ${{ inputs.runner }} timeout-minutes: 5 permissions: diff --git a/.yamllint b/.yamllint index ee45cee..eba451e 100644 --- a/.yamllint +++ b/.yamllint @@ -5,6 +5,9 @@ # 2. Aligned multi-line `with:` blocks for readability extends: default +ignore: | + .claude/worktrees/ + rules: line-length: disable document-start: disable # --- header is optional in this project diff --git a/manifest.yml b/manifest.yml index 0bb9831..4282d10 100644 --- a/manifest.yml +++ b/manifest.yml @@ -101,7 +101,7 @@ deps: softprops/action-gh-release: "b4309332981a82ec1c5618f44dd2e27cc8bfbfda" # v3.0.0 # ── Self (OpenCI vendoring itself via remote action reference) ────────── - YiAgent/OpenCI: "d280a64a392b7f1a3e906246286ca983e610f920" # resolve-openci bootstrap + YiAgent/OpenCI: "ebe8fca3260dce68d34d51b74703169e776bc72d" # resolve-openci bootstrap # ───────────────────────────────────────────────────────────────────────────── # Reusable workflow catalog (consumed via `uses: YiAgent/OpenCI/.github/workflows/.yml@`) diff --git a/scripts/bump-self-sha.sh b/scripts/bump-self-sha.sh new file mode 100755 index 0000000..f869d35 --- /dev/null +++ b/scripts/bump-self-sha.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# bump-self-sha.sh — Update the YiAgent/OpenCI self-reference SHA. +# ───────────────────────────────────────────────────────────────────────────── +# Finds the latest commit on the main branch that contains +# .github/workflows/reusable/, then writes it to manifest.yml and all +# workflow files that reference YiAgent/OpenCI. +# +# Usage: +# bash scripts/bump-self-sha.sh # update in-place +# bash scripts/bump-self-sha.sh --dry-run # print new SHA, make no changes +# +# Requirements: git (fetch access to origin), yq, sed +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MANIFEST="$REPO_ROOT/manifest.yml" +REQUIRED_PATH=".github/workflows/reusable" +REMOTE="${REMOTE:-origin}" +BASE_BRANCH="${BASE_BRANCH:-main}" +DRY_RUN=false + +for arg in "$@"; do + [ "$arg" = "--dry-run" ] && DRY_RUN=true +done + +die() { echo "::error::$*" >&2; exit 1; } +info() { echo " $*"; } + +# ── 1. Resolve the latest remote SHA for BASE_BRANCH ───────────────────────── + +info "Fetching $REMOTE/$BASE_BRANCH ..." +git fetch --quiet "$REMOTE" "$BASE_BRANCH" 2>/dev/null || \ + die "Cannot fetch $REMOTE/$BASE_BRANCH. Check your remote and network access." + +remote_sha="$(git rev-parse "refs/remotes/$REMOTE/$BASE_BRANCH" 2>/dev/null)" || \ + die "Could not resolve $REMOTE/$BASE_BRANCH after fetch." + +info "Remote HEAD: $remote_sha" + +# ── 2. Walk back until we find a commit that has the required directory ─────── + +candidate="$remote_sha" +max_walk=20 +walked=0 + +while true; do + tree_output="$(git ls-tree "$candidate" "$REQUIRED_PATH/" 2>/dev/null || true)" + if [ -n "$tree_output" ]; then + break + fi + walked=$((walked + 1)) + if [ "$walked" -ge "$max_walk" ]; then + die "Walked $max_walk commits back from $remote_sha without finding '$REQUIRED_PATH/'. Is the path correct?" + fi + # Step to parent. + candidate="$(git rev-parse "${candidate}^" 2>/dev/null)" || \ + die "Ran out of history before finding '$REQUIRED_PATH/'." +done + +if [ "$walked" -gt 0 ]; then + echo "::warning::Remote HEAD ($remote_sha) is missing '$REQUIRED_PATH/'. Using ancestor $candidate instead (walked $walked commits back)." +fi + +new_sha="$candidate" + +# ── 3. Read current SHA from manifest.yml ──────────────────────────────────── + +if ! command -v yq >/dev/null 2>&1; then + die "yq is required (brew install yq / apt install yq)" +fi + +old_sha="$(yq -r '.deps["YiAgent/OpenCI"] // ""' "$MANIFEST")" + +if [ "$old_sha" = "$new_sha" ]; then + echo "manifest.yml already at $new_sha — nothing to do." + exit 0 +fi + +echo "" +echo " old SHA: ${old_sha:-}" +echo " new SHA: $new_sha" +echo "" + +if [ "$DRY_RUN" = true ]; then + echo "[dry-run] Would update manifest.yml and all workflow files." + echo "[dry-run] Run without --dry-run to apply." + exit 0 +fi + +# ── 4. Update manifest.yml ──────────────────────────────────────────────────── + +if [ -z "$old_sha" ]; then + die "YiAgent/OpenCI not found in manifest.yml .deps — add it manually first." +fi + +sed -i'' -e "s|${old_sha}|${new_sha}|g" "$MANIFEST" +info "Updated manifest.yml" + +# ── 5. Update all workflow files that reference the old SHA ────────────────── + +updated=0 +while IFS= read -r -d '' f; do + if grep -q "$old_sha" "$f" 2>/dev/null; then + sed -i'' -e "s|${old_sha}|${new_sha}|g" "$f" + info "Updated $f" + updated=$((updated + 1)) + fi +done < <(find "$REPO_ROOT/.github/workflows" "$REPO_ROOT/actions" \ + -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0') + +echo "" +echo "Done. Updated manifest.yml + $updated workflow file(s) to $new_sha" +echo "Stage and commit: git add manifest.yml .github/workflows && git commit -m 'chore(manifest): bump YiAgent/OpenCI SHA to $new_sha'" diff --git a/tests/actions/self-test-routing.bats b/tests/actions/self-test-routing.bats index 1110ffd..3026a01 100644 --- a/tests/actions/self-test-routing.bats +++ b/tests/actions/self-test-routing.bats @@ -227,7 +227,7 @@ setup() { @test "issue-ops.yml calls reusable issue.yml" { local issue_entry="${PROJECT_ROOT}/.github/workflows/issue-ops.yml" - grep -qP 'uses:\s+YiAgent/OpenCI/\.github/workflows/reusable/issue\.yml@[0-9a-f]{40}' "$issue_entry" + grep -qE 'uses:[[:space:]]+YiAgent/OpenCI/\.github/workflows/reusable/issue\.yml@[0-9a-f]{40}' "$issue_entry" } @test "issue-ops.yml has lifecycle job" {