diff --git a/.github/actions/build-image/action.yml b/.github/actions/build-image/action.yml index beddd7e2..9d902d69 100644 --- a/.github/actions/build-image/action.yml +++ b/.github/actions/build-image/action.yml @@ -82,7 +82,7 @@ runs: using: composite steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Set up environment uses: ./.github/actions/setup-env @@ -107,7 +107,7 @@ runs: - name: Extract metadata id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ inputs.registry }} tags: | @@ -124,7 +124,7 @@ runs: - name: Build image (tar output) if: inputs.output-type == 'tar' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./build file: ./build/Containerfile @@ -174,7 +174,7 @@ runs: - name: Build image (registry output) if: inputs.output-type == 'registry' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./build file: ./build/Containerfile diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml index 895f2588..5715d4b6 100644 --- a/.github/actions/setup-env/action.yml +++ b/.github/actions/setup-env/action.yml @@ -7,15 +7,19 @@ # - hadolint (for Containerfile linting in pre-commit) # - BATS + helper libraries (for shell script testing) # -# IMPORTANT: The caller must checkout the repository before using this action. -# This action does NOT checkout code, allowing callers to control ref, token, -# persist-credentials, and other checkout options. +# IMPORTANT: +# - This action does NOT checkout code, allowing callers to control ref, token, +# persist-credentials, and other checkout options. +# - Checkout is only required for operations that read repository files +# (for example, sync-dependencies or devcontainer CLI version lookup). # # Inputs: +# install-python: Install Python (default: true) +# python-version: Python version fallback when pyproject.toml is unavailable (default: '3.12') # sync-dependencies: Run uv sync to install project deps (default: false) # install-podman: Install podman (default: false) # install-node: Install Node.js (default: false) -# node-version: Node.js version (default: '20') +# node-version: Node.js version (default: '24') # install-devcontainer-cli: Install devcontainer CLI + docker-compose wrapper (default: false) # install-hadolint: Install hadolint binary (default: false) # install-taplo: Install taplo TOML linter/formatter (default: false) @@ -25,10 +29,15 @@ # uv-version: The version of uv that was installed # # Usage: -# # Minimal (Python + uv only) +# # Default (Python + uv only) # - uses: actions/checkout@v4 # - uses: ./.github/actions/setup-env # +# # uv only (skip Python setup) +# - uses: ./.github/actions/setup-env +# with: +# install-python: 'false' +# # # With project dependencies # - uses: actions/checkout@v4 # - uses: ./.github/actions/setup-env @@ -47,6 +56,14 @@ name: 'Setup Environment' description: 'Set up CI environment with Python, uv, and optional tools (podman, Node.js, devcontainer CLI, hadolint, BATS)' inputs: + install-python: + description: 'Install Python runtime' + required: false + default: 'true' + python-version: + description: 'Python version fallback when pyproject.toml is unavailable' + required: false + default: '3.12' sync-dependencies: description: 'Run uv sync to install project dependencies' required: false @@ -62,7 +79,7 @@ inputs: node-version: description: 'Node.js version (when install-node is true)' required: false - default: '20' + default: '24' install-devcontainer-cli: description: 'Install @devcontainers/cli and docker-compose wrapper (requires Node.js)' required: false @@ -87,31 +104,145 @@ inputs: outputs: uv-version: description: 'Version of uv installed' - value: ${{ steps.setup-uv.outputs.uv-version }} + value: ${{ steps.setup-uv-retry.outputs.uv-version || steps.setup-uv.outputs.uv-version }} runs: using: composite steps: # ── Python ─────────────────────────────────────────────────────────── - - name: "Set up Python" + - name: "Set up Python from pyproject" + if: inputs.install-python == 'true' && hashFiles('pyproject.toml') != '' uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version-file: "pyproject.toml" + - name: "Set up Python fallback" + if: inputs.install-python == 'true' && hashFiles('pyproject.toml') == '' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: ${{ inputs.python-version }} + # ── uv ───────────────────────────────────────────────────────────── - name: Install uv id: setup-uv + continue-on-error: true + uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7 + with: + enable-cache: true + # Install a specific version of uv. + version: "0.10.0" + + - name: Wait before retrying uv install + if: steps.setup-uv.outcome == 'failure' + shell: bash + run: sleep 15 + + - name: Install uv (retry) + id: setup-uv-retry + if: steps.setup-uv.outcome == 'failure' uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7 with: enable-cache: true # Install a specific version of uv. version: "0.10.0" + # ── retry() shell helper ─────────────────────────────────────────── + - name: Export retry helper function + shell: bash + run: | + set -euo pipefail + RETRY_HELPER="$RUNNER_TEMP/setup-env-retry.sh" + PREV_BASH_ENV="${BASH_ENV:-}" + + cat > "$RETRY_HELPER" <<'EOF' + retry() { + local retries=3 + local backoff=1 + local max_backoff=60 + local rc=1 + + while [ "$#" -gt 0 ]; do + case "$1" in + --retries) + retries="$2" + shift 2 + ;; + --backoff) + backoff="$2" + shift 2 + ;; + --max-backoff) + max_backoff="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + echo "ERROR: Unknown retry option '$1'" + return 2 + ;; + esac + done + + if [ "$#" -eq 0 ]; then + echo "ERROR: retry requires a command after '--'" + return 2 + fi + + local attempt=1 + local current_backoff="$backoff" + while [ "$attempt" -le "$retries" ]; do + if "$@"; then + return 0 + fi + rc=$? + if [ "$attempt" -lt "$retries" ]; then + local wait="$current_backoff" + if [ "$wait" -gt "$max_backoff" ]; then + wait="$max_backoff" + fi + echo "Retry $attempt/$retries failed (exit $rc), waiting ${wait}s..." + sleep "$wait" + current_backoff=$((current_backoff * 2)) + fi + attempt=$((attempt + 1)) + done + + echo "ERROR: Command failed after $retries attempts: $*" + return "$rc" + } + export -f retry + EOF + + if [ -n "$PREV_BASH_ENV" ] && [ -f "$PREV_BASH_ENV" ] && [ "$PREV_BASH_ENV" != "$RETRY_HELPER" ]; then + { + echo "source \"$PREV_BASH_ENV\"" + cat "$RETRY_HELPER" + } > "${RETRY_HELPER}.merged" + mv "${RETRY_HELPER}.merged" "$RETRY_HELPER" + fi + + echo "BASH_ENV=$RETRY_HELPER" >> "$GITHUB_ENV" + # ── Python dependencies ─────────────────────────────────────────────── - name: Sync Python dependencies if: inputs.sync-dependencies == 'true' shell: bash - run: uv sync --frozen --all-extras + run: | + set -euo pipefail + + if uv sync --frozen --all-extras; then + : + else + rc=$? + echo "WARNING: uv sync failed (exit $rc), clearing cache and .venv before retry..." + uv cache clean + rm -rf .venv + echo "Retrying uv sync..." + uv sync --frozen --all-extras + fi # ── Podman ────────────────────────────────────────────────────────── - name: Install podman @@ -130,7 +261,7 @@ runs: # Also installed when install-devcontainer-cli is true (npm is required) - name: Install Node.js if: inputs.install-node == 'true' || inputs.install-devcontainer-cli == 'true' - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ inputs.node-version }} @@ -162,8 +293,10 @@ runs: BIN_FILE="hadolint-${ARCH}" SHA_FILE="${BIN_FILE}.sha256" - curl -fsSL "${BASE_URL}/${BIN_FILE}" -o "${BIN_FILE}" - curl -fsSL "${BASE_URL}/${SHA_FILE}" -o "${SHA_FILE}" + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + curl -fsSL "${BASE_URL}/${BIN_FILE}" -o "${BIN_FILE}" + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + curl -fsSL "${BASE_URL}/${SHA_FILE}" -o "${SHA_FILE}" EXPECTED_SHA="$(awk '{print $1}' "${SHA_FILE}")" echo "${EXPECTED_SHA} ${BIN_FILE}" | sha256sum -c - @@ -189,11 +322,17 @@ runs: ;; esac - TAPLO_VERSION="$(curl -fsSL https://api.github.com/repos/tamasfe/taplo/releases/latest | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')" + TAPLO_VERSION="$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + curl -fsSL https://api.github.com/repos/tamasfe/taplo/releases/latest | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')" + if [ -z "${TAPLO_VERSION:-}" ]; then + echo "ERROR: Failed to resolve Taplo version from GitHub releases API" + exit 1 + fi BASE_URL="https://github.com/tamasfe/taplo/releases/download/${TAPLO_VERSION}" BIN_FILE="taplo-linux-${ARCH}.gz" - curl -fsSL "${BASE_URL}/${BIN_FILE}" -o "${BIN_FILE}" + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + curl -fsSL "${BASE_URL}/${BIN_FILE}" -o "${BIN_FILE}" gunzip "${BIN_FILE}" sudo install -m 0755 "taplo-linux-${ARCH}" /usr/local/bin/taplo rm -f "taplo-linux-${ARCH}" diff --git a/.github/actions/test-image/action.yml b/.github/actions/test-image/action.yml index 5bf76899..d8ebcae4 100644 --- a/.github/actions/test-image/action.yml +++ b/.github/actions/test-image/action.yml @@ -68,7 +68,7 @@ runs: using: composite steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.ref || github.ref }} @@ -125,21 +125,8 @@ runs: echo "Pulling image: $IMAGE_TAG" # Retry logic for podman pull (network flakiness) - RETRIES=3 - for i in $(seq 1 $RETRIES); do - if podman pull "$IMAGE_TAG"; then - echo "Image pulled successfully" - break - else - if [ $i -lt $RETRIES ]; then - echo "Pull failed, retrying ($i/$RETRIES)..." - sleep 3 - else - echo "Pull failed after $RETRIES attempts" - exit 1 - fi - fi - done + uv run retry --retries 3 --backoff 3 --max-backoff 3 -- podman pull "$IMAGE_TAG" + echo "Image pulled successfully" - name: Verify image is available if: inputs.image-source == 'local' diff --git a/.github/actions/test-integration/action.yml b/.github/actions/test-integration/action.yml index ac7584da..f57eb46e 100644 --- a/.github/actions/test-integration/action.yml +++ b/.github/actions/test-integration/action.yml @@ -46,7 +46,7 @@ runs: using: composite steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.ref || github.ref }} diff --git a/.github/actions/test-project/action.yml b/.github/actions/test-project/action.yml index 0ee2d0c2..98f95332 100644 --- a/.github/actions/test-project/action.yml +++ b/.github/actions/test-project/action.yml @@ -39,7 +39,7 @@ runs: using: composite steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up test environment uses: ./.github/actions/setup-env @@ -51,7 +51,7 @@ runs: - name: Cache pre-commit hooks if: inputs.suite == 'all' || inputs.suite == 'lint' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} @@ -147,7 +147,7 @@ runs: - name: Upload coverage report if: always() && inputs.suite == 'all' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: coverage-report path: coverage.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index daba82ae..825acc6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Generate version for dev build id: version @@ -89,7 +89,7 @@ jobs: output-file: /tmp/image.tar - name: Upload image artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: container-image-${{ steps.version.outputs.version }}-amd64 path: /tmp/image.tar @@ -108,10 +108,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: container-image-${{ needs.build-image.outputs.version }}-amd64 path: /tmp @@ -134,10 +134,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: container-image-${{ needs.build-image.outputs.version }}-amd64 path: /tmp @@ -149,7 +149,7 @@ jobs: - name: Upload test artifacts on failure if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: integration-test-artifacts path: | @@ -169,7 +169,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run project checks uses: ./.github/actions/test-project @@ -185,7 +185,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up environment uses: ./.github/actions/setup-env @@ -193,7 +193,9 @@ jobs: sync-dependencies: 'true' - name: Install safety - run: uv pip install safety==3.7.0 + run: | + set -euo pipefail + uv run retry --retries 3 --backoff 5 --max-backoff 30 -- uv pip install safety==3.7.0 - name: Run Bandit (Python security linting) id: bandit @@ -209,7 +211,7 @@ jobs: - name: Upload security reports as artifacts if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: python-security-reports path: | @@ -241,10 +243,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: container-image-${{ needs.build-image.outputs.version }}-amd64 path: /tmp @@ -303,7 +305,7 @@ jobs: - name: Upload SBOM artifact if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: sbom-${{ needs.build-image.outputs.version }}-amd64 path: sbom-cyclonedx.json @@ -311,7 +313,7 @@ jobs: - name: Upload SARIF to GitHub Security if: always() - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: sarif_file: trivy-results.sarif category: 'container-image' @@ -327,7 +329,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Validate dependency-review exceptions id: exceptions diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 71de891f..3ee84be2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,11 +48,11 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: languages: ${{ matrix.language }} - name: Run CodeQL analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml index 2b4e0d39..428c3225 100644 --- a/.github/workflows/pr-title-check.yml +++ b/.github/workflows/pr-title-check.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up environment uses: ./.github/actions/setup-env diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 2c09a9f1..a1d8bfe5 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -54,7 +54,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: dev fetch-depth: 0 @@ -156,20 +156,20 @@ jobs: steps: - name: Generate Commit App Token id: commit-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} - name: Generate Release App Token id: release-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Checkout dev branch - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: dev fetch-depth: 0 @@ -185,7 +185,8 @@ jobs: GH_TOKEN: ${{ steps.commit-app-token.outputs.token }} run: | set -euo pipefail - PREPARE_START_SHA=$(gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') + PREPARE_START_SHA=$(uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') echo "prepare_start_sha=$PREPARE_START_SHA" >> "$GITHUB_OUTPUT" echo "✓ Captured pre-prepare dev SHA: $PREPARE_START_SHA" @@ -198,6 +199,12 @@ jobs: uv run prepare-changelog prepare "$VERSION" CHANGELOG.md echo "✓ CHANGELOG prepared (Unreleased → [${VERSION}] - TBD + fresh Unreleased)" + - name: Sync workspace manifest after changelog prepare + run: | + set -euo pipefail + uv run python scripts/sync_manifest.py sync assets/workspace/ + echo "✓ Synced workspace manifest after changelog prepare" + - name: Extract CHANGELOG content for PR body id: changelog env: @@ -222,7 +229,7 @@ jobs: Move Unreleased content to [${{ needs.validate.outputs.version }}] - TBD and create fresh empty Unreleased section for continued development. - FILE_PATHS: CHANGELOG.md + FILE_PATHS: CHANGELOG.md assets/workspace/.devcontainer/CHANGELOG.md - name: Create release branch from dev id: create-branch @@ -231,9 +238,11 @@ jobs: RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} run: | set -euo pipefail - DEV_SHA=$(gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') + DEV_SHA=$(uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') echo "Creating branch $RELEASE_BRANCH from dev at $DEV_SHA..." - gh api "repos/${{ github.repository }}/git/refs" \ + uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/refs" \ -f ref="refs/heads/$RELEASE_BRANCH" \ -f sha="$DEV_SHA" echo "dev_sha=$DEV_SHA" >> "$GITHUB_OUTPUT" @@ -255,6 +264,12 @@ jobs: " echo "✓ Stripped empty Unreleased section from CHANGELOG" + - name: Sync workspace manifest after release changelog strip + run: | + set -euo pipefail + uv run python scripts/sync_manifest.py sync assets/workspace/ + echo "✓ Synced workspace manifest after release changelog strip" + - name: Commit stripped CHANGELOG to release branch via API uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 env: @@ -266,7 +281,7 @@ jobs: Strip empty Unreleased section from release branch. Release date TBD (set during finalization). - FILE_PATHS: CHANGELOG.md + FILE_PATHS: CHANGELOG.md assets/workspace/.devcontainer/CHANGELOG.md - name: Create draft PR to main id: pr @@ -285,22 +300,10 @@ jobs: ### Release Content $CHANGELOG_CONTENT - - ### Testing Checklist - - [ ] All tests pass (\`just test\`) - - [ ] Manual testing complete - - [ ] No critical bugs found - - [ ] Ready for release - - ### When Ready to Release - - Publish candidate: \`just publish-candidate $VERSION\` - - Publish final release: \`just finalize-release $VERSION\` - - ### Related - - Release automation: #48, #172 " - PR_URL=$(gh pr create \ + PR_URL=$(uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh pr create \ --base main \ --head "$RELEASE_BRANCH" \ --title "chore: release $VERSION" \ @@ -336,8 +339,10 @@ jobs: exit 0 fi - if gh api "repos/${{ github.repository }}/git/ref/heads/$RELEASE_BRANCH" >/dev/null 2>&1; then - gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/$RELEASE_BRANCH" + if uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/$RELEASE_BRANCH" >/dev/null 2>&1; then + uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/$RELEASE_BRANCH" BRANCH_DELETED=true echo "✓ Deleted partially created release branch: $RELEASE_BRANCH" else @@ -345,7 +350,8 @@ jobs: fi if [ -n "${POST_FREEZE_DEV_SHA:-}" ]; then - CURRENT_DEV_SHA="$(gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha')" + CURRENT_DEV_SHA="$(uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha')" if [ "$CURRENT_DEV_SHA" != "$POST_FREEZE_DEV_SHA" ]; then echo "w dev advanced after freeze commit; skipping CHANGELOG rollback to avoid clobbering concurrent updates" echo "branch_deleted=$BRANCH_DELETED" >> "$GITHUB_OUTPUT" @@ -354,13 +360,16 @@ jobs: fi fi - gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=$PREPARE_START_SHA" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.pre-prepare.md - gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=dev" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.current-dev.md + uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=$PREPARE_START_SHA" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.pre-prepare.md + uv run retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=dev" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.current-dev.md if cmp -s /tmp/changelog.pre-prepare.md /tmp/changelog.current-dev.md; then echo "i dev CHANGELOG already matches pre-prepare state" else cp /tmp/changelog.pre-prepare.md CHANGELOG.md + uv run python scripts/sync_manifest.py sync assets/workspace/ CHANGELOG_ROLLBACK_NEEDED=true echo "✓ Prepared CHANGELOG rollback content for dev" fi @@ -379,7 +388,7 @@ jobs: chore: rollback failed prepare-release ${{ needs.validate.outputs.version }} Restore CHANGELOG.md on dev to pre-prepare state after prepare-release failed. - FILE_PATHS: CHANGELOG.md + FILE_PATHS: CHANGELOG.md assets/workspace/.devcontainer/CHANGELOG.md - name: Rollback summary if: ${{ failure() }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d70ff55..7484a041 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,12 @@ # 1. Validate: Check all prerequisites (PR status, CI passed, CHANGELOG ready) # 2. Finalize: For final releases only, set release date in CHANGELOG # 3. Build & Test: Build and test images for all architectures -# 4. Publish: Create tag and publish images only if all tests pass +# 4. Publish: Create tag, publish images, and publish final GitHub Release # 5. Rollback (on failure): Revert changes and create issue # # Release kinds: # candidate: Publishes X.Y.Z-rcN. No CHANGELOG changes, no sync-issues. -# final: Publishes X.Y.Z. Sets release date in CHANGELOG, triggers sync-issues. +# final: Publishes X.Y.Z. Sets release date in CHANGELOG, triggers sync-issues, publishes GitHub Release. # # Design: Everything happens in one workflow dispatch before creating the tag. # This ensures no broken releases are tagged. @@ -72,12 +72,19 @@ jobs: publish_version: ${{ steps.publish_meta.outputs.publish_version }} next_rc: ${{ steps.publish_meta.outputs.next_rc }} pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} + latest_rc: ${{ steps.latest_rc.outputs.latest_rc }} matrix: ${{ steps.matrix.outputs.matrix }} architectures: ${{ steps.matrix.outputs.architectures }} steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' - name: Validate and prepare variables id: vars @@ -116,7 +123,8 @@ jobs: env: VERSION: ${{ steps.vars.outputs.version }} run: | - git fetch origin "release/$VERSION" || { + set -euo pipefail + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin "release/$VERSION" || { echo "ERROR: Release branch not found: release/$VERSION" echo "Did you run: just prepare-release $VERSION" exit 1 @@ -148,13 +156,20 @@ jobs: while IFS= read -r tag; do [ -z "$tag" ] && continue - if ! echo "$tag" | grep -qE "^${VERSION}-rc[0-9]+$"; then + rc_prefix="${VERSION}-rc" + if [[ "$tag" != "$rc_prefix"* ]]; then + echo "ERROR: Malformed candidate tag detected: $tag" + echo "Expected format: ${VERSION}-rcN" + exit 1 + fi + + rc_num="${tag#"$rc_prefix"}" + if ! printf '%s' "$rc_num" | grep -qE '^[0-9]+$'; then echo "ERROR: Malformed candidate tag detected: $tag" echo "Expected format: ${VERSION}-rcN" exit 1 fi - rc_num="${tag##*-rc}" if [ "$rc_num" -gt "$MAX_RC" ]; then MAX_RC="$rc_num" fi @@ -173,6 +188,124 @@ jobs: echo "release_url=$RELEASE_URL" >> $GITHUB_OUTPUT echo "Publish version: $PUBLISH_VERSION" + - name: Find latest RC tag + if: steps.vars.outputs.release_kind == 'final' + id: latest_rc + env: + VERSION: ${{ steps.vars.outputs.version }} + run: | + set -euo pipefail + TAG_PATTERN="${VERSION}-rc*" + EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" \ + | awk '{print $2}' | sed 's#refs/tags/##') + + if [ -z "$EXISTING_TAGS" ]; then + echo "ERROR: No RC tags found for version $VERSION" + echo "Publish at least one candidate before finalizing: just publish-candidate $VERSION" + exit 1 + fi + + while IFS= read -r tag; do + [ -z "$tag" ] && continue + + rc_prefix="${VERSION}-rc" + if [[ "$tag" != "$rc_prefix"* ]]; then + echo "ERROR: Malformed candidate tag detected: $tag" + echo "Expected format: ${VERSION}-rcN" + exit 1 + fi + + rc_num="${tag#"$rc_prefix"}" + if ! printf '%s' "$rc_num" | grep -qE '^[0-9]+$'; then + echo "ERROR: Malformed candidate tag detected: $tag" + echo "Expected format: ${VERSION}-rcN" + exit 1 + fi + done <<< "$EXISTING_TAGS" + + LATEST_RC=$(echo "$EXISTING_TAGS" | sort -V | tail -1) + echo "latest_rc=$LATEST_RC" >> "$GITHUB_OUTPUT" + echo "Latest RC: $LATEST_RC" + + - name: Generate cross-repo token for downstream gate + if: steps.vars.outputs.release_kind == 'final' + id: smoke-check-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: vig-os + repositories: devcontainer-smoke-test + + - name: Verify downstream pre-release for latest RC + if: steps.vars.outputs.release_kind == 'final' + env: + GH_TOKEN: ${{ steps.smoke-check-token.outputs.token }} + LATEST_RC: ${{ steps.latest_rc.outputs.latest_rc }} + VERSION: ${{ steps.vars.outputs.version }} + run: | + set -euo pipefail + echo "Checking downstream pre-release for $LATEST_RC..." + + RELEASE_JSON="" + LAST_ERROR="" + RETRIES=5 + for i in $(seq 1 "$RETRIES"); do + API_OUTPUT="" + if API_OUTPUT=$(gh api "repos/vig-os/devcontainer-smoke-test/releases/tags/$LATEST_RC" 2>&1); then + RELEASE_JSON="$API_OUTPUT" + break + fi + + LAST_ERROR="$API_OUTPUT" + if printf '%s' "$API_OUTPUT" | grep -Eqi "401|403|forbidden|bad credentials|authentication|authorization|insufficient scopes"; then + echo "ERROR: Non-retryable authentication/authorization failure while querying downstream release API for $LATEST_RC" + echo "$API_OUTPUT" + exit 1 + fi + + if printf '%s' "$API_OUTPUT" | grep -Eqi "404|not found|429|5[0-9]{2}|timed out|timeout|temporary|connection reset|connection refused|network|tls"; then + if [ "$i" -lt "$RETRIES" ]; then + BACKOFF=$((i * 5)) + echo "Downstream release lookup failed transiently, retrying in ${BACKOFF}s ($i/$RETRIES)..." + sleep "$BACKOFF" + fi + continue + fi + + echo "ERROR: Failed querying downstream release API for $LATEST_RC" + echo "$API_OUTPUT" + exit 1 + done + + if [ -z "$RELEASE_JSON" ]; then + echo "ERROR: No downstream release found for RC tag $LATEST_RC" + if [ -n "$LAST_ERROR" ]; then + echo "Last API error:" + echo "$LAST_ERROR" + fi + echo "" + echo "The final release requires a successful smoke-test pre-release." + echo "Steps to resolve:" + echo " 1. Verify the candidate dispatch succeeded:" + echo " gh -R vig-os/devcontainer-smoke-test run list --limit 5" + echo " 2. If no dispatch ran, re-publish the candidate:" + echo " just publish-candidate $VERSION" + echo " 3. Wait for the downstream pre-release to appear, then retry." + exit 1 + fi + + TAG_NAME=$(echo "$RELEASE_JSON" | jq -r '.tag_name') + IS_PRERELEASE=$(echo "$RELEASE_JSON" | jq -r '.prerelease') + + if [ "$IS_PRERELEASE" != "true" ]; then + echo "ERROR: Downstream release for $LATEST_RC exists but is not a pre-release" + echo "Expected prerelease=true for RC gate enforcement" + exit 1 + fi + + echo "✓ Downstream release verified: $TAG_NAME" + - name: Verify CHANGELOG has TBD entry env: VERSION: ${{ steps.vars.outputs.version }} @@ -207,7 +340,7 @@ jobs: RELEASE_BRANCH="release/$VERSION" # Find PR from release branch to main - PR_JSON=$(gh pr list \ + PR_JSON=$(retry --retries 3 --backoff 5 --max-backoff 30 -- gh pr list \ --head "$RELEASE_BRANCH" \ --base main \ --json number,isDraft,reviewDecision,statusCheckRollup \ @@ -318,6 +451,7 @@ jobs: RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} NEXT_RC: ${{ steps.publish_meta.outputs.next_rc }} + LATEST_RC: ${{ steps.latest_rc.outputs.latest_rc }} PR_NUMBER: ${{ steps.pr.outputs.pr_number }} RELEASE_DATE: ${{ steps.vars.outputs.release_date }} ARCHITECTURES: ${{ steps.matrix.outputs.architectures }} @@ -332,6 +466,9 @@ jobs: if [ -n "$NEXT_RC" ]; then echo " Next RC Number: $NEXT_RC" fi + if [ "$RELEASE_KIND" = "final" ]; then + echo " Latest RC Gate: $LATEST_RC" + fi echo " Release Branch: release/$VERSION" echo " PR: #$PR_NUMBER" echo " Release Date: $RELEASE_DATE" @@ -353,23 +490,23 @@ jobs: steps: - name: Generate GitHub App Token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Checkout release branch - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: release/${{ needs.validate.outputs.version }} token: ${{ steps.app-token.outputs.token }} persist-credentials: true - name: Set up environment - if: needs.validate.outputs.release_kind == 'final' uses: ./.github/actions/setup-env with: sync-dependencies: 'true' + install-just: 'false' - name: Set release date in CHANGELOG if: needs.validate.outputs.release_kind == 'final' @@ -381,6 +518,47 @@ jobs: uv run prepare-changelog finalize "$VERSION" "$RELEASE_DATE" echo "✓ Release date set in CHANGELOG.md" + - name: Regenerate docs for finalized release + if: needs.validate.outputs.release_kind == 'final' + run: | + set -euo pipefail + uv run python docs/generate.py + echo "✓ Regenerated docs from finalized CHANGELOG.md" + + - name: Sync workspace manifest for finalized release + if: needs.validate.outputs.release_kind == 'final' + run: | + set -euo pipefail + uv run python scripts/sync_manifest.py sync assets/workspace/ + echo "✓ Synced workspace manifest after changelog finalization" + + - name: Collect finalization files + if: needs.validate.outputs.release_kind == 'final' + id: finalize-files + run: | + set -euo pipefail + + CHANGED_FILES="$(git diff --name-only)" + if [ -z "$CHANGED_FILES" ]; then + echo "ERROR: Finalization produced no tracked file changes." + echo "Expected CHANGELOG.md and possibly generated docs to change." + exit 1 + fi + + FILE_PATHS="$(echo "$CHANGED_FILES" | tr '\n' ' ' | sed 's/[[:space:]]*$//')" + echo "file_paths=$FILE_PATHS" >> "$GITHUB_OUTPUT" + echo "Finalization files: $FILE_PATHS" + + for expected_doc in README.md CONTRIBUTE.md TESTING.md docs/SKILL_PIPELINE.md; do + if git diff --quiet -- "$expected_doc"; then + continue + fi + if ! printf '%s\n' "$CHANGED_FILES" | grep -Fxq -- "$expected_doc"; then + echo "ERROR: Expected modified doc '$expected_doc' missing from commit file list." + exit 1 + fi + done + - name: Commit finalization changes via API if: needs.validate.outputs.release_kind == 'final' uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 @@ -391,10 +569,10 @@ jobs: COMMIT_MESSAGE: |- chore: finalize release ${{ needs.validate.outputs.version }} - Set release date to ${{ needs.validate.outputs.release_date }} in CHANGELOG.md + Set release date to ${{ needs.validate.outputs.release_date }} and regenerate release docs. Refs: #${{ needs.validate.outputs.pr_number }} - FILE_PATHS: CHANGELOG.md + FILE_PATHS: ${{ steps.finalize-files.outputs.file_paths }} - name: Trigger sync-issues workflow if: needs.validate.outputs.release_kind == 'final' @@ -404,7 +582,7 @@ jobs: run: | set -euo pipefail echo "Triggering sync-issues workflow..." - gh workflow run sync-issues.yml \ + retry --retries 2 --backoff 5 --max-backoff 20 -- gh workflow run sync-issues.yml \ -f "target-branch=release/$VERSION" echo "✓ sync-issues workflow triggered" @@ -459,10 +637,40 @@ jobs: VERSION: ${{ needs.validate.outputs.version }} run: | set -euo pipefail - git fetch origin "release/$VERSION" + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin "release/$VERSION" git reset --hard "origin/release/$VERSION" echo "✓ Synced with remote release branch" + - name: Refresh release PR body from finalized changelog + if: needs.validate.outputs.release_kind == 'final' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_DATE: ${{ needs.validate.outputs.release_date }} + PR_NUMBER: ${{ needs.validate.outputs.pr_number }} + run: | + set -euo pipefail + CHANGELOG_CONTENT=$(sed -n "/## \[$VERSION\]/,/^## \[/p" CHANGELOG.md | sed '$d') + + if [ -z "$CHANGELOG_CONTENT" ]; then + echo "ERROR: Could not extract release section for version $VERSION from CHANGELOG.md" + exit 1 + fi + + cat > /tmp/release-pr-body.md <> $GITHUB_OUTPUT echo "Release kind: $RELEASE_KIND — SHA: $FINALIZE_SHA" @@ -490,7 +699,7 @@ jobs: steps: - name: Checkout release commit - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ needs.finalize.outputs.finalize_sha }} @@ -533,7 +742,7 @@ jobs: trivyignores: '.trivyignore' - name: Upload tested image - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: container-image-${{ needs.validate.outputs.publish_version }}-${{ matrix.arch }} path: /tmp/image.tar @@ -554,22 +763,29 @@ jobs: packages: write # push images to GHCR id-token: write # keyless cosign signing via OIDC attestations: write # build provenance attestations + artifact-metadata: write # persist attestation storage records steps: - name: Generate GitHub App Token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Checkout release commit - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ needs.finalize.outputs.finalize_sha }} token: ${{ steps.app-token.outputs.token }} persist-credentials: true + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Configure git env: GIT_USER_NAME: ${{ github.event.inputs.git-user-name }} @@ -593,28 +809,65 @@ jobs: run: | set -euo pipefail if [ "$RELEASE_KIND" = "candidate" ]; then - if git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then + if retry --retries 3 --backoff 5 --max-backoff 30 -- git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then echo "ERROR: Candidate tag '$PUBLISH_VERSION' already exists on origin" echo "Another candidate publish likely completed concurrently; re-run workflow to infer next RC." exit 1 fi fi - git push origin "$PUBLISH_VERSION" + if ! retry --retries 3 --backoff 5 --max-backoff 30 -- git push origin "$PUBLISH_VERSION"; then + if retry --retries 3 --backoff 5 --max-backoff 30 -- git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then + LOCAL_TAG_TARGET_SHA=$(git rev-parse "$PUBLISH_VERSION^{}") + REMOTE_TAG_TARGET_SHA=$(retry --retries 3 --backoff 5 --max-backoff 30 -- git ls-remote --tags origin "refs/tags/$PUBLISH_VERSION^{}" | awk '{print $1}') + if [ -z "$REMOTE_TAG_TARGET_SHA" ]; then + echo "ERROR: Remote tag exists but target SHA could not be resolved: $PUBLISH_VERSION" + exit 1 + fi + if [ "$REMOTE_TAG_TARGET_SHA" != "$LOCAL_TAG_TARGET_SHA" ]; then + echo "ERROR: Remote tag target SHA mismatch for $PUBLISH_VERSION" + echo "Local tag target: $LOCAL_TAG_TARGET_SHA" + echo "Remote tag target: $REMOTE_TAG_TARGET_SHA" + exit 1 + fi + echo "Tag already present on origin with matching target SHA: $PUBLISH_VERSION" + else + echo "ERROR: Failed to push tag $PUBLISH_VERSION" + exit 1 + fi + fi echo "✓ Tag pushed: $PUBLISH_VERSION" + - name: Generate final release notes from CHANGELOG + if: needs.validate.outputs.release_kind == 'final' + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + awk -v version="$VERSION" ' + index($0, "## [" version "] - ") == 1 {found=1; next} + /^## \[/ && found {found=0} + found {print} + ' CHANGELOG.md > /tmp/github-release-notes.md + + if [ ! -s /tmp/github-release-notes.md ]; then + echo "ERROR: Failed to generate GitHub Release notes for $VERSION from CHANGELOG.md" + echo "Expected section heading: ## [$VERSION] - YYYY-MM-DD" + exit 1 + fi + - name: Install cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4 + uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4 - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ github.token }} - name: Download image artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: /tmp/images pattern: container-image-${{ needs.validate.outputs.publish_version }}-* @@ -646,22 +899,8 @@ jobs: IMAGE_TAG="$REPO:$PUBLISH_VERSION-$arch" docker tag "$SOURCE_IMAGE_TAG" "$IMAGE_TAG" echo "Pushing $arch image: $IMAGE_TAG" - - RETRIES=3 - for i in $(seq 1 $RETRIES); do - if docker push "$IMAGE_TAG"; then - echo "✓ Pushed: $IMAGE_TAG" - break - else - if [ $i -lt $RETRIES ]; then - echo "Push failed, retrying ($i/$RETRIES)..." - sleep 3 - else - echo "ERROR: Failed to push after $RETRIES attempts" - exit 1 - fi - fi - done + retry --retries 3 --backoff 3 --max-backoff 3 -- docker push "$IMAGE_TAG" + echo "✓ Pushed: $IMAGE_TAG" done - name: Create multi-arch manifest @@ -683,45 +922,20 @@ jobs: done echo "Creating version manifest: $REPO:$PUBLISH_VERSION" - RETRIES=3 - for i in $(seq 1 $RETRIES); do - if docker buildx imagetools create \ + retry --retries 3 --backoff 3 --max-backoff 3 -- \ + docker buildx imagetools create \ --tag "$REPO:$PUBLISH_VERSION" \ - "${ARCH_IMAGES[@]}"; then - echo "✓ Created version manifest: $REPO:$PUBLISH_VERSION" - break - else - if [ $i -lt $RETRIES ]; then - echo "Manifest creation failed, retrying ($i/$RETRIES)..." - sleep 3 - else - echo "ERROR: Failed to create version manifest" - exit 1 - fi - fi - done + "${ARCH_IMAGES[@]}" + echo "✓ Created version manifest: $REPO:$PUBLISH_VERSION" # Update latest manifest only for final releases building both architectures. if [ "$RELEASE_KIND" = "final" ] && [ ${#ARCH_ARRAY[@]} -eq 2 ]; then echo "Creating/updating latest manifest: $REPO:latest" - - RETRIES=3 - for i in $(seq 1 $RETRIES); do - if docker buildx imagetools create \ + retry --retries 3 --backoff 3 --max-backoff 3 -- \ + docker buildx imagetools create \ --tag "$REPO:latest" \ - "${ARCH_IMAGES[@]}"; then - echo "✓ Created/updated latest manifest: $REPO:latest" - break - else - if [ $i -lt $RETRIES ]; then - echo "Latest manifest creation failed, retrying ($i/$RETRIES)..." - sleep 3 - else - echo "ERROR: Failed to create latest manifest" - exit 1 - fi - fi - done + "${ARCH_IMAGES[@]}" + echo "✓ Created/updated latest manifest: $REPO:latest" else echo "Skipping 'latest' manifest (single architecture or limited build)" fi @@ -773,8 +987,23 @@ jobs: done fi - - name: Generate SBOM - uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2 + - name: Generate SBOM (attempt 1) + id: sbom_generate + continue-on-error: true + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + with: + image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.publish_version }} + artifact-name: sbom-${{ needs.validate.outputs.publish_version }}.spdx.json + output-file: /tmp/sbom.spdx.json + format: spdx-json + + - name: Wait before retrying SBOM generation + if: steps.sbom_generate.outcome == 'failure' + run: sleep 15 + + - name: Generate SBOM (retry) + if: steps.sbom_generate.outcome == 'failure' + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 with: image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.publish_version }} artifact-name: sbom-${{ needs.validate.outputs.publish_version }}.spdx.json @@ -782,7 +1011,7 @@ jobs: format: spdx-json - name: Upload SBOM artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: sbom-${{ needs.validate.outputs.publish_version }} path: /tmp/sbom.spdx.json @@ -796,11 +1025,13 @@ jobs: REPO="ghcr.io/vig-os/devcontainer" # Get the digest for the multi-arch manifest - DIGEST=$(docker buildx imagetools inspect "$REPO:$PUBLISH_VERSION" --format '{{json .Manifest.Digest}}' | tr -d '"') + DIGEST=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + docker buildx imagetools inspect "$REPO:$PUBLISH_VERSION" --format '{{json .Manifest.Digest}}' | tr -d '"') echo "Signing image: $REPO@$DIGEST" # Keyless signing using GitHub Actions OIDC identity - cosign sign --yes "$REPO@$DIGEST" + retry --retries 3 --backoff 15 --max-backoff 15 -- \ + cosign sign --yes "$REPO@$DIGEST" echo "✓ Image signed with cosign (keyless)" - name: Capture image digest for attestation @@ -808,26 +1039,82 @@ jobs: env: PUBLISH_VERSION: ${{ needs.validate.outputs.publish_version }} run: | + set -euo pipefail REPO="ghcr.io/vig-os/devcontainer" - DIGEST=$(docker buildx imagetools inspect "$REPO:$PUBLISH_VERSION" --format '{{json .Manifest.Digest}}' | tr -d '"') + DIGEST=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + docker buildx imagetools inspect "$REPO:$PUBLISH_VERSION" --format '{{json .Manifest.Digest}}' | tr -d '"') echo "digest=$DIGEST" >> $GITHUB_OUTPUT echo "Image digest: $DIGEST" - - name: Attest build provenance - uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3 + - name: Attest build provenance (attempt 1) + id: attest_provenance + continue-on-error: true + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-name: ghcr.io/vig-os/devcontainer + subject-digest: ${{ steps.digest.outputs.digest }} + push-to-registry: true + + - name: Wait before retrying build provenance attestation + if: steps.attest_provenance.outcome == 'failure' + run: sleep 30 + + - name: Attest build provenance (retry) + if: steps.attest_provenance.outcome == 'failure' + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ghcr.io/vig-os/devcontainer subject-digest: ${{ steps.digest.outputs.digest }} push-to-registry: true - - name: Attest SBOM - uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3 + - name: Attest SBOM (attempt 1) + id: attest_sbom + continue-on-error: true + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-name: ghcr.io/vig-os/devcontainer + subject-digest: ${{ steps.digest.outputs.digest }} + sbom-path: /tmp/sbom.spdx.json + push-to-registry: true + + - name: Wait before retrying SBOM attestation + if: steps.attest_sbom.outcome == 'failure' + run: sleep 30 + + - name: Attest SBOM (retry) + if: steps.attest_sbom.outcome == 'failure' + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ghcr.io/vig-os/devcontainer subject-digest: ${{ steps.digest.outputs.digest }} sbom-path: /tmp/sbom.spdx.json push-to-registry: true + - name: Publish final GitHub Release + if: needs.validate.outputs.release_kind == 'final' + env: + PUBLISH_VERSION: ${{ needs.validate.outputs.publish_version }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + if retry --retries 2 --backoff 5 --max-backoff 20 -- gh release view "$PUBLISH_VERSION" >/dev/null 2>&1; then + echo "ERROR: GitHub Release already exists for tag $PUBLISH_VERSION" + exit 1 + fi + + retry --retries 3 --backoff 5 --max-backoff 30 -- gh release create "$PUBLISH_VERSION" \ + --title "$PUBLISH_VERSION" \ + --notes-file /tmp/github-release-notes.md \ + --verify-tag || { + if retry --retries 2 --backoff 5 --max-backoff 20 -- gh release view "$PUBLISH_VERSION" >/dev/null 2>&1; then + echo "GitHub Release already present after create attempt: $PUBLISH_VERSION" + else + exit 1 + fi + } + + echo "✓ GitHub Release published: $PUBLISH_VERSION" + - name: Summary env: BASE_VERSION: ${{ needs.validate.outputs.version }} @@ -852,49 +1139,122 @@ jobs: echo "" echo "Next steps:" if [ "$RELEASE_KIND" = "candidate" ]; then - echo " 1. Verify smoke-test repo dispatch run status before final release" - echo " 2. If smoke tests pass, run: just finalize-release $BASE_VERSION" + echo " 1. Smoke-test dispatch runs in the smoke-test job." + echo " 2. If downstream pre-release passes, run: just finalize-release $BASE_VERSION" else - echo " 1. Merge release PR to main (triggers sync-main-to-dev workflow automatically)" + echo " 1. Smoke-test dispatch runs in the smoke-test job." + echo " 2. Merge release PR to main (triggers sync-main-to-dev workflow automatically)" fi + smoke-test: + name: Smoke-Test Dispatch + needs: [validate, finalize, publish] + runs-on: ubuntu-22.04 + timeout-minutes: 35 + if: ${{ github.event.inputs.dry-run != 'true' }} + permissions: + contents: read + issues: write + steps: - name: Generate smoke-test dispatch token - if: needs.validate.outputs.release_kind == 'candidate' id: smoke-app-token - continue-on-error: true - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} owner: vig-os repositories: devcontainer-smoke-test + # Local actions from ./.github/actions/* require repository checkout in this job workspace. + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Trigger smoke-test repository dispatch - if: needs.validate.outputs.release_kind == 'candidate' && steps.smoke-app-token.outcome == 'success' - continue-on-error: true env: GH_TOKEN: ${{ steps.smoke-app-token.outputs.token }} - RC_TAG: ${{ needs.validate.outputs.publish_version }} + RELEASE_TAG: ${{ needs.validate.outputs.publish_version }} + RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + SOURCE_REPO: ${{ github.repository }} + SOURCE_WORKFLOW: ${{ github.workflow }} + SOURCE_RUN_ID: ${{ github.run_id }} + SOURCE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + SOURCE_SHA: ${{ needs.finalize.outputs.finalize_sha }} + CORRELATION_ID: ${{ github.repository }}:${{ github.run_id }}:${{ needs.validate.outputs.publish_version }} run: | set -euo pipefail - gh api repos/vig-os/devcontainer-smoke-test/dispatches \ - -f event_type=smoke-test-trigger \ - -f "client_payload[tag]=$RC_TAG" - echo "✓ Triggered smoke-test dispatch for RC tag: $RC_TAG" + retry --retries 3 --backoff 10 --max-backoff 10 -- \ + gh api repos/vig-os/devcontainer-smoke-test/dispatches \ + -f event_type=smoke-test-trigger \ + -f "client_payload[tag]=$RELEASE_TAG" \ + -f "client_payload[release_kind]=$RELEASE_KIND" \ + -f "client_payload[event_type]=smoke-test-trigger" \ + -f "client_payload[source_repo]=$SOURCE_REPO" \ + -f "client_payload[source_workflow]=$SOURCE_WORKFLOW" \ + -f "client_payload[source_run_id]=$SOURCE_RUN_ID" \ + -f "client_payload[source_run_url]=$SOURCE_RUN_URL" \ + -f "client_payload[source_sha]=$SOURCE_SHA" \ + -f "client_payload[correlation_id]=$CORRELATION_ID" + echo "✓ Triggered smoke-test dispatch for release tag: $RELEASE_TAG" - - name: Warn if smoke-test dispatch token generation failed - if: needs.validate.outputs.release_kind == 'candidate' && steps.smoke-app-token.outcome != 'success' + - name: Summary + env: + RELEASE_TAG: ${{ needs.validate.outputs.publish_version }} run: | - echo "WARNING: Could not generate cross-repo token for smoke-test dispatch." - echo "Install RELEASE_APP on vig-os/devcontainer-smoke-test with contents:read permission." + echo "✓ Triggered smoke-test dispatch for release tag: $RELEASE_TAG" + echo "Downstream validation is asynchronous; verify in devcontainer-smoke-test." + + - name: Create issue for smoke dispatch failure + if: ${{ failure() }} + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const publishVersion = '${{ needs.validate.outputs.publish_version }}'; + const releaseKind = '${{ needs.validate.outputs.release_kind }}'; + const workflowUrl = `${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`; + const title = `Release ${publishVersion} smoke dispatch failed`; + const body = ` + Release ${publishVersion} (${releaseKind}) failed while triggering downstream smoke-test dispatch. + + **Workflow Run:** [View logs](${workflowUrl}) + **Failed Job:** smoke-test + + **Important:** + - Upstream publish already completed before this dispatch step. + - Published artifacts (GHCR images, tag, signatures, attestations, and final GitHub Release if applicable) are intentionally left intact. + - No branch reset or tag deletion is performed for dispatch-only failures. + + **Next Steps:** + 1. Review smoke-test dispatch logs in this workflow run. + 2. Validate token/repository_dispatch permissions for \`vig-os/devcontainer-smoke-test\`. + 3. Re-trigger dispatch after fixing the root cause. + `; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: ['bug', 'area:ci'] + }); + + core.info(`Created issue: ${title}`); rollback: name: Rollback on Failure + # Rollback is intentionally scoped to pre-publish/publish failures. + # Dispatch-only smoke-test failures happen after publish and should not delete tags or reset branches. needs: [validate, finalize, build-and-test, publish] runs-on: ubuntu-22.04 timeout-minutes: 10 - if: failure() + if: ${{ always() && (needs.validate.result == 'failure' || needs.finalize.result == 'failure' || needs.build-and-test.result == 'failure' || needs.publish.result == 'failure') }} permissions: + contents: read # required by actions/checkout in rollback job issues: write # create failure issue # Branch rollback and tag deletion use the RELEASE_APP token (not GITHUB_TOKEN), # which has Contents read/write configured on the GitHub App. @@ -902,11 +1262,20 @@ jobs: steps: - name: Generate GitHub App Token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Rollback release branch id: rollback-branch continue-on-error: true @@ -917,7 +1286,7 @@ jobs: run: | set -euo pipefail echo "Rolling back release branch to pre-finalization state..." - gh api "repos/${{ github.repository }}/git/refs/heads/release/$VERSION" \ + retry --retries 3 --backoff 5 --max-backoff 30 -- gh api "repos/${{ github.repository }}/git/refs/heads/release/$VERSION" \ -X PATCH \ -f sha="$PRE_SHA" \ -F force=true @@ -932,16 +1301,16 @@ jobs: run: | set -euo pipefail TAG="$PUBLISH_VERSION" - if gh api "repos/${{ github.repository }}/git/refs/tags/$TAG" >/dev/null 2>&1; then + if retry --retries 2 --backoff 5 --max-backoff 20 -- gh api "repos/${{ github.repository }}/git/refs/tags/$TAG" >/dev/null 2>&1; then echo "Deleting remote tag: $TAG" - gh api "repos/${{ github.repository }}/git/refs/tags/$TAG" -X DELETE + retry --retries 3 --backoff 5 --max-backoff 30 -- gh api "repos/${{ github.repository }}/git/refs/tags/$TAG" -X DELETE echo "✓ Tag deleted" else echo "Tag does not exist on remote (not created)" fi - name: Create failure issue - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const publishVersion = '${{ needs.validate.outputs.publish_version }}'; diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 042bacde..f1524863 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -44,7 +44,7 @@ jobs: publish_results: true - name: Upload SARIF to GitHub Security - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: sarif_file: results.sarif category: 'scorecard' diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 00513184..11afb43e 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: dev @@ -66,6 +66,14 @@ jobs: output-type: tar output-file: /tmp/image.tar + - name: Upload image artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: container-image-${{ steps.version.outputs.version }}-amd64 + path: /tmp/image.tar + retention-days: 1 + compression-level: 0 # Image is already compressed; avoid double-compression overhead + security-scan: name: Security Scan needs: build-image @@ -77,10 +85,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: container-image-${{ needs.build-image.outputs.version }}-amd64 path: /tmp @@ -114,14 +122,14 @@ jobs: trivyignores: '.trivyignore' - name: Upload SBOM artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: sbom-${{ needs.build-image.outputs.version }}-amd64 path: sbom-cyclonedx.json retention-days: 90 - name: Upload SARIF to GitHub Security - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: sarif_file: trivy-results.sarif category: 'container-image-scheduled' diff --git a/.github/workflows/sync-issues.yml b/.github/workflows/sync-issues.yml index affe2a9f..2379242b 100644 --- a/.github/workflows/sync-issues.yml +++ b/.github/workflows/sync-issues.yml @@ -52,7 +52,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} @@ -63,9 +63,15 @@ jobs: ref: ${{ github.event.inputs.target-branch || 'dev' }} persist-credentials: false + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Restore sync state (last synced timestamp) id: restore-state - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: .sync-state key: sync-issues-state-${{ github.repository }} @@ -83,10 +89,12 @@ jobs: sleep 2 # Try to delete cache using GitHub API CACHE_KEY="sync-issues-state-${{ github.repository }}" - CACHE_ID=$(gh api repos/${{ github.repository }}/actions/caches --jq ".actions_caches[] | select(.key == \"$CACHE_KEY\") | .id" | head -1) + CACHE_ID=$(retry --retries 3 --backoff 3 --max-backoff 15 -- \ + gh api repos/${{ github.repository }}/actions/caches --jq ".actions_caches[] | select(.key == \"$CACHE_KEY\") | .id" | head -1) if [ -n "$CACHE_ID" ]; then echo "Found cache ID: $CACHE_ID, attempting deletion..." - gh api repos/${{ github.repository }}/actions/caches/$CACHE_ID -X DELETE && echo "Cache deleted successfully" || echo "Cache deletion failed (may be locked or already deleted)" + retry --retries 3 --backoff 3 --max-backoff 15 -- \ + gh api repos/${{ github.repository }}/actions/caches/$CACHE_ID -X DELETE && echo "Cache deleted successfully" || echo "Cache deletion failed (may be locked or already deleted)" else echo "No cache found with key: $CACHE_KEY (this is OK for first run)" fi @@ -118,7 +126,7 @@ jobs: - name: Save sync state if: always() - uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: .sync-state key: sync-issues-state-${{ github.repository }} diff --git a/.github/workflows/sync-main-to-dev.yml b/.github/workflows/sync-main-to-dev.yml index 5af05f2d..653afe95 100644 --- a/.github/workflows/sync-main-to-dev.yml +++ b/.github/workflows/sync-main-to-dev.yml @@ -7,10 +7,10 @@ # Pipeline: # check - (early exit if dev already contains all main commits) # sync - clean up stale sync branches -# - trial merge to detect conflicts +# - merge-tree (in-memory) merge to detect conflicts # - create chore/sync-main-to-dev-- branch via API -# - open PR (auto-merge enabled, or labelled "merge-conflict" with -# resolution instructions when conflicts exist) +# - open PR; enable auto-merge when clean, or label "merge-conflict" with +# resolution instructions when conflicts exist # # Auth: Two GitHub App tokens: # - COMMIT_APP_* for git/ref operations (least-privilege commit identity) @@ -49,11 +49,17 @@ jobs: with: fetch-depth: 0 + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Check if dev is up to date with main id: check run: | set -euo pipefail - git fetch origin main dev + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev if ! git show-ref --verify --quiet refs/remotes/origin/main; then echo "Error: remote branch 'origin/main' not found after fetch." exit 1 @@ -73,7 +79,7 @@ jobs: sync: name: Merge main into dev via PR - needs: check + needs: [check] if: needs.check.outputs.up_to_date != 'true' runs-on: ubuntu-22.04 timeout-minutes: 10 @@ -87,7 +93,7 @@ jobs: steps: - name: Generate Commit App Token id: commit-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} @@ -99,11 +105,17 @@ jobs: fetch-depth: 0 token: ${{ steps.commit-app-token.outputs.token }} + - name: Set up environment + uses: ./.github/actions/setup-env + with: + install-python: 'false' + install-just: 'false' + - name: Re-check if dev is still behind main id: recheck run: | set -euo pipefail - git fetch origin main dev + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev BEHIND=$(git rev-list --count origin/main ^origin/dev) if [ "${BEHIND}" = "0" ]; then echo "up_to_date=true" >> "$GITHUB_OUTPUT" @@ -116,7 +128,7 @@ jobs: - name: Generate Release App Token if: steps.recheck.outputs.up_to_date != 'true' id: release-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} @@ -128,7 +140,8 @@ jobs: GH_TOKEN: ${{ steps.release-app-token.outputs.token }} run: | set -euo pipefail - OPEN=$(gh pr list --base dev --state open --limit 200 \ + OPEN=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --state open --limit 200 \ --json headRefName \ --jq '[.[] | select(.headRefName | startswith("chore/sync-main-to-dev-"))] | length') echo "count=${OPEN}" >> "$GITHUB_OUTPUT" @@ -142,13 +155,16 @@ jobs: GH_TOKEN: ${{ steps.commit-app-token.outputs.token }} run: | set -euo pipefail - REFS=$(gh api --paginate "repos/${{ github.repository }}/git/matching-refs/heads/chore/sync-main-to-dev-" \ + REFS=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api --paginate "repos/${{ github.repository }}/git/matching-refs/heads/chore/sync-main-to-dev-" \ --jq '.[].ref' | sed 's|refs/heads/||') || true for branch in ${REFS}; do - HAS_PR=$(GH_TOKEN="${{ steps.release-app-token.outputs.token }}" gh pr list --base dev --head "${branch}" \ + HAS_PR=$(GH_TOKEN="${{ steps.release-app-token.outputs.token }}" retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --head "${branch}" \ --state open --json number --jq 'length') if [ "${HAS_PR}" = "0" ]; then - gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${branch}" 2>/dev/null || true + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${branch}" 2>/dev/null || true echo "Deleted stale sync branch: ${branch}" fi done @@ -158,13 +174,22 @@ jobs: id: merge-check run: | set -euo pipefail - git fetch origin main - if git merge --no-commit --no-ff origin/main 2>/dev/null; then + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev + if merge_out="$(git merge-tree --write-tree origin/dev origin/main 2>&1)"; then echo "conflict=false" >> "$GITHUB_OUTPUT" + echo "Merge-tree check: no conflicts between origin/dev and origin/main." else - echo "conflict=true" >> "$GITHUB_OUTPUT" + merge_rc=$? + if [ "${merge_rc}" -eq 1 ]; then + echo "conflict=true" >> "$GITHUB_OUTPUT" + echo "::warning::Merge conflicts detected between origin/dev and origin/main." + echo "${merge_out}" + else + echo "::error::git merge-tree failed with exit code ${merge_rc}" + echo "${merge_out}" + exit "${merge_rc}" + fi fi - git merge --abort 2>/dev/null || true - name: Create sync branch from main if: steps.existing-pr.outputs.count == '0' && steps.recheck.outputs.up_to_date != 'true' @@ -173,9 +198,17 @@ jobs: run: | set -euo pipefail MAIN_SHA=$(git rev-parse origin/main) - gh api "repos/${{ github.repository }}/git/refs" \ + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api "repos/${{ github.repository }}/git/refs" \ -f ref="refs/heads/${SYNC_BRANCH}" \ - -f sha="${MAIN_SHA}" + -f sha="${MAIN_SHA}" || { + if retry --retries 2 --backoff 5 --max-backoff 20 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/${SYNC_BRANCH}" >/dev/null 2>&1; then + echo "Sync branch already exists: ${SYNC_BRANCH}" + else + exit 1 + fi + } echo "Sync branch ${SYNC_BRANCH} created from main at ${MAIN_SHA}" - name: Create PR @@ -211,14 +244,22 @@ jobs: BODY="Syncs \`dev\` with \`main\` (sync-main-to-dev workflow)." fi - PR_URL=$(gh pr create --base dev --head "${SYNC_BRANCH}" \ - --title "${TITLE}" --body "${BODY}") + EXISTING_PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --head "${SYNC_BRANCH}" --state open --json url --jq '.[0].url // empty') + if [ -n "${EXISTING_PR_URL}" ]; then + PR_URL="${EXISTING_PR_URL}" + else + PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr create --base dev --head "${SYNC_BRANCH}" \ + --title "${TITLE}" --body "${BODY}") + fi echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT" echo "Created PR: ${PR_URL}" if [ "${CONFLICT}" = "true" ]; then gh label create "merge-conflict" --color "B60205" --force 2>/dev/null || true - gh pr edit "${SYNC_BRANCH}" --add-label "merge-conflict" || \ + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr edit "${SYNC_BRANCH}" --add-label "merge-conflict" || \ echo "Warning: failed to add merge-conflict label." fi @@ -231,5 +272,6 @@ jobs: PR_URL: ${{ steps.create-pr.outputs.pr_url }} run: | set -euo pipefail - gh pr merge "${PR_URL}" --auto --merge || \ + retry --retries 2 --backoff 5 --max-backoff 20 -- \ + gh pr merge "${PR_URL}" --auto --merge || \ echo "Warning: could not enable auto-merge (may require branch protection settings)" diff --git a/.trivyignore b/.trivyignore index 407498ee..1fa18d95 100644 --- a/.trivyignore +++ b/.trivyignore @@ -105,6 +105,17 @@ CVE-2025-61730 Expiration: 2026-04-15 CVE-2025-15558 +# CVE-2026-33186: gRPC-Go authorization bypass via missing leading slash in :path +# Risk Assessment: LOW (devcontainer context) +# - CRITICAL severity in google.golang.org/grpc v1.79.2 embedded in gh v2.88.1 +# - Upstream gh latest release still ships grpc v1.79.2 in go.mod +# - Fix is available in grpc v1.79.3 and requires upstream gh release/rebuild +# - gh is used as a client to GitHub APIs in trusted workflows in this image +# - Temporary exception to keep blocking policy for other HIGH/CRITICAL findings +# - Tracking: https://github.com/vig-os/devcontainer/issues/361 +Expiration: 2026-05-15 +CVE-2026-33186 + # CVE-2026-31812: quinn-proto unauthenticated remote DoS via QUIC transport parameter panic # Risk Assessment: LOW (devcontainer context) # - HIGH severity in quinn-proto v0.11.12 embedded in uv/uvx Rust binaries diff --git a/CHANGELOG.md b/CHANGELOG.md index b66fd4c4..f80aaa16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,184 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.1] - TBD + +### Added + +- **Split downstream release workflow with project-owned extension hook** ([#326](https://github.com/vig-os/devcontainer/issues/326)) + - Add local `workflow_call` release phases (`release-core.yml`, `release-publish.yml`) and a lightweight `release.yml` orchestrator in `assets/workspace/.github/workflows/` + - Add `release_kind` support with candidate mode (`X.Y.Z-rcN`) and final mode (`X.Y.Z`) in downstream release workflows + - Candidate mode now auto-computes the next RC tag, skips CHANGELOG finalization/sync-issues, and publishes a GitHub pre-release + - Add project-owned `release-extension.yml` stub and preserve it during `init-workspace.sh --force` upgrades + - Add `validate-contract` composite action for single-source contract version validation + - Add downstream release contract documentation and GHCR extension example in `docs/DOWNSTREAM_RELEASE.md` +- **`jq` in devcontainer image** ([#425](https://github.com/vig-os/devcontainer/issues/425)) + - Install the `jq` CLI in the GHCR image so containerized workflows (e.g. `release-core` validate / downstream Release Core) can pipe JSON through `jq` + +### Changed + +- **Dependabot dependency update batch** ([#302](https://github.com/vig-os/devcontainer/pull/302), [#303](https://github.com/vig-os/devcontainer/pull/303), [#305](https://github.com/vig-os/devcontainer/pull/305), [#306](https://github.com/vig-os/devcontainer/pull/306), [#307](https://github.com/vig-os/devcontainer/pull/307), [#308](https://github.com/vig-os/devcontainer/pull/308), [#309](https://github.com/vig-os/devcontainer/pull/309)) + - Bump `@devcontainers/cli` from `0.81.1` to `0.84.0` and `bats-assert` from `v2.2.0` to `v2.2.4` + - Bump GitHub Actions: `actions/download-artifact` (`4.3.0` -> `8.0.1`), `actions/github-script` (`7.1.0` -> `8.0.0`), `actions/attest-build-provenance` (`3.0.0` -> `4.1.0`), `actions/checkout` (`4.3.1` -> `6.0.2`) + - Bump release workflow action pins: `sigstore/cosign-installer` (`4.0.0` -> `4.1.0`) and `anchore/sbom-action` (`0.22.2` -> `0.23.1`) +- **Dependabot dependency update batch** ([#314](https://github.com/vig-os/devcontainer/pull/314), [#315](https://github.com/vig-os/devcontainer/pull/315), [#316](https://github.com/vig-os/devcontainer/pull/316), [#317](https://github.com/vig-os/devcontainer/pull/317)) + - Bump GitHub Actions: `actions/attest-sbom` (`3.0.0` -> `4.0.0`), `actions/upload-artifact` (`4.6.2` -> `7.0.0`), `actions/create-github-app-token` (`2.2.1` -> `3.0.0`) + - Bump `docker/login-action` from `3.7.0` to `4.0.0` + - Bump `just` minor version from `1.46` to `1.47` +- **Node24-ready GitHub Actions pin refresh for shared composite actions** ([#321](https://github.com/vig-os/devcontainer/issues/321)) + - Update Docker build path pins in `build-image` (`docker/setup-buildx-action`, `docker/metadata-action`, `docker/build-push-action`) to Node24-compatible releases + - Set `setup-env` default Node runtime to `24` and upgrade `actions/setup-node` + - Align test composite actions with newer pins (`actions/checkout`, `actions/cache`, `actions/upload-artifact`) +- **Smoke-test dispatch payload now carries source run traceability metadata** ([#289](https://github.com/vig-os/devcontainer/issues/289)) + - Candidate release dispatches now include source repo/workflow/run/SHA metadata plus a deterministic `correlation_id` + - Smoke-test dispatch receiver logs normalized source context, derives source run URL when possible, and writes it to workflow summary output + - Release-cycle docs now define required vs optional dispatch payload keys and the future callback contract path for `publish-candidate` +- **Smoke-test repository dispatch now runs for final releases too** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - `release.yml` now triggers the existing smoke-test dispatch contract for both `candidate` and `final` release kinds + - Final release summaries and release-cycle documentation now reflect dispatch behavior for both release modes +- **Workspace CI templates now use a single container-based workflow** ([#327](https://github.com/vig-os/devcontainer/issues/327)) + - Consolidate `assets/workspace/.github/workflows/ci.yml` as the canonical CI workflow and remove the obsolete `ci-container.yml` template + - Extract reusable `assets/workspace/.github/actions/resolve-image` and run workspace release tests in the same containerized workflow model + - Update smoke-test and release-cycle documentation to reference the single CI workflow contract +- **Final release now requires downstream RC pre-release gate** ([#331](https://github.com/vig-os/devcontainer/issues/331)) + - Add upstream final-release validation that requires a downstream GitHub pre-release for the latest published RC tag + - Move smoke-test dispatch to a dedicated release job and include `release_kind` in the dispatch payload + - Add downstream `repository-dispatch.yml` template that runs smoke tests and creates pre-release/final release artifacts +- **Ship changelog into workspace payload and smoke-test deploy root** ([#333](https://github.com/vig-os/devcontainer/issues/333)) + - Sync canonical `CHANGELOG.md` into both workspace root and `.devcontainer/` template paths + - Smoke-test dispatch now copies `.devcontainer/CHANGELOG.md` to repository root so deploy output keeps a root changelog +- **Final release now publishes a GitHub Release with finalized notes** ([#310](https://github.com/vig-os/devcontainer/issues/310)) + - Add a final-only publish step in `.github/workflows/release.yml` that creates a GitHub Release for `X.Y.Z` + - Source GitHub Release notes from the finalized `CHANGELOG.md` section and fail the run if notes extraction or release publishing fails +- **Release dispatch and publish ordering hardened for 0.3.1** ([#336](https://github.com/vig-os/devcontainer/issues/336)) + - Make smoke-test dispatch fire-and-forget in `.github/workflows/release.yml` and decouple rollback from downstream completion timing + - Add bounded retries to the final-release downstream RC pre-release gate API check + - Move final GitHub Release creation to the end of publish so artifact publication/signing completes before release object creation + - Add concurrency control to `assets/smoke-test/.github/workflows/repository-dispatch.yml` to prevent overlapping dispatch races + - Handle smoke-test dispatch failures with a targeted issue while avoiding destructive rollback after publish artifacts are already released +- **Redesigned smoke-test dispatch release orchestration** ([#358](https://github.com/vig-os/devcontainer/issues/358)) + - Replace premature `publish-release` behavior with full downstream orchestration: deploy-to-dev merge gate, `prepare-release.yml`, release PR readiness/approval, and `release.yml` dispatch polling + - Add upstream failure issue reporting with job-phase results and cleanup guidance when dispatch orchestration fails +- **Smoke-test release orchestration now runs as two phases** ([#402](https://github.com/vig-os/devcontainer/issues/402)) + - Keep `repository-dispatch.yml` focused on deploy/prepare/release-PR readiness and move release dispatch to a dedicated merged-PR workflow (`on-release-pr-merge.yml`) + - Add release-kind labeling and auto-merge enablement for release PRs, and keep upstream failure notifications in both phases + - Remove release-branch upstream `CHANGELOG.md` sync from `repository-dispatch.yml` (previously added in [#358](https://github.com/vig-os/devcontainer/issues/358)) +- **Dependabot dependency update batch** ([#414](https://github.com/vig-os/devcontainer/pull/414)) + - Bump `github/codeql-action` from `4.32.6` to `4.34.1` and `anchore/sbom-action` from `0.23.1` to `0.24.0` + - Bump `actions/cache` restore/save pins from `5.0.3` to `5.0.4` in `sync-issues.yml` +- **Dependabot dependency update batch** ([#413](https://github.com/vig-os/devcontainer/pull/413)) + - Bump `@devcontainers/cli` from `0.84.0` to `0.84.1` + +### Fixed + +- **Smoke-test deploy restores workspace CHANGELOG for prepare-release** ([#417](https://github.com/vig-os/devcontainer/issues/417)) + - Add `prepare-changelog unprepare` to rename the top `## [semver] - …` heading to `## Unreleased` + - `init-workspace.sh --smoke-test` copies `.devcontainer/CHANGELOG.md` into workspace `CHANGELOG.md` and runs unprepare; remove duplicate remap from smoke-test dispatch workflow +- **Release app permission docs now include downstream workflow dispatch requirements** ([#397](https://github.com/vig-os/devcontainer/issues/397)) + - Update `docs/RELEASE_CYCLE.md` to require `Actions` read/write for `RELEASE_APP` on the validation repository + - Clarify this is required so downstream `repository-dispatch.yml` can trigger release orchestration workflows via `workflow_dispatch` +- **Smoke-test dispatch no longer fails on release PR self-approval** ([#402](https://github.com/vig-os/devcontainer/issues/402)) + - Remove bot self-approval from `repository-dispatch.yml` and replace with release-kind labeling plus auto-merge enablement + - Remove in-job polling for release PR merge and downstream release execution from phase 1 orchestration + - Phase 2 (`on-release-pr-merge.yml`) fails validation unless the merged release PR has `release-kind:final` or `release-kind:candidate` +- **Sync-main-to-dev PRs now trigger CI reliably in downstream repos** ([#398](https://github.com/vig-os/devcontainer/issues/398)) + - Replace API-based sync branch creation with `git push` in `assets/workspace/.github/workflows/sync-main-to-dev.yml` +- **Sync-main-to-dev no longer dispatches CI via workflow_dispatch** ([#405](https://github.com/vig-os/devcontainer/issues/405)) + - `workflow_dispatch` runs are omitted from the PR status check rollup, so they do not satisfy branch protection on the sync PR + - Remove the post-PR `gh workflow run ci.yml` step and drop `actions: write` from the sync job in `.github/workflows/sync-main-to-dev.yml` and `assets/workspace/.github/workflows/sync-main-to-dev.yml` +- **Sync-main-to-dev conflict detection uses merge-tree** ([#410](https://github.com/vig-os/devcontainer/issues/410)) + - Replace working-tree trial merge with `git merge-tree --write-tree` so clean merges are not mislabeled as conflicts + - Enable auto-merge when dev merges cleanly with main; print merge-tree output on conflicts; fail the step on unexpected errors +- **Smoke-test release phase 2 branch-not-found failure** ([#419](https://github.com/vig-os/devcontainer/issues/419)) + - Merge phase 2 (`on-release-pr-merge.yml`) back into `repository-dispatch.yml` so the release runs while `release/` still exists, matching the normal release flow + - Remove `on-release-pr-merge.yml` from the smoke-test template + +- **Release finalization now commits generated docs and refreshes PR content** ([#300](https://github.com/vig-os/devcontainer/issues/300)) + - Final release automation regenerates docs before committing so pre-commit `generate-docs` does not fail CI with tracked file diffs + - Release PR body is refreshed from finalized `CHANGELOG.md` +- **Release attestation warnings reduced by granting artifact metadata permission** ([#348](https://github.com/vig-os/devcontainer/issues/348)) + - Add `artifact-metadata: write` to the release publish job so attestation steps can persist metadata storage records + - Keep `actions/attest`-based SBOM attestation path and remove missing-permission warnings from publish runs +- **Smoke-test dispatch deploy now repairs workspace ownership before changelog copy** ([#352](https://github.com/vig-os/devcontainer/issues/352)) + - Add a write probe and conditional `sudo chown -R` in `assets/smoke-test/.github/workflows/repository-dispatch.yml` after installer execution + - Prevent `Permission denied` failures when copying `.devcontainer/CHANGELOG.md` to repository root in GitHub-hosted runner jobs +- **Smoke-test release lookup no longer treats missing tags as existing releases** ([#355](https://github.com/vig-os/devcontainer/issues/355)) + - Change `assets/smoke-test/.github/workflows/repository-dispatch.yml` to branch on `gh api` exit status when querying `releases/tags/` + - Ensure missing release tags follow the create path instead of failing with `prerelease=null` mismatch +- **Bounded retry added for network-dependent setup and prepare-release calls** ([#357](https://github.com/vig-os/devcontainer/issues/357)) + - Replace shell-based retry helper with pure Python `retry` CLI in `vig-utils` (`packages/vig-utils/src/vig_utils/retry.py`) + - Update this repository CI workflows to call `uv run retry` after `setup-env` dependency sync + - Update downstream workflow templates to call `retry` directly in devcontainer jobs and remove `source` lines + - Ensure downstream containerized jobs resolve image tags from `.vig-os` instead of hardcoded `latest` + - Bundle idempotency guards for branch/PR/tag/release creation paths to keep retried network calls safe on reruns + - Remove synced `retry.sh` artifacts and BATS retry tests in favor of `vig-utils` pytest coverage +- **Release workflow no longer fails when retry tooling is unavailable** ([#365](https://github.com/vig-os/devcontainer/issues/365)) + - Extend `.github/actions/setup-env/action.yml` with a reusable `retry` shell function exported via `BASH_ENV` as the retry single source of truth + - Add `setup-env` input support for uv-only usage by allowing Python setup to be disabled when jobs only need retry tooling + - Switch release workflow retry calls from `uv run retry` to shared `retry` and remove duplicated inline retry implementations +- **Upstream sync workflows no longer depend on pre-published GHCR image tags** ([#367](https://github.com/vig-os/devcontainer/issues/367)) + - Remove upstream `.vig-os` files at repository root and `assets/smoke-test/` to eliminate downstream-only configuration from upstream CI + - Refactor `.github/workflows/sync-issues.yml` and `.github/workflows/sync-main-to-dev.yml` to run natively on runners via `./.github/actions/setup-env` instead of `resolve-image` + `container` +- **Release test-image setup now recovers from uv sync crashes** ([#370](https://github.com/vig-os/devcontainer/issues/370)) + - Harden `.github/actions/setup-env/action.yml` to retry `uv sync --frozen --all-extras` once after clearing uv cache and removing stale `.venv` + - Prevent repeat release test failures when `setup-env` is executed multiple times in the same job +- **Release setup-env no longer self-sources retry helper via BASH_ENV** ([#374](https://github.com/vig-os/devcontainer/issues/374)) + - Guard the retry-helper merge logic in `.github/actions/setup-env/action.yml` to skip merging when `PREV_BASH_ENV` already equals `RETRY_HELPER` + - Prevent infinite `source` recursion and exit 139 crashes when `setup-env` is invoked multiple times in one job +- **Smoke-test dispatch now checks out repository before local setup action** ([#376](https://github.com/vig-os/devcontainer/issues/376)) + - Add `actions/checkout` to the `smoke-test` job in `.github/workflows/release.yml` before invoking `./.github/actions/setup-env` + - Prevent dispatch failures caused by missing local action metadata (`action.yml`) in a fresh job workspace +- **Workspace resolve-image jobs now checkout local action metadata** ([#380](https://github.com/vig-os/devcontainer/issues/380)) + - Update `sparse-checkout` in workspace `resolve-image` jobs to include `.github/actions/resolve-image` in addition to `.vig-os` + - Prevent CI failures in downstream deploy PRs where local composite actions were missing from sparse checkout +- **Smoke-test dispatch gh jobs now set explicit repo context** ([#386](https://github.com/vig-os/devcontainer/issues/386)) + - Add job-level `GH_REPO: ${{ github.repository }}` to `cleanup-release`, `trigger-prepare-release`, `ready-release-pr`, and `trigger-release` in `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Prevent `gh` CLI failures (`fatal: not a git repository`) in runner jobs that do not perform `actions/checkout` +- **Smoke-test release orchestration now validates workflow contract before dispatch** ([#389](https://github.com/vig-os/devcontainer/issues/389)) + - Add a preflight check that verifies `prepare-release.yml` and `release.yml` are resolvable on dispatch ref `dev` before downstream orchestration starts + - Dispatch and polling now use explicit ref/branch context (`--ref dev` / `--branch dev`) to avoid default-branch workflow registry drift and `404 workflow not found` failures +- **Smoke-test preflight now uses gh CLI ref-compatible workflow validation** ([#392](https://github.com/vig-os/devcontainer/issues/392)) + - Update `assets/smoke-test/.github/workflows/repository-dispatch.yml` preflight checks to call `gh workflow view` with `--yaml` when `--ref` is set + - Prevent false preflight failures caused by newer GitHub CLI argument validation before `prepare-release` dispatch +- **Downstream release workflow templates hardened for smoke-test orchestration** ([#394](https://github.com/vig-os/devcontainer/issues/394)) + - Add missing `git config --global --add safe.directory "$GITHUB_WORKSPACE"` in containerized release and sync jobs that run git after checkout + - Decouple `release.yml` rollback container startup from `needs.core.outputs.image_tag` by resolving the image in a dedicated `resolve-image` job + - Add explicit release caller/reusable workflow permissions for `actions` and `pull-requests` operations, and update dispatch header comments to reference only current CI workflows +- **Workspace containerized workflows now pin bash for run steps** ([#395](https://github.com/vig-os/devcontainer/issues/395)) + - Set `defaults.run.shell: bash` in containerized workspace release and prepare jobs so `set -euo pipefail` scripts do not execute under POSIX `sh` + - Prevent downstream smoke-test failures caused by `set: Illegal option -o pipefail` in container jobs +- **Downstream release templates now require explicit app tokens for write paths** ([#400](https://github.com/vig-os/devcontainer/issues/400)) + - Update `assets/workspace/.github/workflows/prepare-release.yml`, `release-core.yml`, `release-publish.yml`, `release.yml`, and `sync-issues.yml` to remove `github.token` fallback from protected write operations + - Route protected branch/ref writes through Commit App tokens and release orchestration/issue operations through Release App tokens + - Document downstream token requirements in `docs/DOWNSTREAM_RELEASE.md` and `docs/CROSS_REPO_RELEASE_GATE.md` + - Use `github.token` specifically for Actions cache deletion in `sync-issues.yml` because that API path requires explicit `actions: write` job token scope + - Use Commit App credentials for rollback checkout in `release.yml` so rollback branch/tag writes can still bypass protected refs +- **setup-env retries uv install on transient GitHub Releases download failures** ([#407](https://github.com/vig-os/devcontainer/issues/407)) + - Add `continue-on-error` plus a delayed second attempt for `astral-sh/setup-uv` in `.github/actions/setup-env/action.yml` + - Reduce flaky release publish failures when GitHub CDN returns transient HTTP errors for uv release assets +- **Smoke-test deploy keeps workspace scaffold as root CHANGELOG** ([#403](https://github.com/vig-os/devcontainer/issues/403)) + - Stop overwriting `CHANGELOG.md` with a minimal stub in `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Require the workspace `CHANGELOG.md` from `init-workspace` so downstream `prepare-release` validation matches shipped layout + - When the first changelog section is `## [X.Y.Z] - …` (TBD or a release date), remap that top version header to `## Unreleased` so downstream `prepare-release` can run +- **Smoke-test dispatch release validate no longer runs docker inside devcontainer** ([#421](https://github.com/vig-os/devcontainer/issues/421)) + - Remove redundant `docker manifest inspect` step from `release-core.yml` validate job (container image is already proof of accessibility; `resolve-image` validates on the runner) + - Set `GH_REPO` for rollback `gh issue create` in workspace `release.yml` when git checkout is skipped +- **Container image tests expect current uv minor line** ([#423](https://github.com/vig-os/devcontainer/issues/423)) + - Update `tests/test_image.py` `EXPECTED_VERSIONS["uv"]` to match uv 0.11.x from the latest release install path in the image build +- **Container image tests expect current just minor line** ([#423](https://github.com/vig-os/devcontainer/issues/423)) + - Update `tests/test_image.py` `EXPECTED_VERSIONS["just"]` to match just 1.48.x from the latest release install path in the image build + +### Security + +- **Smoke-test dispatch workflow permissions now follow least privilege** ([#340](https://github.com/vig-os/devcontainer/issues/340)) + - Reduce `assets/smoke-test/.github/workflows/repository-dispatch.yml` workflow token permissions from write to read by default + - Grant `contents: write` only to `publish-release`, the single job that creates or edits GitHub Releases + ## [0.3.0](https://github.com/vig-os/devcontainer/releases/tag/0.3.0) - 2026-03-13 ### Added -- **Image tools** ([[#212](https://github.com/vig-os/devcontainer/issues/212)]) +- **Image tools** ([#212](https://github.com/vig-os/devcontainer/issues/212)) - Install rsync - **Preserve user-authored files during `--force` workspace upgrades** ([#212](https://github.com/vig-os/devcontainer/issues/212)) - `init-workspace --force` no longer overwrites `README.md`, `CHANGELOG.md`, `LICENSE`, `.github/CODEOWNERS`, or `justfile.project` diff --git a/Containerfile b/Containerfile index 74eac1a4..d1b725de 100644 --- a/Containerfile +++ b/Containerfile @@ -53,6 +53,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ git \ + jq \ openssh-client \ locales \ ca-certificates \ diff --git a/assets/init-workspace.sh b/assets/init-workspace.sh index 5bb126f3..f854566f 100755 --- a/assets/init-workspace.sh +++ b/assets/init-workspace.sh @@ -29,6 +29,7 @@ PRESERVE_FILES=( "CHANGELOG.md" "LICENSE" ".github/CODEOWNERS" + ".github/workflows/release-extension.yml" "justfile.project" ) @@ -227,6 +228,18 @@ if [[ "$SMOKE_TEST" == "true" ]]; then else echo "Warning: Smoke-test directory not found at $SMOKE_TEST_DIR" >&2 fi + + # Workspace scaffold CHANGELOG is empty; copy devcontainer changelog and + # rename top ## [version] - … to ## Unreleased for downstream prepare-release. + if [[ -f "$WORKSPACE_DIR/.devcontainer/CHANGELOG.md" ]]; then + echo "Syncing workspace CHANGELOG from .devcontainer/CHANGELOG.md (smoke-test)..." + cp "$WORKSPACE_DIR/.devcontainer/CHANGELOG.md" "$WORKSPACE_DIR/CHANGELOG.md" + if ! command -v prepare-changelog >/dev/null 2>&1; then + echo "ERROR: prepare-changelog not found (required for smoke-test CHANGELOG sync)" >&2 + exit 1 + fi + prepare-changelog unprepare "$WORKSPACE_DIR/CHANGELOG.md" + fi else # Build exclude list for preserved files that already exist EXCLUDE_ARGS=() diff --git a/assets/smoke-test/.github/workflows/repository-dispatch.yml b/assets/smoke-test/.github/workflows/repository-dispatch.yml index e6c16b11..3d5e38af 100644 --- a/assets/smoke-test/.github/workflows/repository-dispatch.yml +++ b/assets/smoke-test/.github/workflows/repository-dispatch.yml @@ -5,11 +5,15 @@ name: Repository Dispatch Listener # - Deploy the requested tag into the smoke-test repo. # - Always create a `chore/deploy-` branch and PR to `dev`. # - Create signed deploy commits via `vig-os/commit-action`. -# - CI (ci.yml + ci-container.yml) triggers on the deploy PR. +# - CI (`ci.yml`) triggers on the deploy PR. # # Dispatch payload: # - Preferred: client_payload.tag -# - Optional: client_payload.event_type, client_payload.source_repo +# - Optional: client_payload.event_type, client_payload.release_kind, +# client_payload.source_repo, client_payload.source_workflow, +# client_payload.source_run_id, +# client_payload.source_run_url, client_payload.source_sha, +# client_payload.correlation_id # # NOTE: Changes to this template may require manual redeploy to # vig-os/devcontainer-smoke-test and promotion through PRs until merged to main. @@ -19,9 +23,16 @@ on: # yamllint disable-line rule:truthy types: - smoke-test-trigger +concurrency: + group: smoke-test-dispatch-${{ github.event.client_payload.tag || github.run_id }} + cancel-in-progress: false + permissions: contents: read +env: + WORKFLOW_REF: dev + jobs: validate: name: Validate dispatch payload @@ -29,6 +40,14 @@ jobs: timeout-minutes: 5 outputs: tag: ${{ steps.extract.outputs.tag }} + base_version: ${{ steps.extract.outputs.base_version }} + release_kind: ${{ steps.extract.outputs.release_kind }} + source_repo: ${{ steps.extract.outputs.source_repo }} + source_workflow: ${{ steps.extract.outputs.source_workflow }} + source_run_id: ${{ steps.extract.outputs.source_run_id }} + source_run_url: ${{ steps.extract.outputs.source_run_url }} + source_sha: ${{ steps.extract.outputs.source_sha }} + correlation_id: ${{ steps.extract.outputs.correlation_id }} steps: - name: Extract and validate tag id: extract @@ -36,13 +55,26 @@ jobs: EVENT_ACTION: ${{ github.event.action }} EVENT_TYPE: ${{ github.event.client_payload.event_type || '' }} TAG: ${{ github.event.client_payload.tag || '' }} + RELEASE_KIND: ${{ github.event.client_payload.release_kind || '' }} SOURCE_REPO: ${{ github.event.client_payload.source_repo || '' }} + SOURCE_WORKFLOW: ${{ github.event.client_payload.source_workflow || '' }} + SOURCE_RUN_ID: ${{ github.event.client_payload.source_run_id || '' }} + SOURCE_RUN_URL: ${{ github.event.client_payload.source_run_url || '' }} + SOURCE_SHA: ${{ github.event.client_payload.source_sha || '' }} + CORRELATION_ID: ${{ github.event.client_payload.correlation_id || '' }} + GITHUB_SERVER_URL: ${{ github.server_url }} run: | echo "repository_dispatch received" echo "action=${EVENT_ACTION}" echo "event_type=${EVENT_TYPE}" echo "tag=${TAG}" + echo "release_kind=${RELEASE_KIND}" echo "source_repo=${SOURCE_REPO}" + echo "source_workflow=${SOURCE_WORKFLOW}" + echo "source_run_id=${SOURCE_RUN_ID}" + echo "source_run_url=${SOURCE_RUN_URL}" + echo "source_sha=${SOURCE_SHA}" + echo "correlation_id=${CORRELATION_ID}" if [ -z "${TAG}" ]; then echo "ERROR: tag is required in github.event.client_payload" @@ -54,7 +86,45 @@ jobs: exit 1 fi + EFFECTIVE_RELEASE_KIND="${RELEASE_KIND}" + if [ -z "${EFFECTIVE_RELEASE_KIND}" ]; then + if printf '%s' "${TAG}" | grep -Eq -- '-rc[0-9]+$'; then + EFFECTIVE_RELEASE_KIND="candidate" + else + EFFECTIVE_RELEASE_KIND="final" + fi + fi + + if [ "${EFFECTIVE_RELEASE_KIND}" != "candidate" ] && [ "${EFFECTIVE_RELEASE_KIND}" != "final" ]; then + echo "ERROR: release_kind must be candidate or final (got '${EFFECTIVE_RELEASE_KIND}')" + exit 1 + fi + + if [ "${EFFECTIVE_RELEASE_KIND}" = "candidate" ] && ! printf '%s' "${TAG}" | grep -Eq -- '-rc[0-9]+$'; then + echo "ERROR: candidate release_kind requires RC tag format (expected X.Y.Z-rcN, got '${TAG}')" + exit 1 + fi + + if [ "${EFFECTIVE_RELEASE_KIND}" = "final" ] && printf '%s' "${TAG}" | grep -Eq -- '-rc[0-9]+$'; then + echo "ERROR: final release_kind requires final tag format (expected X.Y.Z, got '${TAG}')" + exit 1 + fi + + BASE_VERSION="$(printf '%s' "${TAG}" | sed 's/-rc[0-9]*$//')" + EFFECTIVE_SOURCE_RUN_URL="${SOURCE_RUN_URL}" + if [ -z "${EFFECTIVE_SOURCE_RUN_URL}" ] && [ -n "${SOURCE_REPO}" ] && [ -n "${SOURCE_RUN_ID}" ]; then + EFFECTIVE_SOURCE_RUN_URL="${GITHUB_SERVER_URL}/${SOURCE_REPO}/actions/runs/${SOURCE_RUN_ID}" + fi + echo "tag=${TAG}" >> "${GITHUB_OUTPUT}" + echo "base_version=${BASE_VERSION}" >> "${GITHUB_OUTPUT}" + echo "release_kind=${EFFECTIVE_RELEASE_KIND}" >> "${GITHUB_OUTPUT}" + echo "source_repo=${SOURCE_REPO}" >> "${GITHUB_OUTPUT}" + echo "source_workflow=${SOURCE_WORKFLOW}" >> "${GITHUB_OUTPUT}" + echo "source_run_id=${SOURCE_RUN_ID}" >> "${GITHUB_OUTPUT}" + echo "source_run_url=${EFFECTIVE_SOURCE_RUN_URL}" >> "${GITHUB_OUTPUT}" + echo "source_sha=${SOURCE_SHA}" >> "${GITHUB_OUTPUT}" + echo "correlation_id=${CORRELATION_ID}" >> "${GITHUB_OUTPUT}" deploy: name: Deploy tag and open PR to dev @@ -66,7 +136,7 @@ jobs: steps: - name: Generate release app token for PR/issue operations id: generate_release_token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} @@ -75,7 +145,7 @@ jobs: - name: Generate commit app token for signed commits id: generate_commit_token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} @@ -115,8 +185,61 @@ jobs: env: TAG: ${{ needs.validate.outputs.tag }} run: | - curl -sSf "https://raw.githubusercontent.com/vig-os/devcontainer/${TAG}/install.sh" \ - | bash -s -- --version "${TAG}" --smoke-test --force --docker . + set -euo pipefail + INSTALL_URL="https://raw.githubusercontent.com/vig-os/devcontainer/${TAG}/install.sh" + ATTEMPT=1 + MAX_ATTEMPTS=3 + until [ "${ATTEMPT}" -gt "${MAX_ATTEMPTS}" ]; do + if curl -sSf "${INSTALL_URL}" | bash -s -- --version "${TAG}" --smoke-test --force --docker .; then + break + fi + if [ "${ATTEMPT}" -eq "${MAX_ATTEMPTS}" ]; then + echo "ERROR: failed to download/install after ${MAX_ATTEMPTS} attempts: ${INSTALL_URL}" + exit 1 + fi + echo "Install attempt ${ATTEMPT}/${MAX_ATTEMPTS} failed; retrying in 5s..." + ATTEMPT=$((ATTEMPT + 1)) + sleep 5 + done + + # Docker-based install can leave bind-mounted files owned by root. + # Repair ownership only when required by local writability/readability probes. + NEEDS_CHOWN=false + if [ ! -r ".devcontainer/CHANGELOG.md" ]; then + NEEDS_CHOWN=true + fi + if [ -e "CHANGELOG.md" ] && [ ! -w "CHANGELOG.md" ]; then + NEEDS_CHOWN=true + fi + if [ -e "CHANGELOG.md" ] && [ ! -r "CHANGELOG.md" ]; then + NEEDS_CHOWN=true + fi + + if [ "${NEEDS_CHOWN}" = "true" ]; then + OWNER_UID_GID="$(id -u):$(id -g)" + if command -v sudo >/dev/null 2>&1; then + sudo chown -R "${OWNER_UID_GID}" . + else + chown -R "${OWNER_UID_GID}" . + fi + fi + + if [ ! -f ".devcontainer/CHANGELOG.md" ]; then + echo "ERROR: expected .devcontainer/CHANGELOG.md after install" + exit 1 + fi + if [ ! -r ".devcontainer/CHANGELOG.md" ]; then + echo "ERROR: .devcontainer/CHANGELOG.md is not readable after ownership repair" + exit 1 + fi + if [ ! -f "CHANGELOG.md" ]; then + echo "ERROR: expected CHANGELOG.md after install (workspace scaffold)" + exit 1 + fi + if [ ! -r "CHANGELOG.md" ]; then + echo "ERROR: CHANGELOG.md is not readable after ownership repair" + exit 1 + fi - name: Prepare deploy branch and metadata id: prepare_branch @@ -184,13 +307,413 @@ jobs: gh pr merge "${PR_URL}" --auto --merge || \ echo "Warning: could not enable auto-merge" + wait-deploy-merge: + name: Wait for deploy PR merge + runs-on: ubuntu-22.04 + timeout-minutes: 35 + needs: deploy + permissions: + pull-requests: read + steps: + - name: Poll deploy PR merge status + env: + GH_TOKEN: ${{ github.token }} + PR_URL: ${{ needs.deploy.outputs.pr_url }} + run: | + set -euo pipefail + if [ -z "${PR_URL}" ]; then + echo "ERROR: missing deploy PR URL" + exit 1 + fi + + TIMEOUT=1800 + INTERVAL=30 + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do + STATE="$(gh pr view "${PR_URL}" --json state --jq '.state' 2>/dev/null || echo unknown)" + if [ "${STATE}" = "MERGED" ]; then + echo "Deploy PR merged: ${PR_URL}" + exit 0 + fi + if [ "${STATE}" = "CLOSED" ]; then + echo "ERROR: deploy PR closed without merge: ${PR_URL}" + exit 1 + fi + sleep "${INTERVAL}" + ELAPSED=$((ELAPSED + INTERVAL)) + echo "Waiting for deploy PR merge... (${ELAPSED}s/${TIMEOUT}s)" + done + + echo "ERROR: timed out waiting for deploy PR merge" + exit 1 + + cleanup-release: + name: Cleanup stale release branch and PR + runs-on: ubuntu-22.04 + timeout-minutes: 5 + env: + GH_REPO: ${{ github.repository }} + needs: [validate, wait-deploy-merge] + steps: + - name: Generate release app token for PR/branch cleanup + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Close stale release PR and delete stale release branch + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BASE_VERSION: ${{ needs.validate.outputs.base_version }} + run: | + set -euo pipefail + RELEASE_BRANCH="release/${BASE_VERSION}" + + if ! gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${RELEASE_BRANCH}" >/dev/null 2>&1; then + echo "No stale release branch found: ${RELEASE_BRANCH}" + exit 0 + fi + + mapfile -t RELEASE_PRS < <( + gh pr list --head "${RELEASE_BRANCH}" --state open --json number --jq '.[].number' + ) + for pr_number in "${RELEASE_PRS[@]}"; do + echo "Closing stale release PR #${pr_number}" + gh pr close "${pr_number}" + done + + gh api -X DELETE "repos/${GITHUB_REPOSITORY}/git/refs/heads/${RELEASE_BRANCH}" + echo "Deleted stale release branch: ${RELEASE_BRANCH}" + + trigger-prepare-release: + name: Trigger and wait for prepare-release workflow + runs-on: ubuntu-22.04 + timeout-minutes: 25 + env: + GH_REPO: ${{ github.repository }} + needs: [validate, cleanup-release] + outputs: + before_run_id: ${{ steps.capture_prepare_before.outputs.before_run_id }} + steps: + - name: Generate release app token for workflow dispatch + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Preflight check required release workflows on dispatch ref + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + run: | + set -euo pipefail + # Canonical cross-repo dispatch contract lives in docs/CROSS_REPO_RELEASE_GATE.md. + REQUIRED_WORKFLOWS=(prepare-release.yml release.yml) + for workflow_file in "${REQUIRED_WORKFLOWS[@]}"; do + if WORKFLOW_CHECK_OUTPUT="$(gh workflow view "${workflow_file}" --ref "${WORKFLOW_REF}" --yaml 2>&1 >/dev/null)"; then + echo "Workflow available on ${WORKFLOW_REF}: ${workflow_file}" + else + if printf '%s' "${WORKFLOW_CHECK_OUTPUT}" | grep -Eqi "404|not found"; then + echo "ERROR: required workflow '${workflow_file}' is not resolvable on ref '${WORKFLOW_REF}'" + echo "Dispatch contract drift detected; aborting before orchestration dispatch." + else + echo "ERROR: failed to validate workflow '${workflow_file}' on ref '${WORKFLOW_REF}'" + echo "Validation failed due to a non-contract error (auth/permission/API/network)." + fi + echo "${WORKFLOW_CHECK_OUTPUT}" + exit 1 + fi + done + + - name: Capture latest prepare-release run id + id: capture_prepare_before + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + run: | + set -euo pipefail + BEFORE_RUN_ID="$( + gh run list --workflow prepare-release.yml --branch "${WORKFLOW_REF}" --limit 1 --json databaseId --jq '.[0].databaseId // 0' 2>/dev/null || echo 0 + )" + echo "before_run_id=${BEFORE_RUN_ID}" >> "${GITHUB_OUTPUT}" + + - name: Trigger prepare-release + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BASE_VERSION: ${{ needs.validate.outputs.base_version }} + run: | + set -euo pipefail + gh workflow run prepare-release.yml --ref "${WORKFLOW_REF}" -f version="${BASE_VERSION}" + + - name: Wait for prepare-release completion + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BEFORE_RUN_ID: ${{ steps.capture_prepare_before.outputs.before_run_id }} + run: | + set -euo pipefail + TIMEOUT=1200 + INTERVAL=30 + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do + RUN_ID="$(gh run list --workflow prepare-release.yml --branch "${WORKFLOW_REF}" --limit 1 --json databaseId --jq '.[0].databaseId // empty' 2>/dev/null || true)" + if [ -n "${RUN_ID}" ] && [ "${RUN_ID}" -gt "${BEFORE_RUN_ID}" ]; then + STATUS="$(gh run view "${RUN_ID}" --json status --jq '.status' 2>/dev/null || echo unknown)" + if [ "${STATUS}" = "completed" ]; then + CONCLUSION="$(gh run view "${RUN_ID}" --json conclusion --jq '.conclusion' 2>/dev/null || echo unknown)" + if [ "${CONCLUSION}" != "success" ]; then + echo "ERROR: prepare-release workflow concluded with '${CONCLUSION}'" + exit 1 + fi + echo "prepare-release workflow completed successfully" + exit 0 + fi + fi + + sleep "${INTERVAL}" + ELAPSED=$((ELAPSED + INTERVAL)) + echo "Waiting for prepare-release workflow... (${ELAPSED}s/${TIMEOUT}s)" + done + + echo "ERROR: timed out waiting for prepare-release workflow completion" + exit 1 + + ready-release-pr: + name: Prepare release PR + runs-on: ubuntu-22.04 + timeout-minutes: 35 + env: + GH_REPO: ${{ github.repository }} + needs: [validate, trigger-prepare-release] + outputs: + release_pr: ${{ steps.locate_release_pr.outputs.release_pr }} + release_pr_url: ${{ steps.locate_release_pr.outputs.release_pr_url }} + steps: + - name: Generate release app token for PR operations + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Locate release PR + id: locate_release_pr + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BASE_VERSION: ${{ needs.validate.outputs.base_version }} + run: | + set -euo pipefail + PR_NUMBER="$(gh pr list --base main --head "release/${BASE_VERSION}" --state open --json number --jq '.[0].number // empty')" + if [ -z "${PR_NUMBER}" ]; then + echo "ERROR: could not find release PR for release/${BASE_VERSION}" + exit 1 + fi + PR_URL="$(gh pr view "${PR_NUMBER}" --json url --jq '.url')" + echo "release_pr=${PR_NUMBER}" >> "${GITHUB_OUTPUT}" + echo "release_pr_url=${PR_URL}" >> "${GITHUB_OUTPUT}" + + - name: Mark release PR ready + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + PR_NUMBER: ${{ steps.locate_release_pr.outputs.release_pr }} + run: | + set -euo pipefail + IS_DRAFT="$(gh pr view "${PR_NUMBER}" --json isDraft --jq '.isDraft')" + if [ "${IS_DRAFT}" = "true" ]; then + gh pr ready "${PR_NUMBER}" + fi + + - name: Label release PR with release kind + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + PR_NUMBER: ${{ steps.locate_release_pr.outputs.release_pr }} + RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + run: | + set -euo pipefail + LABEL="release-kind:${RELEASE_KIND}" + gh label create "${LABEL}" --color "5319E7" \ + --description "Automated release kind label for dispatch orchestration" \ + --force >/dev/null 2>&1 || true + gh pr edit "${PR_NUMBER}" --remove-label "release-kind:candidate" >/dev/null 2>&1 || true + gh pr edit "${PR_NUMBER}" --remove-label "release-kind:final" >/dev/null 2>&1 || true + gh pr edit "${PR_NUMBER}" --add-label "${LABEL}" + + trigger-release: + name: Trigger and wait for release workflow + runs-on: ubuntu-22.04 + timeout-minutes: 35 + env: + GH_REPO: ${{ github.repository }} + needs: [validate, ready-release-pr] + steps: + - name: Generate release app token for release workflow dispatch + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Capture latest release run id + id: capture_release_before + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + run: | + set -euo pipefail + BEFORE_RUN_ID="$( + gh run list --workflow release.yml --branch "${WORKFLOW_REF}" --limit 1 --json databaseId --jq '.[0].databaseId // 0' 2>/dev/null || echo 0 + )" + echo "before_run_id=${BEFORE_RUN_ID}" >> "${GITHUB_OUTPUT}" + + - name: Trigger release workflow + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BASE_VERSION: ${{ needs.validate.outputs.base_version }} + RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + run: | + set -euo pipefail + gh workflow run release.yml \ + --ref "${WORKFLOW_REF}" \ + -f version="${BASE_VERSION}" \ + -f release-kind="${RELEASE_KIND}" + + - name: Wait for release workflow completion + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + BEFORE_RUN_ID: ${{ steps.capture_release_before.outputs.before_run_id }} + run: | + set -euo pipefail + TIMEOUT=1800 + INTERVAL=30 + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do + RUN_ID="$(gh run list --workflow release.yml --branch "${WORKFLOW_REF}" --limit 1 --json databaseId --jq '.[0].databaseId // empty' 2>/dev/null || true)" + if [ -n "${RUN_ID}" ] && [ "${RUN_ID}" -gt "${BEFORE_RUN_ID}" ]; then + STATUS="$(gh run view "${RUN_ID}" --json status --jq '.status' 2>/dev/null || echo unknown)" + if [ "${STATUS}" = "completed" ]; then + CONCLUSION="$(gh run view "${RUN_ID}" --json conclusion --jq '.conclusion' 2>/dev/null || echo unknown)" + if [ "${CONCLUSION}" != "success" ]; then + echo "ERROR: release workflow concluded with '${CONCLUSION}'" + exit 1 + fi + echo "release workflow completed successfully" + exit 0 + fi + fi + + sleep "${INTERVAL}" + ELAPSED=$((ELAPSED + INTERVAL)) + echo "Waiting for release workflow... (${ELAPSED}s/${TIMEOUT}s)" + done + + echo "ERROR: timed out waiting for release workflow completion" + exit 1 + + merge-release-pr: + name: Enable auto-merge and wait for release PR merge + runs-on: ubuntu-22.04 + timeout-minutes: 35 + env: + GH_REPO: ${{ github.repository }} + needs: [ready-release-pr, trigger-release] + steps: + - name: Generate release app token for PR merge + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Enable release PR auto-merge + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + PR_NUMBER: ${{ needs.ready-release-pr.outputs.release_pr }} + run: | + set -euo pipefail + gh pr merge "${PR_NUMBER}" --auto --merge || \ + echo "Warning: could not enable auto-merge yet" + + - name: Poll release PR merge status + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + PR_URL: ${{ needs.ready-release-pr.outputs.release_pr_url }} + run: | + set -euo pipefail + if [ -z "${PR_URL}" ]; then + echo "ERROR: missing release PR URL" + exit 1 + fi + + TIMEOUT=1800 + INTERVAL=30 + ELAPSED=0 + + while [ "${ELAPSED}" -lt "${TIMEOUT}" ]; do + STATE="$(gh pr view "${PR_URL}" --json state --jq '.state' 2>/dev/null || echo unknown)" + if [ "${STATE}" = "MERGED" ]; then + echo "Release PR merged: ${PR_URL}" + exit 0 + fi + if [ "${STATE}" = "CLOSED" ]; then + echo "ERROR: release PR closed without merge: ${PR_URL}" + exit 1 + fi + sleep "${INTERVAL}" + ELAPSED=$((ELAPSED + INTERVAL)) + echo "Waiting for release PR merge... (${ELAPSED}s/${TIMEOUT}s)" + done + + echo "ERROR: timed out waiting for release PR merge" + exit 1 + summary: name: Dispatch summary runs-on: ubuntu-22.04 timeout-minutes: 5 - needs: [validate, deploy] + needs: + - validate + - deploy + - wait-deploy-merge + - cleanup-release + - trigger-prepare-release + - ready-release-pr + - trigger-release + - merge-release-pr if: always() steps: + - name: Write source context summary + env: + TAG: ${{ needs.validate.outputs.tag }} + SOURCE_REPO: ${{ needs.validate.outputs.source_repo }} + SOURCE_WORKFLOW: ${{ needs.validate.outputs.source_workflow }} + SOURCE_RUN_ID: ${{ needs.validate.outputs.source_run_id }} + SOURCE_RUN_URL: ${{ needs.validate.outputs.source_run_url }} + SOURCE_SHA: ${{ needs.validate.outputs.source_sha }} + CORRELATION_ID: ${{ needs.validate.outputs.correlation_id }} + run: | + { + echo "## Source Dispatch Context" + echo "" + echo "- Tag: ${TAG:-n/a}" + echo "- Source Repo: ${SOURCE_REPO:-n/a}" + echo "- Source Workflow: ${SOURCE_WORKFLOW:-n/a}" + echo "- Source Run ID: ${SOURCE_RUN_ID:-n/a}" + echo "- Source Run URL: ${SOURCE_RUN_URL:-n/a}" + echo "- Source SHA: ${SOURCE_SHA:-n/a}" + echo "- Correlation ID: ${CORRELATION_ID:-n/a}" + } >> "${GITHUB_STEP_SUMMARY}" + - name: Check orchestration results run: | echo "Repository Dispatch Results Summary" @@ -198,7 +721,14 @@ jobs: echo "" echo "Validate: ${{ needs.validate.result }}" echo "Deploy: ${{ needs.deploy.result }}" + echo "Wait deploy: ${{ needs.wait-deploy-merge.result }}" + echo "Cleanup: ${{ needs.cleanup-release.result }}" + echo "Prepare: ${{ needs.trigger-prepare-release.result }}" + echo "Release PR: ${{ needs.ready-release-pr.result }}" + echo "Release run: ${{ needs.trigger-release.result }}" + echo "Release merge: ${{ needs.merge-release-pr.result }}" echo "Deploy PR: ${{ needs.deploy.outputs.pr_url }}" + echo "Release PR: ${{ needs.ready-release-pr.outputs.release_pr_url }}" echo "" FAILED=false @@ -213,6 +743,36 @@ jobs: FAILED=true fi + if [ "${{ needs.wait-deploy-merge.result }}" != "success" ]; then + echo "ERROR: Wait-for-deploy-merge job failed" + FAILED=true + fi + + if [ "${{ needs.cleanup-release.result }}" != "success" ]; then + echo "ERROR: Cleanup release job failed" + FAILED=true + fi + + if [ "${{ needs.trigger-prepare-release.result }}" != "success" ]; then + echo "ERROR: Prepare-release orchestration job failed" + FAILED=true + fi + + if [ "${{ needs.ready-release-pr.result }}" != "success" ]; then + echo "ERROR: Release PR readiness job failed" + FAILED=true + fi + + if [ "${{ needs.trigger-release.result }}" != "success" ]; then + echo "ERROR: Trigger-release job failed" + FAILED=true + fi + + if [ "${{ needs.merge-release-pr.result }}" != "success" ]; then + echo "ERROR: Merge-release-pr job failed" + FAILED=true + fi + if [ "${FAILED}" = "true" ]; then echo "" echo "Dispatch orchestration failed" @@ -221,3 +781,88 @@ jobs: echo "" echo "Dispatch orchestration passed" + + notify-failure: + name: Notify upstream on smoke-test dispatch failure + runs-on: ubuntu-22.04 + timeout-minutes: 5 + if: failure() + needs: + - validate + - deploy + - wait-deploy-merge + - cleanup-release + - trigger-prepare-release + - ready-release-pr + - trigger-release + - merge-release-pr + - summary + steps: + - name: Generate release app token for upstream issue creation + id: generate_release_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + owner: vig-os + repositories: devcontainer + + - name: Create upstream failure issue + env: + GH_TOKEN: ${{ steps.generate_release_token.outputs.token }} + TAG: ${{ needs.validate.outputs.tag }} + RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + SOURCE_REPO: ${{ needs.validate.outputs.source_repo }} + SOURCE_WORKFLOW: ${{ needs.validate.outputs.source_workflow }} + SOURCE_RUN_ID: ${{ needs.validate.outputs.source_run_id }} + SOURCE_RUN_URL: ${{ needs.validate.outputs.source_run_url }} + SOURCE_SHA: ${{ needs.validate.outputs.source_sha }} + CORRELATION_ID: ${{ needs.validate.outputs.correlation_id }} + DEPLOY_PR_URL: ${{ needs.deploy.outputs.pr_url }} + RELEASE_PR_URL: ${{ needs.ready-release-pr.outputs.release_pr_url }} + run: | + set -euo pipefail + WORKFLOW_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + + ISSUE_BODY="$( + cat <\` branch. + - Re-dispatch using a new RC tag/version once root cause is fixed. + EOF + )" + + gh issue create \ + --repo vig-os/devcontainer \ + --title "Smoke-test dispatch failed for ${TAG:-unknown}" \ + --label bug \ + --body "${ISSUE_BODY}" diff --git a/assets/smoke-test/README.md b/assets/smoke-test/README.md index 649b0709..40851d21 100644 --- a/assets/smoke-test/README.md +++ b/assets/smoke-test/README.md @@ -46,7 +46,7 @@ from `vig-os/devcontainer` and runs an automated deploy-and-test cycle: 1. validate the dispatch payload and extract the tag 2. deploy that tag with the online installer 3. create branch `chore/deploy-`, commit (always), and open a PR to `dev` -4. CI workflows (`ci.yml`, `ci-container.yml`) trigger on the PR +4. CI workflow (`ci.yml`) triggers on the PR 5. enable auto-merge once checks pass This flow applies to both RC tags and final tags. @@ -74,8 +74,7 @@ If this repository is lost or needs to be rebuilt, recreate it from the ``` 3. Commit the generated files and push to `main`. -4. Open a PR and confirm both shipped workflows pass: +4. Open a PR and confirm the shipped CI workflow passes: - `.github/workflows/ci.yml` - - `.github/workflows/ci-container.yml` 5. Verify `.github/workflows/repository-dispatch.yml` exists and listens for `repository_dispatch` events. diff --git a/assets/workspace/.devcontainer/CHANGELOG.md b/assets/workspace/.devcontainer/CHANGELOG.md new file mode 100644 index 00000000..f80aaa16 --- /dev/null +++ b/assets/workspace/.devcontainer/CHANGELOG.md @@ -0,0 +1,898 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.1] - TBD + +### Added + +- **Split downstream release workflow with project-owned extension hook** ([#326](https://github.com/vig-os/devcontainer/issues/326)) + - Add local `workflow_call` release phases (`release-core.yml`, `release-publish.yml`) and a lightweight `release.yml` orchestrator in `assets/workspace/.github/workflows/` + - Add `release_kind` support with candidate mode (`X.Y.Z-rcN`) and final mode (`X.Y.Z`) in downstream release workflows + - Candidate mode now auto-computes the next RC tag, skips CHANGELOG finalization/sync-issues, and publishes a GitHub pre-release + - Add project-owned `release-extension.yml` stub and preserve it during `init-workspace.sh --force` upgrades + - Add `validate-contract` composite action for single-source contract version validation + - Add downstream release contract documentation and GHCR extension example in `docs/DOWNSTREAM_RELEASE.md` +- **`jq` in devcontainer image** ([#425](https://github.com/vig-os/devcontainer/issues/425)) + - Install the `jq` CLI in the GHCR image so containerized workflows (e.g. `release-core` validate / downstream Release Core) can pipe JSON through `jq` + +### Changed + +- **Dependabot dependency update batch** ([#302](https://github.com/vig-os/devcontainer/pull/302), [#303](https://github.com/vig-os/devcontainer/pull/303), [#305](https://github.com/vig-os/devcontainer/pull/305), [#306](https://github.com/vig-os/devcontainer/pull/306), [#307](https://github.com/vig-os/devcontainer/pull/307), [#308](https://github.com/vig-os/devcontainer/pull/308), [#309](https://github.com/vig-os/devcontainer/pull/309)) + - Bump `@devcontainers/cli` from `0.81.1` to `0.84.0` and `bats-assert` from `v2.2.0` to `v2.2.4` + - Bump GitHub Actions: `actions/download-artifact` (`4.3.0` -> `8.0.1`), `actions/github-script` (`7.1.0` -> `8.0.0`), `actions/attest-build-provenance` (`3.0.0` -> `4.1.0`), `actions/checkout` (`4.3.1` -> `6.0.2`) + - Bump release workflow action pins: `sigstore/cosign-installer` (`4.0.0` -> `4.1.0`) and `anchore/sbom-action` (`0.22.2` -> `0.23.1`) +- **Dependabot dependency update batch** ([#314](https://github.com/vig-os/devcontainer/pull/314), [#315](https://github.com/vig-os/devcontainer/pull/315), [#316](https://github.com/vig-os/devcontainer/pull/316), [#317](https://github.com/vig-os/devcontainer/pull/317)) + - Bump GitHub Actions: `actions/attest-sbom` (`3.0.0` -> `4.0.0`), `actions/upload-artifact` (`4.6.2` -> `7.0.0`), `actions/create-github-app-token` (`2.2.1` -> `3.0.0`) + - Bump `docker/login-action` from `3.7.0` to `4.0.0` + - Bump `just` minor version from `1.46` to `1.47` +- **Node24-ready GitHub Actions pin refresh for shared composite actions** ([#321](https://github.com/vig-os/devcontainer/issues/321)) + - Update Docker build path pins in `build-image` (`docker/setup-buildx-action`, `docker/metadata-action`, `docker/build-push-action`) to Node24-compatible releases + - Set `setup-env` default Node runtime to `24` and upgrade `actions/setup-node` + - Align test composite actions with newer pins (`actions/checkout`, `actions/cache`, `actions/upload-artifact`) +- **Smoke-test dispatch payload now carries source run traceability metadata** ([#289](https://github.com/vig-os/devcontainer/issues/289)) + - Candidate release dispatches now include source repo/workflow/run/SHA metadata plus a deterministic `correlation_id` + - Smoke-test dispatch receiver logs normalized source context, derives source run URL when possible, and writes it to workflow summary output + - Release-cycle docs now define required vs optional dispatch payload keys and the future callback contract path for `publish-candidate` +- **Smoke-test repository dispatch now runs for final releases too** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - `release.yml` now triggers the existing smoke-test dispatch contract for both `candidate` and `final` release kinds + - Final release summaries and release-cycle documentation now reflect dispatch behavior for both release modes +- **Workspace CI templates now use a single container-based workflow** ([#327](https://github.com/vig-os/devcontainer/issues/327)) + - Consolidate `assets/workspace/.github/workflows/ci.yml` as the canonical CI workflow and remove the obsolete `ci-container.yml` template + - Extract reusable `assets/workspace/.github/actions/resolve-image` and run workspace release tests in the same containerized workflow model + - Update smoke-test and release-cycle documentation to reference the single CI workflow contract +- **Final release now requires downstream RC pre-release gate** ([#331](https://github.com/vig-os/devcontainer/issues/331)) + - Add upstream final-release validation that requires a downstream GitHub pre-release for the latest published RC tag + - Move smoke-test dispatch to a dedicated release job and include `release_kind` in the dispatch payload + - Add downstream `repository-dispatch.yml` template that runs smoke tests and creates pre-release/final release artifacts +- **Ship changelog into workspace payload and smoke-test deploy root** ([#333](https://github.com/vig-os/devcontainer/issues/333)) + - Sync canonical `CHANGELOG.md` into both workspace root and `.devcontainer/` template paths + - Smoke-test dispatch now copies `.devcontainer/CHANGELOG.md` to repository root so deploy output keeps a root changelog +- **Final release now publishes a GitHub Release with finalized notes** ([#310](https://github.com/vig-os/devcontainer/issues/310)) + - Add a final-only publish step in `.github/workflows/release.yml` that creates a GitHub Release for `X.Y.Z` + - Source GitHub Release notes from the finalized `CHANGELOG.md` section and fail the run if notes extraction or release publishing fails +- **Release dispatch and publish ordering hardened for 0.3.1** ([#336](https://github.com/vig-os/devcontainer/issues/336)) + - Make smoke-test dispatch fire-and-forget in `.github/workflows/release.yml` and decouple rollback from downstream completion timing + - Add bounded retries to the final-release downstream RC pre-release gate API check + - Move final GitHub Release creation to the end of publish so artifact publication/signing completes before release object creation + - Add concurrency control to `assets/smoke-test/.github/workflows/repository-dispatch.yml` to prevent overlapping dispatch races + - Handle smoke-test dispatch failures with a targeted issue while avoiding destructive rollback after publish artifacts are already released +- **Redesigned smoke-test dispatch release orchestration** ([#358](https://github.com/vig-os/devcontainer/issues/358)) + - Replace premature `publish-release` behavior with full downstream orchestration: deploy-to-dev merge gate, `prepare-release.yml`, release PR readiness/approval, and `release.yml` dispatch polling + - Add upstream failure issue reporting with job-phase results and cleanup guidance when dispatch orchestration fails +- **Smoke-test release orchestration now runs as two phases** ([#402](https://github.com/vig-os/devcontainer/issues/402)) + - Keep `repository-dispatch.yml` focused on deploy/prepare/release-PR readiness and move release dispatch to a dedicated merged-PR workflow (`on-release-pr-merge.yml`) + - Add release-kind labeling and auto-merge enablement for release PRs, and keep upstream failure notifications in both phases + - Remove release-branch upstream `CHANGELOG.md` sync from `repository-dispatch.yml` (previously added in [#358](https://github.com/vig-os/devcontainer/issues/358)) +- **Dependabot dependency update batch** ([#414](https://github.com/vig-os/devcontainer/pull/414)) + - Bump `github/codeql-action` from `4.32.6` to `4.34.1` and `anchore/sbom-action` from `0.23.1` to `0.24.0` + - Bump `actions/cache` restore/save pins from `5.0.3` to `5.0.4` in `sync-issues.yml` +- **Dependabot dependency update batch** ([#413](https://github.com/vig-os/devcontainer/pull/413)) + - Bump `@devcontainers/cli` from `0.84.0` to `0.84.1` + +### Fixed + +- **Smoke-test deploy restores workspace CHANGELOG for prepare-release** ([#417](https://github.com/vig-os/devcontainer/issues/417)) + - Add `prepare-changelog unprepare` to rename the top `## [semver] - …` heading to `## Unreleased` + - `init-workspace.sh --smoke-test` copies `.devcontainer/CHANGELOG.md` into workspace `CHANGELOG.md` and runs unprepare; remove duplicate remap from smoke-test dispatch workflow +- **Release app permission docs now include downstream workflow dispatch requirements** ([#397](https://github.com/vig-os/devcontainer/issues/397)) + - Update `docs/RELEASE_CYCLE.md` to require `Actions` read/write for `RELEASE_APP` on the validation repository + - Clarify this is required so downstream `repository-dispatch.yml` can trigger release orchestration workflows via `workflow_dispatch` +- **Smoke-test dispatch no longer fails on release PR self-approval** ([#402](https://github.com/vig-os/devcontainer/issues/402)) + - Remove bot self-approval from `repository-dispatch.yml` and replace with release-kind labeling plus auto-merge enablement + - Remove in-job polling for release PR merge and downstream release execution from phase 1 orchestration + - Phase 2 (`on-release-pr-merge.yml`) fails validation unless the merged release PR has `release-kind:final` or `release-kind:candidate` +- **Sync-main-to-dev PRs now trigger CI reliably in downstream repos** ([#398](https://github.com/vig-os/devcontainer/issues/398)) + - Replace API-based sync branch creation with `git push` in `assets/workspace/.github/workflows/sync-main-to-dev.yml` +- **Sync-main-to-dev no longer dispatches CI via workflow_dispatch** ([#405](https://github.com/vig-os/devcontainer/issues/405)) + - `workflow_dispatch` runs are omitted from the PR status check rollup, so they do not satisfy branch protection on the sync PR + - Remove the post-PR `gh workflow run ci.yml` step and drop `actions: write` from the sync job in `.github/workflows/sync-main-to-dev.yml` and `assets/workspace/.github/workflows/sync-main-to-dev.yml` +- **Sync-main-to-dev conflict detection uses merge-tree** ([#410](https://github.com/vig-os/devcontainer/issues/410)) + - Replace working-tree trial merge with `git merge-tree --write-tree` so clean merges are not mislabeled as conflicts + - Enable auto-merge when dev merges cleanly with main; print merge-tree output on conflicts; fail the step on unexpected errors +- **Smoke-test release phase 2 branch-not-found failure** ([#419](https://github.com/vig-os/devcontainer/issues/419)) + - Merge phase 2 (`on-release-pr-merge.yml`) back into `repository-dispatch.yml` so the release runs while `release/` still exists, matching the normal release flow + - Remove `on-release-pr-merge.yml` from the smoke-test template + +- **Release finalization now commits generated docs and refreshes PR content** ([#300](https://github.com/vig-os/devcontainer/issues/300)) + - Final release automation regenerates docs before committing so pre-commit `generate-docs` does not fail CI with tracked file diffs + - Release PR body is refreshed from finalized `CHANGELOG.md` +- **Release attestation warnings reduced by granting artifact metadata permission** ([#348](https://github.com/vig-os/devcontainer/issues/348)) + - Add `artifact-metadata: write` to the release publish job so attestation steps can persist metadata storage records + - Keep `actions/attest`-based SBOM attestation path and remove missing-permission warnings from publish runs +- **Smoke-test dispatch deploy now repairs workspace ownership before changelog copy** ([#352](https://github.com/vig-os/devcontainer/issues/352)) + - Add a write probe and conditional `sudo chown -R` in `assets/smoke-test/.github/workflows/repository-dispatch.yml` after installer execution + - Prevent `Permission denied` failures when copying `.devcontainer/CHANGELOG.md` to repository root in GitHub-hosted runner jobs +- **Smoke-test release lookup no longer treats missing tags as existing releases** ([#355](https://github.com/vig-os/devcontainer/issues/355)) + - Change `assets/smoke-test/.github/workflows/repository-dispatch.yml` to branch on `gh api` exit status when querying `releases/tags/` + - Ensure missing release tags follow the create path instead of failing with `prerelease=null` mismatch +- **Bounded retry added for network-dependent setup and prepare-release calls** ([#357](https://github.com/vig-os/devcontainer/issues/357)) + - Replace shell-based retry helper with pure Python `retry` CLI in `vig-utils` (`packages/vig-utils/src/vig_utils/retry.py`) + - Update this repository CI workflows to call `uv run retry` after `setup-env` dependency sync + - Update downstream workflow templates to call `retry` directly in devcontainer jobs and remove `source` lines + - Ensure downstream containerized jobs resolve image tags from `.vig-os` instead of hardcoded `latest` + - Bundle idempotency guards for branch/PR/tag/release creation paths to keep retried network calls safe on reruns + - Remove synced `retry.sh` artifacts and BATS retry tests in favor of `vig-utils` pytest coverage +- **Release workflow no longer fails when retry tooling is unavailable** ([#365](https://github.com/vig-os/devcontainer/issues/365)) + - Extend `.github/actions/setup-env/action.yml` with a reusable `retry` shell function exported via `BASH_ENV` as the retry single source of truth + - Add `setup-env` input support for uv-only usage by allowing Python setup to be disabled when jobs only need retry tooling + - Switch release workflow retry calls from `uv run retry` to shared `retry` and remove duplicated inline retry implementations +- **Upstream sync workflows no longer depend on pre-published GHCR image tags** ([#367](https://github.com/vig-os/devcontainer/issues/367)) + - Remove upstream `.vig-os` files at repository root and `assets/smoke-test/` to eliminate downstream-only configuration from upstream CI + - Refactor `.github/workflows/sync-issues.yml` and `.github/workflows/sync-main-to-dev.yml` to run natively on runners via `./.github/actions/setup-env` instead of `resolve-image` + `container` +- **Release test-image setup now recovers from uv sync crashes** ([#370](https://github.com/vig-os/devcontainer/issues/370)) + - Harden `.github/actions/setup-env/action.yml` to retry `uv sync --frozen --all-extras` once after clearing uv cache and removing stale `.venv` + - Prevent repeat release test failures when `setup-env` is executed multiple times in the same job +- **Release setup-env no longer self-sources retry helper via BASH_ENV** ([#374](https://github.com/vig-os/devcontainer/issues/374)) + - Guard the retry-helper merge logic in `.github/actions/setup-env/action.yml` to skip merging when `PREV_BASH_ENV` already equals `RETRY_HELPER` + - Prevent infinite `source` recursion and exit 139 crashes when `setup-env` is invoked multiple times in one job +- **Smoke-test dispatch now checks out repository before local setup action** ([#376](https://github.com/vig-os/devcontainer/issues/376)) + - Add `actions/checkout` to the `smoke-test` job in `.github/workflows/release.yml` before invoking `./.github/actions/setup-env` + - Prevent dispatch failures caused by missing local action metadata (`action.yml`) in a fresh job workspace +- **Workspace resolve-image jobs now checkout local action metadata** ([#380](https://github.com/vig-os/devcontainer/issues/380)) + - Update `sparse-checkout` in workspace `resolve-image` jobs to include `.github/actions/resolve-image` in addition to `.vig-os` + - Prevent CI failures in downstream deploy PRs where local composite actions were missing from sparse checkout +- **Smoke-test dispatch gh jobs now set explicit repo context** ([#386](https://github.com/vig-os/devcontainer/issues/386)) + - Add job-level `GH_REPO: ${{ github.repository }}` to `cleanup-release`, `trigger-prepare-release`, `ready-release-pr`, and `trigger-release` in `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Prevent `gh` CLI failures (`fatal: not a git repository`) in runner jobs that do not perform `actions/checkout` +- **Smoke-test release orchestration now validates workflow contract before dispatch** ([#389](https://github.com/vig-os/devcontainer/issues/389)) + - Add a preflight check that verifies `prepare-release.yml` and `release.yml` are resolvable on dispatch ref `dev` before downstream orchestration starts + - Dispatch and polling now use explicit ref/branch context (`--ref dev` / `--branch dev`) to avoid default-branch workflow registry drift and `404 workflow not found` failures +- **Smoke-test preflight now uses gh CLI ref-compatible workflow validation** ([#392](https://github.com/vig-os/devcontainer/issues/392)) + - Update `assets/smoke-test/.github/workflows/repository-dispatch.yml` preflight checks to call `gh workflow view` with `--yaml` when `--ref` is set + - Prevent false preflight failures caused by newer GitHub CLI argument validation before `prepare-release` dispatch +- **Downstream release workflow templates hardened for smoke-test orchestration** ([#394](https://github.com/vig-os/devcontainer/issues/394)) + - Add missing `git config --global --add safe.directory "$GITHUB_WORKSPACE"` in containerized release and sync jobs that run git after checkout + - Decouple `release.yml` rollback container startup from `needs.core.outputs.image_tag` by resolving the image in a dedicated `resolve-image` job + - Add explicit release caller/reusable workflow permissions for `actions` and `pull-requests` operations, and update dispatch header comments to reference only current CI workflows +- **Workspace containerized workflows now pin bash for run steps** ([#395](https://github.com/vig-os/devcontainer/issues/395)) + - Set `defaults.run.shell: bash` in containerized workspace release and prepare jobs so `set -euo pipefail` scripts do not execute under POSIX `sh` + - Prevent downstream smoke-test failures caused by `set: Illegal option -o pipefail` in container jobs +- **Downstream release templates now require explicit app tokens for write paths** ([#400](https://github.com/vig-os/devcontainer/issues/400)) + - Update `assets/workspace/.github/workflows/prepare-release.yml`, `release-core.yml`, `release-publish.yml`, `release.yml`, and `sync-issues.yml` to remove `github.token` fallback from protected write operations + - Route protected branch/ref writes through Commit App tokens and release orchestration/issue operations through Release App tokens + - Document downstream token requirements in `docs/DOWNSTREAM_RELEASE.md` and `docs/CROSS_REPO_RELEASE_GATE.md` + - Use `github.token` specifically for Actions cache deletion in `sync-issues.yml` because that API path requires explicit `actions: write` job token scope + - Use Commit App credentials for rollback checkout in `release.yml` so rollback branch/tag writes can still bypass protected refs +- **setup-env retries uv install on transient GitHub Releases download failures** ([#407](https://github.com/vig-os/devcontainer/issues/407)) + - Add `continue-on-error` plus a delayed second attempt for `astral-sh/setup-uv` in `.github/actions/setup-env/action.yml` + - Reduce flaky release publish failures when GitHub CDN returns transient HTTP errors for uv release assets +- **Smoke-test deploy keeps workspace scaffold as root CHANGELOG** ([#403](https://github.com/vig-os/devcontainer/issues/403)) + - Stop overwriting `CHANGELOG.md` with a minimal stub in `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Require the workspace `CHANGELOG.md` from `init-workspace` so downstream `prepare-release` validation matches shipped layout + - When the first changelog section is `## [X.Y.Z] - …` (TBD or a release date), remap that top version header to `## Unreleased` so downstream `prepare-release` can run +- **Smoke-test dispatch release validate no longer runs docker inside devcontainer** ([#421](https://github.com/vig-os/devcontainer/issues/421)) + - Remove redundant `docker manifest inspect` step from `release-core.yml` validate job (container image is already proof of accessibility; `resolve-image` validates on the runner) + - Set `GH_REPO` for rollback `gh issue create` in workspace `release.yml` when git checkout is skipped +- **Container image tests expect current uv minor line** ([#423](https://github.com/vig-os/devcontainer/issues/423)) + - Update `tests/test_image.py` `EXPECTED_VERSIONS["uv"]` to match uv 0.11.x from the latest release install path in the image build +- **Container image tests expect current just minor line** ([#423](https://github.com/vig-os/devcontainer/issues/423)) + - Update `tests/test_image.py` `EXPECTED_VERSIONS["just"]` to match just 1.48.x from the latest release install path in the image build + +### Security + +- **Smoke-test dispatch workflow permissions now follow least privilege** ([#340](https://github.com/vig-os/devcontainer/issues/340)) + - Reduce `assets/smoke-test/.github/workflows/repository-dispatch.yml` workflow token permissions from write to read by default + - Grant `contents: write` only to `publish-release`, the single job that creates or edits GitHub Releases + +## [0.3.0](https://github.com/vig-os/devcontainer/releases/tag/0.3.0) - 2026-03-13 + +### Added + +- **Image tools** ([#212](https://github.com/vig-os/devcontainer/issues/212)) + - Install rsync +- **Preserve user-authored files during `--force` workspace upgrades** ([#212](https://github.com/vig-os/devcontainer/issues/212)) + - `init-workspace --force` no longer overwrites `README.md`, `CHANGELOG.md`, `LICENSE`, `.github/CODEOWNERS`, or `justfile.project` +- **Devcontainer and git recipes in justfile.base** ([#71](https://github.com/vig-os/devcontainer/issues/71)) + - Devcontainer group (host-side only): `up`, `down`, `status`, `logs`, `shell`, `restart`, `open` + - Auto-detect podman/docker compose; graceful failure if run inside container + - Git group: `log` (pretty one-line, last 20), `branch` (current + recent) +- **CI status column in just gh-issues PR table** ([#143](https://github.com/vig-os/devcontainer/issues/143)) + - PR table shows CI column with pass/fail/pending summary (✓ 6/6, ⏳ 3/6, ✗ 5/6) + - Failed check names visible when checks fail + - CI cell links to GitHub PR checks page +- **Config-driven model tier assignments for agent skills** ([#103](https://github.com/vig-os/devcontainer/issues/103)) + - Extended `.cursor/agent-models.toml` with `standard` tier (sonnet-4.5) and `[skill-tiers]` mapping for skill categories (data-gathering, formatting, review, orchestration) + - New rule `.cursor/rules/subagent-delegation.mdc` documenting when and how to delegate mechanical sub-steps to lightweight subagents via the Task tool + - Added `## Delegation` sections to 12 skills identifying steps that should spawn lightweight/standard-tier subagents to reduce token consumption on the primary autonomous model + - Skills updated: `worktree_solve-and-pr`, `worktree_brainstorm`, `worktree_plan`, `worktree_execute`, `worktree_verify`, `worktree_pr`, `worktree_ci-check`, `worktree_ci-fix`, `code_review`, `issue_triage`, `pr_post-merge`, `ci_check` +- **hadolint pre-commit hook for Containerfile linting** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Add `hadolint` hook to `.pre-commit-config.yaml`, pinned by SHA (v2.9.3) + - Enforce Dockerfile best practices: pinned base image tags, consolidated `RUN` layers, shellcheck for inline scripts + - Fix `tests/fixtures/sidecar.Containerfile` to pass hadolint with no warnings +- **tmux installed in container image for worktree session persistence** ([#130](https://github.com/vig-os/devcontainer/issues/130)) + - Add `tmux` to the Containerfile `apt-get install` block + - Enables autonomous worktree agents to survive Cursor session disconnects +- **pr_solve skill — diagnose PR failures, plan fixes, execute** ([#133](https://github.com/vig-os/devcontainer/issues/133)) + - Single entry point that gathers CI failures, review feedback, and merge state into a consolidated diagnosis + - Presents diagnosis for approval before any fixes, plans fixes using design_plan conventions, executes with TDD discipline + - Pre-commit hook `check-skill-names` enforces `[a-z0-9][a-z0-9_-]*` naming for skill directories + - BATS test suite with canary test that injects a bad name into the real repo + - TDD scenario checklist expanded with canary, idempotency, and concurrency categories +- **Optional reviewer parameter for autonomous worktree pipeline** ([#102](https://github.com/vig-os/devcontainer/issues/102)) + - Support `reviewer` parameter in `just worktree-start` + - Propagate `PR_REVIEWER` via tmux environment to the autonomous agent + - Update `worktree_pr` skill to automatically request review when `PR_REVIEWER` is set +- **Inception skill family for pre-development product thinking** ([#90](https://github.com/vig-os/devcontainer/issues/90)) + - Four-phase pipeline: `inception_explore` (divergent problem understanding), `inception_scope` (convergent scoping), `inception_architect` (pattern-validated design), `inception_plan` (decomposition into GitHub issues) + - Document templates: `docs/templates/RFC.md` (Problem Statement, Proposed Solution, Alternatives, Impact, Phasing) and `docs/templates/DESIGN.md` (Architecture, Components, Data Flow, Technology Stack, Testing) + - Document directories: `docs/rfcs/` and `docs/designs/` for durable artifacts + - Certified architecture reference repos embedded in `inception_architect` skill: ByteByteGoHq/system-design-101, donnemartin/system-design-primer, karanpratapsingh/system-design, binhnguyennus/awesome-scalability, mehdihadeli/awesome-software-architecture + - Fills the gap between "I have an idea" and "I have issues ready for design" +- **Automatic update notifications on devcontainer attach** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Wire `version-check.sh` into `post-attach.sh` for automatic update checks + - Silent, throttled checks (24-hour interval by default) + - Graceful failure - never disrupts the attach process +- **Host-side devcontainer upgrade recipe** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - New `just devcontainer-upgrade` recipe for convenient upgrades from host + - Container detection - prevents accidental execution inside devcontainer + - Clear error messages with instructions when run from wrong context +- **`just check` recipe for version management** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Expose version-check.sh subcommands: `just check`, `just check config`, `just check on/off`, `just check 7d` + - User-friendly interface for managing update notifications +- **Cursor worktree support for parallel agent development** ([#64](https://github.com/vig-os/devcontainer/issues/64)) + - `.cursor/worktrees.json` for native Cursor worktree initialization (macOS/Linux local) + - `justfile.worktree` with tmux + cursor-agent CLI recipes (`worktree-start`, `worktree-list`, `worktree-attach`, `worktree-stop`, `worktree-clean`) for devcontainer environments + - Autonomous worktree skills: `worktree_brainstorm`, `worktree_plan`, `worktree_execute`, `worktree_verify`, `worktree_pr`, `worktree_ask`, `worktree_solve-and-pr` + - Sync manifest updated to propagate worktree config and recipes to downstream projects +- **GitHub issue and PR dashboard recipe** ([#84](https://github.com/vig-os/devcontainer/issues/84)) + - `just gh-issues` displays open issues grouped by milestone in rich tables with columns for type, title, assignee, linked branch, priority, scope, effort, and semver + - Open pull requests section with author, branch, review status, and diff delta + - Linked branches fetched via a single GraphQL call + - Ships to downstream workspaces via sync manifest (`.devcontainer/justfile.gh` + `.devcontainer/scripts/gh_issues.py`) +- **Issue triage agent skill** ([#81](https://github.com/vig-os/devcontainer/issues/81)) + - Cursor skill at `.cursor/skills/issue_triage/` for triaging open issues across priority, area, effort, SemVer impact, dependencies, and release readiness + - Decision matrix groups issues into parent/sub-issue clusters with milestone suggestions + - Predefined label taxonomy (`label-taxonomy.md`) for priority, area, effort, and SemVer dimensions + - Sync manifest updated to propagate skill to workspace template +- **Cursor commands and rules for agent-driven development workflows** ([#63](https://github.com/vig-os/devcontainer/issues/63)) + - Always-on rules: `coding-principles.mdc` (YAGNI, minimal diff, DRY, no secrets, traceability, single responsibility) and `tdd.mdc` (RED-GREEN-REFACTOR discipline) + - Tier 1 commands: `start-issue.md`, `create-issue.md`, `brainstorm.md`, `tdd.md`, `review.md`, `verify.md` + - Tier 2 commands: `check-ci.md`, `fix-ci.md` + - Tier 3 commands: `plan.md`, `execute-plan.md`, `debug.md` +- **Agent-friendly issue templates, changelog rule, and PR template enhancements** ([#61](https://github.com/vig-os/devcontainer/issues/61)) + - Cursor rule `.cursor/rules/changelog.mdc` (always applied) guiding agents on when, where, and how to update CHANGELOG.md + - Changelog Category dropdown added to `bug_report.yml`, `feature_request.yml`, and `task.yml` issue templates + - New issue templates: `refactor.yml` (scope/invariants), `documentation.yml` (docs/templates workflow), `ci_build.yml` (target workflows/triggers/release impact) + - Template chooser `config.yml` disabling blank issues and linking to project docs + - PR template enhanced with explicit Changelog Entry section, CI/Build change type, and updated checklist referencing `docs/templates/` and `just docs` +- **GitHub issue and PR templates in workspace template** ([#63](https://github.com/vig-os/devcontainer/issues/63)) + - Pull request template, issue templates, Dependabot config, and `.gitmessage` synced to `assets/workspace/` + - Ground truth lives in repo root; `assets/workspace/` is generated output +- **cursor-agent CLI pre-installed in devcontainer image** ([#108](https://github.com/vig-os/devcontainer/issues/108)) + - Enables `just worktree-start` to work out of the box without manual installation +- **Automatic merge commit message compliance** ([#79](https://github.com/vig-os/devcontainer/issues/79)) + - `setup-gh-repo.sh` configures repo merge settings via `gh api` (`merge_commit_title=PR_TITLE`, `merge_commit_message=PR_BODY`, `allow_auto_merge=true`) + - Wired into `post-create.sh` so downstream devcontainer projects get compliant merge commits automatically + - `--subject-only` flag for `validate-commit-msg` to validate PR titles without requiring body or Refs + - `pr-title-check.yml` CI workflow enforces commit message standard on PR titles + - PR body template includes `Refs: #` placeholder for merge commit traceability +- **Smoke-test repo bootstrap validation** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Downstream smoke coverage that bootstraps a workspace from the template and verifies `ci.yml` passes on a real GitHub-hosted runner +- **`bandit` pre-installed in devcontainer image** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - `bandit[toml]` added to the system Python install in the Containerfile +- **`pre-commit` pre-installed in CI `setup-env` action** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Workspace `setup-env` composite action now installs `pre-commit` as a mandatory step so hooks are available in bare-runner CI without a devcontainer +- **`setup-gh-repo.sh` detaches org default code security configuration** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - On post-create, detach any org-level default security config from the repo to avoid conflicts with the security workflows shipped in the workspace template + - Graceful fallback when repo ID cannot be resolved or permissions are insufficient +- **`init-workspace.sh` runs `just sync` after placeholder replacement** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Resolves the `uv.lock` for the new project name and installs the project package into the venv during workspace bootstrap +- **Candidate publishing mode in release workflow** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `release.yml` now supports `release-kind=candidate` (default) and infers the next available `X.Y.Z-rcN` tag automatically + - Candidate runs create and push Git tags, publish candidate manifests, and keep candidate tags after final release + - Final runs remain available via `release-kind=final` and are exposed by `just finalize-release` +- **PR-based dev sync after release** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `sync-main-to-dev.yml` replaces `post-release.yml` — syncs main into dev via PR instead of direct push, satisfying branch protection rules + - Detects merge conflicts, labels `merge-conflict` with resolution instructions + - Auto-merge enabled for conflict-free PRs; stale sync branches cleaned up automatically +- **hadolint installed and wired into CI tooling** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Install `hadolint` in the devcontainer image with SHA-256 checksum verification + - Add image test coverage to verify `hadolint` is available in the built image + - Configure pre-commit to use the local `hadolint` binary and install it in `setup-env`/`test-project` workflows +- **Taplo TOML linting in pre-commit** ([#181](https://github.com/vig-os/devcontainer/issues/181)) + - Add SHA-pinned `taplo-format` and `taplo-lint` hooks to enforce TOML formatting and schema-aware validation + - Add `.taplo.toml` configuration (local to this repository, not synced downstream) +- **Add `--smoke-test` flag to deploy smoke-test-specific assets** ([#250](https://github.com/vig-os/devcontainer/issues/250)) + - `init-workspace.sh --smoke-test` deploys files from `assets/smoke-test/` (currently `repository-dispatch.yml` and `README.md`) + - `install.sh` forwards `--smoke-test` flag to `init-workspace.sh` + - Smoke mode implies `--force --no-prompts` for unattended use + - Refactor `initialized_workspace` fixture into reusable `_init_workspace()` with `smoke_test` parameter +- **Root `.vig-os` config file as devcontainer version SSoT** ([#257](https://github.com/vig-os/devcontainer/issues/257)) + - Add committed `assets/workspace/.vig-os` key/value config with `DEVCONTAINER_VERSION` as the canonical version source + - Update `docker-compose.yml`, `initialize.sh`, and `version-check.sh` to consume `.vig-os`-driven version flow + - Extend integration/image tests for `.vig-os` presence and graceful handling when `.vig-os` is missing +- **VS Code settings synced via manifest** + - Added `.vscode/settings.json` to `scripts/manifest.toml` to keep editor settings consistent across root repo and workspace template +- **Cross-repo smoke-test dispatch on RC publish** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - RC candidate publishes now trigger `repository_dispatch` in `vig-os/devcontainer-smoke-test` with the RC tag payload + - Release process now includes a documented manual smoke gate before running final publish +- **Automated RC deploy-and-test via PR in smoke-test repo** ([#258](https://github.com/vig-os/devcontainer/issues/258)) + - Dispatch workflow now deploys the tag, creates a signed commit on `chore/deploy-`, and opens a PR to `dev` + - CI workflows (`ci.yml`, `ci-container.yml`) trigger on the deploy PR, and auto-merge is enabled when checks pass + - Stale deploy PRs are closed before each new deployment + - The smoke-test repo keeps audit trail through deploy PRs and merge history instead of a local changelog + - Dispatch payload tag validation now enforces semver format `X.Y.Z` or `X.Y.Z-rcN` before using the tag in refs/URLs + - CI security scan now includes a time-bounded exception for `CVE-2026-31812` in `uv`/`uvx` pending upstream dependency patch release + +### Changed + +- **Release CHANGELOG flow redesigned** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `prepare-release.yml` now freezes CHANGELOG on dev (Unreleased → [X.Y.Z] - TBD + fresh empty Unreleased), then forks release branch and strips the empty Unreleased section + - Dev never enters a state without `## Unreleased`; both branches share the [X.Y.Z] section for clean merges + - Candidate releases skip CHANGELOG changes; only final releases set the date + - No CHANGELOG reset needed during post-release sync +- **Release automation now uses dedicated GitHub App identities** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - Replaced deprecated `APP_SYNC_ISSUES_*` secrets with `RELEASE_APP_*` for release and preparation workflows + - `sync-issues.yml` now uses `COMMIT_APP_*`; `sync-main-to-dev.yml` uses both apps (commit app for refs, release app for PR operations) + - Removed automatic `sync-issues` trigger from `sync-main-to-dev.yml` and documented the app permission model in `docs/RELEASE_CYCLE.md` +- **Container CI defaults image tag from `.vig-os`** ([#264](https://github.com/vig-os/devcontainer/issues/264)) + - `ci.yml` and `ci-container.yml` now run only on `pull_request` and `workflow_dispatch` after removing unused `workflow_call` triggers + - `ci-container.yml` now resolves `DEVCONTAINER_VERSION` from `.vig-os` before container jobs start + - Manual `workflow_dispatch` runs can still override the image via `image-tag`; fallback remains `latest` when no version is available + - Added an early manifest check in `resolve-image` so workflows fail fast if the resolved image tag is unavailable or inaccessible + +- **worktree-clean: add filter mode for stopped-only vs all** ([#158](https://github.com/vig-os/devcontainer/issues/158)) + - Default `just worktree-clean` (no args) now cleans only stopped worktrees, skips running tmux sessions + - `just worktree-clean all` retains previous behavior (clean all worktrees) with warning + - Summary output shows cleaned vs skipped worktrees + - `just wt-clean` alias unchanged +- **Consolidate sync_manifest.py and utils.py into manifest-as-config architecture** ([#89](https://github.com/vig-os/devcontainer/issues/89)) + - Extract transform classes (Sed, RemoveLines, etc.) to `scripts/transforms.py` + - Unify sed logic: `substitute_in_file()` in utils shared by sed_inplace and Sed transform + - Convert MANIFEST from Python code to declarative `scripts/manifest.toml` +- **justfile.base is canonical at repo root, synced via manifest** ([#71](https://github.com/vig-os/devcontainer/issues/71)) + - Root `justfile.base` is now the single source of truth; synced to `assets/workspace/.devcontainer/justfile.base` via `sync_manifest.py` + - `just sync-workspace` and prepare-build keep workspace template in sync +- **Autonomous PR skills use pull request template** ([#147](https://github.com/vig-os/devcontainer/issues/147)) + - `pr_create` and `worktree_pr` now read `.github/pull_request_template.md` and fill each section from available context + - Explicit read-then-fill procedure with section-by-section mapping (Description, Type of Change, Changelog Entry, Testing, Checklist, Refs) + - Ensures autonomous PRs match manual PR structure and include all checklist items +- **Rename skill namespace separator from colon to underscore** ([#128](https://github.com/vig-os/devcontainer/issues/128)) + - All skill directories under `.cursor/skills/` and `assets/workspace/.cursor/skills/` renamed (e.g. `issue:create` → `issue_create`) + - All internal cross-references, frontmatter, prose, `CLAUDE.md` command table, and label taxonomy updated + - `issue_create` skill enhanced: gathers context via `just gh-issues` before drafting, suggests parent/child relationships and milestones + - `issue_create` skill now includes TDD acceptance criterion for testable issue types + - Remaining `sync-issues` workflow trigger references removed from skills + - `tdd.mdc` expanded with test scenario checklist and test type guidance; switched from always-on to glob-triggered on source/test files + - `code_tdd`, `code_execute`, and `worktree_execute` skills now reference `tdd.mdc` explicitly +- **Clickable issue and PR numbers in gh-issues table** ([#104](https://github.com/vig-os/devcontainer/issues/104)) + - `#` column in issue and PR tables now uses Rich OSC 8 hyperlinks to GitHub URLs + - Clicking an issue or PR number opens it in the browser (or Cursor's integrated terminal) +- **PR template aligned with canonical commit types** ([#115](https://github.com/vig-os/devcontainer/issues/115)) + - Replace ad-hoc Type of Change checkboxes with the 10 canonical commit types + - Move breaking change from type to a separate modifier checkbox + - Add release-branch hint to Related Issues section +- **Updated update notification message** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Fixed misleading `just update` instruction (Python deps, not devcontainer upgrade) + - Show correct upgrade instructions: `just devcontainer-upgrade` and curl fallback + - Clarify that upgrade must run from host terminal, not inside container + - Add reminder to rebuild container in VS Code after upgrade +- **Declarative Python sync manifest** ([#67](https://github.com/vig-os/devcontainer/issues/67)) + - Replaced `sync-manifest.txt` + bash function and `sync-workspace.sh` with `scripts/sync_manifest.py` + - Single source of truth for which files to sync and what transformations to apply + - `prepare-build.sh` and `just sync-workspace` both call the same manifest +- **Namespace-prefixed Cursor skill names** ([#67](https://github.com/vig-os/devcontainer/issues/67)) + - Renamed all 15 skills with colon-separated namespace prefixes (`issue:`, `design:`, `code:`, `git:`, `ci:`, `pr:`) + - Enables filtering by namespace when invoking skills (e.g., typing `code:` shows implementation skills) +- **`--org` flag for install script** ([#33](https://github.com/vig-os/devcontainer/issues/33)) + - Allows overriding the default organization name (default: `vigOS`) + - Passes `ORG_NAME` as environment variable to the container + - Usage: `curl -sSf ... | bash -s --org MyOrg -- ~/my-project` + - Unit tests for `--org` flag in help, default value, and custom override +- **Virtual environment prompt renaming** ([#34](https://github.com/vig-os/devcontainer/issues/34)) + - Post-create script updates venv prompt from "template-project" to project short name + - Integration test verifies venv activate script does not contain "template-project" +- **BATS (Bash Automated Testing System) shell testing framework** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - npm dependencies for bats, bats-support, bats-assert, and bats-file + - `test-bats` justfile task and requirements configuration + - `test_helper.bash` supporting both local (node_modules) and CI (BATS_LIB_PATH) library resolution + - CI integration in setup-env and test-project actions with conditional parallel execution via GNU parallel + - Comprehensive BATS test suites for build, clean, init, install, and prepare-build scripts + - Tests verify script structure, argument parsing, function definitions, error handling, and OS/runtime detection patterns +- **Post-install user configuration step** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Automatically call copy-host-user-conf.sh after workspace initialization + - `run_user_conf()` helper for host-side setup (git, ssh, gh) + - Integration tests for .devcontainer/.conf/ directory creation and expected config files +- **Git repository initialization in install script** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - `setup_git_repo()` function to initialize git if missing + - Creates initial commit "chore: initial project scaffold" for new repos + - Automatically creates main and dev branches + - `test-install` justfile recipe for running install tests + - Integration tests for git repo initialization, branches, and initial commit +- **Commit message standardization** ([#36](https://github.com/vig-os/devcontainer/issues/36)) + - Commit message format: `type(scope)!: subject` with mandatory `Refs: #` line + - Documentation: `docs/COMMIT_MESSAGE_STANDARD.md` defining format, approved types (feat, fix, docs, chore, refactor, test, ci, build, revert, style), and traceability requirements + - Validation script: `scripts/validate_commit_msg.py` enforcing the standard + - Commit-msg hook: `.githooks/commit-msg` runs validation on every commit + - Pre-commit integration: commit-msg stage hook in `.pre-commit-config.yaml` + - Git commit template: `.gitmessage` with format placeholder + - Cursor integration: `.cursor/rules/commit-messages.mdc` and `.cursor/commands/commit-msg.md` for AI-assisted commit messages + - Workspace template: all commit message tooling included in `assets/workspace/` for new projects + - Tests: `tests/test_validate_commit_msg.py` with comprehensive validation test cases +- **nano text editor** in devcontainer image ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Chore Refs exemption** in commit message standard ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - `chore` commits may omit the `Refs:` line when no issue or PR is directly related + - Validator updated with `REFS_OPTIONAL_TYPES` to accept chore commits without Refs +- **Dependency review allowlist entry** for debug@0.6.0 ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Added GHSA-9vvw-cc9w-f27h exception to `.github/dependency-review-allow.txt` + - Addresses ReDoS vulnerability in transitive test dependency (bats-assert → verbose → debug) + - High risk severity but isolated to CI/development environment with expiration 2026-11-17 +|- **Dependency review exception for legacy test vulnerabilities** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Comprehensive acceptance register for 9 transitive vulnerabilities in unmaintained BATS test framework dependencies + - All 9 vulnerabilities are isolated to CI/development environment (engine.io, debug, node-uuid, qs, tough-cookie, ws, xmlhttprequest, form-data) + - Formal risk assessments and mitigations documented in `SECURITY.md` and `.github/dependency-review-allow.txt` + - Expiration-enforced exceptions with 2026-11-17 expiration date to force periodic re-evaluation + +- **Bandit and Safety security scanning** ([#37](https://github.com/vig-os/devcontainer/issues/37), [#50](https://github.com/vig-os/devcontainer/issues/50)) + - Bandit pre-commit hook for medium/high/critical severity Python code analysis + - CI pipeline job with Bandit static analysis and Safety dependency vulnerability scanning + - Reports uploaded as artifacts (30-day retention) with job summary integration +- **Scheduled weekly security scan workflow** (`security-scan.yml`) ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Full Trivy vulnerability scan (all severities) against `dev` branch every Monday 06:00 UTC + - SBOM generation (CycloneDX) and SARIF upload to GitHub Security tab + - Non-blocking: catches newly published CVEs between pull requests +- **Non-blocking unfixed vulnerability reporting in CI** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Additional CI scan step reports unfixed HIGH/CRITICAL CVEs for awareness without blocking the pipeline +- **Comprehensive `.trivyignore` vulnerability acceptance register** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Formal medtech-compliant register (IEC 62304 / ISO 13485) documenting 10 accepted CVEs + - Each entry includes risk assessment, exploitability justification, fix status, and mitigation + - 6-month expiration dates enforce periodic re-evaluation +- **Expiration-enforced dependency-review exceptions** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Allow GHSA-wvrr-2x4r-394v (bats-file false positive) via `.github/dependency-review-allow.txt` + - CI validation step parses expiration dates and fails the pipeline when exceptions expire, forcing periodic review +- **Branch name enforcement as a pre-commit hook** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - New `branch-name` hook enforcing `/-` convention (e.g. `feature/38-standardize-branching-strategy-enforcement`) + - Pre-commit configuration updated in repo and in workspace assets (`.pre-commit-config.yaml`, `assets/workspace/.pre-commit-config.yaml`) + - Integration tests added for valid and invalid branch names +- **Cursor rules for branch naming and issue workflow** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - `.cursor/rules/branch-naming.mdc`: topic branch naming format, branch types, workflow for creating/linking branches via `gh issue develop` + - Guidelines for inferring branch type from issue labels and deriving short summary from issue title +- **Release cycle documentation** ([#38](https://github.com/vig-os/devcontainer/issues/38), [#48](https://github.com/vig-os/devcontainer/issues/48)) + - `docs/RELEASE_CYCLE.md` with complete release workflow, branching strategy, and CI/CD integration + - Cursor commands: `after-pr-merge.md`, `submit-pr.md` +- **pip-licenses** installed system-wide with version verification test ([#43](https://github.com/vig-os/devcontainer/issues/43)) +- **just-lsp** language server and VS Code extension for Just files ([#44](https://github.com/vig-os/devcontainer/issues/44)) +- **Automated release cycle** ([#48](https://github.com/vig-os/devcontainer/issues/48)) + - `prepare-release` and `finalize-release` justfile commands triggering GitHub Actions workflows + - `prepare-changelog.py` script with prepare, validate, reset, and finalize commands for CHANGELOG automation + - `reset-changelog` justfile command for post-merge CHANGELOG cleanup + - `prepare-release.yml` GitHub Actions workflow: validates semantic version, creates release branch, prepares CHANGELOG + - Unified `release.yml` pipeline: validate → finalize → build/test → publish → rollback + - Comprehensive test suite in `tests/test_release_cycle.py` +- **CI testing infrastructure** ([#48](https://github.com/vig-os/devcontainer/issues/48)) + - `ci.yml` workflow replacing `test.yml` with streamlined project checks (lint, changelog validation, utility and release-cycle tests) + - Reusable composite actions: `setup-env`, `build-image`, `test-image`, `test-integration`, `test-project` + - Artifact transfer between jobs for consistent image testing + - Retry logic across all CI operations for transient failure handling +- **GitHub Actions SHA pinning enforcement** ([#50](https://github.com/vig-os/devcontainer/issues/50)) + - `scripts/check_action_pins.py` pre-commit hook and CI check ensuring all GitHub Actions and Docker actions reference commit SHAs + - Comprehensive test suite in `tests/test_check_action_pins.py` +- **CODEOWNERS** for automated review assignment ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **SECURITY.md** with vulnerability reporting procedures and supported version policy ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **OpenSSF Scorecard workflow** (`scorecard.yml`) for supply chain security scoring ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **CodeQL analysis workflow** (`codeql.yml`) for automated static security analysis ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Dependabot configuration** for automated dependency update PRs with license compliance monitoring ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Vulnerability scanning and dependency review** in CI pipeline with non-blocking MEDIUM severity reporting ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **SBOM generation, container signing, and provenance attestation** in release and CI pipelines ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Edge case tests** for changelog validation, action SHA pinning, and install script ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **`vig-utils` reusable CLI utilities package** ([#51](https://github.com/vig-os/devcontainer/issues/51)) + - Python package in `packages/vig-utils/` for shared validation and build utilities + - `validate_commit_msg` module: enforces commit message format and references standards + - Configurable commit scopes validation: scope list can be customized per project + - Scopes are optional by default; if used, must be in the approved list + - Support for multiple scopes, comma-separated (e.g., `feat(api, cli): add feature`) + - Support for GitHub auto-linked issue references (e.g., PR cross-repo links) + - Comprehensive test suite with edge case coverage for PR and cross-repo issue links + - `prepare_changelog` module: CHANGELOG management and validation + - `check_action_pins` module: GitHub Actions SHA pinning enforcement + - Integrated into CI/CD pipeline and pre-commit hooks as standard Python package + - Package version tests verify installation and correct versioning +- **Code coverage reporting in CI** ([#52](https://github.com/vig-os/devcontainer/issues/52)) + - Code coverage measurement integrated into test action workflow + - Coverage threshold raised to 50% for unit tests + - Expanded unit tests to improve overall test coverage +- **File duplication detection and elimination** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Build-time manifest system detects and removes duplicated workspace assets + - Replaces duplicated files with sync manifest entries, reducing redundancy + - Workspace assets now synchronized from central manifest during build preparation + - GitHub workflow templates for devcontainer projects included in sync manifest + - Automated npm dependency management with centralized version pinning in `.github/package.json` + - Extract build preparation into dedicated `prepare-build.sh` script with manifest sync + - SHA-256 checksum verification tests for synced files via `parse_manifest` fixture and `test_manifest_files` +- **GitHub workflow templates for devcontainer projects** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Reusable workflow templates for continuous integration and deployment + - Support for projects using devcontainer-based development environments +- **Centralized `@devcontainers/cli` version management** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Version pinned in `.github/package.json` for consistent behavior across workflows and builds + - Ensures reproducibility across build and setup environments +- **`--require-scope` flag for `validate-commit-msg`** ([#58](https://github.com/vig-os/devcontainer/issues/58)) + - New CLI flag to mandate that all commits include at least one scope (e.g. `feat(api): ...`) + - When enabled, scopeless commits (e.g. `feat: ...`) are rejected at the commit-msg stage + - Comprehensive tests added to `test_validate_commit_msg.py` +- **`post-start.sh` devcontainer lifecycle script** ([#60](https://github.com/vig-os/devcontainer/issues/60)) + - New script runs on every container start (create + restart) + - Handles Docker socket permissions and dependency sync via `just sync` + - Replaces inline `postStartCommand` in `devcontainer.json` +- **Dependency sync delegated to `just sync` across all lifecycle hooks** ([#60](https://github.com/vig-os/devcontainer/issues/60)) + - `post-create.sh`, `post-start.sh`, and `post-attach.sh` now call `just sync` instead of `uv sync` directly + - `justfile.base` `sync` recipe updated with `--all-extras --no-install-project` flags and `pyproject.toml` guard + - Abstracts toolchain details so future dependency managers only need a recipe change + +- **Git initialization default branch** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Updated git initialization to set the default branch to 'main' instead of 'master' + - Consolidated Podman installation with other apt commands in Containerfile +- **CI release workflow uses GitHub API** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Replace local git operations with GitHub API in prepare-release workflow + - Use commit-action for CHANGELOG updates instead of local git + - Replace git operations with GitHub API in release finalization flow + - Simplify rollback and tag deletion to use gh api + - Add sync-dependencies input to setup-env action (default: false) + - Remove checkout step from setup-env; callers must checkout explicitly + - Update all workflow callers to pass sync-dependencies input + - Update CI security job to use uv with setup-env action +- **Commit message guidelines** - updated documentation ([#36](https://github.com/vig-os/devcontainer/issues/37)) +- **Expected version checks** - updated ruff and pre-commit versions in test suite ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Bumped `actions/create-github-app-token`** from v1 to v2 across workflows with updated SHA pins ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Pinned `@devcontainers/cli`** to version 0.81.1 in CI for consistent behavior ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **CI and release Trivy scans gate on fixable CVEs only** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Added `ignore-unfixed: true` to blocking scan steps in `ci.yml` and `release.yml` + - Unfixable CVEs no longer block the pipeline; documented in `.trivyignore` with risk assessments +- **Updated pre-commit hook configuration in the devcontainer** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - Exclude issue and template docs from .github_data + - Autofix shellcheck + - Autofix pymarkdown + - Add license compliance check +- **Renamed `publish-container-image.yml` to `release.yml`** and expanded into unified release pipeline ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Merged `prepare-build.sh` into `build.sh`** — consolidated directory preparation, asset copying, placeholder replacement, and README updates into a single entry point ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Consolidated test files by domain** — reorganized from 6 files to 4 (`test_image.py`, `test_integration.py`, `test_utils.py`, `test_release_cycle.py`) ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Replaced `setup-python-uv` with flexible `setup-env` composite action** supporting optional inputs for podman, Node.js, and devcontainer CLI ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Reduced `sync-issues` workflow triggers** — removed `edited` event type from issues and pull_request triggers ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Release workflow pushes tested images** instead of rebuilding after tests pass ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Updated CONTRIBUTE.md** release workflow documentation to match automated process ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **CodeQL Action v3 → v4 upgrade** + - Updated all CodeQL Action references from v3 (deprecated Dec 2026) to v4.32.2 + - Updated in `.github/workflows/codeql.yml`, `security-scan.yml`, and `ci.yml` + - Uses commit hash `45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2` for integrity +- **Sync-issues workflow output directory** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Changed output directory from '.github_data' to 'docs' for better project structure alignment +- **Workspace `validate-commit-msg` hook configured strict-by-default** ([#58](https://github.com/vig-os/devcontainer/issues/58)) + - `assets/workspace/.pre-commit-config.yaml` now ships with explicit `args` instead of commented-out examples + - Default args enable type enforcement, scope enforcement with `--require-scope`, and `chore` refs exemption + - Link to `vig-utils` README added as a comment above the hook for discoverability +- **Refresh pinned Python base image digest** ([#213](https://github.com/vig-os/devcontainer/issues/213)) + - Update `python:3.12-slim-bookworm` pinned digest in `Containerfile` to the latest upstream value while keeping the same tag +- **Pre-commit hook removal transform preserves section comments** ([#171](https://github.com/vig-os/devcontainer/issues/171)) + - `scripts/transforms.py` keeps section comments intact while removing configured hooks during manifest sync + - `scripts/manifest.toml` and related sync/test updates keep workspace pre-commit outputs aligned with container CI workflow changes +- **Migrate shared scripts into `vig-utils` package entrypoints** ([#217](https://github.com/vig-os/devcontainer/issues/217), [#161](https://github.com/vig-os/devcontainer/issues/161), [#179](https://github.com/vig-os/devcontainer/issues/179)) + - Shell scripts (`check-skill-names.sh`, `derive-branch-summary.sh`, `resolve-branch.sh`, `setup-labels.sh`) bundled inside `vig_utils.shell` and exposed as `vig-` CLI entrypoints + - Python scripts (`gh_issues.py`, `check-agent-identity.py`, `check-pr-agent-fingerprints.py`, `prepare-commit-msg-strip-trailers.py`) migrated into `vig-utils` modules with entrypoints + - Agent fingerprint helpers consolidated into shared `vig_utils.utils` module + - Callers (justfiles, pre-commit hooks, CI workflows) switched from direct script paths to `vig-utils` entrypoints +- **Restructure workspace justfile into devc/project split** ([#219](https://github.com/vig-os/devcontainer/issues/219)) + - Rename `justfile.base` to `justfile.devc` and keep devcontainer lifecycle recipes there + - Move project-level recipes (`lint`, `format`, `precommit`, `test`, `sync`, `update`, `clean-artifacts`, `log`, `branch`) into `justfile.project` + - Add tracked `justfile.local` template for personal recipes while keeping it ignored in downstream workspaces, and update workspace imports/manifests to the new structure +- **Update base Python image and GitHub Actions dependencies** ([#240](https://github.com/vig-os/devcontainer/issues/240)) + - Containerfile: pin `python:3.12-slim-bookworm` to latest digest + - Bump trivy CLI v0.69.2 → v0.69.3, trivy-action v0.33.1 → v0.35.0 + - Update astral-sh/setup-uv, taiki-e/install-action, docker/build-push-action, github/codeql-action, actions/dependency-review-action, actions/attest-build-provenance +- **Bump GitHub CLI to 2.88.x** + - Update expected `gh` version in image tests from 2.87 to 2.88 +- **Manifest sync includes `sync-main-to-dev` workflow** ([#278](https://github.com/vig-os/devcontainer/issues/278)) + - Add `.github/workflows/sync-main-to-dev.yml` to `scripts/manifest.toml` so workspace sync includes the release-to-dev PR automation workflow + + +### Removed + +- **`post-release.yml`** — replaced by `sync-main-to-dev.yml` ([#172](https://github.com/vig-os/devcontainer/issues/172)) +- **`scripts/prepare-build.sh`** — merged into `build.sh` ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **`scripts/sync-prs-issues.sh`** — deprecated sync script ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **`test.yml` workflow** — replaced by `ci.yml` ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Stale `.github_data/` directory** — 98 files superseded by `docs/issues/` and `docs/pull-requests/` ([#91](https://github.com/vig-os/devcontainer/issues/91)) +- **Legacy standalone script copies** ([#217](https://github.com/vig-os/devcontainer/issues/217)) + - Removed `scripts/check-agent-identity.py`, `scripts/check-skill-names.sh`, `scripts/derive-branch-summary.sh`, `scripts/resolve-branch.sh` — now in `vig-utils` + - Removed `assets/workspace/.devcontainer/scripts/gh_issues.py`, `check-pr-agent-fingerprints.py`, `prepare-commit-msg-strip-trailers.py` — now in `vig-utils` + - Removed `scripts/utils.py` shim — superseded by `vig_utils.utils` + +### Fixed + +- **`just` default recipe hidden by lint recipe** ([#254](https://github.com/vig-os/devcontainer/issues/254)) + - The `default` recipe must appear before any other recipe in the justfile; `lint` was placed first, shadowing the recipe listing + - Moved `default` recipe above `lint` to restore `just` with no arguments showing available recipes +- **Broken `gh-issues --help` guard in justfile recipe** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - `gh-issues` CLI has no `--help` flag, so the availability check always failed even when the binary was installed + - Removed the broken guard; binary availability is now verified by the image test suite +- **Smoke-test redeploy preserves synced docs directories** ([#262](https://github.com/vig-os/devcontainer/issues/262)) + - `init-workspace.sh --smoke-test` now excludes `docs/issues/` and `docs/pull-requests/` from `rsync --delete` + - Re-deploying smoke assets no longer removes docs synced by `sync-issues` +- **Prepare-release uses scoped app tokens for protected branch writes** ([#268](https://github.com/vig-os/devcontainer/issues/268)) + - `prepare-release.yml` now uses `COMMIT_APP_*` for git/ref and `commit-action` operations that touch `dev` and release refs + - Draft PR creation in prepare-release now uses `RELEASE_APP_*` token scope for pull-request operations +- **generate-docs picks up unreleased TBD version on release branches** ([#271](https://github.com/vig-os/devcontainer/issues/271)) + - `get_version_from_changelog()` and `get_release_date_from_changelog()` now skip entries without a concrete release date +- **PR fingerprint check false positives on plain-text AI tool mentions** ([#274](https://github.com/vig-os/devcontainer/issues/274)) + - `contains_agent_fingerprint` now restricts name matching to attribution-context lines (e.g. "generated by", "authored by") instead of scanning the entire content + - Wire up `allow_patterns` from `agent-blocklist.toml` to strip known-safe text (dotfile paths, doc filenames) before checking +- **Release candidate publish retags loaded images before push** ([#281](https://github.com/vig-os/devcontainer/issues/281)) + - `release.yml` now tags `ghcr.io/vig-os/devcontainer:X.Y.Z-arch` artifacts as `X.Y.Z-rcN-arch` before `docker push` in candidate runs + - Prevents publish failures caused by pushing candidate tags that were never created locally after `docker load` +- **Pinned commit-action to the malformed path fix release** ([#286](https://github.com/vig-os/devcontainer/issues/286)) + - Updated smoke-test and release-related workflows to `vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646` (`v0.1.5`) + - Resolves failures when commit-action receives `FILE_PATHS: .` and accidentally includes invalid `.git/*` tree paths +- **Smoke-test deploy commit no longer references non-local issue IDs** ([#284](https://github.com/vig-os/devcontainer/issues/284)) + - `assets/smoke-test/.github/workflows/repository-dispatch.yml` no longer injects `Refs: #258` into automated `chore: deploy ` commits in the smoke-test repository + - Added maintainer note that workflow-template changes require manual redeploy to `vig-os/devcontainer-smoke-test` and promotion through PRs to `main` +- **Install name sanitization trims invalid package boundaries** ([#291](https://github.com/vig-os/devcontainer/issues/291)) + - `install.sh` now normalizes sanitized project names to ensure they start/end with alphanumeric characters before passing `SHORT_NAME` + - `init-workspace.sh` mirrors the same normalization so generated `pyproject.toml` names cannot end with separators like `_` + +### Security + +- **Eliminated 13 transitive vulnerabilities in BATS test dependencies** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Bumped bats-assert from v2.1.0 to v2.2.0, which dropped a bogus runtime dependency on the `verbose` npm package + - Removed entire transitive dependency tree: engine.io, debug, node-uuid, qs, tough-cookie, ws, xmlhttprequest, form-data, request, sockjs, and others (50+ packages reduced to 5) + - Cleaned 13 now-unnecessary GHSA exceptions from `.github/dependency-review-allow.txt` +- **Go stdlib CVEs from gh binary accepted and documented** ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- CVE-2025-68121, CVE-2025-61726, CVE-2025-61728, CVE-2025-61730 added to `.trivyignore` +- Vulnerabilities embedded in statically-linked GitHub CLI binary; low exploitability in devcontainer context +- Each entry includes risk assessment, justification, and 3-month expiration date to force re-review +- Awaiting upstream `gh` release with Go 1.25.7 or later +- **GHSA-wvrr-2x4r-394v (bats-file false positive) accepted in dependency review** ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- Added to `.github/dependency-review-allow.txt` with 6-month expiration date enforced by CI +- **Upgraded pip** in Containerfile to fix CVE-2025-8869 (symbolic link extraction vulnerability) ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Digest-pinned base image** (`python:3.12-slim-bookworm`) with SHA256 checksum verification for all downloaded binaries and `.trivyignore` risk-assessment policy in Containerfile ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Minisign signature verification** for cargo-binstall downloads ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **GitHub Actions and Docker actions pinned to commit SHAs** across all workflows ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Pre-commit hook repos pinned to commit SHAs** ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Workflow permissions hardened** with least-privilege principle and explicit token scoping ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Input sanitization** — inline expression interpolation replaced with environment variables in workflow run blocks to prevent injection ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Update vulnerable Python dependencies** ([#88](https://github.com/vig-os/devcontainer/issues/88)) + - Add uv constraints for transitive dependencies: `urllib3>=2.6.3`, `filelock>=3.20.3`, and `virtualenv>=20.36.1` + - Regenerate `uv.lock` with patched resolutions (`urllib3 2.6.3`, `filelock 3.25.0`, `virtualenv 21.1.0`) +- **Temporary Trivy exception for CVE-2025-15558 in gh binary** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Added `CVE-2025-15558` to `.trivyignore` with risk assessment, upstream dependency context, and an expiration date + - Keeps CI vulnerability scan unblocked while waiting for an upstream `gh` release that includes the patched `github.com/docker/cli` dependency + +## [0.2.1](https://github.com/vig-os/devcontainer/releases/tag/0.2.1) - 2026-01-28 + +### Added + +- **Manual target branch specification** for sync-issues workflow + - Added `target-branch` input to `workflow_dispatch` trigger for manually specifying commit target branch + - Allows explicit branch selection when triggering workflow manually (e.g., `main`, `dev`) +- **cargo-binstall** in Containerfile + - Install via official install script; binaries in `/root/.cargo/bin` with `ENV PATH` set + - Version check in `tests/test_image.py` +- **typstyle** linting + - Install via `cargo-binstall` in Containerfile + - Version check in test suite + - Pre-commit hook configuration for typstyle +- **Just command runner** installation and version verification + - Added installation of the latest version of `just` (1.46.0) in the Containerfile + - Added tests to verify `just` installation and version in `test_image.py` + - Added integration tests for `just` recipes (`test_just_default`, `test_just_help`, `test_just_info`, `test_just_pytest`) +- **GitHub Actions workflow for multi-architecture container image publishing** (`.github/workflows/release.yml`) + - Automated build and publish workflow triggered on semantic version tags (X.Y.Z) + - Multi-architecture support (amd64, arm64) with parallel builds on native runners + - Image testing before push: runs `pytest tests/test_image.py` against built images + - Manual dispatch support for testing workflow changes without pushing images (default version: 99.0.1) + - Optional manual publishing: `workflow_dispatch` can publish images/manifests when `publish=true` (default false) + - Architecture validation and dynamic selection: users can specify single or multiple architectures (amd64, arm64) with validation + - Comprehensive error handling and verification steps + - OCI-standard labels via `docker/metadata-action` + - Build log artifacts for debugging (always uploaded for manual dispatch and on failure) + - Multi-architecture manifest creation for automatic platform selection + - Centralized version extraction job for reuse across build and manifest jobs + - Concurrency control to prevent duplicate builds + - Timeout protection (60 minutes for builds, 10 minutes for manifest) +- **GitHub Actions workflow for syncing issues and PRs** (`.github/workflows/sync-issues.yml`) + - Automated sync of GitHub issues and PRs to markdown files in `.github_data/` + - Runs on schedule (daily), manual trigger, issue events, and PR events + - Smart branch selection: commits to `main` when PRs are merged into `main`, otherwise commits to `dev` + - Cache-based state management to track last sync timestamp + - Force update option for manual workflow dispatch +- **Enhanced test suite** + - Added utility function tests (`tests/test_utils.py`) for `sed_inplace` and `update_version_line` + - Improved test organization in justfile with grouped test commands (`just test-all`, `just test-image`, `just test-utils`) +- **Documentation improvements** + - Added workflow status badge to README template showing publish workflow status + - Simplified contribution guidelines by removing QEMU build instructions + +### Changed + +- **Sync-issues workflow branch protection bypass** + - Added GitHub App token generation step using `actions/create-github-app-token@v2` + - Updated commit-action to use GitHub App token for bypassing branch protection rules + - Updated `vig-os/commit-action` from `v0.1.1` to `v0.1.3` + - Changed commit-action environment variable from `GITHUB_TOKEN`/`GITHUB_REF` to `GH_TOKEN`/`TARGET_BRANCH` to match action's expected interface +- **Devcontainer test fixtures** (`tests/conftest.py`) + - Shared helpers for `devcontainer_up` and `devcontainer_with_sidecar`: path resolution, env/SSH, project yaml mount, run up, teardown + - `devcontainer_with_sidecar` scope set to session (one bring-up per session for sidecar tests) + - Cleanup uses same approach as `just clean-test-containers` (list containers by name, `podman rm -f`) so stacks are torn down reliably + - Redundant imports removed; fixture logic simplified for maintainability +- **Build process refactoring** + - Separated build preparation into dedicated `prepare-build.sh` script + - Handles template replacement, asset copying, and README version updates + - Improved build script with `--no-cache` flag support and better error handling +- **Development workflow streamlining** + - Simplified contribution guidelines: removed QEMU build instructions and registry testing complexity + - Consolidated test commands in justfile for better clarity + - Updated development setup instructions to reflect simplified workflow +- **Package versions** + - Updated `ruff` from 0.14.10 to 0.14.11 in test expectations + +### Removed + +- **Deprecated justfile test recipe and test** + - Removed deprecated test command from justfile + - Removed deprecated test for default recipe in justfile (`TestJustIntegration.test_default_recipe_includes_check`) +- **Registry testing infrastructure** (moved to GitHub Actions workflow) + - Removed `scripts/push.sh` (455 lines) - functionality now in GitHub Actions workflow + - Removed `tests/test_registry.py` (788 lines) - registry tests now in CI/CD pipeline + - Removed `scripts/update_readme.py` (80 lines) - README updates handled by workflow + - Removed `scripts/utils.sh` (75 lines) - utilities consolidated into other scripts + - Removed `just test-registry` command - no longer needed with automated workflow + +### Fixed + +- **Multi-platform container builds** in Containerfile + - Removed default value from `TARGETARCH` ARG to allow Docker BuildKit's automatic platform detection + - Fixes "Exec format error" when building for different architectures (amd64, arm64) + - Ensures correct architecture-specific binaries are downloaded during build +- **Image tagging after podman load** in publish workflow + - Explicitly tag loaded images with expected name format (`ghcr.io/vig-os/devcontainer:VERSION-ARCH`) + - Fixes test failures where tests couldn't find the image after loading from tar file + - Ensures proper image availability for testing before publishing +- **GHCR publish workflow push permissions** + - Authenticate to `ghcr.io` with the repository owner and token context, and set explicit job-level `packages: write` permissions to prevent `403 Forbidden` errors when pushing layers. +- **Sync-issues workflow branch determination logic** + - Fixed branch selection to prioritize manual `target-branch` input when provided via `workflow_dispatch` + - Improved branch detection: manual input → PR merge detection → default to `dev` +- **Justfile default recipe conflict** + - Fixed multiple default recipes issue by moving `help` command to the main justfile + - Removed default command from `justfile.project` and `justfile.base` to prevent conflicts + - Updated just recipe tests to handle variable whitespace in command output formatting +- **Invalid docker-compose.project.yaml** + - Added empty services section to docker-compose.project.yaml to fix YAML validation +- **Python import resolution in tests** + - Fixed import errors in `tests/test_utils.py` by using `importlib.util` for explicit module loading + - Improved compatibility with static analysis tools and linters +- **Build script improvements** + - Fixed shellcheck warnings by properly quoting script paths + - Improved debug output and error messages + +## [0.2.0](https://github.com/vig-os/devcontainer/releases/tag/0.2.0) - 2026-01-06 + +### Added + +- **Automatic version check** for devcontainer updates with DRY & SOLID design + - Checks GitHub API for new releases and notifies users when updates are available + - Silent mode with graceful failure (no disruption to workflow) + - Configurable check interval (default: 24 hours) with spam prevention + - Mute notifications for specified duration (`just check 7d`, `1w`, `12h`, etc.) + - Enable/disable toggle (`just check on|off`) + - One-command update: `just update` downloads install script and updates template files + - Configuration stored in `.devcontainer/.local/` (gitignored, machine-specific) + - Auto-runs on `just` default command (can be disabled) + - Comprehensive test suite (`tests/test_version_check.py`) with 24 tests covering all functionality +- **One-line install script** (`install.sh`) for curl-based devcontainer deployment + - Auto-detection of podman/docker runtime (prefers podman) + - Auto-detection and sanitization of project name from folder name (lowercase, underscores) + - OS-specific installation instructions when runtime is missing (macOS, Ubuntu, Fedora, Arch, Windows) + - Runtime health check with troubleshooting advice (e.g., "podman machine start" on macOS) + - Flags: `--force`, `--version`, `--name`, `--dry-run`, `--docker`, `--podman`, `--skip-pull` +- `--no-prompts` flag for `init-workspace.sh` enabling non-interactive/CI usage +- `SHORT_NAME` environment variable support in `init-workspace.sh` +- Test suite for install script (`tests/test_install_script.py`) with unit and integration tests +- `just` as build automation tool (replaces `make`) +- Layered justfile architecture: `justfile.base` (managed), `justfile.project` (team-shared), `justfile.local` (personal) +- Generic sidecar passthrough: `just sidecar ` for executing commands in sidecar containers +- Documentation generation system (`docs/generate.py`) with Jinja2 templates +- Python project template with `pyproject.toml` and standard structure (`src/`, `tests/`, `docs/`) +- Pre-built Python virtual environment with common dev/science dependencies (numpy, scipy, pandas, matplotlib, pytest, jupyter) +- Auto-sync Python dependencies on container attach via `uv sync` +- `UV_PROJECT_ENVIRONMENT` environment variable for instant venv access without rebuild +- `pip-licenses` pre-commit hook for dependency license compliance checking (blocks GPL-3.0/AGPL-3.0) +- Pre-flight container cleanup check in test suite with helpful error messages +- `just clean-test-containers` recipe for removing lingering test containers +- `PYTEST_AUTO_CLEANUP` environment variable for automatic test container cleanup +- `docker-compose.project.yaml` for team-shared configuration (git-tracked, preserved during upgrades) +- `docker-compose.local.yaml` for personal configuration (git-ignored, preserved during upgrades) +- Build-time manifest generation for optimized placeholder replacement +- `tests/CLEANUP.md` documentation for test container management + +### Changed + +- `ORG_NAME` now defaults to `"vigOS/devc"` instead of requiring user input +- `init-workspace.sh` now escapes special characters in placeholder values (fixes sed errors with `/` in ORG_NAME) +- Documentation updated with curl-based install as primary quick start method +- **BREAKING**: Replaced `make` with `just` - all build commands now use `just` (e.g., `just test`, `just build`, `just push`) +- **Versioning scheme**: Switched from X.Y format to Semantic Versioning (X.Y.Z format). +All new releases use MAJOR.MINOR.PATCH format (e.g., 0.2.0). +The previous v0.1 release is kept as-is for backwards compatibility. +- **Package versions**: Bumped tool and project versions from previous release: + - `uv` (0.9.17 → 0.9.21) + - `gh` (2.83.1 → 2.83.2) + - `pre-commit` (4.5.0 → 4.5.1) + - `ruff` (0.14.8 → 0.14.10) +- VS Code Python interpreter now points to pre-built venv (`/root/assets/workspace/.venv`) +- Test container cleanup check runs once at start of `just test` instead of each test phase +- **BREAKING**: Docker Compose file hierarchy now uses `project.yaml` and `local.yaml` instead of `override.yml` +- Socket detection prioritizes Podman over Docker Desktop on macOS and Linux +- `{{TAG}}` placeholder replacement moved to container with build-time manifest generation (significantly faster initialization) +- Socket mount configuration uses environment variable with fallback: `${CONTAINER_SOCKET_PATH:-/var/run/docker.sock}` +- `initialize.sh` writes socket path to `.env` file instead of modifying YAML directly +- `init-workspace.sh` simplified: removed cross-platform `sed` handling (always runs in Linux) + +### Removed + +- Deprecated `version` field from all Docker Compose files +- `:Z` SELinux flag from socket mounts (incompatible with macOS socket files) +- `docker-compose.override.yml` (replaced by `project.yaml` and `local.yaml`) +- `docker-compose.sidecar.yaml` (merged into main `docker-compose.yml`) + +### Fixed + +- Test failures from lingering containers between test phases +(now detected and reported before test run; added `PYTEST_SKIP_CONTAINER_CHECK` environment variable) +- Improved error messages for devcontainer startup failures +- SSH commit signing: Changed `user.signingkey` from file path to email identifier to support SSH agent forwarding. + Git now uses the SSH agent for signing by looking up the email in allowed-signers and matching with the agent key. +- Fixed `gpg.ssh.allowedSignersFile` path to use container path instead of host path after copying git configuration. +- Automatically add git user email to allowed-signers file during setup to ensure commits can be signed and verified. +- macOS Podman socket mounting errors caused by SELinux `:Z` flag on socket files +- Socket detection during tests now matches runtime behavior (Podman-first) + +## [0.1](https://github.com/vig-os/devcontainer/releases/tag/0.1) - 2025-12-10 + +### Core Image + +- Development container image based on Python 3.12 (Debian Trixie) +- Multi-architecture support (AMD64, ARM64) +- System tools: git, gh (GitHub CLI), curl, openssh-client, ca-certificates +- Python tools: uv, pre-commit, ruff +- Pre-configured development environment with minimal overhead + +### Devcontainer Integration + +- VS Code devcontainer template with init-workspace script setting organization and project name +- Docker Compose orchestration for flexible container management +- Support for mounting additional folders via docker-compose.override.yml +- Container lifecycle scripts `post-create.sh`, `initialize.sh` and `post-attach.sh` for seamless development setup +- Automatic Git configuration synchronization from host machine +- SSH commit signing support with signature verification +- Focused `.devcontainer/README.md` with version tracking, lifecycle documentation, and workspace configuration guide +- User-specific `workspace.code-workspace.example` for multi-root VS Code workspaces (actual file is gitignored) + +### Testing Infrastructure + +- Three-tiered test suite: image tests, integration tests, and registry tests +- Automated testing with pytest and testinfra +- Registry tests with optimized minimal Containerfile (10-20s builds) +- Session-scoped fixtures for efficient test execution +- Comprehensive validation of push/pull/clean workflows +- Tests verify devcontainer README version in pushed images +- Helper function tests for README update utilities + +### Automation and Tooling + +- Justfile with build, test, push, pull, and clean recipes +- Automated version management and git tagging +- Automatic README.md updates with version and image size during releases +- Push script with multi-architecture builds and registry validation +- Setup script for development environment initialization +- `update_readme.py` helper script for patching README metadata (version, size, development reset) +- Automatic devcontainer README version updates during releases + +### Documentation and Templates + +- GitHub issue templates (bug report, feature request, task) +- Pull request template with comprehensive checklist +- Complete project documentation (README.md, CONTRIBUTE.md, TESTING.md) +- Detailed testing strategy and workflow documentation +- Push script updates README files in both project and assets diff --git a/assets/workspace/.github/actions/resolve-image/action.yml b/assets/workspace/.github/actions/resolve-image/action.yml new file mode 100644 index 00000000..8782ed8d --- /dev/null +++ b/assets/workspace/.github/actions/resolve-image/action.yml @@ -0,0 +1,74 @@ +name: Resolve devcontainer image +description: Resolve and validate the devcontainer image tag for CI jobs + +inputs: + image-tag: + description: Optional image tag override + required: false + default: '' + +outputs: + image-tag: + description: Resolved image tag + value: ${{ steps.resolve.outputs.tag }} + +runs: + using: composite + steps: + - name: Resolve image tag + id: resolve + shell: bash + env: + INPUT_IMAGE_TAG: ${{ inputs.image-tag }} + run: | + set -euo pipefail + + if [[ -n "$INPUT_IMAGE_TAG" ]]; then + echo "tag=$INPUT_IMAGE_TAG" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [[ -f .vig-os ]]; then + VERSION="" + while IFS= read -r line || [[ -n "${line:-}" ]]; do + [[ -z "${line//[[:space:]]/}" ]] && continue + [[ "$line" =~ ^[[:space:]]*# ]] && continue + + case "$line" in + DEVCONTAINER_VERSION=*) + VERSION="${line#*=}" + VERSION="${VERSION#"${VERSION%%[![:space:]]*}"}" + VERSION="${VERSION%"${VERSION##*[![:space:]]}"}" + + if [[ "$VERSION" =~ ^\".*\"$ ]]; then + VERSION="${VERSION:1:-1}" + elif [[ "$VERSION" =~ ^\'.*\'$ ]]; then + VERSION="${VERSION:1:-1}" + fi + break + ;; + esac + done < .vig-os + + if [[ -n "$VERSION" ]]; then + echo "tag=$VERSION" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + + echo "ERROR: Could not resolve DEVCONTAINER_VERSION from .vig-os and no image-tag override was provided." + exit 1 + + - name: Validate image accessibility + shell: bash + env: + IMAGE_TAG: ${{ steps.resolve.outputs.tag }} + run: | + set -euo pipefail + IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" + echo "Validating image availability: $IMAGE" + if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then + echo "ERROR: Cannot access image manifest: $IMAGE" + echo "Check whether the tag exists and whether this workflow has access to GHCR." + exit 1 + fi diff --git a/assets/workspace/.github/actions/setup-env/action.yml b/assets/workspace/.github/actions/setup-env/action.yml deleted file mode 100644 index 1f775ed4..00000000 --- a/assets/workspace/.github/actions/setup-env/action.yml +++ /dev/null @@ -1,179 +0,0 @@ -# Composite action to set up the CI environment -# -# Installs Python, uv, pre-commit, and optionally syncs project dependencies and installs: -# - Podman (for container operations) -# - Node.js (for JS tooling) -# - Devcontainer CLI + docker-compose wrapper (for integration tests) -# - BATS + helper libraries (for shell script testing) -# -# IMPORTANT: The caller must checkout the repository before using this action. -# This action does NOT checkout code, allowing callers to control ref, token, -# persist-credentials, and other checkout options. -# -# Inputs: -# sync-dependencies: Run uv sync to install project deps (default: false) -# install-podman: Install podman (default: false) -# install-node: Install Node.js (default: false) -# node-version: Node.js version (default: '20') -# install-devcontainer-cli: Install devcontainer CLI + docker-compose wrapper (default: false) -# install-bats: Install BATS + helper libraries (default: false) -# -# Outputs: -# uv-version: The version of uv that was installed -# -# Usage: -# # Minimal (Python + uv only) -# - uses: actions/checkout@v4 -# - uses: ./.github/actions/setup-env -# -# # With project dependencies -# - uses: actions/checkout@v4 -# - uses: ./.github/actions/setup-env -# with: -# sync-dependencies: 'true' -# -# # All tools -# - uses: actions/checkout@v4 -# - uses: ./.github/actions/setup-env -# with: -# sync-dependencies: 'true' -# install-podman: 'true' -# install-devcontainer-cli: 'true' - -name: 'Setup Environment' -description: 'Set up CI environment with Python, uv, pre-commit, and optional tools (podman, Node.js, devcontainer CLI, BATS)' - -inputs: - sync-dependencies: - description: 'Run uv sync to install project dependencies' - required: false - default: 'false' - install-podman: - description: 'Install podman for container operations' - required: false - default: 'false' - install-node: - description: 'Install Node.js' - required: false - default: 'false' - node-version: - description: 'Node.js version (when install-node is true)' - required: false - default: '20' - install-devcontainer-cli: - description: 'Install @devcontainers/cli and docker-compose wrapper (requires Node.js)' - required: false - default: 'false' - install-just: - description: 'Install just command runner for Justfile support' - required: false - default: 'true' - install-bats: - description: 'Install BATS and helper libraries (support, assert, file) for shell testing' - required: false - default: 'false' - -outputs: - uv-version: - description: 'Version of uv installed' - value: ${{ steps.setup-uv.outputs.uv-version }} - -runs: - using: composite - steps: - # ── Python ─────────────────────────────────────────────────────────── - - name: "Set up Python" - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - with: - python-version-file: "pyproject.toml" - - # ── uv ───────────────────────────────────────────────────────────── - - name: Install uv - id: setup-uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 - with: - enable-cache: true - # Install a specific version of uv. - version: "0.10.0" - - # ── pre-commit ─────────────────────────────────────────────────────────── - - name: Install pre-commit - shell: bash - run: python -m pip install --upgrade pre-commit - - # ── Python dependencies ─────────────────────────────────────────────── - - name: Sync Python dependencies - if: inputs.sync-dependencies == 'true' - shell: bash - run: uv sync --frozen --all-extras - - # ── Podman ────────────────────────────────────────────────────────── - - name: Install podman - if: inputs.install-podman == 'true' - shell: bash - run: | - set -euo pipefail - if ! command -v podman &> /dev/null; then - echo "Installing podman..." - sudo apt-get update -qq - sudo apt-get install -y podman - fi - echo "podman $(podman --version)" - - # ── Node.js ───────────────────────────────────────────────────────── - # Also installed when install-devcontainer-cli is true (npm is required) - - name: Install Node.js - if: inputs.install-node == 'true' || inputs.install-devcontainer-cli == 'true' - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ inputs.node-version }} - - # ── Just (task runner) ────────────────────────────────────────────── - - name: Install just - if: inputs.install-just == 'true' - uses: taiki-e/install-action@3035223527de4d6eb6207d7b9f901df966359a8e # just - with: - tool: just - - # ── Devcontainer CLI + docker-compose wrapper ─────────────────────── - # Requires Node.js (automatically installed above when this flag is set) - - name: Install devcontainer CLI - if: inputs.install-devcontainer-cli == 'true' - shell: bash - run: | - set -euo pipefail - - # Create docker-compose wrapper (devcontainer CLI needs standalone docker-compose; - # the runner only has 'docker compose' v2 plugin, not the standalone binary) - printf '#!/bin/sh\nexec docker compose "$@"\n' | sudo tee /usr/local/bin/docker-compose > /dev/null - sudo chmod +x /usr/local/bin/docker-compose - echo "docker-compose wrapper: $(docker-compose version --short)" - - # Install devcontainer CLI (version from package.json) - DEVCONTAINER_VERSION=$(node -p "require('./package.json').dependencies['@devcontainers/cli']") - echo "Installing @devcontainers/cli@${DEVCONTAINER_VERSION}..." - npm install -g "@devcontainers/cli@${DEVCONTAINER_VERSION}" - echo "devcontainer $(devcontainer --version)" - - # ── BATS (shell testing) ───────────────────────────────────────── - # Installs BATS core and helper libraries via the official action. - # Versions match package.json to keep local and CI environments in sync. - - name: Setup BATS and libraries - id: bats - if: inputs.install-bats == 'true' - uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # v4.0.0 - with: - support-version: '0.3.0' - assert-version: '2.1.0' - file-version: '0.4.0' - detik-install: 'false' - - - name: Export BATS_LIB_PATH - if: inputs.install-bats == 'true' - shell: bash - run: | - # The bats-core/bats-action installs libraries to standard system paths - # Set BATS_LIB_PATH so bats_load_library can find them - BATS_LIB_PATH="/usr/lib/bats-support:/usr/lib/bats-assert:/usr/lib/bats-file" - echo "BATS_LIB_PATH=$BATS_LIB_PATH" >> "$GITHUB_ENV" - echo "Exported BATS_LIB_PATH=$BATS_LIB_PATH" diff --git a/assets/workspace/.github/workflows/ci-container.yml b/assets/workspace/.github/workflows/ci-container.yml deleted file mode 100644 index a37893b9..00000000 --- a/assets/workspace/.github/workflows/ci-container.yml +++ /dev/null @@ -1,191 +0,0 @@ -# Container CI Workflow -# -# Proof-of-concept: runs CI jobs inside the devcontainer image using the -# GitHub Actions container: directive. Only tools already baked into the -# image are used — no additional installs. -# -# Jobs: -# 1. lint — Pre-commit hooks (uses image-provided pre-commit + ruff) -# 2. test — Pytest with coverage (pytest installed via uv sync) -# 3. summary — Aggregate results for branch protection -# -# Differences from bare-runner ci.yml are documented in -# docs/container-ci-quirks.md. -# -# Triggers: -# - Pull requests to dev, release/**, and main -# - Manual workflow_dispatch -# - -name: CI (Container) - -on: # yamllint disable-line rule:truthy - pull_request: - branches: - - dev - - 'release/**' - - main - workflow_dispatch: - inputs: - test-suite: - description: 'Test suite to run' - required: false - default: 'all' - type: choice - options: - - all - - lint - - test - image-tag: - description: 'Container image tag to use' - required: false - type: string - -permissions: - contents: read - -jobs: - resolve-image: - name: Resolve image tag - runs-on: ubuntu-22.04 - timeout-minutes: 2 - outputs: - image-tag: ${{ steps.resolve.outputs.tag }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - sparse-checkout: .vig-os - sparse-checkout-cone-mode: false - - - name: Resolve image tag - id: resolve - run: | - MANUAL="${{ inputs.image-tag }}" - if [[ -n "$MANUAL" ]]; then - echo "tag=$MANUAL" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [[ -f .vig-os ]]; then - VERSION="$(grep '^DEVCONTAINER_VERSION=' .vig-os | cut -d= -f2)" - if [[ -n "$VERSION" ]]; then - echo "tag=$VERSION" >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - - echo "tag=latest" >> "$GITHUB_OUTPUT" - - - name: Validate image accessibility - run: | - IMAGE="ghcr.io/vig-os/devcontainer:${{ steps.resolve.outputs.tag }}" - echo "Validating image availability: $IMAGE" - if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then - echo "ERROR: Cannot access image manifest: $IMAGE" - echo "Check whether the tag exists and whether this workflow has access to GHCR." - exit 1 - fi - - lint: - name: Lint & Format (container) - needs: [resolve-image] - runs-on: ubuntu-22.04 - container: - image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} - env: - # Image-baked cache; may miss if repo pins hooks by hash (see docs/container-ci-quirks.md) - PRE_COMMIT_HOME: /opt/pre-commit-cache - # Reuse the venv already present in the image instead of creating a new one - UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv - timeout-minutes: 10 - if: | - github.event_name != 'workflow_dispatch' || - inputs.test-suite == 'all' || - inputs.test-suite == 'lint' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Fix git safe.directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Sync project dependencies - run: uv sync --frozen --all-extras - - - name: Run pre-commit hooks - run: uv run pre-commit run --all-files --show-diff-on-failure - - test: - name: Tests (container) - needs: [resolve-image] - runs-on: ubuntu-22.04 - container: - image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} - env: - # Reuse the venv already present in the image instead of creating a new one - UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv - timeout-minutes: 15 - if: | - github.event_name != 'workflow_dispatch' || - inputs.test-suite == 'all' || - inputs.test-suite == 'test' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Fix git safe.directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Sync project dependencies - run: uv sync --frozen --all-extras - - - name: Run tests with coverage - run: uv run pytest --cov --cov-report=term-missing - - summary: - name: CI Summary (container) - runs-on: ubuntu-22.04 - timeout-minutes: 5 - needs: [resolve-image, lint, test] - if: always() - - steps: - - name: Check results - run: | - echo "CI (Container) Results Summary" - echo "===============================" - echo "" - echo "Resolve image: ${{ needs.resolve-image.result }}" - echo "Lint: ${{ needs.lint.result }}" - echo "Test: ${{ needs.test.result }}" - echo "" - - FAILED=false - - if [ "${{ needs.resolve-image.result }}" = "failure" ] || [ "${{ needs.resolve-image.result }}" = "cancelled" ]; then - echo "ERROR: Image resolution failed" - FAILED=true - fi - - if [ "${{ needs.lint.result }}" = "failure" ]; then - echo "ERROR: Lint checks failed" - FAILED=true - fi - - if [ "${{ needs.test.result }}" = "failure" ]; then - echo "ERROR: Tests failed" - FAILED=true - fi - - if [ "$FAILED" = "true" ]; then - echo "" - echo "One or more checks failed" - exit 1 - fi - - echo "" - echo "All executed checks passed" diff --git a/assets/workspace/.github/workflows/ci.yml b/assets/workspace/.github/workflows/ci.yml index fece7c9e..2e382bf3 100644 --- a/assets/workspace/.github/workflows/ci.yml +++ b/assets/workspace/.github/workflows/ci.yml @@ -1,18 +1,17 @@ # CI Workflow # -# Runs automated code quality checks and tests on pull requests. -# Default template for Python projects using the vigOS devcontainer. +# Runs CI jobs inside the vigOS devcontainer image. +# This is the canonical CI workflow template for workspace projects. # # Jobs: -# 1. lint — Pre-commit hooks (Ruff, yamllint, pymarkdown, shellcheck, etc.) -# 2. test — Pytest with coverage -# 3. security — Python security scanning (Bandit) -# 4. dependency-review — Dependency vulnerability check (PRs only) -# 5. summary — Aggregate results for branch protection +# 1. resolve-image — Determine and validate image tag +# 2. lint — Pre-commit hooks +# 3. test — Pytest +# 4. summary — Aggregate results for branch protection # # Triggers: -# - Pull requests to dev, release/**, and main (runs all checks) -# - Manual workflow_dispatch (select specific suite to run) +# - Pull requests to dev, release/**, and main +# - Manual workflow_dispatch # name: CI @@ -34,15 +33,48 @@ on: # yamllint disable-line rule:truthy - all - lint - test - - security + image-tag: + description: 'Container image tag to use' + required: false + type: string permissions: contents: read jobs: + resolve-image: + name: Resolve image tag + runs-on: ubuntu-22.04 + timeout-minutes: 2 + outputs: + image-tag: ${{ steps.resolve.outputs.image-tag }} + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + with: + image-tag: ${{ inputs.image-tag }} + lint: name: Lint & Format + needs: [resolve-image] runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} + env: + # Image-baked cache; may miss if repo pins hooks by hash (see docs/container-ci-quirks.md) + PRE_COMMIT_HOME: /opt/pre-commit-cache + # Reuse the venv already present in the image instead of creating a new one + UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv timeout-minutes: 10 if: | github.event_name != 'workflow_dispatch' || @@ -53,17 +85,24 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up environment - uses: ./.github/actions/setup-env - with: - sync-dependencies: 'true' + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Sync project dependencies + run: just sync - name: Run pre-commit hooks - run: uv run pre-commit run --all-files --show-diff-on-failure + run: just precommit test: name: Tests + needs: [resolve-image] runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} + env: + # Reuse the venv already present in the image instead of creating a new one + UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv timeout-minutes: 15 if: | github.event_name != 'workflow_dispatch' || @@ -74,98 +113,20 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up environment - uses: ./.github/actions/setup-env - with: - sync-dependencies: 'true' - - - name: Run tests with coverage - run: uv run pytest --cov --cov-report=term-missing --cov-report=xml - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: coverage-report - path: coverage.xml - retention-days: 30 - if-no-files-found: ignore - - security: - name: Security Scan - runs-on: ubuntu-22.04 - timeout-minutes: 10 - if: | - github.event_name != 'workflow_dispatch' || - inputs.test-suite == 'all' || - inputs.test-suite == 'security' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up environment - uses: ./.github/actions/setup-env - with: - sync-dependencies: 'true' - - - name: Install safety - run: uv pip install safety==3.7.0 - - - name: Run Bandit (Python security linting) - id: bandit - run: | - uv run bandit -r src/ -ll -f json -o bandit-report.json || true - echo "Bandit scan completed" - - - name: Run Safety (dependency vulnerability check) - id: safety - run: | - safety check --json > safety-report.json || true - echo "Safety scan completed" - - - name: Upload security reports - if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: security-reports - path: | - bandit-report.json - safety-report.json - retention-days: 30 - if-no-files-found: ignore - - - name: Report Bandit findings - if: always() && steps.bandit.outcome == 'success' - run: | - if [ -f bandit-report.json ]; then - echo "## Security: Bandit" >> $GITHUB_STEP_SUMMARY - python -m json.tool bandit-report.json | head -50 >> $GITHUB_STEP_SUMMARY || true - fi - - dependency-review: - name: Dependency Review - runs-on: ubuntu-22.04 - timeout-minutes: 10 - if: github.event_name == 'pull_request' - permissions: - contents: read - pull-requests: write + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Sync project dependencies + run: just sync - - name: Review dependencies for vulnerabilities - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4 - with: - fail-on-severity: high + - name: Run tests + run: just test summary: name: CI Summary runs-on: ubuntu-22.04 timeout-minutes: 5 - needs: [lint, test, security, dependency-review] + needs: [resolve-image, lint, test] if: always() steps: @@ -174,14 +135,18 @@ jobs: echo "CI Results Summary" echo "===================" echo "" - echo "Lint: ${{ needs.lint.result }}" - echo "Test: ${{ needs.test.result }}" - echo "Security: ${{ needs.security.result }}" - echo "Dependency Review: ${{ needs.dependency-review.result }}" + echo "Resolve image: ${{ needs.resolve-image.result }}" + echo "Lint: ${{ needs.lint.result }}" + echo "Test: ${{ needs.test.result }}" echo "" FAILED=false + if [ "${{ needs.resolve-image.result }}" = "failure" ] || [ "${{ needs.resolve-image.result }}" = "cancelled" ]; then + echo "ERROR: Image resolution failed" + FAILED=true + fi + if [ "${{ needs.lint.result }}" = "failure" ]; then echo "ERROR: Lint checks failed" FAILED=true @@ -192,16 +157,6 @@ jobs: FAILED=true fi - if [ "${{ needs.security.result }}" = "failure" ]; then - echo "ERROR: Security scan failed" - FAILED=true - fi - - if [ "${{ needs.dependency-review.result }}" = "failure" ]; then - echo "ERROR: Dependency review failed" - FAILED=true - fi - if [ "$FAILED" = "true" ]; then echo "" echo "One or more checks failed" diff --git a/assets/workspace/.github/workflows/codeql.yml b/assets/workspace/.github/workflows/codeql.yml index 71de891f..3ee84be2 100644 --- a/assets/workspace/.github/workflows/codeql.yml +++ b/assets/workspace/.github/workflows/codeql.yml @@ -48,11 +48,11 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: languages: ${{ matrix.language }} - name: Run CodeQL analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: category: '/language:${{ matrix.language }}' diff --git a/assets/workspace/.github/workflows/prepare-release.yml b/assets/workspace/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..b316f0f8 --- /dev/null +++ b/assets/workspace/.github/workflows/prepare-release.yml @@ -0,0 +1,360 @@ +name: Prepare Release + +on: # yamllint disable-line rule:truthy + workflow_dispatch: + inputs: + version: + description: "Semantic version to prepare (e.g., 1.2.3)" + required: true + type: string + dry-run: + description: "Validate without making changes" + required: false + default: false + type: boolean + git-user-name: + description: "Git user name for commits" + required: false + default: "github-actions[bot]" + type: string + git-user-email: + description: "Git user email for commits" + required: false + default: "41898282+github-actions[bot]@users.noreply.github.com" + type: string + +permissions: + contents: read + +jobs: + validate: + name: Validate Release Preparation + runs-on: ubuntu-22.04 + timeout-minutes: 10 + outputs: + version: ${{ steps.vars.outputs.version }} + release_branch: ${{ steps.vars.outputs.release_branch }} + image_tag: ${{ steps.resolve_image.outputs.image_tag }} + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: dev + fetch-depth: 0 + + - name: Validate and prepare variables + id: vars + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + VERSION="$INPUT_VERSION" + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "ERROR: Invalid version format '$VERSION'" + echo "Version must follow semantic versioning format: MAJOR.MINOR.PATCH (e.g., 1.2.3)" + exit 1 + fi + RELEASE_BRANCH="release/$VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Resolve container image tag + id: resolve_image + run: | + set -euo pipefail + TAG="" + if [ -f ".vig-os" ]; then + TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) + fi + if [ -z "${TAG:-}" ]; then + TAG="latest" + fi + echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Validate image accessibility + env: + IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} + run: | + set -euo pipefail + IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" + if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then + echo "ERROR: Cannot access image manifest: $IMAGE" + exit 1 + fi + + - name: Verify release branch does not exist + env: + RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} + run: | + set -euo pipefail + if git show-ref --verify --quiet "refs/heads/$RELEASE_BRANCH"; then + echo "ERROR: Release branch already exists locally: $RELEASE_BRANCH" + exit 1 + fi + if git ls-remote --exit-code --heads origin "$RELEASE_BRANCH" > /dev/null 2>&1; then + echo "ERROR: Release branch already exists on remote: $RELEASE_BRANCH" + exit 1 + fi + + - name: Verify tag does not exist + env: + VERSION: ${{ steps.vars.outputs.version }} + run: | + set -euo pipefail + if git rev-parse -q --verify "refs/tags/$VERSION" > /dev/null; then + echo "ERROR: Tag $VERSION already exists" + exit 1 + fi + if git ls-remote --exit-code --tags --refs origin "refs/tags/$VERSION" > /dev/null 2>&1; then + echo "ERROR: Tag $VERSION already exists on remote" + exit 1 + fi + + - name: Verify CHANGELOG has Unreleased section entries + run: | + set -euo pipefail + if ! grep -q "^## Unreleased" CHANGELOG.md; then + echo "ERROR: CHANGELOG.md is missing '## Unreleased' section" + exit 1 + fi + if ! awk ' + /^## Unreleased$/ {in_unreleased=1; next} + /^## \[/ && in_unreleased {exit 1} + in_unreleased && /^[[:space:]]*-[[:space:]]/ {found_entry=1} + END {exit(found_entry ? 0 : 1)} + ' CHANGELOG.md; then + echo "ERROR: CHANGELOG.md Unreleased section has no entries" + exit 1 + fi + + - name: Summary + env: + VERSION: ${{ steps.vars.outputs.version }} + RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} + IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} + DRY_RUN: ${{ inputs.dry-run }} + run: | + echo "Validation passed" + echo "Version: $VERSION" + echo "Release branch: $RELEASE_BRANCH" + echo "Image tag: $IMAGE_TAG" + echo "Dry-run: $DRY_RUN" + + prepare: + name: Prepare Release Branch + needs: validate + runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }} + env: + UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv + timeout-minutes: 15 + defaults: + run: + shell: bash + if: ${{ inputs.dry-run != true }} + permissions: + contents: write + pull-requests: write + + steps: + - name: Generate Commit App Token + id: commit_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.COMMIT_APP_ID }} + private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} + + - name: Generate Release App Token + id: release_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Checkout dev branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: dev + fetch-depth: 0 + + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Capture pre-prepare dev SHA + id: pre_state + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + run: | + set -euo pipefail + PREPARE_START_SHA=$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') + echo "prepare_start_sha=$PREPARE_START_SHA" >> "$GITHUB_OUTPUT" + + - name: Prepare CHANGELOG (freeze + reset) + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + prepare-changelog prepare "$VERSION" CHANGELOG.md + + - name: Extract CHANGELOG content for PR body + id: changelog + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + CHANGELOG_CONTENT=$(sed -n "/## \[$VERSION\]/,/## \[/p" CHANGELOG.md | sed '$d') + { + echo "changelog<> "$GITHUB_OUTPUT" + + - name: Commit prepared CHANGELOG to dev via API + uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + TARGET_BRANCH: refs/heads/dev + COMMIT_MESSAGE: |- + chore: freeze changelog for release ${{ needs.validate.outputs.version }} + + Move Unreleased content to [${{ needs.validate.outputs.version }}] - TBD + and create fresh empty Unreleased section for continued development. + FILE_PATHS: CHANGELOG.md + + - name: Create release branch from dev + id: create_branch + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} + run: | + set -euo pipefail + DEV_SHA=$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/refs" \ + -f ref="refs/heads/$RELEASE_BRANCH" \ + -f sha="$DEV_SHA" || { + if retry --retries 2 --backoff 5 --max-backoff 20 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/$RELEASE_BRANCH" >/dev/null 2>&1; then + echo "Release branch already exists: $RELEASE_BRANCH" + else + exit 1 + fi + } + echo "dev_sha=$DEV_SHA" >> "$GITHUB_OUTPUT" + + - name: Strip empty Unreleased section for release branch + run: | + set -euo pipefail + python3 -c " + import re + from pathlib import Path + content = Path('CHANGELOG.md').read_text() + content = re.sub( + r'## Unreleased\s*\n(?:### \w+\s*\n\s*)*\n*(?=## \[)', + '', + content, + ) + Path('CHANGELOG.md').write_text(content) + " + + - name: Commit stripped CHANGELOG to release branch via API + uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + TARGET_BRANCH: refs/heads/${{ needs.validate.outputs.release_branch }} + COMMIT_MESSAGE: |- + chore: prepare release ${{ needs.validate.outputs.version }} + + Strip empty Unreleased section from release branch. + Release date TBD (set during finalization). + FILE_PATHS: CHANGELOG.md + + - name: Create draft PR to main + id: pr + env: + GH_TOKEN: ${{ steps.release_app_token.outputs.token }} + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} + CHANGELOG_CONTENT: ${{ steps.changelog.outputs.changelog }} + run: | + set -euo pipefail + PR_BODY="## Release $VERSION + + This PR prepares release $VERSION for merge to main. + + ### Release Content + + $CHANGELOG_CONTENT + " + + EXISTING_PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh pr list --head "$RELEASE_BRANCH" --base main --state open --json url --jq '.[0].url // empty') + if [ -n "$EXISTING_PR_URL" ]; then + PR_URL="$EXISTING_PR_URL" + else + PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh pr create \ + --base main \ + --head "$RELEASE_BRANCH" \ + --title "chore: release $VERSION" \ + --body "$PR_BODY" \ + --draft) + fi + + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - name: Roll back prepare-release side effects on failure + id: rollback_prepare + if: ${{ failure() }} + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} + PREPARE_START_SHA: ${{ steps.pre_state.outputs.prepare_start_sha }} + POST_FREEZE_DEV_SHA: ${{ steps.create_branch.outputs.dev_sha }} + run: | + set -euo pipefail + CHANGELOG_ROLLBACK_NEEDED=false + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/$RELEASE_BRANCH" || true + + if [ -z "${PREPARE_START_SHA:-}" ] || [ -z "${POST_FREEZE_DEV_SHA:-}" ]; then + echo "changelog_rollback_needed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + CURRENT_DEV_SHA=$(retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') + if [ "$CURRENT_DEV_SHA" != "$POST_FREEZE_DEV_SHA" ]; then + echo "changelog_rollback_needed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=$PREPARE_START_SHA" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.pre.md + retry --retries 3 --backoff 5 --max-backoff 60 -- \ + gh api "repos/${{ github.repository }}/contents/CHANGELOG.md?ref=dev" --jq '.content' | tr -d '\n' | base64 -d > /tmp/changelog.current.md + if ! cmp -s /tmp/changelog.pre.md /tmp/changelog.current.md; then + cp /tmp/changelog.pre.md CHANGELOG.md + CHANGELOG_ROLLBACK_NEEDED=true + fi + echo "changelog_rollback_needed=$CHANGELOG_ROLLBACK_NEEDED" >> "$GITHUB_OUTPUT" + + - name: Commit CHANGELOG rollback to dev via API + if: ${{ failure() && steps.rollback_prepare.outputs.changelog_rollback_needed == 'true' }} + uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + TARGET_BRANCH: refs/heads/dev + COMMIT_MESSAGE: |- + chore: rollback failed prepare-release ${{ needs.validate.outputs.version }} + + Restore CHANGELOG.md on dev to pre-prepare state after prepare-release failed. + FILE_PATHS: CHANGELOG.md diff --git a/assets/workspace/.github/workflows/release-core.yml b/assets/workspace/.github/workflows/release-core.yml new file mode 100644 index 00000000..0e8cd41a --- /dev/null +++ b/assets/workspace/.github/workflows/release-core.yml @@ -0,0 +1,509 @@ +name: Release Core + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + version: + description: "Semantic version to release (e.g., 1.2.3)" + required: true + type: string + release_kind: + description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" + required: false + default: "candidate" + type: string + dry_run: + description: "Validate without making changes" + required: false + default: false + type: boolean + git_user_name: + description: "Git user name for release commits" + required: false + default: "github-actions[bot]" + type: string + git_user_email: + description: "Git user email for release commits" + required: false + default: "41898282+github-actions[bot]@users.noreply.github.com" + type: string + secrets: + token: + required: false + outputs: + version: + description: "Resolved release version" + value: ${{ jobs.validate.outputs.version }} + pr_number: + description: "Release PR number" + value: ${{ jobs.validate.outputs.pr_number }} + release_date: + description: "UTC release date used for finalization" + value: ${{ jobs.validate.outputs.release_date }} + pre_finalize_sha: + description: "Release branch SHA before finalization" + value: ${{ jobs.validate.outputs.pre_finalize_sha }} + release_kind: + description: "Release kind (candidate or final)" + value: ${{ jobs.validate.outputs.release_kind }} + publish_version: + description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" + value: ${{ jobs.validate.outputs.publish_version }} + finalize_sha: + description: "Release branch SHA after finalization" + value: ${{ jobs.finalize.outputs.finalize_sha }} + image_tag: + description: "Resolved devcontainer image tag" + value: ${{ jobs.validate.outputs.image_tag }} + +concurrency: + group: release-core + cancel-in-progress: false + +permissions: + contents: read + +jobs: + resolve-image: + name: Resolve image tag + runs-on: ubuntu-22.04 + timeout-minutes: 2 + outputs: + image-tag: ${{ steps.resolve.outputs.image-tag }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + + validate: + name: Validate Release Core + needs: [resolve-image] + runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: read + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} + timeout-minutes: 10 + defaults: + run: + shell: bash + outputs: + version: ${{ steps.vars.outputs.version }} + pr_number: ${{ steps.pr.outputs.pr_number }} + release_date: ${{ steps.vars.outputs.release_date }} + pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} + release_kind: ${{ steps.vars.outputs.release_kind }} + publish_version: ${{ steps.publish_meta.outputs.publish_version }} + image_tag: ${{ needs.resolve-image.outputs.image-tag }} + + steps: + - name: Generate release app token + id: release_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Resolve auth token + id: auth + env: + PROVIDED_TOKEN: ${{ secrets.token }} + FALLBACK_TOKEN: ${{ steps.release_app_token.outputs.token }} + run: | + set -euo pipefail + if [ -n "${PROVIDED_TOKEN:-}" ]; then + echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" + else + echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" + fi + + - name: Validate and prepare variables + id: vars + env: + INPUT_VERSION: ${{ inputs.version }} + INPUT_RELEASE_KIND: ${{ inputs.release_kind }} + run: | + set -euo pipefail + VERSION="$INPUT_VERSION" + RELEASE_KIND="$INPUT_RELEASE_KIND" + + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "ERROR: Invalid version format '$VERSION'" + echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" + exit 1 + fi + if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then + echo "ERROR: Invalid release-kind '$RELEASE_KIND'" + echo "release-kind must be one of: candidate, final" + exit 1 + fi + + RELEASE_DATE=$(date -u +%Y-%m-%d) + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" + echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" + + - name: Checkout release branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: release/${{ steps.vars.outputs.version }} + fetch-depth: 0 + token: ${{ steps.auth.outputs.token }} + + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Record pre-finalization SHA + id: pre_sha + run: | + PRE_FINALIZE_SHA=$(git rev-parse HEAD) + echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" + echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" + + - name: Verify CHANGELOG has TBD entry + env: + VERSION: ${{ steps.vars.outputs.version }} + run: | + if ! grep -Fq "## [$VERSION] - TBD" CHANGELOG.md; then + echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" + exit 1 + fi + + - name: Compute publish version + id: publish_meta + env: + VERSION: ${{ steps.vars.outputs.version }} + RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} + run: | + set -euo pipefail + NEXT_RC="" + if [ "$RELEASE_KIND" = "candidate" ]; then + TAG_PATTERN="${VERSION}-rc*" + EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" | awk '{print $2}' | sed 's#refs/tags/##') + + MAX_RC=0 + if [ -n "$EXISTING_TAGS" ]; then + while IFS= read -r tag; do + [ -z "$tag" ] && continue + if [ "${tag#${VERSION}-rc}" = "$tag" ]; then + echo "ERROR: Malformed candidate tag detected: $tag" + echo "Expected format: ${VERSION}-rcN" + exit 1 + fi + rc_num="${tag#${VERSION}-rc}" + if ! echo "$rc_num" | grep -qE '^[0-9]+$'; then + echo "ERROR: Malformed candidate tag detected: $tag" + echo "Expected format: ${VERSION}-rcN" + exit 1 + fi + if [ "$rc_num" -gt "$MAX_RC" ]; then + MAX_RC="$rc_num" + fi + done <<< "$EXISTING_TAGS" + fi + + NEXT_RC=$((MAX_RC + 1)) + PUBLISH_VERSION="${VERSION}-rc${NEXT_RC}" + else + PUBLISH_VERSION="$VERSION" + fi + echo "publish_version=$PUBLISH_VERSION" >> "$GITHUB_OUTPUT" + echo "next_rc=$NEXT_RC" >> "$GITHUB_OUTPUT" + + - name: Verify publish tag does not exist + env: + PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} + run: | + if git ls-remote --exit-code --tags --refs origin "refs/tags/$PUBLISH_VERSION" > /dev/null 2>&1; then + echo "ERROR: Tag $PUBLISH_VERSION already exists" + exit 1 + fi + if git rev-parse -q --verify "refs/tags/$PUBLISH_VERSION" > /dev/null; then + echo "ERROR: Local tag $PUBLISH_VERSION already exists" + exit 1 + fi + + - name: Find and verify PR + id: pr + env: + VERSION: ${{ steps.vars.outputs.version }} + GH_TOKEN: ${{ steps.auth.outputs.token }} + run: | + set -euo pipefail + + PR_JSON=$(retry --retries 3 --backoff 5 --max-backoff 30 -- gh pr list \ + --head "release/$VERSION" \ + --base main \ + --json number,isDraft,reviewDecision,statusCheckRollup \ + --limit 1) + + PR_COUNT=$(echo "$PR_JSON" | jq 'length') + if [ "$PR_COUNT" != "1" ]; then + echo "ERROR: Expected exactly 1 PR from release/$VERSION to main, found $PR_COUNT" + exit 1 + fi + + PR_NUMBER=$(echo "$PR_JSON" | jq -r '.[0].number') + IS_DRAFT=$(echo "$PR_JSON" | jq -r '.[0].isDraft') + REVIEW_DECISION=$(echo "$PR_JSON" | jq -r '.[0].reviewDecision') + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + if [ "$IS_DRAFT" = "true" ]; then + echo "ERROR: PR #$PR_NUMBER is still in draft" + exit 1 + fi + if [ "$REVIEW_DECISION" != "APPROVED" ]; then + echo "ERROR: PR #$PR_NUMBER is not approved (status: $REVIEW_DECISION)" + exit 1 + fi + + STATUS_ROLLUP=$(echo "$PR_JSON" | jq -r '.[0].statusCheckRollup // []') + CI_FAILED=$(echo "$STATUS_ROLLUP" | jq '[.[] | select(.conclusion == "FAILURE" or .conclusion == "ERROR")] | length') + if [ "$CI_FAILED" != "0" ]; then + echo "ERROR: PR #$PR_NUMBER has failed CI checks" + exit 1 + fi + + - name: Summary + env: + VERSION: ${{ steps.vars.outputs.version }} + RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} + PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} + NEXT_RC: ${{ steps.publish_meta.outputs.next_rc }} + PR_NUMBER: ${{ steps.pr.outputs.pr_number }} + RELEASE_DATE: ${{ steps.vars.outputs.release_date }} + DRY_RUN: ${{ inputs.dry_run }} + IMAGE_TAG: ${{ needs.resolve-image.outputs.image-tag }} + run: | + echo "Validation passed" + echo "" + echo " Version: $VERSION" + echo " Release Kind: $RELEASE_KIND" + echo " Publish Tag: $PUBLISH_VERSION" + if [ -n "$NEXT_RC" ]; then + echo " Next RC: $NEXT_RC" + fi + echo " Branch: release/$VERSION" + echo " PR: #$PR_NUMBER" + echo " Release Date: $RELEASE_DATE" + echo " Image Tag: $IMAGE_TAG" + echo " Dry-run: $DRY_RUN" + + finalize: + name: Finalize Release Core + needs: validate + runs-on: ubuntu-22.04 + permissions: + actions: write + contents: write + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }} + env: + UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv + timeout-minutes: 15 + defaults: + run: + shell: bash + if: ${{ inputs.dry_run != true }} + outputs: + finalize_sha: ${{ steps.finalize.outputs.finalize_sha }} + + steps: + - name: Generate commit app token + id: commit_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.COMMIT_APP_ID }} + private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} + + - name: Generate release app token + id: release_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Resolve auth token + id: auth + env: + PROVIDED_TOKEN: ${{ secrets.token }} + FALLBACK_TOKEN: ${{ steps.release_app_token.outputs.token }} + run: | + set -euo pipefail + if [ -n "${PROVIDED_TOKEN:-}" ]; then + echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" + else + echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout release branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: release/${{ needs.validate.outputs.version }} + fetch-depth: 0 + token: ${{ steps.auth.outputs.token }} + + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Finalize CHANGELOG date + if: ${{ inputs.release_kind == 'final' }} + env: + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_DATE: ${{ needs.validate.outputs.release_date }} + run: | + set -euo pipefail + prepare-changelog finalize "$VERSION" "$RELEASE_DATE" CHANGELOG.md + + - name: Commit and push finalization changes via API + if: ${{ inputs.release_kind == 'final' }} + uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 + env: + GH_TOKEN: ${{ steps.commit_app_token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + TARGET_BRANCH: refs/heads/release/${{ needs.validate.outputs.version }} + COMMIT_MESSAGE: |- + chore: finalize release ${{ needs.validate.outputs.version }} + + Set release date to ${{ needs.validate.outputs.release_date }} in CHANGELOG.md + + Refs: #${{ needs.validate.outputs.pr_number }} + FILE_PATHS: CHANGELOG.md + + - name: Trigger sync-issues workflow + if: ${{ inputs.release_kind == 'final' }} + env: + GH_TOKEN: ${{ steps.auth.outputs.token }} + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + retry --retries 2 --backoff 5 --max-backoff 20 -- \ + gh workflow run sync-issues.yml -f "target-branch=release/$VERSION" + + - name: Wait for sync-issues completion + if: ${{ inputs.release_kind == 'final' }} + id: wait_sync + env: + GH_TOKEN: ${{ steps.auth.outputs.token }} + run: | + set -euo pipefail + TIMEOUT=120 + ELAPSED=0 + INTERVAL=10 + RUN_STATUS="unknown" + sleep 5 + ELAPSED=5 + + while [ $ELAPSED -lt $TIMEOUT ]; do + RUN_STATUS=$(gh run list \ + --workflow sync-issues.yml \ + --limit 1 \ + --json status,conclusion \ + --jq '.[0].status' 2>/dev/null || echo "unknown") + + if [ "$RUN_STATUS" = "completed" ]; then + break + fi + + sleep "$INTERVAL" + ELAPSED=$((ELAPSED + INTERVAL)) + done + + if [ "$RUN_STATUS" != "completed" ]; then + echo "ERROR: Timed out waiting for sync-issues workflow completion" + exit 1 + fi + + RUN_CONCLUSION=$(gh run list \ + --workflow sync-issues.yml \ + --limit 1 \ + --json conclusion \ + --jq '.[0].conclusion' 2>/dev/null || echo "unknown") + if [ "$RUN_CONCLUSION" != "success" ]; then + echo "ERROR: sync-issues workflow finished with conclusion '$RUN_CONCLUSION'" + exit 1 + fi + + - name: Pull sync-issues changes + if: ${{ inputs.release_kind == 'final' }} + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin "release/$VERSION" + git reset --hard "origin/release/$VERSION" + + - name: Output finalize SHA + id: finalize + env: + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_KIND: ${{ inputs.release_kind }} + GH_TOKEN: ${{ steps.auth.outputs.token }} + run: | + set -euo pipefail + if [ "$RELEASE_KIND" = "final" ]; then + FINALIZE_SHA=$(git rev-parse HEAD) + else + FINALIZE_SHA=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/release/$VERSION" --jq '.object.sha') + fi + echo "finalize_sha=$FINALIZE_SHA" >> "$GITHUB_OUTPUT" + + test: + name: Test Finalized Release + needs: [validate, finalize] + runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }} + env: + UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv + timeout-minutes: 20 + defaults: + run: + shell: bash + if: ${{ inputs.dry_run != true }} + + steps: + - name: Generate release app token + id: release_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Resolve auth token + id: auth + env: + PROVIDED_TOKEN: ${{ secrets.token }} + FALLBACK_TOKEN: ${{ steps.release_app_token.outputs.token }} + run: | + set -euo pipefail + if [ -n "${PROVIDED_TOKEN:-}" ]; then + echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" + else + echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout finalized commit + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.finalize.outputs.finalize_sha }} + token: ${{ steps.auth.outputs.token }} + + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Sync Python dependencies + run: just sync + + - name: Run tests + run: just test diff --git a/assets/workspace/.github/workflows/release-extension.yml b/assets/workspace/.github/workflows/release-extension.yml new file mode 100644 index 00000000..89c24501 --- /dev/null +++ b/assets/workspace/.github/workflows/release-extension.yml @@ -0,0 +1,45 @@ +name: Release Extension + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + version: + description: "Release version" + required: true + type: string + finalize_sha: + description: "Finalized commit SHA" + required: true + type: string + release_date: + description: "Release date" + required: true + type: string + release_kind: + description: "Release kind (candidate or final)" + required: true + type: string + publish_version: + description: "Version tag that will be published (X.Y.Z or X.Y.Z-rcN)" + required: true + type: string + +jobs: + extension: + name: Extension Hook (Default No-op) + runs-on: ubuntu-22.04 + steps: + - name: Default extension summary + env: + VERSION: ${{ inputs.version }} + FINALIZE_SHA: ${{ inputs.finalize_sha }} + RELEASE_DATE: ${{ inputs.release_date }} + RELEASE_KIND: ${{ inputs.release_kind }} + PUBLISH_VERSION: ${{ inputs.publish_version }} + run: | + echo "No release extension configured." + echo "Version: $VERSION" + echo "Release kind: $RELEASE_KIND" + echo "Publish version: $PUBLISH_VERSION" + echo "Finalize SHA: $FINALIZE_SHA" + echo "Release date: $RELEASE_DATE" diff --git a/assets/workspace/.github/workflows/release-publish.yml b/assets/workspace/.github/workflows/release-publish.yml new file mode 100644 index 00000000..60ae8dd7 --- /dev/null +++ b/assets/workspace/.github/workflows/release-publish.yml @@ -0,0 +1,208 @@ +name: Release Publish + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + version: + description: "Semantic version to release (e.g., 1.2.3)" + required: true + type: string + finalize_sha: + description: "Finalized commit SHA to publish" + required: true + type: string + release_date: + description: "Release date used for summary output" + required: true + type: string + publish_version: + description: "Version tag to publish (X.Y.Z or X.Y.Z-rcN)" + required: true + type: string + release_kind: + description: "Release kind (candidate or final)" + required: true + type: string + git_user_name: + description: "Git user name for release tag operations" + required: false + default: "github-actions[bot]" + type: string + git_user_email: + description: "Git user email for release tag operations" + required: false + default: "41898282+github-actions[bot]@users.noreply.github.com" + type: string + secrets: + token: + required: false + outputs: + tag: + description: "Published tag" + value: ${{ jobs.publish.outputs.tag }} + release_url: + description: "Published release URL" + value: ${{ jobs.publish.outputs.release_url }} + +permissions: + contents: read + +jobs: + resolve-image: + name: Resolve image tag + runs-on: ubuntu-22.04 + timeout-minutes: 2 + outputs: + image-tag: ${{ steps.resolve.outputs.image-tag }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + + publish: + name: Publish Release + needs: [resolve-image] + runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} + timeout-minutes: 10 + defaults: + run: + shell: bash + permissions: + contents: write + outputs: + tag: ${{ steps.out.outputs.tag }} + release_url: ${{ steps.out.outputs.release_url }} + + steps: + - name: Generate release app token + id: release_app_token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Resolve auth token + id: auth + env: + PROVIDED_TOKEN: ${{ secrets.token }} + FALLBACK_TOKEN: ${{ steps.release_app_token.outputs.token }} + run: | + set -euo pipefail + if [ -n "${PROVIDED_TOKEN:-}" ]; then + echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" + else + echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" + fi + + - name: Validate release kind + env: + RELEASE_KIND: ${{ inputs.release_kind }} + run: | + set -euo pipefail + if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then + echo "ERROR: Invalid release_kind '$RELEASE_KIND'" + echo "release_kind must be one of: candidate, final" + exit 1 + fi + + - name: Checkout finalized commit + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.finalize_sha }} + fetch-depth: 0 + token: ${{ steps.auth.outputs.token }} + + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Configure git + env: + GIT_USER_NAME: ${{ inputs.git_user_name }} + GIT_USER_EMAIL: ${{ inputs.git_user_email }} + run: | + git config user.name "$GIT_USER_NAME" + git config user.email "$GIT_USER_EMAIL" + + - name: Create and push tag + env: + PUBLISH_VERSION: ${{ inputs.publish_version }} + run: | + set -euo pipefail + git tag -a "$PUBLISH_VERSION" -m "Release $PUBLISH_VERSION" + if ! retry --retries 3 --backoff 5 --max-backoff 30 -- git push origin "$PUBLISH_VERSION"; then + if retry --retries 3 --backoff 5 --max-backoff 30 -- git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then + LOCAL_TAG_TARGET_SHA=$(git rev-parse "$PUBLISH_VERSION^{}") + REMOTE_TAG_TARGET_SHA=$(retry --retries 3 --backoff 5 --max-backoff 30 -- git ls-remote --tags origin "refs/tags/$PUBLISH_VERSION^{}" | awk '{print $1}') + if [ -z "$REMOTE_TAG_TARGET_SHA" ]; then + echo "ERROR: Remote tag exists but target SHA could not be resolved: $PUBLISH_VERSION" + exit 1 + fi + if [ "$REMOTE_TAG_TARGET_SHA" != "$LOCAL_TAG_TARGET_SHA" ]; then + echo "ERROR: Remote tag target SHA mismatch for $PUBLISH_VERSION" + echo "Local tag target: $LOCAL_TAG_TARGET_SHA" + echo "Remote tag target: $REMOTE_TAG_TARGET_SHA" + exit 1 + fi + echo "Tag already present on origin with matching target SHA: $PUBLISH_VERSION" + else + echo "ERROR: Failed to push tag $PUBLISH_VERSION" + exit 1 + fi + fi + + - name: Extract release notes from CHANGELOG + env: + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + awk -v version="$VERSION" ' + index($0, "## [" version "]") == 1 {found=1; next} + /^## \[/ && found {found=0} + found + ' CHANGELOG.md > /tmp/release-notes.md + if [ ! -s /tmp/release-notes.md ]; then + echo "No changelog notes found for $VERSION" > /tmp/release-notes.md + fi + + - name: Create GitHub Release + env: + PUBLISH_VERSION: ${{ inputs.publish_version }} + RELEASE_KIND: ${{ inputs.release_kind }} + GH_TOKEN: ${{ steps.auth.outputs.token }} + run: | + set -euo pipefail + if retry --retries 2 --backoff 5 --max-backoff 20 -- gh release view "$PUBLISH_VERSION" >/dev/null 2>&1; then + echo "ERROR: GitHub Release already exists for tag $PUBLISH_VERSION" + exit 1 + fi + if [ "$RELEASE_KIND" = "candidate" ]; then + retry --retries 3 --backoff 5 --max-backoff 30 -- gh release create "$PUBLISH_VERSION" \ + --title "$PUBLISH_VERSION" \ + --notes-file /tmp/release-notes.md \ + --verify-tag \ + --prerelease + else + retry --retries 3 --backoff 5 --max-backoff 30 -- gh release create "$PUBLISH_VERSION" \ + --title "$PUBLISH_VERSION" \ + --notes-file /tmp/release-notes.md \ + --verify-tag + fi + + - name: Set outputs + id: out + env: + PUBLISH_VERSION: ${{ inputs.publish_version }} + RELEASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ inputs.publish_version }} + run: | + echo "tag=$PUBLISH_VERSION" >> "$GITHUB_OUTPUT" + echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT" diff --git a/assets/workspace/.github/workflows/release.yml b/assets/workspace/.github/workflows/release.yml index c1905ca6..e54c182b 100644 --- a/assets/workspace/.github/workflows/release.yml +++ b/assets/workspace/.github/workflows/release.yml @@ -1,51 +1,31 @@ -# Release Workflow -# -# Creates a versioned release from a release branch. -# Default template for Python projects using the vigOS devcontainer. -# -# Prerequisites: -# - A release branch (release/X.Y.Z) exists -# - CHANGELOG.md contains "## [X.Y.Z] - TBD" -# - A PR from release/X.Y.Z to main is open, approved, and CI has passed -# - Tag X.Y.Z does not yet exist -# -# Steps: -# 1. Validate — Check all prerequisites -# 2. Finalize — Set release date in CHANGELOG, commit, push -# 3. Test — Run full test suite on finalized code -# 4. Release — Create tag and GitHub Release with changelog notes -# 5. Rollback — Revert changes and create issue on failure -# -# Trigger: -# - Manual workflow_dispatch with version input -# -# TODO: For branch-protected repos, replace GITHUB_TOKEN with a GitHub App token -# to allow pushing finalization commits. See: -# https://github.com/apps/settings → create app with contents:write permission. - name: Release on: # yamllint disable-line rule:truthy workflow_dispatch: inputs: version: - description: 'Semantic version to release (e.g., 1.2.3)' + description: "Semantic version to release (e.g., 1.2.3)" required: true type: string + release-kind: + description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" + required: false + default: "candidate" + type: string dry-run: - description: 'Validate without making changes' + description: "Validate without making changes" required: false default: false type: boolean git-user-name: - description: 'Git user name for release commits' + description: "Git user name for release commits" required: false - default: 'github-actions[bot]' + default: "github-actions[bot]" type: string git-user-email: - description: 'Git user email for release commits' + description: "Git user email for release commits" required: false - default: '41898282+github-actions[bot]@users.noreply.github.com' + default: "41898282+github-actions[bot]@users.noreply.github.com" type: string concurrency: @@ -56,427 +36,176 @@ permissions: contents: read jobs: - validate: - name: Validate Release + resolve-image: + name: Resolve image tag runs-on: ubuntu-22.04 - timeout-minutes: 10 + timeout-minutes: 2 outputs: - version: ${{ steps.vars.outputs.version }} - pr_number: ${{ steps.pr.outputs.pr_number }} - release_date: ${{ steps.vars.outputs.release_date }} - pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} - + image-tag: ${{ steps.resolve.outputs.image-tag }} steps: - - name: Validate and prepare variables - id: vars - env: - INPUT_VERSION: ${{ github.event.inputs.version }} - run: | - set -euo pipefail - VERSION="$INPUT_VERSION" - - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "ERROR: Invalid version format '$VERSION'" - echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" - exit 1 - fi - - RELEASE_DATE=$(date -u +%Y-%m-%d) - - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT - - - name: Checkout release branch + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: release/${{ steps.vars.outputs.version }} - fetch-depth: 0 - - - name: Record pre-finalization SHA - id: pre_sha - run: | - PRE_FINALIZE_SHA=$(git rev-parse HEAD) - echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> $GITHUB_OUTPUT - echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" - - - name: Verify CHANGELOG has TBD entry - env: - VERSION: ${{ steps.vars.outputs.version }} - run: | - if ! grep -q "## \[$VERSION\] - TBD" CHANGELOG.md; then - echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" - exit 1 - fi - - - name: Verify tag does not exist - env: - VERSION: ${{ steps.vars.outputs.version }} - run: | - if git tag -l | grep -q "^${VERSION}$"; then - echo "ERROR: Tag $VERSION already exists" - exit 1 - fi - - - name: Find and verify PR - id: pr - env: - VERSION: ${{ steps.vars.outputs.version }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - PR_JSON=$(gh pr list \ - --head "release/$VERSION" \ - --base main \ - --json number,isDraft,reviewDecision,statusCheckRollup \ - --limit 1) - - PR_COUNT=$(echo "$PR_JSON" | jq 'length') - - if [ "$PR_COUNT" != "1" ]; then - echo "ERROR: Expected exactly 1 PR from release/$VERSION to main, found $PR_COUNT" - exit 1 - fi - - PR_NUMBER=$(echo "$PR_JSON" | jq -r '.[0].number') - IS_DRAFT=$(echo "$PR_JSON" | jq -r '.[0].isDraft') - REVIEW_DECISION=$(echo "$PR_JSON" | jq -r '.[0].reviewDecision') - - echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - - if [ "$IS_DRAFT" = "true" ]; then - echo "ERROR: PR #$PR_NUMBER is still in draft" - exit 1 - fi - - if [ "$REVIEW_DECISION" != "APPROVED" ]; then - echo "ERROR: PR #$PR_NUMBER is not approved (status: $REVIEW_DECISION)" - exit 1 - fi - - STATUS_ROLLUP=$(echo "$PR_JSON" | jq -r '.[0].statusCheckRollup // []') - CI_FAILED=$(echo "$STATUS_ROLLUP" | jq '[.[] | select(.conclusion == "FAILURE" or .conclusion == "ERROR")] | length') - - if [ "$CI_FAILED" != "0" ]; then - echo "ERROR: PR #$PR_NUMBER has failed CI checks" - exit 1 - fi - - echo "PR #$PR_NUMBER verified: ready, approved, CI passed" - - - name: Summary - env: - VERSION: ${{ steps.vars.outputs.version }} - PR_NUMBER: ${{ steps.pr.outputs.pr_number }} - RELEASE_DATE: ${{ steps.vars.outputs.release_date }} - DRY_RUN: ${{ github.event.inputs.dry-run }} - run: | - echo "Validation passed" - echo "" - echo " Version: $VERSION" - echo " Branch: release/$VERSION" - echo " PR: #$PR_NUMBER" - echo " Release Date: $RELEASE_DATE" - echo " Dry-run: $DRY_RUN" - - finalize: - name: Finalize Release - needs: validate - runs-on: ubuntu-22.04 - timeout-minutes: 10 - if: ${{ github.event.inputs.dry-run != 'true' }} + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + + core: + name: Release Core + uses: ./.github/workflows/release-core.yml permissions: + actions: write contents: write - outputs: - finalize_sha: ${{ steps.finalize.outputs.finalize_sha }} - - steps: - - name: Checkout release branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: release/${{ needs.validate.outputs.version }} - persist-credentials: true - - - name: Set release date in CHANGELOG - env: - VERSION: ${{ needs.validate.outputs.version }} - RELEASE_DATE: ${{ needs.validate.outputs.release_date }} - run: | - set -euo pipefail - sed -i "s/## \[$VERSION\] - TBD/## [$VERSION] - $RELEASE_DATE/" CHANGELOG.md - - if ! grep -q "## \[$VERSION\] - $RELEASE_DATE" CHANGELOG.md; then - echo "ERROR: Failed to set release date in CHANGELOG" - exit 1 - fi - echo "Release date set in CHANGELOG.md" - - - name: Commit and push finalization changes via API - uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - TARGET_BRANCH: refs/heads/release/${{ needs.validate.outputs.version }} - COMMIT_MESSAGE: |- - chore: finalize release ${{ needs.validate.outputs.version }} - - Set release date to ${{ needs.validate.outputs.release_date }} in CHANGELOG.md - - Refs: #${{ needs.validate.outputs.pr_number }} - FILE_PATHS: CHANGELOG.md - - - name: Trigger sync-issues workflow - env: - GH_TOKEN: ${{ github.token }} - VERSION: ${{ needs.validate.outputs.version }} - run: | - set -euo pipefail - echo "Triggering sync-issues workflow..." - gh workflow run sync-issues.yml \ - -f "target-branch=release/$VERSION" - echo "✓ sync-issues workflow triggered" - - - name: Wait for sync-issues completion - run: | - set -euo pipefail - TIMEOUT=120 - ELAPSED=0 - INTERVAL=10 - - sleep 5 - ELAPSED=5 - - while [ $ELAPSED -lt $TIMEOUT ]; do - RUN_STATUS=$(gh run list \ - --workflow sync-issues.yml \ - --limit 1 \ - --json status,conclusion \ - --jq '.[0].status' 2>/dev/null || echo "unknown") - - if [ "$RUN_STATUS" = "completed" ]; then - RUN_CONCLUSION=$(gh run list \ - --workflow sync-issues.yml \ - --limit 1 \ - --json conclusion \ - --jq '.[0].conclusion' 2>/dev/null || echo "unknown") - - if [ "$RUN_CONCLUSION" = "success" ]; then - echo "✓ sync-issues workflow completed successfully" - else - echo "⚠ sync-issues workflow completed with status: $RUN_CONCLUSION" - fi - break - fi - - sleep "$INTERVAL" - ELAPSED=$((ELAPSED + INTERVAL)) - echo "Waiting for sync-issues... (${ELAPSED}s / ${TIMEOUT}s)" - done - - if [ $ELAPSED -ge $TIMEOUT ]; then - echo "⚠ Timed out waiting for sync-issues workflow" - echo "The release may continue, but PR documentation may not be synced" - fi - - env: - GH_TOKEN: ${{ github.token }} - - - name: Pull sync-issues changes - env: - VERSION: ${{ needs.validate.outputs.version }} - run: | - set -euo pipefail - git fetch origin "release/$VERSION" - git reset --hard "origin/release/$VERSION" - echo "✓ Synced with remote release branch" - - - name: Output finalize SHA - id: finalize - run: | - FINALIZE_SHA=$(git rev-parse HEAD) - echo "finalize_sha=$FINALIZE_SHA" >> $GITHUB_OUTPUT - echo "Finalized at commit: $FINALIZE_SHA" - - test: - name: Test (Finalized) - needs: [validate, finalize] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - - steps: - - name: Checkout finalized commit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ needs.finalize.outputs.finalize_sha }} - - - name: Set up environment - uses: ./.github/actions/setup-env - with: - sync-dependencies: 'true' - - - name: Run tests - run: uv run pytest - - release: - name: Create Release - needs: [validate, finalize, test] - runs-on: ubuntu-22.04 - timeout-minutes: 10 + pull-requests: read + with: + version: ${{ inputs.version }} + release_kind: ${{ inputs.release-kind }} + dry_run: ${{ inputs.dry-run }} + git_user_name: ${{ inputs.git-user-name }} + git_user_email: ${{ inputs.git-user-email }} + secrets: inherit + + extension: + name: Release Extension + needs: core + if: ${{ inputs.dry-run != true }} + uses: ./.github/workflows/release-extension.yml + with: + version: ${{ needs.core.outputs.version }} + finalize_sha: ${{ needs.core.outputs.finalize_sha }} + release_date: ${{ needs.core.outputs.release_date }} + release_kind: ${{ needs.core.outputs.release_kind }} + publish_version: ${{ needs.core.outputs.publish_version }} + secrets: inherit + + publish: + name: Publish Release + needs: [core, extension] + if: ${{ inputs.dry-run != true }} + uses: ./.github/workflows/release-publish.yml permissions: contents: write - - steps: - - name: Checkout finalized commit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ needs.finalize.outputs.finalize_sha }} - persist-credentials: true - - - name: Configure git - env: - GIT_USER_NAME: ${{ github.event.inputs.git-user-name }} - GIT_USER_EMAIL: ${{ github.event.inputs.git-user-email }} - run: | - git config user.name "$GIT_USER_NAME" - git config user.email "$GIT_USER_EMAIL" - - - name: Create and push tag - env: - VERSION: ${{ needs.validate.outputs.version }} - run: | - set -euo pipefail - git tag -a "$VERSION" -m "Release $VERSION" - git push origin "$VERSION" - echo "Tag $VERSION created and pushed" - - - name: Extract release notes from CHANGELOG - id: notes - env: - VERSION: ${{ needs.validate.outputs.version }} - run: | - set -euo pipefail - # Extract content between this version header and the next version header - awk '/^## \['"$VERSION"'\]/{found=1; next} /^## \[/{found=0} found' \ - CHANGELOG.md > /tmp/release-notes.md - - if [ ! -s /tmp/release-notes.md ]; then - echo "No changelog notes found for $VERSION" > /tmp/release-notes.md - fi - - echo "Release notes:" - cat /tmp/release-notes.md - - - name: Create GitHub Release - env: - VERSION: ${{ needs.validate.outputs.version }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - gh release create "$VERSION" \ - --title "$VERSION" \ - --notes-file /tmp/release-notes.md \ - --verify-tag - echo "GitHub Release $VERSION created" - - - name: Summary - env: - VERSION: ${{ needs.validate.outputs.version }} - run: | - echo "Release published successfully" - echo "" - echo " Version: $VERSION" - echo " Tag: $VERSION" - echo " Release: https://github.com/${{ github.repository }}/releases/tag/$VERSION" - echo "" - echo "Next steps:" - echo " 1. Merge the release PR to main" - echo " 2. Merge main back into dev" + with: + version: ${{ needs.core.outputs.version }} + finalize_sha: ${{ needs.core.outputs.finalize_sha }} + release_date: ${{ needs.core.outputs.release_date }} + publish_version: ${{ needs.core.outputs.publish_version }} + release_kind: ${{ needs.core.outputs.release_kind }} + git_user_name: ${{ inputs.git-user-name }} + git_user_email: ${{ inputs.git-user-email }} + secrets: inherit rollback: name: Rollback on Failure - needs: [validate, finalize, test, release] + needs: [resolve-image, core, extension, publish] runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} timeout-minutes: 10 - if: failure() + defaults: + run: + shell: bash + if: >- + ${{ + always() && + inputs.dry-run != true && + needs.resolve-image.result == 'success' && + ( + needs.core.result == 'failure' || + needs.extension.result == 'failure' || + needs.publish.result == 'failure' + ) + }} permissions: contents: write issues: write - steps: + - name: Generate release app token + id: release_app_token + if: ${{ needs.core.outputs.version != '' }} + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Generate commit app token + id: commit_app_token + if: ${{ needs.core.outputs.pre_finalize_sha != '' }} + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + with: + app-id: ${{ secrets.COMMIT_APP_ID }} + private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} + - name: Checkout repository + if: ${{ needs.core.outputs.pre_finalize_sha != '' }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - persist-credentials: true + fetch-depth: 0 + token: ${{ steps.commit_app_token.outputs.token }} + + - name: Fix git safe.directory + if: ${{ needs.core.outputs.pre_finalize_sha != '' }} + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Configure git + if: ${{ needs.core.outputs.pre_finalize_sha != '' }} env: - GIT_USER_NAME: ${{ github.event.inputs.git-user-name }} - GIT_USER_EMAIL: ${{ github.event.inputs.git-user-email }} + GIT_USER_NAME: ${{ inputs.git-user-name }} + GIT_USER_EMAIL: ${{ inputs.git-user-email }} run: | git config user.name "$GIT_USER_NAME" git config user.email "$GIT_USER_EMAIL" - name: Rollback release branch + if: ${{ needs.core.outputs.pre_finalize_sha != '' }} continue-on-error: true env: - VERSION: ${{ needs.validate.outputs.version }} - PRE_SHA: ${{ needs.validate.outputs.pre_finalize_sha }} + VERSION: ${{ needs.core.outputs.version }} + PRE_SHA: ${{ needs.core.outputs.pre_finalize_sha }} run: | set -euo pipefail - git fetch origin "release/$VERSION" + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin "release/$VERSION" git checkout "release/$VERSION" - if git reset --hard "$PRE_SHA" 2>/dev/null; then - if git push --force-with-lease origin "release/$VERSION"; then + if retry --retries 3 --backoff 5 --max-backoff 30 -- git push --force-with-lease origin "release/$VERSION"; then echo "Release branch rolled back" - else - echo "Warning: Could not push rollback" fi fi - name: Delete tag if created + if: ${{ needs.core.outputs.publish_version != '' }} continue-on-error: true env: - VERSION: ${{ needs.validate.outputs.version }} + PUBLISH_VERSION: ${{ needs.core.outputs.publish_version }} run: | - if git ls-remote origin "refs/tags/$VERSION" | grep -q "$VERSION"; then - git push origin ":refs/tags/$VERSION" && echo "Tag deleted" || echo "Warning: Could not delete tag" - else - echo "Tag does not exist (not created yet)" + set -euo pipefail + if git ls-remote origin "refs/tags/$PUBLISH_VERSION" | grep -q "$PUBLISH_VERSION"; then + retry --retries 3 --backoff 5 --max-backoff 30 -- git push origin ":refs/tags/$PUBLISH_VERSION" || true fi - name: Create failure issue + if: ${{ needs.core.outputs.version != '' }} env: - VERSION: ${{ needs.validate.outputs.version }} - PR_NUMBER: ${{ needs.validate.outputs.pr_number }} - GH_TOKEN: ${{ github.token }} + VERSION: ${{ needs.core.outputs.version }} + PR_NUMBER: ${{ needs.core.outputs.pr_number }} + GH_TOKEN: ${{ steps.release_app_token.outputs.token }} + GH_REPO: ${{ github.repository }} run: | set -euo pipefail WORKFLOW_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - gh issue create \ + retry --retries 3 --backoff 5 --max-backoff 30 -- gh issue create \ --title "Release $VERSION failed — automatic rollback" \ --label "bug" \ - --body "Release $VERSION encountered an error during the automated release workflow. + --body "Release $VERSION failed during the automated release workflow. **Workflow Run:** [View logs]($WORKFLOW_URL) **Release PR:** #$PR_NUMBER **Automatic rollback attempted:** - Release branch reset to pre-finalization state - - Release tag deleted (if created) - - **Next steps:** - 1. Review the workflow logs to identify the root cause - 2. Verify rollback completed cleanly - 3. Fix the issue on the release branch - 4. Re-run the release workflow" - - - name: Summary - run: | - echo "Release workflow failed" - echo "" - echo "Automatic rollback completed." - echo "A GitHub issue has been created for investigation." - echo "Check the workflow logs for details." + - Release tag deleted (if created)" diff --git a/assets/workspace/.github/workflows/scorecard.yml b/assets/workspace/.github/workflows/scorecard.yml index 042bacde..f1524863 100644 --- a/assets/workspace/.github/workflows/scorecard.yml +++ b/assets/workspace/.github/workflows/scorecard.yml @@ -44,7 +44,7 @@ jobs: publish_results: true - name: Upload SARIF to GitHub Security - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: sarif_file: results.sarif category: 'scorecard' diff --git a/assets/workspace/.github/workflows/sync-issues.yml b/assets/workspace/.github/workflows/sync-issues.yml index affe2a9f..f71e8fd0 100644 --- a/assets/workspace/.github/workflows/sync-issues.yml +++ b/assets/workspace/.github/workflows/sync-issues.yml @@ -2,6 +2,8 @@ # Uses: # - sync-issues action from this public repository (vig-os/sync-issues-action) # - commit-action from the public repository (vig-os/commit-action) +# NOTE: This workspace workflow is intentionally decoupled from upstream +# `.github/workflows/sync-issues.yml` and maintained separately. name: Sync Issues and PRs @@ -36,8 +38,32 @@ on: # yamllint disable-line rule:truthy permissions: {} # restrict default; job declares its own jobs: + resolve-image: + name: Resolve image tag + runs-on: ubuntu-22.04 + timeout-minutes: 2 + permissions: + contents: read + outputs: + image-tag: ${{ steps.resolve.outputs.image-tag }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + sync: + needs: [resolve-image] runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} timeout-minutes: 10 # Prevent concurrent runs to avoid race conditions when committing and cache collisions concurrency: @@ -52,7 +78,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} @@ -83,10 +109,12 @@ jobs: sleep 2 # Try to delete cache using GitHub API CACHE_KEY="sync-issues-state-${{ github.repository }}" - CACHE_ID=$(gh api repos/${{ github.repository }}/actions/caches --jq ".actions_caches[] | select(.key == \"$CACHE_KEY\") | .id" | head -1) + CACHE_ID=$(retry --retries 3 --backoff 3 --max-backoff 15 -- \ + gh api repos/${{ github.repository }}/actions/caches --jq ".actions_caches[] | select(.key == \"$CACHE_KEY\") | .id" | head -1) if [ -n "$CACHE_ID" ]; then echo "Found cache ID: $CACHE_ID, attempting deletion..." - gh api repos/${{ github.repository }}/actions/caches/$CACHE_ID -X DELETE && echo "Cache deleted successfully" || echo "Cache deletion failed (may be locked or already deleted)" + retry --retries 3 --backoff 3 --max-backoff 15 -- \ + gh api repos/${{ github.repository }}/actions/caches/$CACHE_ID -X DELETE && echo "Cache deleted successfully" || echo "Cache deletion failed (may be locked or already deleted)" else echo "No cache found with key: $CACHE_KEY (this is OK for first run)" fi @@ -110,7 +138,7 @@ jobs: uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 env: # Use App token so push can bypass branch protection when App is in bypass list - GH_TOKEN: ${{ steps.generate-token.outputs.token || github.token }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} TARGET_BRANCH: refs/heads/${{ github.event.inputs.target-branch || 'dev' }} COMMIT_MESSAGE: "${{ github.event.inputs.commit-msg || 'chore: sync issues and PRs' }}" diff --git a/assets/workspace/.github/workflows/sync-main-to-dev.yml b/assets/workspace/.github/workflows/sync-main-to-dev.yml index 5af05f2d..37aa5af9 100644 --- a/assets/workspace/.github/workflows/sync-main-to-dev.yml +++ b/assets/workspace/.github/workflows/sync-main-to-dev.yml @@ -7,15 +7,18 @@ # Pipeline: # check - (early exit if dev already contains all main commits) # sync - clean up stale sync branches -# - trial merge to detect conflicts -# - create chore/sync-main-to-dev-- branch via API -# - open PR (auto-merge enabled, or labelled "merge-conflict" with -# resolution instructions when conflicts exist) +# - merge-tree (in-memory) merge to detect conflicts +# - create chore/sync-main-to-dev-- branch via git push +# - open PR; enable auto-merge when clean, or label "merge-conflict" with +# resolution instructions when conflicts exist # # Auth: Two GitHub App tokens: # - COMMIT_APP_* for git/ref operations (least-privilege commit identity) # - RELEASE_APP_* for PR/label operations that require pull-request scopes # +# NOTE: This workspace workflow is intentionally decoupled from upstream +# `.github/workflows/sync-main-to-dev.yml` and maintained separately. +# # With the new CHANGELOG flow, dev already has ## Unreleased (created during # prepare-release), so no CHANGELOG reset is needed here. # @@ -36,10 +39,35 @@ permissions: contents: read jobs: + resolve-image: + name: Resolve image tag + runs-on: ubuntu-22.04 + timeout-minutes: 2 + outputs: + image-tag: ${{ steps.resolve.outputs.image-tag }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .vig-os + .github/actions/resolve-image + sparse-checkout-cone-mode: false + + - name: Resolve container image + id: resolve + uses: ./.github/actions/resolve-image + check: name: Check if dev is up to date + needs: [resolve-image] runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} timeout-minutes: 5 + defaults: + run: + shell: bash outputs: up_to_date: ${{ steps.check.outputs.up_to_date }} @@ -49,11 +77,14 @@ jobs: with: fetch-depth: 0 + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Check if dev is up to date with main id: check run: | set -euo pipefail - git fetch origin main dev + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev if ! git show-ref --verify --quiet refs/remotes/origin/main; then echo "Error: remote branch 'origin/main' not found after fetch." exit 1 @@ -73,10 +104,15 @@ jobs: sync: name: Merge main into dev via PR - needs: check + needs: [resolve-image, check] if: needs.check.outputs.up_to_date != 'true' runs-on: ubuntu-22.04 + container: + image: ghcr.io/vig-os/devcontainer:${{ needs.resolve-image.outputs.image-tag }} timeout-minutes: 10 + defaults: + run: + shell: bash env: SYNC_BRANCH: chore/sync-main-to-dev-${{ github.run_number }}-${{ github.run_attempt }} permissions: @@ -87,7 +123,7 @@ jobs: steps: - name: Generate Commit App Token id: commit-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.COMMIT_APP_ID }} private-key: ${{ secrets.COMMIT_APP_PRIVATE_KEY }} @@ -99,11 +135,14 @@ jobs: fetch-depth: 0 token: ${{ steps.commit-app-token.outputs.token }} + - name: Fix git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Re-check if dev is still behind main id: recheck run: | set -euo pipefail - git fetch origin main dev + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev BEHIND=$(git rev-list --count origin/main ^origin/dev) if [ "${BEHIND}" = "0" ]; then echo "up_to_date=true" >> "$GITHUB_OUTPUT" @@ -116,7 +155,7 @@ jobs: - name: Generate Release App Token if: steps.recheck.outputs.up_to_date != 'true' id: release-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} @@ -128,7 +167,8 @@ jobs: GH_TOKEN: ${{ steps.release-app-token.outputs.token }} run: | set -euo pipefail - OPEN=$(gh pr list --base dev --state open --limit 200 \ + OPEN=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --state open --limit 200 \ --json headRefName \ --jq '[.[] | select(.headRefName | startswith("chore/sync-main-to-dev-"))] | length') echo "count=${OPEN}" >> "$GITHUB_OUTPUT" @@ -142,13 +182,16 @@ jobs: GH_TOKEN: ${{ steps.commit-app-token.outputs.token }} run: | set -euo pipefail - REFS=$(gh api --paginate "repos/${{ github.repository }}/git/matching-refs/heads/chore/sync-main-to-dev-" \ + REFS=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api --paginate "repos/${{ github.repository }}/git/matching-refs/heads/chore/sync-main-to-dev-" \ --jq '.[].ref' | sed 's|refs/heads/||') || true for branch in ${REFS}; do - HAS_PR=$(GH_TOKEN="${{ steps.release-app-token.outputs.token }}" gh pr list --base dev --head "${branch}" \ + HAS_PR=$(GH_TOKEN="${{ steps.release-app-token.outputs.token }}" retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --head "${branch}" \ --state open --json number --jq 'length') if [ "${HAS_PR}" = "0" ]; then - gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${branch}" 2>/dev/null || true + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${branch}" 2>/dev/null || true echo "Deleted stale sync branch: ${branch}" fi done @@ -158,25 +201,32 @@ jobs: id: merge-check run: | set -euo pipefail - git fetch origin main - if git merge --no-commit --no-ff origin/main 2>/dev/null; then + retry --retries 3 --backoff 3 --max-backoff 20 -- git fetch origin main dev + if merge_out="$(git merge-tree --write-tree origin/dev origin/main 2>&1)"; then echo "conflict=false" >> "$GITHUB_OUTPUT" + echo "Merge-tree check: no conflicts between origin/dev and origin/main." else - echo "conflict=true" >> "$GITHUB_OUTPUT" + merge_rc=$? + if [ "${merge_rc}" -eq 1 ]; then + echo "conflict=true" >> "$GITHUB_OUTPUT" + echo "::warning::Merge conflicts detected between origin/dev and origin/main." + echo "${merge_out}" + else + echo "::error::git merge-tree failed with exit code ${merge_rc}" + echo "${merge_out}" + exit "${merge_rc}" + fi fi - git merge --abort 2>/dev/null || true - name: Create sync branch from main if: steps.existing-pr.outputs.count == '0' && steps.recheck.outputs.up_to_date != 'true' - env: - GH_TOKEN: ${{ steps.commit-app-token.outputs.token }} run: | set -euo pipefail MAIN_SHA=$(git rev-parse origin/main) - gh api "repos/${{ github.repository }}/git/refs" \ - -f ref="refs/heads/${SYNC_BRANCH}" \ - -f sha="${MAIN_SHA}" - echo "Sync branch ${SYNC_BRANCH} created from main at ${MAIN_SHA}" + git checkout -b "${SYNC_BRANCH}" origin/main + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + git push origin "${SYNC_BRANCH}" + echo "Sync branch ${SYNC_BRANCH} pushed from main at ${MAIN_SHA}" - name: Create PR if: steps.existing-pr.outputs.count == '0' && steps.recheck.outputs.up_to_date != 'true' @@ -211,14 +261,22 @@ jobs: BODY="Syncs \`dev\` with \`main\` (sync-main-to-dev workflow)." fi - PR_URL=$(gh pr create --base dev --head "${SYNC_BRANCH}" \ - --title "${TITLE}" --body "${BODY}") + EXISTING_PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr list --base dev --head "${SYNC_BRANCH}" --state open --json url --jq '.[0].url // empty') + if [ -n "${EXISTING_PR_URL}" ]; then + PR_URL="${EXISTING_PR_URL}" + else + PR_URL=$(retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr create --base dev --head "${SYNC_BRANCH}" \ + --title "${TITLE}" --body "${BODY}") + fi echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT" echo "Created PR: ${PR_URL}" if [ "${CONFLICT}" = "true" ]; then gh label create "merge-conflict" --color "B60205" --force 2>/dev/null || true - gh pr edit "${SYNC_BRANCH}" --add-label "merge-conflict" || \ + retry --retries 3 --backoff 5 --max-backoff 30 -- \ + gh pr edit "${SYNC_BRANCH}" --add-label "merge-conflict" || \ echo "Warning: failed to add merge-conflict label." fi @@ -231,5 +289,6 @@ jobs: PR_URL: ${{ steps.create-pr.outputs.pr_url }} run: | set -euo pipefail - gh pr merge "${PR_URL}" --auto --merge || \ + retry --retries 2 --backoff 5 --max-backoff 20 -- \ + gh pr merge "${PR_URL}" --auto --merge || \ echo "Warning: could not enable auto-merge (may require branch protection settings)" diff --git a/assets/workspace/CHANGELOG.md b/assets/workspace/CHANGELOG.md index 79573c49..bb3637cc 100644 --- a/assets/workspace/CHANGELOG.md +++ b/assets/workspace/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +### Deprecated + ### Removed ### Fixed diff --git a/assets/workspace/docs/container-ci-quirks.md b/assets/workspace/docs/container-ci-quirks.md index 663551cb..db834206 100644 --- a/assets/workspace/docs/container-ci-quirks.md +++ b/assets/workspace/docs/container-ci-quirks.md @@ -1,22 +1,22 @@ -# Container CI vs Bare-Runner CI +# Container CI Notes -Behavioral differences when running CI inside -`ghcr.io/vig-os/devcontainer:latest` via the GitHub Actions `container:` -directive, compared to the bare-runner workflow (`ci.yml`). +Behavioral notes for the workspace CI workflow (`.github/workflows/ci.yml`) +when jobs run inside `ghcr.io/vig-os/devcontainer:*` via GitHub Actions +`container:`. -## No `setup-env` composite action +## Tool bootstrap model -The bare-runner CI uses `.github/actions/setup-env` to install Python, uv, -pre-commit, and other tools on a fresh Ubuntu runner. Inside the container -these tools are already present in the image, and marketplace setup actions -(`actions/setup-python`, `astral-sh/setup-uv`) target the runner OS rather -than the container filesystem. The container workflow skips `setup-env` -entirely and calls tools directly. +The workflow runs with tools already provided by the devcontainer image, then +uses downstream `just` recipes to keep CI aligned with project commands: + +```yaml +- run: just sync +``` ## git safe.directory `actions/checkout` runs on the host and bind-mounts the workspace into the -container. The resulting directory is owned by a different UID than the +container. The resulting directory is owned by a different UID than the container's root user, which triggers git's `safe.directory` rejection. The container workflow adds: @@ -26,7 +26,7 @@ The container workflow adds: ## Root user -The container runs as `root` by default. No `sudo` is required and file +The container runs as `root` by default. No `sudo` is required and file permission issues are unlikely, but any git operations need the `safe.directory` fix above. @@ -36,30 +36,20 @@ The container job does not have access to a Docker or Podman daemon. Jobs that require building or running containers (e.g. integration tests using `devcontainer up`) are not supported in this workflow. -## No dedicated security job +## Security scope -The bare-runner CI runs a standalone security job that invokes `bandit` -and `safety` independently, uploads JSON reports as artifacts, and posts -findings to the step summary. `bandit` still runs inside the container -via the `pre-commit` lint hook (`uv run bandit`), but `safety` is not -available in the image. The dedicated security job with artifact uploads -remains bare-runner-only until `safety` is added to a future image -release. +`bandit` can still run via the `pre-commit` lint hook (`uv run bandit`), but +there is no separate CI security-report job with JSON artifact uploads. -## No dependency-review job +## Dependency review scope -The bare-runner CI includes an `actions/dependency-review-action` job -that checks for dependency vulnerabilities on pull requests. This is a -marketplace action that inspects the GitHub dependency graph and does not -need to run inside the container. It is omitted from the container -workflow to keep it focused on validating tools shipped in the image. +The CI workflow does not include a dedicated `actions/dependency-review-action` +job; it focuses on validating code quality and tests inside the image. ## No coverage artifact upload -The bare-runner test job produces an XML coverage report -(`--cov-report=xml`) and uploads it via `actions/upload-artifact`. The -container test job only prints coverage to the terminal -(`--cov-report=term-missing`) and does not upload artifacts. +The test job runs `just test` (plain `pytest`) and does not upload +coverage artifacts. ## Pre-commit cache miss diff --git a/assets/workspace/justfile.project b/assets/workspace/justfile.project index 7c6ba6e9..664ccbb8 100644 --- a/assets/workspace/justfile.project +++ b/assets/workspace/justfile.project @@ -105,3 +105,18 @@ branch: # [group('app')] # migrate: # just sidecar postgres migrate +# +# # Prepare release branch (manual workflow dispatch helper) +# [group('release')] +# prepare-release version: +# gh workflow run prepare-release.yml --ref dev -f "version={{ version }}" +# +# # Finalize and publish release (manual workflow dispatch helper) +# [group('release')] +# finalize-release version: +# gh workflow run release.yml --ref release/{{ version }} -f "version={{ version }}" -f "release-kind=final" -f "dry-run=false" +# +# # Publish candidate release (manual workflow dispatch helper) +# [group('release')] +# publish-candidate version: +# gh workflow run release.yml --ref release/{{ version }} -f "version={{ version }}" -f "release-kind=candidate" -f "dry-run=false" diff --git a/docs/CROSS_REPO_RELEASE_GATE.md b/docs/CROSS_REPO_RELEASE_GATE.md new file mode 100644 index 00000000..c5fc2484 --- /dev/null +++ b/docs/CROSS_REPO_RELEASE_GATE.md @@ -0,0 +1,131 @@ +# Cross-Repo Release Validation Gate + +This document describes the dedicated cross-repository validation gate used by the release pipeline. + +## Rationale + +The release pipeline publishes candidate and final tags in the main repository. A separate validation repository executes post-publish verification against those tags. + +This gate exists to: + +- validate release artifacts outside the release repository execution context +- enforce a consistent candidate-to-final promotion rule +- provide an auditable, machine-checkable signal before finalization +- keep release orchestration and validation responsibilities separated + +## How It Works + +### Triggering + +During release publish, the orchestrator sends a `repository_dispatch` event to `vig-os/devcontainer-smoke-test`. + +Payload contract: + +- Required: + - `client_payload[tag]` +- Required for current gate behavior: + - `client_payload[release_kind]` (`candidate` or `final`) +- Optional source context: + - `client_payload[event_type]` + - `client_payload[source_repo]` + - `client_payload[source_workflow]` + - `client_payload[source_run_id]` + - `client_payload[source_run_url]` + - `client_payload[source_sha]` + - `client_payload[correlation_id]` + +Workflow dispatch contract: + +- Required downstream workflow IDs/files: + - `prepare-release.yml` + - `release.yml` +- Required dispatch ref: + - `dev` +- Dispatch and wait operations must use the same ref context to avoid default-branch drift: + - dispatch via `gh workflow run --ref dev ...` + - run discovery via `gh run list --workflow --branch dev ...` + +### Receiver Responsibilities + +The receiver workflow (`assets/smoke-test/.github/workflows/repository-dispatch.yml`) performs: + +1. payload validation and metadata normalization +2. deploy orchestration in the validation repository +3. release artifact publication for the dispatched tag: + - candidate tag -> GitHub pre-release + - final tag -> GitHub release +4. idempotency checks when a release object already exists +5. preflight validation that required downstream workflow IDs are resolvable on the dispatch ref before orchestration starts + +### Gate Checks in the Orchestrator + +The orchestrator validates: + +- release completion for the dispatched publish tag +- release type parity with `release_kind` + - candidate expects `prerelease=true` + - final expects `prerelease=false` +- additional finalization precondition: latest RC must already exist as a downstream pre-release + +If any of these checks fail, the release workflow fails and rollback handling is evaluated by workflow conditions. + +## Expected Output + +### Success Signals + +Expected release-run logs include messages equivalent to: + +``` +✓ Triggered validation dispatch for release tag: X.Y.Z-rcN +✓ Downstream release completed successfully for X.Y.Z-rcN +``` + +or for final: + +``` +✓ Triggered validation dispatch for release tag: X.Y.Z +✓ Downstream release completed successfully for X.Y.Z +``` + +### Expected Downstream Release State + +- For candidate publish: + - tag exists in downstream repo as a pre-release +- For final publish: + - tag exists in downstream repo as a non-pre-release release +- Before final publish validation: + - latest RC tag for the base version exists downstream as pre-release + +### Failure Signals + +Common failure patterns: + +- no downstream release found for expected tag within timeout +- downstream release type mismatch (`prerelease` flag differs from expected) +- malformed/insufficient dispatch payload +- downstream workflow failure prior to release artifact publication +- workflow contract drift (required workflow ID missing on expected dispatch ref), which must fail fast in preflight + +## Operational Verification + +Examples for manual inspection: + +```bash +gh -R vig-os/devcontainer-smoke-test run list --workflow repository-dispatch.yml --limit 5 +gh -R vig-os/devcontainer-smoke-test run view +gh -R vig-os/devcontainer-smoke-test release view +``` + +## Source of Truth + +- Orchestrator logic: `.github/workflows/release.yml` +- Validation receiver template: `assets/smoke-test/.github/workflows/repository-dispatch.yml` + +## Token Model for Downstream Write Paths + +For downstream workflow templates used by this gate, repositories must provide both Commit and Release app credentials. + +- Commit App token is required for protected branch writes performed by release preparation/finalization flows. +- Release App token is required for PR/release/workflow dispatch orchestration. + +Using `github.token` for protected downstream write paths is not supported by this gate contract because branch rulesets may reject direct writes without app bypass. diff --git a/docs/DOWNSTREAM_RELEASE.md b/docs/DOWNSTREAM_RELEASE.md new file mode 100644 index 00000000..9c08b930 --- /dev/null +++ b/docs/DOWNSTREAM_RELEASE.md @@ -0,0 +1,143 @@ +# Downstream Release Workflows + +This document describes the downstream release workflows shipped in `assets/workspace/.github/workflows/`. + +## Overview + +The downstream template uses a split release architecture: + +- `prepare-release.yml` (`workflow_dispatch`) prepares `release/X.Y.Z` +- `release.yml` (`workflow_dispatch`) orchestrates: + - `release-core.yml` (`workflow_call`) + - `release-extension.yml` (`workflow_call`, project-owned) + - `release-publish.yml` (`workflow_call`) + +All files are deployed from `assets/workspace/` by `init-workspace.sh`. + +On failure, the orchestrator runs a single consolidated rollback that resets the release branch, removes any created tag, and opens a failure issue. + +## Release Modes + +`release.yml` supports two release modes via `release_kind`: + +- `candidate` (default): computes and publishes the next `X.Y.Z-rcN` tag as a GitHub pre-release +- `final`: publishes `X.Y.Z`, finalizes `CHANGELOG.md` release date, and runs `sync-issues` + +Candidate mode keeps release branch content unchanged (no CHANGELOG date finalization). Final mode performs changelog finalization before publish. + +## Workflow Interface + +The orchestrator `release.yml` passes release context directly to the called reusable workflows: + +- `.github/workflows/release-core.yml` +- `.github/workflows/release-extension.yml` +- `.github/workflows/release-publish.yml` + +There is no separate contract-version handshake; compatibility is defined by the `workflow_call` input schema in each workflow file. + +## Required App Secrets + +Downstream repositories are expected to provide both app credentials: + +- `COMMIT_APP_ID` +- `COMMIT_APP_PRIVATE_KEY` +- `RELEASE_APP_ID` +- `RELEASE_APP_PRIVATE_KEY` + +Template behavior relies on explicit app-token generation for release operations: + +- use **Commit App** token for protected branch/ref writes (`commit-action`, branch/tag mutation) +- use **Release App** token for release orchestration and PR/release API operations + +`github.token` is intentionally not used as a fallback for these release write paths. + +## Input Naming Convention + +All `workflow_call` inputs use underscores (e.g. `release_kind`, `dry_run`, `git_user_name`). The orchestrator `release.yml` translates its own `workflow_dispatch` hyphenated inputs at each call site. + +## Extension Hook + +Project-specific release behavior belongs in `.github/workflows/release-extension.yml`. + +Default template behavior is no-op. Projects can customize this workflow for tasks such as: + +- package publishing +- container publishing +- signing and attestations +- release artifact upload + +Extension contract inputs include both `release_kind` and `publish_version`, so custom logic can branch on candidate vs final behavior. + +`release.yml` requires extension success before publish, so extension failures block release publication. + +## Cross-Repo Validation Gate + +Cross-repository validation gate details are documented in `docs/CROSS_REPO_RELEASE_GATE.md`. + +### Example: GHCR Publishing + +The following shows how a downstream project could customize `release-extension.yml` to build and push a container image to GHCR: + +```yaml +name: Release Extension + +on: + workflow_call: + inputs: + version: + required: true + type: string + finalize_sha: + required: true + type: string + release_date: + required: true + type: string + release_kind: + required: true + type: string + publish_version: + required: true + type: string +jobs: + ghcr-publish: + name: Publish Container Image + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout finalized commit + uses: actions/checkout@v4 + with: + ref: ${{ inputs.finalize_sha }} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ inputs.publish_version }} + ${{ inputs.release_kind == 'final' && format('ghcr.io/{0}:latest', github.repository) || '' }} +``` + +## Upgrade Path + +1. Upgrade downstream devcontainer version (which redeploys `assets/workspace` templates). +2. Keep project-owned `release-extension.yml` (preserved on force upgrades). +3. Ensure project-owned `release-extension.yml` matches the current `workflow_call` inputs used by `release.yml`. +4. Run `prepare-release` / `release` in `--dry-run` mode to validate integration. + +## Pinning and Drift + +Release workflow logic is centralized in shipped local reusable workflows (`release-core.yml`, `release-publish.yml`) while extension logic remains project-owned (`release-extension.yml`). + +This reduces drift in release safety checks while preserving downstream customization boundaries. diff --git a/docs/RELEASE_CYCLE.md b/docs/RELEASE_CYCLE.md index c63f25ad..bd7d096d 100644 --- a/docs/RELEASE_CYCLE.md +++ b/docs/RELEASE_CYCLE.md @@ -118,13 +118,13 @@ graph TB J --> K["just publish-candidate X.Y.Z"] K --> L["Workflow: build & test
(no CHANGELOG changes)"] L --> M["Publish X.Y.Z-rcN"] - M --> U{Smoke tests pass?} + M --> U{External gate pass?} U -->|No| I U -->|Yes| N["just finalize-release X.Y.Z"] N --> O["Workflow: set release date
build & test"] O --> P{Tests pass?} P -->|No| Q["Automatic rollback
+ issue creation"] - P -->|Yes| R["Workflow: create tag
publish images"] + P -->|Yes| R["Workflow: create tag
publish images
publish GitHub Release (final only)"] R --> S["Merge PR to main"] S --> T["Workflow: sync-main-to-dev
PR-based sync"] ``` @@ -133,8 +133,8 @@ graph TB 1. **Preparation** (`prepare-release`): Freeze CHANGELOG on dev, create release branch, reset Unreleased on dev, open draft PR 2. **Review & Testing**: CI validation, mark PR ready, fix issues, get approvals -3. **Candidate Publish** (`publish-candidate`): Build/test/publish `X.Y.Z-rcN` and dispatch smoke-test workflow -4. **Manual Smoke Gate**: Wait for smoke-test repo workflows to pass before final release +3. **Candidate Publish** (`publish-candidate`): Build/test/publish `X.Y.Z-rcN` and dispatch cross-repo validation workflow +4. **Cross-Repo Validation Gate (automated prerequisite)**: Final release validate step requires downstream pre-release for the latest RC tag 5. **Finalization & Post-Release**: Publish final image/tag, then merge PR to main and let sync automation update dev @@ -348,7 +348,7 @@ The `release.yml` workflow performs the entire remaining release process. Behavi 2. ✅ **Finalize** job (skipped if --dry-run) - **Candidate**: No CHANGELOG changes. Outputs current release branch HEAD SHA. - - **Final**: Sets actual release date in CHANGELOG (TBD → YYYY-MM-DD), commits, triggers sync-issues workflow, outputs finalized SHA. + - **Final**: Sets actual release date in CHANGELOG (TBD → YYYY-MM-DD), regenerates docs from templates, commits all tracked finalization changes (dynamic file list), refreshes release PR body from finalized CHANGELOG, triggers sync-issues workflow, outputs finalized SHA. 3. ✅ **Build & Test** jobs (per-architecture, runs in parallel) - Builds container image as tar file @@ -359,15 +359,16 @@ The `release.yml` workflow performs the entire remaining release process. Behavi 4. ✅ **Publish** job (runs only if all builds/tests pass) - Candidate mode: infers next `rcN`, creates annotated tag `X.Y.Z-rcN`, publishes candidate manifests - - Candidate mode: triggers `repository_dispatch` to `vig-os/devcontainer-smoke-test` with `client_payload.tag` - Final mode: creates annotated tag `X.Y.Z`, publishes final manifests - Pushes tag to origin + - Final mode only: extracts release notes from finalized `CHANGELOG.md` and publishes GitHub Release for `X.Y.Z` - Downloads tested images from artifacts - Logs in to GitHub Container Registry - Pushes images to GHCR with architecture-specific tags - Creates multi-architecture manifest `ghcr.io/vig-os/devcontainer:` - Creates/updates `ghcr.io/vig-os/devcontainer:latest` only in final mode (and only when both architectures are built) - Verifies manifests exist + - Candidate and final modes: trigger cross-repository validation dispatch with `client_payload[tag]` plus source metadata (`source_repo`, `source_workflow`, `source_run_id`, `source_run_url`, `source_sha`, `correlation_id`) 5. ✅ **Rollback** job (runs if ANY job failed) - Resets release branch to pre-finalization state @@ -401,37 +402,9 @@ Release Summary: - **Audit trail**: All steps are recorded in GitHub Actions logs with actor information - **Reproducible**: Uses consistent CI environment, not dependent on local tooling -### Phase 4: Manual RC Smoke Gate +### Phase 4: Cross-Repo Validation Gate (automated) -After `just publish-candidate X.Y.Z` succeeds, verify smoke tests before running final release. - -1. Confirm dispatch-triggered smoke-test run in the smoke-test repo: - - ```bash - gh -R vig-os/devcontainer-smoke-test run list --workflow repository-dispatch.yml --limit 1 - ``` - -2. Inspect that run and verify both downstream workflows completed successfully: - - ```bash - gh -R vig-os/devcontainer-smoke-test run view - ``` - -3. Required pass criteria: - - `ci.yml` (bare-runner workflow) passed for the RC tag - - `ci-container.yml` (container workflow) passed for the RC tag - -4. If smoke tests fail: - - Fix the issue on the release branch - - Publish a new candidate (`just publish-candidate X.Y.Z`) to generate the next RC tag - - Re-run this gate - -5. If smoke tests pass: - - Proceed with final release: - - ```bash - just finalize-release X.Y.Z - ``` +Cross-repository validation gate rationale, mechanics, payload contract, and pass/fail interpretation are documented in `docs/CROSS_REPO_RELEASE_GATE.md`. ### Phase 5: Post-Release Cleanup @@ -551,12 +524,12 @@ Release automation relies on two GitHub Apps with different scopes: | App | Secrets | Permissions | Used by | Purpose | |-----|---------|-------------|---------|---------| -| **RELEASE_APP** | `RELEASE_APP_ID`, `RELEASE_APP_PRIVATE_KEY` | Contents read/write, Issues read/write, Pull requests read/write | `release.yml`, `prepare-release.yml`, `sync-main-to-dev.yml` | Release operations, PR creation/updates, rollback, and smoke-test dispatch | +| **RELEASE_APP** | `RELEASE_APP_ID`, `RELEASE_APP_PRIVATE_KEY` | Contents read/write, Issues read/write, Pull requests read/write, Actions read/write | `release.yml`, `prepare-release.yml`, `sync-main-to-dev.yml` | Release operations, PR creation/updates, rollback, and cross-repo validation dispatch | | **COMMIT_APP** | `COMMIT_APP_ID`, `COMMIT_APP_PRIVATE_KEY` | Contents read/write, Issues read, Pull requests read | `sync-issues.yml`, `sync-main-to-dev.yml` | Commits to protected branches and git ref operations | Additional requirement: - `COMMIT_APP` must be allowed in branch protection bypass rules for `dev` so sync commits can be pushed by automation. -- `RELEASE_APP` must be installed on `vig-os/devcontainer-smoke-test` with Contents read permission so `release.yml` can send `repository_dispatch` for RC smoke tests. +- `RELEASE_APP` must be installed on the validation repository with Contents read and Actions read/write permissions so `release.yml` can send `repository_dispatch` and `repository-dispatch.yml` can trigger downstream workflow dispatch events for candidate and final release validation. #### prepare-release.yml (Release Preparation Workflow) @@ -620,7 +593,7 @@ gh workflow run prepare-release.yml --ref dev -f "version=1.0.0" -f "dry-run=tru 2. **finalize** (skipped if dry-run) - Conditionally updates release branch - **Candidate**: No CHANGELOG changes, no sync-issues. Outputs current release branch HEAD SHA. - - **Final**: Sets release date in CHANGELOG (TBD → YYYY-MM-DD), commits, triggers sync-issues, outputs finalized SHA. + - **Final**: Sets release date in CHANGELOG (TBD → YYYY-MM-DD), regenerates docs, commits all tracked finalization changes via dynamic file list, refreshes release PR body from finalized changelog content, triggers sync-issues, outputs finalized SHA. 3. **build-and-test** (matrix: amd64, arm64) - Builds and validates images - Builds container image for architecture @@ -633,13 +606,19 @@ gh workflow run prepare-release.yml --ref dev -f "version=1.0.0" -f "dry-run=tru - Candidate mode creates and pushes `X.Y.Z-rcN` (next available `N`) - Final mode creates and pushes `X.Y.Z` - Pushes tag + - Final mode only: publishes GitHub Release `X.Y.Z` with notes sourced from finalized `CHANGELOG.md` - Downloads tested images from artifacts - Pushes images to GHCR - Creates multi-architecture manifest for computed publish tag - Updates `latest` only in final mode - Verifies manifests exist -5. **rollback** (runs if any job failed) - Cleans up partial state +5. **smoke-test** (runs after publish) - Triggers downstream validation + - Candidate and final modes trigger cross-repository validation `repository_dispatch` with `client_payload[tag]=` + - Dispatch failures mark the workflow as failed and create a targeted issue + - Dispatch failures do **not** rollback branch/tag, because published artifacts are already immutable at this point + +6. **rollback** (runs if validate/finalize/build-and-test/publish fails) - Cleans up partial state - Resets release branch to pre-finalization state - Deletes tag if it was created - Creates GitHub issue with failure details @@ -664,7 +643,7 @@ gh workflow run release.yml \ **Key characteristics:** - Tag created AFTER successful build/test (safer than before) -- Automatic rollback on failure +- Automatic rollback on validate/finalize/build-and-test/publish failure - All in one workflow for atomic operation - Audit trail in GitHub Actions logs - Dispatch is pinned to `release/X.Y.Z` so candidate/final runs use the release branch workflow definition @@ -730,6 +709,20 @@ gh workflow run release.yml \ --- +## Downstream Integration + +Downstream repositories that use the workspace template consume release workflows from: + +- `assets/workspace/.github/workflows/prepare-release.yml` +- `assets/workspace/.github/workflows/release.yml` +- `assets/workspace/.github/workflows/release-core.yml` +- `assets/workspace/.github/workflows/release-extension.yml` +- `assets/workspace/.github/workflows/release-publish.yml` + +The orchestrator (`release.yml`) runs core -> extension -> publish and uses contract validation for local `workflow_call` interfaces. Cross-repository validation details are documented in `docs/CROSS_REPO_RELEASE_GATE.md`. + +For contract details and extension ownership boundaries, see `docs/DOWNSTREAM_RELEASE.md`. + ## QMS and Compliance ### Traceability @@ -1007,6 +1000,7 @@ Follow [Semantic Versioning 2.0.0](https://semver.org/): - [CHANGELOG Format](../CHANGELOG.md) - Keep a Changelog standard - [Commit Message Standard](COMMIT_MESSAGE_STANDARD.md) - Commit format and validation +- [Downstream Release Workflows](DOWNSTREAM_RELEASE.md) - Downstream release contract and extension model - [Branch Naming Rules](../.cursor/rules/branch-naming.mdc) - Topic branch conventions - [IEC 62304](https://www.iso.org/standard/38421.html) - Medical device software lifecycle - [Semantic Versioning](https://semver.org/) - Version numbering scheme diff --git a/docs/issues/issue-169.md b/docs/issues/issue-169.md index e0c1c4dd..f50094a3 100644 --- a/docs/issues/issue-169.md +++ b/docs/issues/issue-169.md @@ -1,19 +1,19 @@ --- type: issue -state: open +state: closed created: 2026-02-24T07:13:32Z -updated: 2026-03-12T11:44:41Z +updated: 2026-03-13T21:58:35Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/devcontainer/issues/169 -comments: 1 +comments: 2 labels: feature, area:ci, area:testing, effort:large, semver:minor assignees: none milestone: 0.3 projects: none parent: none children: 170, 171, 172, 173, 197, 161, 122, 264 -synced: 2026-03-12T12:05:21.429Z +synced: 2026-03-14T04:16:11.145Z --- # [Issue 169]: [[FEATURE] Smoke-test repository to validate shipped CI/CD workflows](https://github.com/vig-os/devcontainer/issues/169) @@ -123,7 +123,7 @@ Added - [x] Smoke-test repo exists with a deployed workspace from the current template - [x] Bare-runner CI (`ci.yml`) runs successfully against the deployed workspace -- [ ] RC publishing works: release workflow can publish `X.Y.Z-rc1` to GHCR +- [x] RC publishing works: release workflow can publish `X.Y.Z-rc1` to GHCR - [ ] Smoke-test repo is triggered via `repository_dispatch` on RC publish - [ ] Container CI (`ci-container.yml`) runs successfully using the RC image via `container:` directive - [ ] TDD compliance (see .cursor/rules/tdd.mdc) @@ -156,34 +156,34 @@ Full end-to-end release-candidate cycle to validate the remaining acceptance cri ### Phase 1: Prepare Release -- [ ] **Trigger the prepare-release workflow** +- [x] **Trigger the prepare-release workflow** ```bash just prepare-release 0.3.0 ``` -- [ ] **Workflow completes successfully** -- monitor in [Actions](https://github.com/vig-os/devcontainer/actions/workflows/prepare-release.yml) +- [x] **Workflow completes successfully** -- [here](https://github.com/vig-os/devcontainer/actions/runs/23001136891) #### Verification checklist -- [ ] `release/0.3.0` branch appears in [branches](https://github.com/vig-os/devcontainer/branches) -- [ ] Draft PR from `release/0.3.0` to `main` appears in [Pull Requests](https://github.com/vig-os/devcontainer/pulls) -- [ ] Release branch CHANGELOG has `## [0.3.0] - TBD` (no `## Unreleased`) -- [ ] `dev` CHANGELOG has empty `## Unreleased` above `## [0.3.0] - TBD` +- [x] `release/0.3.0` branch appears in [branches](https://github.com/vig-os/devcontainer/branches) +- [x] Draft PR from `release/0.3.0` to `main` appears in [Pull Requests](https://github.com/vig-os/devcontainer/pulls) +- [x] Release branch CHANGELOG has `## [0.3.0] - TBD` (no `## Unreleased`) +- [x] `dev` CHANGELOG has empty `## Unreleased` above `## [0.3.0] - TBD` --- ### Phase 2: Review & Test Release Branch -- [ ] **CI passes on release PR** -- check PR's "Checks" tab +- [x] **CI passes on release PR** -- [here](https://github.com/vig-os/devcontainer/actions/runs/23006247353) ```bash gh run list --branch release/0.3.0 --workflow ci.yml --limit 1 ``` -- [ ] **Review CHANGELOG** -- verify all 0.3 changes are documented with correct issue refs -- [ ] **Mark PR as ready for review** -- click "Ready for review" in the PR UI, or: +- [x] **Review CHANGELOG** -- verify all 0.3 changes are documented with correct issue refs +- [x] **Mark PR as ready for review** -- click "Ready for review" in the PR UI, or: ```bash gh pr ready ``` -- [ ] **Get PR approval** -- add a reviewer in the PR sidebar +- [x] **Get PR approval** -- add a reviewer in the PR sidebar > **Fixing issues:** All fixes go through bugfix PRs targeting `release/0.3.0` (branch is write-protected). @@ -193,19 +193,19 @@ Full end-to-end release-candidate cycle to validate the remaining acceptance cri #### 3a. Dry run (recommended) -- [ ] **Run candidate publish in dry-run mode** +- [x] **Run candidate publish in dry-run mode** ```bash - just publish-candidate 0.3.0 -f "dry-run=true" + just publish-candidate 0.3.0 release/0.3.0 -f "dry-run=true" ``` -- [ ] **Validate job passes** -- confirms all prerequisites without making changes +- [x] **Validate job passes** -- [here](https://github.com/vig-os/devcontainer/actions/runs/23009885833) #### 3b. Publish the RC -- [ ] **Trigger the actual candidate publish** +- [x] **Trigger the actual candidate publish** ```bash just publish-candidate 0.3.0 ``` -- [ ] **Monitor in [Actions > Release](https://github.com/vig-os/devcontainer/actions/workflows/release.yml)** -- all 5 jobs should complete: +- [x] **Validate results**: [all 5 jobs completed](https://github.com/vig-os/devcontainer/actions/runs/23016037898): | Job | Expected outcome | |-----|-----------------| @@ -217,18 +217,18 @@ Full end-to-end release-candidate cycle to validate the remaining acceptance cri #### 3c. Verify RC image on GHCR :white_check_mark: Acceptance criterion 1 -- [ ] **Tag `0.3.0-rc1` exists** -- check [tags](https://github.com/vig-os/devcontainer/tags) -- [ ] **Image is pullable** +- [x] **Tag `0.3.0-rc1` exists** -- check [tags](https://github.com/vig-os/devcontainer/tags) +- [x] **Image is pullable** ```bash docker pull ghcr.io/vig-os/devcontainer:0.3.0-rc1 ``` -- [ ] **Multi-arch manifest** -- inspect on [GHCR package page](https://github.com/vig-os/devcontainer/pkgs/container/devcontainer) or: +- [x] **Multi-arch manifest** -- inspect on [GHCR package page](https://github.com/vig-os/devcontainer/pkgs/container/devcontainer) or: ```bash docker buildx imagetools inspect ghcr.io/vig-os/devcontainer:0.3.0-rc1 ``` Expect both `linux/amd64` and `linux/arm64` platforms. -- [ ] **No `latest` tag updated** (candidate mode should not touch `latest`) -- [ ] **Cosign signature verifies** (optional) +- [x] **No `latest` tag updated** (candidate mode should not touch `latest`) +- [x] **Cosign signature verifies** (optional) ```bash cosign verify \ --certificate-identity-regexp='https://github.com/vig-os/devcontainer/' \ @@ -242,39 +242,40 @@ Full end-to-end release-candidate cycle to validate the remaining acceptance cri #### 4a. Dispatch fired :white_check_mark: Acceptance criterion 2 -- [ ] **New dispatch run appears** in [smoke-test Actions > Repository Dispatch](https://github.com/vig-os/devcontainer-smoke-test/actions/workflows/repository-dispatch.yml) +- [x] **New dispatch run appears** in [smoke-test Actions > Repository Dispatch](https://github.com/vig-os/devcontainer-smoke-test/actions/workflows/repository-dispatch.yml) ```bash gh run list --repo vig-os/devcontainer-smoke-test --workflow repository-dispatch.yml --limit 3 ``` - > If no run appears, check the devcontainer release workflow's publish job logs for the "Trigger smoke-test repository dispatch" step. Look for token generation warnings. - -- [ ] **Dispatch `validate` job passes** -- tag `0.3.0-rc1` matches `^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$` -- [ ] **Dispatch `deploy` job passes** -- runs installer with `--version 0.3.0-rc1`, creates branch `chore/deploy-0.3.0-rc1`, opens PR to `dev` +> **Several bugs encountered**: required upstream fixes in [commit-action](https://github.com/vig-os/commit-action/releases/tag/v0.1.5) and new Release Candidates [0.3.0-rc2](https://github.com/vig-os/devcontainer/releases/tag/0.3.0-rc2) and [0.3.0-rc3](https://github.com/vig-os/devcontainer/releases/tag/0.3.0-rc3) +- [x] **Dispatch `validate` job passes** -- tag `0.3.0-rc3` matches `^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$` +- [x] **Dispatch `deploy` job passes** -- runs installer with `--version 0.3.0-rc3`, creates branch `chore/deploy-0.3.0-rc3`, opens PR to `dev` #### 4b. Bare-runner CI passes -- [ ] **CI triggered on deploy PR** -- check [smoke-test PRs](https://github.com/vig-os/devcontainer-smoke-test/pulls), then the PR's Checks tab +- [x] **CI triggered on deploy PR** -- check [smoke-test PRs](https://github.com/vig-os/devcontainer-smoke-test/pulls), then the PR's Checks tab. Successful run: [23056631299](https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23056631299) ```bash - gh pr checks --repo vig-os/devcontainer-smoke-test + gh pr checks 23056631299 --repo vig-os/devcontainer-smoke-test ``` -- [ ] Lint & Format: pre-commit hooks pass -- [ ] Tests: pytest passes with coverage -- [ ] Security Scan: bandit + safety pass -- [ ] CI Summary: green +- [x] Lint & Format: pre-commit hooks pass +- [x] Tests: pytest passes with coverage +- [x] Security Scan: bandit + safety pass +- [x] CI Summary: green #### 4c. Container CI passes with RC image :white_check_mark: Acceptance criterion 3 This is the **critical test** -- the one that previously failed with `does-not-exist-tag`. -- [ ] **`ci-container.yml` triggered** on the deploy PR -- [ ] **"Initialize containers" succeeds** -- runner pulls `ghcr.io/vig-os/devcontainer:0.3.0-rc1` and starts the container -- [ ] Lint & Format (container): pre-commit hooks pass inside the devcontainer image -- [ ] Tests (container): pytest passes inside the devcontainer image -- [ ] CI Summary (container): green +Successful run: [23056631295](https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23056631295) + +- [x] **`ci-container.yml` triggered** on the deploy PR +- [x] **"Initialize containers" succeeds** -- runner pulls `ghcr.io/vig-os/devcontainer:0.3.0-rc3` and starts the container +- [x] Lint & Format (container): pre-commit hooks pass inside the devcontainer image +- [x] Tests (container): pytest passes inside the devcontainer image +- [x] CI Summary (container): green ```bash gh run list --repo vig-os/devcontainer-smoke-test --workflow ci-container.yml --limit 1 -gh run view --repo vig-os/devcontainer-smoke-test +gh run view 23056631295 --repo vig-os/devcontainer-smoke-test ``` --- @@ -297,12 +298,12 @@ gh run view --repo vig-os/devcontainer-smoke-test gh pr create --base release/0.3.0 --head bugfix/N-fix-description ``` -3. **Re-publish candidate** (auto-increments to `0.3.0-rc2`): +3. **Re-publish candidate** (auto-increments to `0.3.0-rc2` and `0.3.0-rc3` ): ```bash just publish-candidate 0.3.0 ``` -4. Repeat Phase 4 for the new RC. +4. Repeat Phase 4 for the new RCs. @@ -312,22 +313,22 @@ gh run view --repo vig-os/devcontainer-smoke-test > Only proceed when **both** smoke-test workflows (bare-runner + container) are green. -- [ ] **Trigger final release** +- [x] **Trigger final release** ```bash just finalize-release 0.3.0 ``` -- [ ] **Workflow completes** -- monitor in [Actions > Release](https://github.com/vig-os/devcontainer/actions/workflows/release.yml) +- [x] **Workflow completes** -- successful run [23058092503](https://github.com/vig-os/devcontainer/actions/runs/23058092503) #### Verification checklist -- [ ] CHANGELOG date set (`[0.3.0] - TBD` replaced with `[0.3.0] - YYYY-MM-DD`) -- [ ] sync-issues triggered and completed -- [ ] Both architectures build and test successfully -- [ ] Tag `0.3.0` created -- check [tags](https://github.com/vig-os/devcontainer/tags) -- [ ] Image published as `ghcr.io/vig-os/devcontainer:0.3.0` -- [ ] `latest` tag updated to `0.3.0` -- [ ] Cosign signature + SBOM + provenance attached -- [ ] Verify images: +- [x] CHANGELOG date set (`[0.3.0] - TBD` replaced with `[0.3.0] - YYYY-MM-DD`) +- [ ] sync-issues triggered and completed -> failed because version in `main` is outdated +- [x] Both architectures build and test successfully +- [x] Tag `0.3.0` created -- check [tags](https://github.com/vig-os/devcontainer/tags) +- [x] Image published as `ghcr.io/vig-os/devcontainer:0.3.0` +- [x] `latest` tag updated to `0.3.0` +- [x] Cosign signature + SBOM + provenance attached +- [x] Verify images: ```bash docker pull ghcr.io/vig-os/devcontainer:0.3.0 docker pull ghcr.io/vig-os/devcontainer:latest @@ -338,13 +339,13 @@ gh run view --repo vig-os/devcontainer-smoke-test ### Phase 7: Post-Release -- [ ] **Merge release PR to main** -- click "Merge pull request" in the PR UI, or: +- [x] **Merge release PR to main** -- click "Merge pull request" in the PR UI, or: ```bash gh pr merge --merge ``` -- [ ] **sync-main-to-dev fires** -- check [Actions > sync-main-to-dev](https://github.com/vig-os/devcontainer/actions/workflows/sync-main-to-dev.yml) -- [ ] **Dev is updated** -- sync PR auto-merges if conflict-free -- [ ] **Final state** -- both tags exist: +- [x] **sync-main-to-dev fires** -- check [Actions > sync-main-to-dev](https://github.com/vig-os/devcontainer/actions/workflows/sync-main-to-dev.yml) +- [x] **Dev is updated** -- sync PR auto-merges if conflict-free +- [x] **Final state** -- both tags exist: ```bash git fetch --all --tags git tag | grep 0.3.0 @@ -374,3 +375,11 @@ gh run view --repo vig-os/devcontainer-smoke-test | RC tag collision from concurrent run | Tag push step detects collision; re-run to infer next RC number | +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on March 13, 2026 at 09:58 PM_ + +Closed with [Release 0.3.0](https://github.com/vig-os/devcontainer/pull/270) + diff --git a/docs/issues/issue-171.md b/docs/issues/issue-171.md index 76ad0009..bd56d2c7 100644 --- a/docs/issues/issue-171.md +++ b/docs/issues/issue-171.md @@ -1,18 +1,19 @@ --- type: issue -state: open +state: closed created: 2026-02-24T10:13:19Z -updated: 2026-02-24T10:13:19Z +updated: 2026-03-03T20:22:50Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/devcontainer/issues/171 comments: 0 labels: feature, area:ci, effort:medium, area:testing, semver:minor -assignees: none -milestone: Backlog +assignees: c-vigo +milestone: 0.3 projects: none -relationship: none -synced: 2026-02-25T04:25:55.384Z +parent: 169 +children: none +synced: 2026-03-14T04:16:10.608Z --- # [Issue 171]: [[FEATURE] Add container-based CI workflow to smoke-test repo](https://github.com/vig-os/devcontainer/issues/171) @@ -56,9 +57,9 @@ Added ### Acceptance Criteria -- [ ] `ci-container.yml` exists in the smoke-test repo -- [ ] CI jobs run inside the devcontainer image via `container:` directive -- [ ] Lint and test jobs pass inside the container -- [ ] Both bare-runner (`ci.yml`) and container (`ci-container.yml`) CI run on PRs -- [ ] Quirks and differences vs bare-runner CI are documented -- [ ] TDD compliance (see .cursor/rules/tdd.mdc) +- [x] `ci-container.yml` exists in the smoke-test repo +- [x] CI jobs run inside the devcontainer image via `container:` directive +- [x] Lint and test jobs pass inside the container +- [x] Both bare-runner (`ci.yml`) and container (`ci-container.yml`) CI run on PRs +- [x] Quirks and differences vs bare-runner CI are documented +- [x] TDD compliance (see .cursor/rules/tdd.mdc) diff --git a/docs/issues/issue-212.md b/docs/issues/issue-212.md new file mode 100644 index 00000000..4d226627 --- /dev/null +++ b/docs/issues/issue-212.md @@ -0,0 +1,56 @@ +--- +type: issue +state: closed +created: 2026-03-03T07:25:04Z +updated: 2026-03-03T09:34:18Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/212 +comments: 0 +labels: bug, priority:high, area:workspace, effort:small, semver:patch +assignees: c-vigo +milestone: 0.3 +projects: none +parent: none +children: none +synced: 2026-03-14T04:16:05.046Z +--- + +# [Issue 212]: [[BUG] --force overwrites preserved files when rsync is unavailable](https://github.com/vig-os/devcontainer/issues/212) + +## Description +Running `install.sh` with `--force` should preserve user files listed in `PRESERVE_FILES` (including `README.md` and `CHANGELOG.md`). +When `rsync` is not available in the container, the fallback copy path overwrites those files anyway. + +## Steps to Reproduce +1. Use an image/environment where `rsync` is not installed. +2. In an existing workspace, customize `README.md` and `CHANGELOG.md`. +3. Run: + `install.sh --version dev --skip-pull --force --org vigOS` +4. Observe init output: + `Warning: rsync not available, preserved files may be overwritten` +5. Check `README.md` and `CHANGELOG.md`. + +## Expected Behavior +Files in the preserve list are never overwritten during `--force`, regardless of copy backend. + +## Actual Behavior +Preserved files are overwritten when fallback copy is used (no `rsync`). + +## Environment +- **OS**: Linux +- **Container Runtime**: Podman +- **Image Version/Tag**: `ghcr.io/vig-os/devcontainer:dev` +- **Architecture**: amd64 + +## Additional Context +The init output lists preserved files correctly before copy, but fallback logic does not enforce preservation. +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + +## Possible Solution +- Add `rsync` to the image to use the primary safe copy path. +- Keep fallback logic preserving files explicitly for robustness. +- Add regression coverage for fallback behavior. + +## Changelog Category +Fixed diff --git a/docs/issues/issue-213.md b/docs/issues/issue-213.md new file mode 100644 index 00000000..d2d534ec --- /dev/null +++ b/docs/issues/issue-213.md @@ -0,0 +1,49 @@ +--- +type: issue +state: closed +created: 2026-03-03T07:30:26Z +updated: 2026-03-03T09:55:29Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/213 +comments: 0 +labels: chore, priority:low, area:image, effort:small, semver:patch +assignees: c-vigo +milestone: 0.3 +projects: none +parent: none +children: 88 +synced: 2026-03-14T04:16:04.780Z +--- + +# [Issue 213]: [[CHORE] Refresh pinned python:3.12-slim-bookworm image digest in Containerfile](https://github.com/vig-os/devcontainer/issues/213) + +### Chore Type +Dependency update + +### Description +The base image line in `Containerfile` pins `python:3.12-slim-bookworm` to a specific digest. Security/update checks now report that the pinned digest is out of date. +Update the digest to the current upstream value for the same tag so builds stay reproducible while receiving latest base-image patches. + +### Acceptance Criteria +- [ ] Resolve the current digest for `python:3.12-slim-bookworm` +- [ ] Update the pinned digest in `Containerfile` +- [ ] Build/validate succeeds with the new digest +- [ ] No tag change (remain on `python:3.12-slim-bookworm`), only digest refresh + +### Implementation Notes +- Target file: `Containerfile` +- Keep pinning-by-digest policy intact (tag + digest) +- Prefer minimal diff (single-line base image update + any required note updates) + +### Related Issues +- Related to #88 (dependency/security maintenance in image context) + +### Priority +Low + +### Changelog Category +No changelog needed + +### Additional Context +Observed warning: `The image digest is out of date`. diff --git a/docs/issues/issue-271.md b/docs/issues/issue-271.md new file mode 100644 index 00000000..fbdeaab6 --- /dev/null +++ b/docs/issues/issue-271.md @@ -0,0 +1,63 @@ +--- +type: issue +state: closed +created: 2026-03-12T12:17:38Z +updated: 2026-03-12T12:49:02Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/271 +comments: 0 +labels: bug, priority:high, area:ci, area:docs, semver:patch +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:58.248Z +--- + +# [Issue 271]: [[BUG] generate-docs selects unreleased changelog version and fails release branch CI](https://github.com/vig-os/devcontainer/issues/271) + +## Description +The release branch CI fails in `Project Checks` because the `generate-docs` pre-commit hook modifies `README.md` during CI. + +The generated \"Latest Version\" line is changed from the last released tag (`0.2.1 - 2026-01-28`) to an unreleased entry (`0.3.0 - TBD`), which causes a dirty diff and fails the job. + +## Steps to Reproduce +1. Run prepare-release workflow for `release/0.3.0`. +2. Run `pre-commit run --all-files` (or let CI run `Project Checks`). +3. Observe `generate-docs` modifies `README.md` with: + - `- **Latest Version**: [0.3.0](...) - TBD` +4. CI exits non-zero due to hook-modified files. + +## Expected Behavior +`generate-docs` should resolve \"Latest Version\" from the latest **released** changelog entry (with a real date), not from unreleased/TBD entries. + +## Actual Behavior +`docs/generate.py` reads the first `## [x.y.z]` heading in `CHANGELOG.md`, which can point to a TBD/unreleased version and produce unstable generated docs in release CI. + +## Environment +- **OS**: GitHub Actions `ubuntu-latest` +- **Container Runtime**: N/A (workflow execution context) +- **Image Version/Tag**: release branch `release/0.3.0` +- **Architecture**: AMD64 +- **Workflow Run**: https://github.com/vig-os/devcontainer/actions/runs/23001181161 +- **Failing Job**: https://github.com/vig-os/devcontainer/actions/runs/23001181161/job/66785817335?pr=270 + +## Additional Context +- Failure occurs at pre-commit hook `generate-docs` in `Project Checks`. +- CI shows hook-generated diff in `README.md` for the latest-version line. +- Related code: `docs/generate.py` function `get_version_from_changelog()`. + +### Acceptance Criteria +- [ ] `get_version_from_changelog()` returns the latest version that has a real release date (not `TBD`) +- [ ] Generated docs are stable across local and CI runs on release branches +- [ ] `pre-commit run --all-files` passes without modifying docs after prepare-release +- [ ] Add regression test covering changelog parsing for released vs unreleased/TBD entries +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + +## Possible Solution +Update `docs/generate.py` (`get_version_from_changelog`) to parse changelog entries and choose the latest version whose heading includes a concrete date, excluding `TBD`/unreleased entries. + +## Changelog Category +Fixed diff --git a/docs/issues/issue-272.md b/docs/issues/issue-272.md new file mode 100644 index 00000000..a9bf1f83 --- /dev/null +++ b/docs/issues/issue-272.md @@ -0,0 +1,63 @@ +--- +type: issue +state: closed +created: 2026-03-12T12:18:38Z +updated: 2026-03-12T13:15:29Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/272 +comments: 0 +labels: bug, priority:blocking, area:testing, security +assignees: none +milestone: 0.3 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:58.019Z +--- + +# [Issue 272]: [[BUG] Fix high-severity CodeQL URL sanitization alerts in integration tests](https://github.com/vig-os/devcontainer/issues/272) + +## Description +The CodeQL check run for PR #270 (`check_run_id=66785943362`) failed with **3 new high-severity** alerts: +- **Incomplete URL substring sanitization** (3 occurrences) + +All alerts are reported in `tests/test_integration.py`. + +## Steps to Reproduce +1. Open PR checks for `https://github.com/vig-os/devcontainer/pull/270/checks`. +2. Open the failed **CodeQL** run (`check_run_id=66785943362`). +3. Inspect annotations and observe 3 findings titled **Incomplete URL substring sanitization**. +4. See findings on: + - `tests/test_integration.py` (line ~1200) + - `tests/test_integration.py` (line ~1214) + - `tests/test_integration.py` (line ~1468) + +## Expected Behavior +CodeQL security analysis passes with no high-severity URL sanitization alerts. + +## Actual Behavior +CodeQL reports 3 high-severity alerts and fails the check. + +## Environment +- **OS**: GitHub Actions runner (Linux) +- **Container Runtime**: Podman (invoked by integration tests) +- **Image Version/Tag**: PR `#270` head (`release/0.3.0`) +- **Architecture**: x86_64 (CI default) + +## Additional Context +- PR: https://github.com/vig-os/devcontainer/pull/270 +- Check run: https://github.com/vig-os/devcontainer/runs/66785943362 +- Branch alerts query: https://github.com/vig-os/devcontainer/security/code-scanning?query=pr%3A270+tool%3ACodeQL+is%3Aopen + +## Possible Solution +Replace substring-based URL trust checks with structured URL parsing and strict host validation (e.g., parse hostname and compare against an allowlist), then update tests accordingly so CodeQL no longer flags the pattern. + +## Changelog Category +Security + +## Acceptance Criteria +- [x] All 3 CodeQL alerts are resolved for PR #270. +- [x] CodeQL check passes for the branch. +- [x] TDD compliance (see `.cursor/rules/tdd.mdc`) + diff --git a/docs/issues/issue-274.md b/docs/issues/issue-274.md new file mode 100644 index 00000000..b2d6a831 --- /dev/null +++ b/docs/issues/issue-274.md @@ -0,0 +1,57 @@ +--- +type: issue +state: closed +created: 2026-03-12T12:54:01Z +updated: 2026-03-12T14:03:18Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/274 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:57.785Z +--- + +# [Issue 274]: [fix: PR fingerprint check blocks plain-text mentions of Cursor/Copilot](https://github.com/vig-os/devcontainer/issues/274) + +## Problem +The PR title/body fingerprint check is producing false positives when normal prose mentions AI tooling names (for example, \"Cursor\" or \"Copilot\"). + +Observed run: +- Workflow run: https://github.com/vig-os/devcontainer/actions/runs/23002702709 +- Job: https://github.com/vig-os/devcontainer/actions/runs/23002702709/job/66791079620?pr=270 + +Note: that specific job failed first on PR title format, but the same workflow includes `check-pr-agent-fingerprints`, and local repro confirms the false-positive behavior. + +## Reproduction +Run locally: + +```bash +PR_TITLE='docs: mention copilot integration' \\ +PR_BODY='This PR updates Cursor docs with plain text mention of Copilot as comparison.' \\ +uv run check-pr-agent-fingerprints +``` + +Current result: +- exits non-zero +- prints: `PR title or body contains blocked AI agent fingerprint: 'cursor'` + +## Expected behavior +Plain-text references to tools/vendors in documentation or explanatory text should not fail the check by default. The check should block agent identity fingerprints (trailers, bot identities, explicit attribution patterns), not generic mentions. + +## Likely root cause +`.github/agent-blocklist.toml` includes broad name substrings under `[patterns].names`, including: +- `cursor` +- `copilot` + +Because matching is case-insensitive substring-based, any ordinary text mentioning those words is rejected. + +## Suggested direction +Narrow matching scope so it targets identity attribution patterns instead of broad substrings. For example: +- keep strict trailer checks (`Co-authored-by`, etc.) +- restrict name matching to attribution contexts (author lines/signatures), or +- add explicit allow patterns/contexts for normal prose references. diff --git a/docs/issues/issue-276.md b/docs/issues/issue-276.md new file mode 100644 index 00000000..97c8c2a8 --- /dev/null +++ b/docs/issues/issue-276.md @@ -0,0 +1,55 @@ +--- +type: issue +state: closed +created: 2026-03-12T13:09:08Z +updated: 2026-03-12T14:03:29Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/276 +comments: 0 +labels: chore, priority:high, area:ci, area:workflow, effort:small, semver:patch +assignees: c-vigo +milestone: 0.3 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:57.536Z +--- + +# [Issue 276]: [[CHORE] Enforce release PR title format as release: X.Y.Z](https://github.com/vig-os/devcontainer/issues/276) + +## Chore Type +CI / Build change + +## Description +The release PR failed the \"Validate PR Title\" check because the current title format (`Release 0.3.0`) does not satisfy repository commit-title rules. + +Standardize release PR titles to: +- `release: X.Y.Z` + +This should be applied by release automation and documented in release process guidance. + +## Acceptance Criteria +- [ ] Release PRs are created with title format `release: X.Y.Z` +- [ ] `Validate PR Title` passes for release PRs +- [ ] Release workflow/tests/docs are updated so this format is preserved +- [ ] Existing release PR creation path is validated with a real or dry-run check + +## Implementation Notes +- Investigate where release PR titles are generated in workflows/scripts. +- Update release PR title generation from `Release X.Y.Z` to `release: X.Y.Z`. +- Verify compatibility with `validate-commit-msg --subject-only` / PR title checks. +- Check whether PR template hints should be updated for release branches. + +## Related Issues +- Related to PR #270 +- Failed run: https://github.com/vig-os/devcontainer/actions/runs/23002702709/job/66791079620?pr=270 + +## Priority +High + +## Changelog Category +No changelog needed + +## Additional Context +This blocks merging the release PR until title validation succeeds. diff --git a/docs/issues/issue-278.md b/docs/issues/issue-278.md new file mode 100644 index 00000000..f058366e --- /dev/null +++ b/docs/issues/issue-278.md @@ -0,0 +1,46 @@ +--- +type: issue +state: closed +created: 2026-03-12T13:36:04Z +updated: 2026-03-12T17:33:27Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/278 +comments: 0 +labels: chore, priority:medium, area:ci, area:workspace, effort:small +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:57.334Z +--- + +# [Issue 278]: [[CHORE] Add sync-main-to-dev workflow to manifest sync](https://github.com/vig-os/devcontainer/issues/278) + +## Chore Type +CI / Build change + +## Description +Add `.github/workflows/sync-main-to-dev.yml` to `scripts/manifest.toml` so the workflow is included in workspace sync output and remains consistent between the root repo and workspace template. + +## Acceptance Criteria +- [ ] Add a new manifest entry for `.github/workflows/sync-main-to-dev.yml` in `scripts/manifest.toml` +- [ ] Verify the manifest entry appears in `uv run python scripts/sync_manifest.py list` +- [ ] Sync output includes the workflow in workspace artifacts as expected + +## Implementation Notes +- Target file: `scripts/manifest.toml` +- Keep the change minimal (single new `[[entries]]` item, no unrelated manifest edits) + +## Related Issues +Related to #169 + +## Priority +Medium + +## Changelog Category +No changelog needed + +## Additional Context +This keeps shipped workflow files aligned with the manifest-driven sync process and reduces drift risk. diff --git a/docs/issues/issue-281.md b/docs/issues/issue-281.md new file mode 100644 index 00000000..e617a01d --- /dev/null +++ b/docs/issues/issue-281.md @@ -0,0 +1,49 @@ +--- +type: issue +state: closed +created: 2026-03-12T15:41:50Z +updated: 2026-03-12T17:38:41Z +author: github-actions[bot] +author_url: https://github.com/github-actions[bot] +url: https://github.com/vig-os/devcontainer/issues/281 +comments: 0 +labels: bug, area:ci +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:57.108Z +--- + +# [Issue 281]: [Release 0.3.0-rc1 failed -- automatic rollback](https://github.com/vig-os/devcontainer/issues/281) + + +Release 0.3.0-rc1 encountered an error during the automated release workflow. + +**Failed Jobs:** publish + +**Workflow Run:** [View logs](https://github.com/vig-os/devcontainer/actions/runs/23009972089) + +**Release PR:** #270 + +**Rollback Results:** +- Branch rollback: success +- Tag deletion: success + +**Actions Taken:** +- Release branch rolled back to pre-finalization state +- Release tag deleted (if created) +- This issue created for investigation + +**Manual Cleanup May Be Needed:** +- If images were pushed to GHCR before the failure, they are **not** automatically deleted. Check `ghcr.io/vig-os/devcontainer:0.3.0-rc1-*` and remove any orphaned images manually. + +**Next Steps:** +1. Review the workflow logs to identify the root cause +2. Check rollback results above; fix any partial rollback manually +3. Fix the issue on the release branch +4. Re-run the workflow when ready + +For details, check the workflow run linked above. + diff --git a/docs/issues/issue-283.md b/docs/issues/issue-283.md new file mode 100644 index 00000000..9d4c21ce --- /dev/null +++ b/docs/issues/issue-283.md @@ -0,0 +1,57 @@ +--- +type: issue +state: closed +created: 2026-03-12T17:42:48Z +updated: 2026-03-13T10:37:48Z +author: github-actions[bot] +author_url: https://github.com/github-actions[bot] +url: https://github.com/vig-os/devcontainer/issues/283 +comments: 1 +labels: bug, area:ci +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:56.871Z +--- + +# [Issue 283]: [Release 0.3.0-rc1 failed -- automatic rollback](https://github.com/vig-os/devcontainer/issues/283) + + +Release 0.3.0-rc1 encountered an error during the automated release workflow. + +**Failed Jobs:** validate, finalize, build-and-test, publish + +**Workflow Run:** [View logs](https://github.com/vig-os/devcontainer/actions/runs/23015734880) + +**Release PR:** #270 + +**Rollback Results:** +- Branch rollback: success +- Tag deletion: success + +**Actions Taken:** +- Release branch rolled back to pre-finalization state +- Release tag deleted (if created) +- This issue created for investigation + +**Manual Cleanup May Be Needed:** +- If images were pushed to GHCR before the failure, they are **not** automatically deleted. Check `ghcr.io/vig-os/devcontainer:0.3.0-rc1-*` and remove any orphaned images manually. + +**Next Steps:** +1. Review the workflow logs to identify the root cause +2. Check rollback results above; fix any partial rollback manually +3. Fix the issue on the release branch +4. Re-run the workflow when ready + +For details, check the workflow run linked above. + +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on March 12, 2026 at 06:26 PM_ + +Release process must wait for CI checks to pass + diff --git a/docs/issues/issue-284.md b/docs/issues/issue-284.md new file mode 100644 index 00000000..e7ea6f30 --- /dev/null +++ b/docs/issues/issue-284.md @@ -0,0 +1,86 @@ +--- +type: issue +state: closed +created: 2026-03-12T18:24:38Z +updated: 2026-03-13T11:19:04Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/284 +comments: 1 +labels: bug, priority:blocking, area:ci, effort:small, semver:patch +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:56.588Z +--- + +# [Issue 284]: [[BUG] Smoke-test receiver should accept dispatch tag payload](https://github.com/vig-os/devcontainer/issues/284) + +## Description + +Two issues in `assets/smoke-test/.github/workflows/repository-dispatch.yml`: + +1. **Payload key mismatch**: Candidate release dispatch sends `client_payload.tag`, but the deployed smoke-test receiver workflow validates `client_payload.rc_tag`, causing dispatch-triggered runs to fail at validation. + +2. **Cross-repo reference**: The deploy commit message uses `Refs: #258`, but this runs in the smoke-test repo context where issue #258 does not exist. It should use a fully-qualified reference (`Refs: vig-os/devcontainer#258`). + +## Steps to Reproduce + +1. Trigger candidate release in `vig-os/devcontainer`. +2. Observe successful dispatch from `release.yml`. +3. Open triggered run in `vig-os/devcontainer-smoke-test`. +4. See validation failure in `Validate dispatch payload`. + +## Expected Behavior + +Receiver accepts `client_payload.tag` and continues to deploy/CI jobs. Deploy commits reference the correct cross-repo issue. + +## Actual Behavior + +Receiver run fails early in payload validation and downstream jobs are skipped. Deploy commits (when they succeed) link to a non-existent local issue. + +## Environment + +- **OS**: GitHub Actions Ubuntu runner +- **Container Runtime**: N/A +- **Image Version/Tag**: `0.3.0-rc1` +- **Architecture**: N/A + +## Additional Context + +- Triggering release run: https://github.com/vig-os/devcontainer/actions/runs/23016037898 +- Failed smoke-test run: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23016596595 +- Failed job: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23016596595/job/66841363171 +- Related incident: https://github.com/vig-os/devcontainer/issues/283 +- Cross-repo reference flagged by Copilot: https://github.com/vig-os/devcontainer-smoke-test/pull/25 + +## Possible Solution + +1. Update receiver to accept `client_payload.tag` (already correct in source-of-truth `assets/smoke-test/.github/workflows/repository-dispatch.yml`; needs manual re-deploy to smoke-test repo). +2. Change `Refs: #258` to `Refs: vig-os/devcontainer#258` in the deploy commit message template. + +After the fix, the user must manually re-deploy the smoke-test workflow before triggering the release workflow again. + +## Changelog Category + +Fixed + +## Acceptance Criteria + +- [ ] Receiver workflow accepts `client_payload.tag` +- [ ] Deploy commit references correct cross-repo issue +- [ ] Validation step succeeds when dispatch contains `tag` +- [ ] Manual re-deploy is documented and completed before re-triggering +- [ ] TDD compliance (see .cursor/rules/tdd.mdc) +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on March 13, 2026 at 10:39 AM_ + +> 1. **Payload key mismatch**: Candidate release dispatch sends `client_payload.tag`, but the deployed smoke-test receiver workflow validates `client_payload.rc_tag`, causing dispatch-triggered runs to fail at validation. + +Fixed in [devcontainer-smoke-test/#24](https://github.com/vig-os/devcontainer-smoke-test/pull/24) + diff --git a/docs/issues/issue-285.md b/docs/issues/issue-285.md new file mode 100644 index 00000000..dcca3d1d --- /dev/null +++ b/docs/issues/issue-285.md @@ -0,0 +1,59 @@ +--- +type: issue +state: closed +created: 2026-03-13T07:53:26Z +updated: 2026-03-13T10:23:27Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/285 +comments: 0 +labels: refactor, area:workspace, effort:small +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:56.314Z +--- + +# [Issue 285]: [[REFACTOR] Replace source with value parsing for .vig-os config loading](https://github.com/vig-os/devcontainer/issues/285) + +## Description + +`initialize.sh` and `version-check.sh` both load `.vig-os` using `source "$config_file"`, which executes the file as shell code. Since `.vig-os` is a simple key-value config (`DEVCONTAINER_VERSION=X.Y.Z`), it should be parsed as data rather than executed. + +This reduces the attack surface if `.vig-os` is ever modified by an untrusted source (e.g. a compromised installer or user-edited workspace). + +Flagged by Copilot review: https://github.com/vig-os/devcontainer-smoke-test/pull/25 + +## Files / Modules in Scope + +- `assets/workspace/.devcontainer/scripts/initialize.sh` (function `load_vig_os_config`) +- `assets/workspace/.devcontainer/scripts/version-check.sh` (function `get_current_version`) + +## Out of Scope + +- `.vig-os` file format (keep as-is: `KEY=VALUE`) +- CI workflows +- Smoke-test assets + +## Invariants / Constraints + +- All existing tests must pass without modification +- Behavior must remain identical: `DEVCONTAINER_VERSION` is read from `.vig-os` and used the same way +- Both macOS and Linux `sed` paths in `initialize.sh` must still work + +## Acceptance Criteria + +- [ ] Both scripts parse `.vig-os` with grep/cut (or equivalent) instead of `source` +- [ ] Invalid or unexpected content in `.vig-os` does not execute +- [ ] All existing tests pass +- [ ] TDD compliance (see .cursor/rules/tdd.mdc) + +## Changelog Category + +No changelog needed + +## Additional Context + +Pattern appears in two files with identical structure. Both were flagged independently by Copilot on vig-os/devcontainer-smoke-test#25. diff --git a/docs/issues/issue-286.md b/docs/issues/issue-286.md new file mode 100644 index 00000000..dd94b2c0 --- /dev/null +++ b/docs/issues/issue-286.md @@ -0,0 +1,59 @@ +--- +type: issue +state: closed +created: 2026-03-13T08:22:30Z +updated: 2026-03-13T10:47:20Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/286 +comments: 0 +labels: chore, priority:blocking, area:ci, effort:small, semver:patch +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:56.118Z +--- + +# [Issue 286]: [[CHORE] Track commit-action fix for malformed tree path and update smoke-test pin](https://github.com/vig-os/devcontainer/issues/286) + +## Chore Type +CI / Build change + +## Description +Track the upstream fix for `vig-os/commit-action` where `FILE_PATHS: .` includes `.git/*` paths and fails GitHub Trees API with `tree.path contains a malformed path component`. + +This repository should update its pinned `vig-os/commit-action` SHA in smoke-test workflow templates after the upstream fix is released. + +## Acceptance Criteria +- [x] Upstream bug issue is resolved in `vig-os/commit-action` ([#15](https://github.com/vig-os/commit-action/issues/15)) +- [x] A fixed `commit-action` release/tag exists and is verified +- [ ] `assets/smoke-test/.github/workflows/repository-dispatch.yml` is updated to the fixed `commit-action` ref +- [ ] `build/assets/smoke-test/.github/workflows/repository-dispatch.yml` is regenerated/updated consistently +- [ ] Smoke-test dispatch run passes end-to-end after pin update +- [ ] TDD compliance (see .cursor/rules/tdd.mdc) + +## Implementation Notes +- Current failing pin in smoke-test template: + - `vig-os/commit-action@e7dc876fbb73df9099831fed9bfc402108fd04c3` (v0.1.4) +- Failure observed in step: + - `Commit and push deploy changes via signed commit-action` +- Root cause tracked upstream: + - Directory expansion with `FILE_PATHS: .` includes `.git/*`, which is invalid for `git.createTree`. + +## Related Issues +- Related to #284 +- Related to #169 +- Blocks release smoke-test stabilization +- Upstream: `vig-os/commit-action#15` + +## Priority +Critical + +## Changelog Category +No changelog needed + +## Additional Context +- Failing job: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23041842084/job/66921644763#step:8:15 +- Upstream fix tracking issue: https://github.com/vig-os/commit-action/issues/15 diff --git a/docs/issues/issue-289.md b/docs/issues/issue-289.md new file mode 100644 index 00000000..b8b16db5 --- /dev/null +++ b/docs/issues/issue-289.md @@ -0,0 +1,55 @@ +--- +type: issue +state: open +created: 2026-03-13T10:56:28Z +updated: 2026-03-13T10:56:28Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/289 +comments: 0 +labels: chore, priority:medium, area:ci, effort:small +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:55.879Z +--- + +# [Issue 289]: [[CHORE] Add source run metadata to smoke-test dispatch payload for downstream reporting](https://github.com/vig-os/devcontainer/issues/289) + +## Chore Type +CI / Build change + +## Description +Increase `repository_dispatch` payload metadata sent by `release.yml` so the downstream smoke-test workflow can report traceable source context (origin repo/workflow run) and optionally post completion status back. + +## Acceptance Criteria +- [ ] `release.yml` dispatch includes metadata beyond `tag` (at minimum source repo and source run URL/ID). +- [ ] Receiver workflow consumes metadata and logs it in summary output. +- [ ] Receiver can construct a deterministic source run link from payload/context. +- [ ] A follow-up mechanism is defined for downstream completion reporting (e.g. dispatch/status callback, issue/pr comment, or check update), with chosen approach documented. +- [ ] Backward compatibility is preserved if older payloads only include `tag`. + +## Implementation Notes +- Sender: `.github/workflows/release.yml` (`gh api .../dispatches`) +- Receiver template: `assets/smoke-test/.github/workflows/repository-dispatch.yml` +- Candidate payload keys: + - `client_payload[source_repo]` + - `client_payload[source_run_id]` + - `client_payload[source_run_url]` + - optional correlation key for callback/reporting +- Keep validation tolerant: required `tag`, optional metadata keys. + +## Related Issues +Related to #284 +Related to #169 + +## Priority +Medium + +## Changelog Category +No changelog needed + +## Additional Context +Current flow only sends `client_payload.tag`, which limits end-to-end traceability and makes completion reporting from downstream workflows harder. diff --git a/docs/issues/issue-291.md b/docs/issues/issue-291.md new file mode 100644 index 00000000..7743eb8f --- /dev/null +++ b/docs/issues/issue-291.md @@ -0,0 +1,82 @@ +--- +type: issue +state: closed +created: 2026-03-13T11:46:28Z +updated: 2026-03-13T12:21:18Z +author: github-actions[bot] +author_url: https://github.com/github-actions[bot] +url: https://github.com/vig-os/devcontainer/issues/291 +comments: 1 +labels: bug, area:ci +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:55.637Z +--- + +# [Issue 291]: [Release 0.3.0-rc2 failed -- automatic rollback](https://github.com/vig-os/devcontainer/issues/291) + + +Release 0.3.0-rc2 encountered an error during the automated release workflow. + +**Failed Jobs:** build-and-test, publish + +**Workflow Run:** [View logs](https://github.com/vig-os/devcontainer/actions/runs/23049118093) + +**Release PR:** #270 + +**Rollback Results:** +- Branch rollback: success +- Tag deletion: success + +**Actions Taken:** +- Release branch rolled back to pre-finalization state +- Release tag deleted (if created) +- This issue created for investigation + +**Manual Cleanup May Be Needed:** +- If images were pushed to GHCR before the failure, they are **not** automatically deleted. Check `ghcr.io/vig-os/devcontainer:0.3.0-rc2-*` and remove any orphaned images manually. + +**Next Steps:** +1. Review the workflow logs to identify the root cause +2. Check rollback results above; fix any partial rollback manually +3. Fix the issue on the release branch +4. Re-run the workflow when ready + +For details, check the workflow run linked above. + +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on March 13, 2026 at 12:01 PM_ + +## Failure Diagnosis + +- Failed in **Build and Test (arm64)** during integration tests. +- Failing test: `tests/test_install_script.py::TestInstallScriptIntegration::test_install_creates_devcontainer_directory`. + +## Root Cause + +- The temp workspace directory generated by `tempfile.mkdtemp(prefix=\"Install-Test-Project-\")` can end with `_`. +- That value flows into `SHORT_NAME`, then into `pyproject.toml` as project `name`. +- In the failing case, it became `install_test_project_fm7hy8q_` (trailing underscore), which `uv sync` rejects because package names must start/end with alphanumeric characters. + +## Why RC1 Passed + +- RC1 used equivalent sanitization logic. +- It passed because the random temp suffix happened to end with an alphanumeric character, so the latent bug did not trigger. + +## Suggested Minimal Fix + +- In `install.sh` (and matching logic in `assets/init-workspace.sh`), normalize sanitized names so they cannot start/end with non-alphanumeric separators (e.g., trim leading/trailing `_`; optional collapse of repeated `_`), with a safe fallback if empty. + +## References + +- Failed workflow run: [23049118093](https://github.com/vig-os/devcontainer/actions/runs/23049118093) +- Failed job: [66945611261](https://github.com/vig-os/devcontainer/actions/runs/23049118093/job/66945611261) +- Successful RC1 workflow run: [23016037898](https://github.com/vig-os/devcontainer/actions/runs/23016037898) +- Successful RC1 arm64 job: [66839490771](https://github.com/vig-os/devcontainer/actions/runs/23016037898/job/66839490771) + diff --git a/docs/issues/issue-293.md b/docs/issues/issue-293.md new file mode 100644 index 00000000..dc0a2dcb --- /dev/null +++ b/docs/issues/issue-293.md @@ -0,0 +1,73 @@ +--- +type: issue +state: closed +created: 2026-03-13T12:57:31Z +updated: 2026-03-13T13:15:06Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/293 +comments: 1 +labels: chore, priority:high, area:ci, effort:small, semver:patch +assignees: c-vigo +milestone: 0.3 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:55.374Z +--- + +# [Issue 293]: [[CHORE] Fix repository-dispatch branch reset boolean payload](https://github.com/vig-os/devcontainer/issues/293) + +### Chore Type +CI / Build change + +### Description +Fix the smoke-test `repository-dispatch` workflow branch-reset step that fails when reusing an existing deploy branch (`chore/deploy-`). + +Current failure in `Prepare deploy branch and metadata`: +- `gh api -X PATCH ... -f force=true` +- GitHub API returns `HTTP 422` because `force` is sent as a string instead of a boolean. + +This blocks deploy PR creation for repeat dispatches/re-runs. + +### Acceptance Criteria +- [ ] `repository-dispatch` updates an existing `chore/deploy-` branch without API validation errors +- [ ] `Deploy tag and open PR to dev` completes successfully for both: + - [ ] first run (branch create path) + - [ ] rerun/redeploy (branch patch path) +- [ ] `Dispatch summary` passes after deploy succeeds +- [ ] Add or update tests/verification evidence for this workflow behavior + +### Implementation Notes +- Update `gh api` argument typing for the PATCH call in: + - `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - `build/assets/smoke-test/.github/workflows/repository-dispatch.yml` +- Prefer typed boolean input (e.g. `-F force=true`) or equivalent JSON payload with boolean `true`. +- Validate with a manual dispatch against an existing deploy branch. + +### Related Issues +- Sub-issue of #169 + +### Priority +High + +### Changelog Category +No changelog needed + +### Additional Context +Failing job: +https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23051686417/job/66954252175 + +Observed error: +`For 'properties/force', "true" is not a boolean. (HTTP 422)` +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on March 13, 2026 at 01:15 PM_ + +> Failing job: +> https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23051686417/job/66954252175 + +Subsequent run of the same job fails at a later stage, but this issue is solved: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23052409303/job/66956710509 + diff --git a/docs/issues/issue-295.md b/docs/issues/issue-295.md new file mode 100644 index 00000000..01488ee9 --- /dev/null +++ b/docs/issues/issue-295.md @@ -0,0 +1,50 @@ +--- +type: issue +state: closed +created: 2026-03-13T13:16:31Z +updated: 2026-03-13T13:47:27Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/295 +comments: 0 +labels: chore, priority:medium, area:ci, effort:small, semver:patch +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:55.103Z +--- + +# [Issue 295]: [[CHORE] Align sync-main-to-dev checkout action pin with repository standard](https://github.com/vig-os/devcontainer/issues/295) + +## Chore Type +CI / Build change + +## Description +The `sync-main-to-dev` workflow currently pins `actions/checkout` to `34e114876b0b11c390a56381ad16ebd13914f8d5 # v4` in two steps, while the repository standard in other workflows is `de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`. + +Align both checkout steps to the repository-standard pinned SHA to keep workflow behavior and supply-chain posture consistent. + +## Acceptance Criteria +- [ ] Update both `actions/checkout` entries in `.github/workflows/sync-main-to-dev.yml` to the repository-standard pin (`de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`) +- [ ] Ensure mirrored/generated workflow assets remain consistent where applicable +- [ ] CI/workflow lint checks pass after the update + +## Implementation Notes +- Target file: `.github/workflows/sync-main-to-dev.yml` +- Context: Copilot review on `vig-os/devcontainer-smoke-test#27` identified the mismatch. +- Keep scope limited to pin alignment only (no behavioral refactors). + +## Related Issues +Related to #169 + +## Priority +Medium + +## Changelog Category +Changed + +## Additional Context +Follow-up from review feedback on: +https://github.com/vig-os/devcontainer-smoke-test/pull/27 diff --git a/docs/issues/issue-296.md b/docs/issues/issue-296.md new file mode 100644 index 00000000..0db6971e --- /dev/null +++ b/docs/issues/issue-296.md @@ -0,0 +1,53 @@ +--- +type: issue +state: closed +created: 2026-03-13T13:21:43Z +updated: 2026-03-13T13:42:22Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/296 +comments: 0 +labels: bug, priority:high, area:ci, area:image, effort:small, semver:patch +assignees: c-vigo +milestone: 0.3 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:54.872Z +--- + +# [Issue 296]: [[BUG] RC smoke-test deploy writes stable DEVCONTAINER_VERSION, breaking container CI](https://github.com/vig-os/devcontainer/issues/296) + +### Description +Release-candidate smoke-test deploys can write `.vig-os` with a stable tag (`X.Y.Z`) instead of the dispatched RC tag (`X.Y.Z-rcN`), causing `ci-container.yml` to resolve a non-existent image tag and fail before lint/tests run. + +### Steps to Reproduce +1. Trigger smoke-test dispatch with RC tag (example: `0.3.0-rc2`). +2. Open generated deploy PR in `vig-os/devcontainer-smoke-test`. +3. Observe `.vig-os` contains `DEVCONTAINER_VERSION=0.3.0` (stable), not `0.3.0-rc2`. +4. Let `CI (Container)` run on that PR. +5. `Resolve image tag` fails at `docker manifest inspect ghcr.io/vig-os/devcontainer:0.3.0`. + +### Expected Behavior +For RC dispatches, generated workspace metadata and CI image resolution should use the exact dispatched tag (`X.Y.Z-rcN`), and container CI should validate that same published RC tag. + +### Actual Behavior +Deploy PR metadata references the RC tag, but `.vig-os` is rendered with the stable base version, so container CI validates `ghcr.io/vig-os/devcontainer:X.Y.Z` and fails when only `X.Y.Z-rcN` exists. + +### Environment +- **OS**: GitHub Actions Ubuntu runner +- **Container Runtime**: Docker (Actions runner) +- **Image Version/Tag**: expected `0.3.0-rc2`, resolved `0.3.0` +- **Architecture**: AMD64 + +### Additional Context +- Failing job: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23052471041?pr=30 +- Related smoke-test PR: https://github.com/vig-os/devcontainer-smoke-test/pull/30 +- Related parent scope: #169 + +- [ ] Permanent fix ensures RC publish version is propagated to rendered workspace `.vig-os` and downstream CI tag resolution. +- [ ] Add/adjust automated test coverage to prevent regression of RC tag propagation. +- [ ] TDD compliance (see .cursor/rules/tdd.mdc) + +### Possible Solution +Propagate `publish_version` (not base `version`) into build-time/template replacement used for workspace assets during candidate releases, or otherwise ensure `.vig-os` receives the exact RC tag used for pushed manifests. diff --git a/docs/issues/issue-299.md b/docs/issues/issue-299.md new file mode 100644 index 00000000..ddb25e75 --- /dev/null +++ b/docs/issues/issue-299.md @@ -0,0 +1,107 @@ +--- +type: issue +state: closed +created: 2026-03-13T14:02:55Z +updated: 2026-03-13T15:54:20Z +author: github-actions[bot] +author_url: https://github.com/github-actions[bot] +url: https://github.com/vig-os/devcontainer/issues/299 +comments: 3 +labels: bug, area:ci +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:54.553Z +--- + +# [Issue 299]: [Release 0.3.0-rc3 failed -- automatic rollback](https://github.com/vig-os/devcontainer/issues/299) + + +Release 0.3.0-rc3 encountered an error during the automated release workflow. + +**Failed Jobs:** build-and-test, publish + +**Workflow Run:** [View logs](https://github.com/vig-os/devcontainer/actions/runs/23054106926) + +**Release PR:** #270 + +**Rollback Results:** +- Branch rollback: success +- Tag deletion: success + +**Actions Taken:** +- Release branch rolled back to pre-finalization state +- Release tag deleted (if created) +- This issue created for investigation + +**Manual Cleanup May Be Needed:** +- If images were pushed to GHCR before the failure, they are **not** automatically deleted. Check `ghcr.io/vig-os/devcontainer:0.3.0-rc3-*` and remove any orphaned images manually. + +**Next Steps:** +1. Review the workflow logs to identify the root cause +2. Check rollback results above; fix any partial rollback manually +3. Fix the issue on the release branch +4. Re-run the workflow when ready + +For details, check the workflow run linked above. + +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on March 13, 2026 at 02:11 PM_ + +Diagnostic findings from release run https://github.com/vig-os/devcontainer/actions/runs/23054106926 (job https://github.com/vig-os/devcontainer/actions/runs/23054106926/job/66962755426): + +- Failure is isolated to -> . +- Failing test: . +- Error is a hard timeout, not an assertion mismatch: + - +- In same release stream, this specific test has passed on arm64 (example: run 23049118093, arm64 job 66945611261), and it also does not reproduce consistently in CI/local. + +Assessment: +- Most likely intermittent runner/resource jitter on amd64 causing to exceed the strict 10s budget. +- Lower likelihood of deterministic logic bug in because behavior is inconsistent across runs/architectures. + +Suggested next action (minimal-risk): +1) Increase this test timeout from 10s to 20-30s to reduce false negatives. +2) Add timeout diagnostics (or lightweight timing logs) around so any future slow path is attributable. + +Note: +- Node.js 20 deprecation warnings are present in this run, but they are warnings and not the direct cause of this failure. + +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on March 13, 2026 at 02:11 PM_ + +Diagnostic findings from release run https://github.com/vig-os/devcontainer/actions/runs/23054106926 (job https://github.com/vig-os/devcontainer/actions/runs/23054106926/job/66962755426): + +- Failure is isolated to Build and Test (amd64) -> Run integration tests. +- Failing test: tests/test_integration.py::TestVigOsConfig::test_initialize_writes_devcontainer_version_to_env. +- Error is a hard timeout, not an assertion mismatch: + - subprocess.TimeoutExpired: initialize.sh timed out after 10 seconds. +- In the same release stream, this specific test has passed on arm64 (example: run 23049118093, arm64 job 66945611261), and it also does not reproduce consistently in CI/local. + +Assessment: +- Most likely intermittent runner/resource jitter on amd64 causing initialize.sh to exceed the strict 10s budget. +- Lower likelihood of deterministic logic bug in initialize.sh because behavior is inconsistent across runs/architectures. + +Suggested next action (minimal risk): +1) Increase this test timeout from 10s to 20-30s to reduce false negatives. +2) Add timeout diagnostics (or lightweight timing logs) around initialize.sh so any future slow path is attributable. + +Note: +- Node.js 20 deprecation warnings are present in this run, but they are warnings and not the direct cause of this failure. + +--- + +# [Comment #3]() by [c-vigo]() + +_Posted on March 13, 2026 at 03:54 PM_ + +The problem was with workflow permissions for the Commit App + diff --git a/docs/issues/issue-300.md b/docs/issues/issue-300.md new file mode 100644 index 00000000..883853d2 --- /dev/null +++ b/docs/issues/issue-300.md @@ -0,0 +1,89 @@ +--- +type: issue +state: open +created: 2026-03-13T16:02:23Z +updated: 2026-03-13T16:21:21Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/300 +comments: 0 +labels: bug, priority:blocking, area:ci, effort:small, semver:patch +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:54.274Z +--- + +# [Issue 300]: [[BUG] finalize-release leaves generated docs stale and breaks release-branch CI](https://github.com/vig-os/devcontainer/issues/300) + +### Description + +Running `just finalize-release 0.3.0` creates a release commit that updates changelog/release metadata, but does not include regenerated docs required by the `generate-docs` pre-commit hook. This causes CI to fail on the release branch/PR. + +Additionally, the release PR text can become stale at finalize time because bugfixes may be added on the release branch and the release date is only set during finalization. + +### Steps to Reproduce + +1. Run `just finalize-release 0.3.0`. +2. Observe the created release commit (e.g. `3656e13`) only includes `CHANGELOG.md`. +3. Let CI run on `release/0.3.0` (PR #270). +4. In `Project Checks`, pre-commit runs `generate-docs` and modifies tracked docs, then exits non-zero. + +### Expected Behavior + +Release finalization should leave the repository in a CI-clean state; no pre-commit hook should modify files in CI. + +Release PR content should be refreshed before merge so it reflects final changelog state (including finalized date and late bugfixes). + +### Actual Behavior + +`generate-docs` modifies: +- `README.md` +- `CONTRIBUTE.md` +- `TESTING.md` +- `docs/SKILL_PIPELINE.md` + +and CI fails with: +`generate-docs (regenerate from templates) ... Failed` +`- files were modified by this hook` + +Additionally, in `CHANGELOG.md`, the release date for `0.3.0` is set, but the release link/reference is not updated as expected. + +### Environment + +- GitHub Actions (`ubuntu-latest`) +- Release branch: `release/0.3.0` +- PR: #270 +- Failing run: https://github.com/vig-os/devcontainer/actions/runs/23058119634/job/66977093968?pr=270 + +### Additional Context + +- Related parent work: #242 +- Related but distinct issue: #144 (hook trigger scope for skills) +- Non-blocking warnings about cache/tar appear in logs, but root failure is hook-generated file diffs. + +- [ ] TDD compliance (see .cursor/rules/tdd.mdc) + +### PR Text Requirements (clarification) + +For the release PR body/content: + +- The checklist/instructions currently shown under: + - `### Testing Checklist` + - `### When Ready to Release` + can be posted as a PR comment instead of being part of the persistent PR body text. + +- The `### Related` section with release automation issue links should not be included in the release PR body. + +### Possible Solution + +Update `just finalize-release` flow to regenerate and stage docs before creating the finalize commit, e.g. run docs generation (or `pre-commit run generate-docs --all-files`) and fail fast if the working tree is dirty after release metadata updates. + +Also add a finalize-time step (before merge) that refreshes release PR content from the finalized `CHANGELOG.md` so the PR reflects the final release state. + +### Changelog Category + +Fixed + diff --git a/docs/issues/issue-310.md b/docs/issues/issue-310.md new file mode 100644 index 00000000..dd4ca441 --- /dev/null +++ b/docs/issues/issue-310.md @@ -0,0 +1,48 @@ +--- +type: issue +state: open +created: 2026-03-13T16:40:10Z +updated: 2026-03-13T21:59:08Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/310 +comments: 0 +labels: chore, area:ci +assignees: none +milestone: 0.3.1 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:54.044Z +--- + +# [Issue 310]: [[CHORE] Add final-release step to publish GitHub Release with notes](https://github.com/vig-os/devcontainer/issues/310) + +### Chore Type +CI / Build change + +### Description +Add an explicit step in the release cycle to publish a proper GitHub Release for final versions (non-RC), including release notes. + +### Acceptance Criteria +- [ ] Final version releases (e.g. `vX.Y.Z`) create a GitHub Release automatically (or via a documented release command step). +- [ ] RC versions (e.g. `vX.Y.Z-rc.N`) do **not** create a final GitHub Release. +- [ ] The created GitHub Release includes release notes generated from the finalized release content. +- [ ] The release process fails clearly if release notes generation/publishing fails. + +### Implementation Notes +- Reuse existing release metadata/changelog as the source of truth for release notes. +- Gate release publication by version type (final vs RC). +- Keep this step integrated with the current release automation flow to avoid manual drift. + +### Related Issues +Related to #300 + +### Priority +Medium + +### Changelog Category +No changelog needed + +### Additional Context +Goal: ensure every final release has a proper GitHub Release entry with notes, while RCs remain pre-release artifacts only. diff --git a/docs/issues/issue-313.md b/docs/issues/issue-313.md new file mode 100644 index 00000000..5cf4f02e --- /dev/null +++ b/docs/issues/issue-313.md @@ -0,0 +1,48 @@ +--- +type: issue +state: open +created: 2026-03-13T22:05:11Z +updated: 2026-03-13T22:05:11Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/313 +comments: 0 +labels: chore, priority:medium, area:ci +assignees: none +milestone: 0.3.1 +projects: none +parent: none +children: none +synced: 2026-03-14T04:15:53.749Z +--- + +# [Issue 313]: [[CHORE] Trigger smoke-test repository_dispatch from finalize-release for existing final tag](https://github.com/vig-os/devcontainer/issues/313) + +### Chore Type +CI / Build change + +### Description +Add a release workflow path to trigger `repository_dispatch` to `vig-os/devcontainer-smoke-test` using an existing final release tag (for example `v0.3.0`) without re-running full candidate/finalize release automation. + +### Acceptance Criteria +- [ ] A documented and supported release command/workflow step triggers `repository_dispatch` to `vig-os/devcontainer-smoke-test`. +- [ ] Dispatch payload includes the selected release tag (e.g. `v0.3.0`). +- [ ] The path is usable when release `0.3.0` already exists (no re-finalize required). +- [ ] The workflow fails clearly if token/app permissions for cross-repo dispatch are missing. + +### Implementation Notes +- Reuse existing dispatch contract already consumed by smoke repo (`event_type=smoke-test-trigger`, `client_payload[tag]=...`). +- Keep behavior consistent with current release automation and permission model (`RELEASE_APP` / GitHub App token where applicable). +- Reference current release workflow dispatch logic and extract/reuse where possible to avoid duplicated logic. + +### Related Issues +Related to #300, #310 + +### Priority +Medium + +### Changelog Category +No changelog needed + +### Additional Context +Operator need: trigger smoke test deployment for an already-published release (e.g. `0.3.0`) without creating a new candidate release run. diff --git a/docs/issues/issue-320.md b/docs/issues/issue-320.md new file mode 100644 index 00000000..db95757f --- /dev/null +++ b/docs/issues/issue-320.md @@ -0,0 +1,49 @@ +--- +type: issue +state: closed +created: 2026-03-16T08:29:03Z +updated: 2026-03-16T09:28:29Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/320 +comments: 0 +labels: chore, priority:high, area:ci, effort:small +assignees: c-vigo +milestone: 0.3.1 +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:06.258Z +--- + +# [Issue 320]: [[CHORE] Fix scheduled security scan artifact handoff between jobs](https://github.com/vig-os/devcontainer/issues/320) + +## Chore Type +CI / Build change + +## Description +The `Scheduled Security Scan` workflow fails in the `Security Scan` job because it attempts to download `container-image-${version}-amd64`, but the `build-image` job in `security-scan.yml` does not upload that artifact. + +## Acceptance Criteria +- [ ] `build-image` uploads `/tmp/image.tar` as `container-image-${{ steps.version.outputs.version }}-amd64` +- [ ] `security-scan` successfully downloads the uploaded artifact +- [ ] The scheduled workflow run completes without failing at `Download image artifact` +- [ ] Node 20 deprecation warnings are tracked separately (or addressed in this issue if you prefer) + +## Implementation Notes +- Add an `actions/upload-artifact` step to `.github/workflows/security-scan.yml` after the image build step. +- Use the same artifact naming convention already used in `.github/workflows/ci.yml`. +- Keep retention/compression consistent with existing image-artifact usage. + +## Related Issues +Related to CI reliability for scheduled security scans. + +## Priority +High + +## Changelog Category +No changelog needed + +## Additional Context +Failing run: https://github.com/vig-os/devcontainer/actions/runs/23131883679 +Error: `Unable to download artifact(s): Artifact not found for name: container-image-scheduled-2026-03-16-763be4a-amd64` diff --git a/docs/issues/issue-321.md b/docs/issues/issue-321.md new file mode 100644 index 00000000..c20397c1 --- /dev/null +++ b/docs/issues/issue-321.md @@ -0,0 +1,49 @@ +--- +type: issue +state: closed +created: 2026-03-16T08:38:39Z +updated: 2026-03-16T09:06:10Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/321 +comments: 0 +labels: chore, priority:medium, area:ci, effort:small +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:05.895Z +--- + +# [Issue 321]: [[CHORE] Migrate GitHub Actions references off Node.js 20 runtime](https://github.com/vig-os/devcontainer/issues/321) + +## Chore Type +CI / Build change + +## Description +GitHub Actions warns that some pinned actions in this repository are still on Node.js 20 and will be forced to Node.js 24 by default starting June 2, 2026. We should proactively update pinned action SHAs/versions to Node.js 24-compatible releases to avoid breakage. + +## Acceptance Criteria +- [ ] Identify workflows using action versions that currently run on Node.js 20 +- [ ] Update pinned action SHAs/versions to releases that support Node.js 24 +- [ ] Validate key workflows (`ci.yml`, `release.yml`, `security-scan.yml`) after updates +- [ ] Document any actions that cannot yet be upgraded and track follow-up work + +## Implementation Notes +- Start with warnings observed in scheduled security scan runs (e.g., `actions/checkout`, `docker/build-push-action`, `docker/metadata-action`, `docker/setup-buildx-action`, `actions/download-artifact`). +- Keep SHA pinning policy intact while upgrading to Node.js 24-compatible versions. +- Reference upstream deprecation notice: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ + +## Related Issues +- Related to https://github.com/vig-os/sync-issues-action/issues/77 + +## Priority +Medium + +## Changelog Category +No changelog needed + +## Additional Context +- Warning observed in run: https://github.com/vig-os/devcontainer/actions/runs/23131883679 +- This is intentionally separate from issue #320 (artifact handoff failure root cause) diff --git a/docs/issues/issue-326.md b/docs/issues/issue-326.md new file mode 100644 index 00000000..981d04f3 --- /dev/null +++ b/docs/issues/issue-326.md @@ -0,0 +1,125 @@ +--- +type: issue +state: closed +created: 2026-03-16T10:37:13Z +updated: 2026-03-16T16:56:19Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/326 +comments: 0 +labels: feature, priority:medium, area:ci, area:workflow, effort:medium, semver:minor +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:05.543Z +--- + +# [Issue 326]: [[FEATURE] Reusable release cycle workflows for downstream projects with safe customization](https://github.com/vig-os/devcontainer/issues/326) + +## Description + +Implement a reusable release cycle for downstream repositories, mirroring this project's release flow while allowing project-specific customization without workflow drift. + +## Problem Statement + +Downstream projects need a consistent release process, but they also need custom steps (for example: GHCR publishing, special packaging, or extra artifact publishing). Copying full workflows across repos causes drift and weakens upgradeability. + +## Design Evaluation + +### Option A (recommended): Core reusable workflows + downstream extension workflow contract + +- This project ships and maintains reusable `prepare-release` and `release` workflows. +- Downstream repos call these workflows pinned to a tag/ref. +- Downstream repos own a local extension workflow (project-specific, not managed by this project), similar ownership model to `assets/workspace/justfile.project`: tracked locally and preserved by default. +- Core `release` workflow calls that extension workflow at a defined hook point and enforces a clear interface/contract. + +**Pros** +- Centralized updates to shared release logic. +- Clear customization boundary. +- Lowest drift for common release behavior. + +**Cons** +- Contract versioning/migrations must be managed. +- Requires robust docs and validation for extension hook usage. + +### Option B: Fully downstream-owned workflow (no extension hook, no central updates) + +- Each downstream project owns and maintains its own full release workflow. + +**Pros** +- Maximum flexibility. + +**Cons** +- Highest drift and maintenance burden. +- Repeated bugfixes and inconsistent release safety. + +### Option C: Centrally shipped workflow file copied into downstream and merged manually + +- This project periodically updates a downstream-tracked workflow file; users resolve updates with git merges. + +**Pros** +- Familiar git-based update model. + +**Cons** +- Still prone to divergence. +- Merge overhead increases over time. +- Ambiguous ownership of local edits vs upstream updates. + +## Recommendation + +Adopt **Option A**. + +1. Keep shared release correctness in centrally maintained reusable workflows. +2. Keep project-specific behavior in a downstream-owned extension workflow. +3. Version the extension contract and document upgrade paths. + +## Proposed Solution + +Implement two reusable workflows in this project: + +1. **`prepare-release`** reusable workflow + - Performs common preparation steps. + - Produces standardized outputs consumed by `release`. + +2. **`release`** reusable workflow + - Performs common validate/finalize/build/test orchestration. + - Calls a downstream extension workflow (if configured) for project-specific steps. + - Publishes the GitHub Release in exactly one canonical step owned by core workflow logic. + +### Release Publication Rule (single-step requirement) + +- The **actual GitHub Release creation/publish must occur once in core `release` workflow**, not inside extensions. +- Extension workflow is for custom project actions (e.g. GHCR/package/signing/artifact preparation), but must not create the GitHub Release object. +- Core workflow should fail clearly if extension fails. + +This preserves one authoritative publish step while still enabling customization. + +## Acceptance Criteria + +- [ ] `prepare-release` and `release` are available as reusable workflows for downstream repos. +- [ ] `release` supports calling a downstream extension workflow through a documented contract. +- [ ] Contract versioning strategy is defined (for example: `contract_version`) and validated. +- [ ] Missing/invalid extension configuration fails with actionable error messages. +- [ ] GitHub Release publication occurs exactly once in a canonical core step. +- [ ] Documentation explains downstream integration, pinning strategy, and upgrade process. +- [ ] At least one downstream example demonstrates custom steps (for example: GHCR publishing). +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + +## Implementation Notes + +- Treat reusable workflows as the productized release engine. +- Treat extension workflow as downstream-owned customization layer. +- Downstream should pin reusable workflow refs to released tags for predictable behavior. +- Document how downstream projects adopt new workflow versions and contract changes. + +## Related Issues + +- Related to #310 + +## Changelog Category + +Added + + diff --git a/docs/issues/issue-327.md b/docs/issues/issue-327.md new file mode 100644 index 00000000..a36c6c0f --- /dev/null +++ b/docs/issues/issue-327.md @@ -0,0 +1,53 @@ +--- +type: issue +state: closed +created: 2026-03-16T11:13:25Z +updated: 2026-03-16T13:11:47Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/327 +comments: 0 +labels: chore, priority:medium, area:ci, area:workspace, effort:small, semver:patch +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:05.208Z +--- + +# [Issue 327]: [[CHORE] Consolidate workspace CI to ci workflow and remove obsolete setup-env action](https://github.com/vig-os/devcontainer/issues/327) + +## Chore Type +CI / Build change + +## Description +`assets/workspace/.github/workflows/ci.yml` is no longer necessary. Release `0.3.0` validated that these CI actions can run directly in a containerized workflow. + +The workspace CI should be consolidated into `assets/workspace/.github/workflows/ci-container.yml`, and that workflow should be renamed to `ci` as the single CI entrypoint. +`assets/workspace/.github/actions/setup-env/` appears obsolete after this consolidation and should be removed if no references remain. + +## Acceptance Criteria +- [ ] `assets/workspace/.github/workflows/ci.yml` is removed +- [ ] `assets/workspace/.github/workflows/ci-container.yml` is renamed/replaced to be the canonical `ci` workflow +- [ ] All workspace CI jobs execute through the containerized CI workflow only +- [ ] `assets/workspace/.github/actions/setup-env/` is removed if unused +- [ ] No remaining references to removed workflow/action paths +- [ ] CI passes after workflow consolidation + +## Implementation Notes +- Update workflow filenames and any references (`uses`, docs, scripts, release/process docs) to the new canonical CI workflow path. +- Verify whether `setup-env` has any remaining consumers before removal. +- Prefer minimal diff scoped to workspace CI templates. + +## Related Issues +Related to #326 + +## Priority +Medium + +## Changelog Category +Changed + +## Additional Context +Release `0.3.0` demonstrated that CI can run directly in a container, making the separate legacy CI workflow and setup action unnecessary. diff --git a/docs/issues/issue-330.md b/docs/issues/issue-330.md new file mode 100644 index 00000000..8aa5fbf4 --- /dev/null +++ b/docs/issues/issue-330.md @@ -0,0 +1,79 @@ +--- +type: issue +state: open +created: 2026-03-16T16:54:15Z +updated: 2026-03-16T16:54:15Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/330 +comments: 0 +labels: feature, priority:medium, area:ci, area:workflow, effort:medium, semver:minor +assignees: none +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:04.826Z +--- + +# [Issue 330]: [[FEATURE] Allow downstream release extension to pass assets and release notes to publish workflow](https://github.com/vig-os/devcontainer/issues/330) + +## Description + +Enable downstream `release-extension.yml` workflows to contribute release payload data (artifacts, checksum files, and optional release notes override) to the canonical publish step in `release-publish.yml`. + +## Problem Statement + +The current downstream release architecture correctly centralizes final GitHub Release creation in `release-publish.yml`, but extension jobs run in separate workflow-call context and cannot directly pass generated files or note content into publish. + +As a result, downstream customization can build/sign assets, but cannot reliably: +- attach artifacts (e.g. binaries, `.sha256`) to the final GitHub Release +- override/augment release notes generated from `CHANGELOG.md` + +## Proposed Solution + +Introduce a new workflow contract version (e.g. `contract_version: "2"`) that allows controlled handoff from extension to publish while preserving a single canonical release creation step. + +Suggested contract additions: +- Optional extension output/artifact channel for release assets (including checksum files) +- Optional release notes override input consumed by publish +- Backward-compatible fallback behavior: + - if override/asset channel is absent, publish behaves exactly as today + - release notes continue to default to `CHANGELOG.md` + +## Acceptance Criteria + +- [ ] Extension can provide one or more files for release attachment (including `.sha256`) +- [ ] Publish workflow uploads those files to the created GitHub Release +- [ ] Extension can optionally provide release notes override; publish uses it when present +- [ ] Default changelog-derived notes remain the fallback when override is absent +- [ ] Contract mismatch fails with actionable error +- [ ] Existing `contract_version: \"1\"` downstream setups remain supported or have a documented migration path +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + +## Alternatives Considered + +- Keep extension as gate-only and require post-release manual upload/edit: + - Simpler contract but poor automation and higher release drift +- Let extension create the release object: + - Rejected because it breaks the single canonical publish-step design + +## Additional Context + +Related to #326 (reusable downstream release workflows contract). +Builds on #310 behavior (final release notes publication). + +Potential docs to update: +- `docs/DOWNSTREAM_RELEASE.md` +- `docs/RELEASE_CYCLE.md` + +## Impact + +This is a backward-compatible enhancement to downstream release customization boundaries: +- improves parity for projects that publish binaries/packages/checksums +- keeps one authoritative release creation step in core publish flow +- reduces manual release editing/upload work in downstream repos + +## Changelog Category + +Changed diff --git a/docs/issues/issue-331.md b/docs/issues/issue-331.md new file mode 100644 index 00000000..00caa35a --- /dev/null +++ b/docs/issues/issue-331.md @@ -0,0 +1,71 @@ +--- +type: issue +state: open +created: 2026-03-16T17:05:56Z +updated: 2026-03-16T17:08:30Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/issues/331 +comments: 0 +labels: feature +assignees: c-vigo +milestone: none +projects: none +parent: none +children: none +synced: 2026-03-17T04:24:04.461Z +--- + +# [Issue 331]: [[FEATURE] Trigger downstream smoke-test release workflow from dispatch and gate upstream release on result](https://github.com/vig-os/devcontainer/issues/331) + +## Description + +When the upstream dispatch workflow is called, trigger the downstream smoke-test repository release workflow and enforce success as a required gate for upstream release completion. + +## Problem Statement + +The current release flow does not guarantee end-to-end validation of downstream release behavior before considering upstream release successful. This can allow upstream release jobs to pass while downstream integration/release logic fails. + +## Proposed Solution + +Extend the release workflow contract so that dispatch from upstream triggers the smoke-test downstream release workflow with version-aware behavior: + +- For RC versions, downstream creates a pre-release +- For final versions, downstream creates a full release + +Then make upstream release dependent on the downstream run outcome, failing upstream when downstream release fails. + +## Alternatives Considered + +- Keep downstream release as best-effort/non-blocking: + - Simpler pipeline, but weak release confidence and can mask integration failures. +- Trigger downstream manually post-release: + - Adds operational overhead and delays failure detection. + +## Additional Context + +Related to: +- #326 (reusable release workflows for downstream projects) +- #330 (downstream extension contract for publish data handoff) + +Target repo for downstream validation: smoke-test repository. + +## Impact + +- Improves release safety by validating downstream release behavior during upstream release. +- Ensures RC and final release semantics are exercised consistently. +- Backward-compatible for consumers that do not enable downstream smoke-test integration (subject to contract defaults). + +## Acceptance Criteria + +- [ ] Upstream dispatch triggers downstream smoke-test release workflow automatically +- [ ] Version detection correctly maps RC versions to pre-release behavior downstream +- [ ] Version detection correctly maps final versions to full release behavior downstream +- [ ] Upstream release job requires downstream release workflow success before completing +- [ ] Failure in downstream release workflow causes upstream release workflow to fail with actionable logs +- [ ] Documentation updated for dispatch inputs, downstream contract, and gating semantics +- [ ] TDD compliance (see `.cursor/rules/tdd.mdc`) + +## Changelog Category + +Changed diff --git a/docs/issues/issue-40.md b/docs/issues/issue-40.md index 2a38a17c..6479a127 100644 --- a/docs/issues/issue-40.md +++ b/docs/issues/issue-40.md @@ -2,17 +2,18 @@ type: issue state: open created: 2026-02-02T08:56:09Z -updated: 2026-02-20T15:18:33Z +updated: 2026-02-20T15:49:23Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/devcontainer/issues/40 -comments: 1 +comments: 2 labels: feature, priority:backlog, area:workflow assignees: none milestone: Backlog projects: none -relationship: none -synced: 2026-02-20T15:25:38.295Z +parent: none +children: none +synced: 2026-03-14T04:16:29.893Z --- # [Issue 40]: [[DISCUSSION] Migration to prek](https://github.com/vig-os/devcontainer/issues/40) @@ -26,3 +27,11 @@ _Posted on February 20, 2026 at 03:18 PM_ @c-vigo Should we target this for the 0.4 milestone, or keep it on the backlog for now? +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on February 20, 2026 at 03:49 PM_ + +@gerchowl backlog + diff --git a/docs/issues/issue-69.md b/docs/issues/issue-69.md index de594e30..485beeb5 100644 --- a/docs/issues/issue-69.md +++ b/docs/issues/issue-69.md @@ -1,18 +1,19 @@ --- type: issue -state: open +state: closed created: 2026-02-18T01:02:45Z -updated: 2026-02-18T12:24:59Z +updated: 2026-03-03T09:13:28Z author: gerchowl author_url: https://github.com/gerchowl url: https://github.com/vig-os/devcontainer/issues/69 -comments: 1 -labels: none +comments: 2 +labels: chore, priority:low, area:ci, effort:small, semver:patch assignees: none -milestone: none +milestone: 0.3 projects: none -relationship: none -synced: 2026-02-18T12:55:27.757Z +parent: none +children: none +synced: 2026-03-14T04:16:24.872Z --- # [Issue 69]: [[TASK] Run pre-commit formatting in sync-issues workflow before committing](https://github.com/vig-os/devcontainer/issues/69) @@ -59,3 +60,11 @@ The fails were happening because the output directory was changed in the workflo The question remains whether to actually run pre-commit once with auto-fix during the sync-issues workflow, overriding the exclusion rule. +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on March 3, 2026 at 09:13 AM_ + +This has moved to [sync-issues-action/#17](https://github.com/vig-os/sync-issues-action/issues/17) + diff --git a/docs/pull-requests/pr-110.md b/docs/pull-requests/pr-110.md index 915e97bd..b662e60c 100644 --- a/docs/pull-requests/pr-110.md +++ b/docs/pull-requests/pr-110.md @@ -12,8 +12,7 @@ labels: none assignees: none milestone: none projects: none -relationship: none -synced: 2026-02-20T13:17:54.336Z +synced: 2026-03-14T04:18:10.090Z --- # [PR 110](https://github.com/vig-os/devcontainer/pull/110) feat: install cursor-agent CLI in devcontainer image @@ -39,9 +38,9 @@ Installs the `cursor-agent` CLI inside the devcontainer image so that --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/110#issuecomment-3934449550) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/110#issuecomment-3934449550) by [@gerchowl](https://github.com/gerchowl) _Posted on February 20, 2026 at 01:14 PM_ diff --git a/docs/pull-requests/pr-120.md b/docs/pull-requests/pr-120.md index a2d3f614..1ee28efd 100644 --- a/docs/pull-requests/pr-120.md +++ b/docs/pull-requests/pr-120.md @@ -1,9 +1,9 @@ --- type: pull_request -state: open +state: closed (merged) branch: feature/79-auto-merge-commit-message → dev created: 2026-02-20T14:33:17Z -updated: 2026-02-20T14:36:45Z +updated: 2026-02-20T15:46:05Z author: gerchowl author_url: https://github.com/gerchowl url: https://github.com/vig-os/devcontainer/pull/120 @@ -12,8 +12,8 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-02-20T15:25:44.140Z +merged: 2026-02-20T15:46:05Z +synced: 2026-03-14T04:18:04.466Z --- # [PR 120](https://github.com/vig-os/devcontainer/pull/120) feat: automatic commit message for pull request merge (#79) @@ -86,3 +86,24 @@ Closes #79 Refs: #79 + + +--- +--- + +## Commits + +### Commit 1: [ff27027](https://github.com/vig-os/devcontainer/commit/ff270277dc2d37fcca93cb055c112e3eb10379fa) by [gerchowl](https://github.com/gerchowl) on February 20, 2026 at 02:31 PM +feat(vig-utils): add --subject-only flag to validate-commit-msg, 109 files modified (packages/vig-utils/src/vig_utils/validate_commit_msg.py, packages/vig-utils/tests/test_validate_commit_msg.py) + +### Commit 2: [bdf1858](https://github.com/vig-os/devcontainer/commit/bdf1858bcd3bb34ecd480750e3dad18f0e97dc0e) by [gerchowl](https://github.com/gerchowl) on February 20, 2026 at 02:31 PM +feat(devcontainer): add setup-gh-repo.sh for merge commit settings, 31 files modified (assets/workspace/.devcontainer/scripts/post-create.sh, assets/workspace/.devcontainer/scripts/setup-gh-repo.sh) + +### Commit 3: [34ccc3d](https://github.com/vig-os/devcontainer/commit/34ccc3d90e805b998347c847acf4e8258abb0bf2) by [gerchowl](https://github.com/gerchowl) on February 20, 2026 at 02:31 PM +ci: add PR title validation workflow, 39 files modified (.github/workflows/pr-title-check.yml) + +### Commit 4: [2cf3c10](https://github.com/vig-os/devcontainer/commit/2cf3c1068b39f037ca24ab29bd989975325daa7c) by [gerchowl](https://github.com/gerchowl) on February 20, 2026 at 02:31 PM +docs: add Refs placeholder to PR template and update changelog, 9 files modified (.github/pull_request_template.md, CHANGELOG.md) + +### Commit 5: [400c94b](https://github.com/vig-os/devcontainer/commit/400c94b6147e59931138bc19283adac02b63b94b) by [gerchowl](https://github.com/gerchowl) on February 20, 2026 at 03:38 PM +Merge branch 'dev' into feature/79-auto-merge-commit-message, 46 files modified (.github/pull_request_template.md, CHANGELOG.md) diff --git a/docs/pull-requests/pr-124.md b/docs/pull-requests/pr-124.md new file mode 100644 index 00000000..9edc3d85 --- /dev/null +++ b/docs/pull-requests/pr-124.md @@ -0,0 +1,98 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/122-add-hadolint-linting → dev +created: 2026-02-20T15:45:51Z +updated: 2026-02-20T15:53:09Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/124 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-02-20T15:52:57Z +synced: 2026-03-14T04:18:03.597Z +--- + +# [PR 124](https://github.com/vig-os/devcontainer/pull/124) chore: add hadolint linting for Containerfiles (#122) + +## Description + +Add [hadolint](https://github.com/hadolint/hadolint) static analysis for all Containerfiles in the repository. Hadolint enforces Dockerfile best practices (pinned base image tags, consolidated `RUN` layers, shellcheck for inline `RUN` scripts) and integrates seamlessly via pre-commit. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [x] `chore` -- Maintenance task (deps, config, etc.) +- [x] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- **`.pre-commit-config.yaml`**: Add `hadolint-docker` hook pinned to SHA `346e4199e4baca7d6827f20ac078b6eee5b39327` (v2.9.3). Runs against `Containerfile` and `tests/fixtures/sidecar.Containerfile`. +- **`tests/fixtures/sidecar.Containerfile`**: Pin base image tag, consolidate `RUN` layers, add `# hadolint ignore=DL3018` for apk packages where pinning individual versions would be brittle. + +## Changelog Entry + +### Added + +- **hadolint pre-commit hook for Containerfile linting** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Add `hadolint` hook to `.pre-commit-config.yaml`, pinned by SHA (v2.9.3) + - Enforce Dockerfile best practices: pinned base image tags, consolidated `RUN` layers, shellcheck for inline scripts + - Fix `tests/fixtures/sidecar.Containerfile` to pass hadolint with no warnings + +## Testing + +- [x] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `uv run pre-commit run --all-files` — exits clean with no warnings. +- `just build no_cache && just test` passed locally. + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [x] Any dependent changes have been merged and published + +## Additional Notes + +The `Containerfile` (main image) already passed hadolint without changes. Only the sidecar fixture required fixes. + +Refs: #122 + + + +--- +--- + +## Commits + +### Commit 1: [5f23132](https://github.com/vig-os/devcontainer/commit/5f231322d15129690249f816778d5b362c4d73b3) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:40 PM +chore(setup): add hadolint hook for Containerfile linting, 7 files modified (.pre-commit-config.yaml) + +### Commit 2: [f01cc5a](https://github.com/vig-os/devcontainer/commit/f01cc5a4c8427a7e2a4477e3c1ce311f605c7e48) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:43 PM +refactor: pin sidecar base image and consolidate RUN layers for hadolint, 11 files modified (tests/fixtures/sidecar.Containerfile) + +### Commit 3: [71af700](https://github.com/vig-os/devcontainer/commit/71af700570b931885aa6afcf9ab792f88a7d1417) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:44 PM +chore: update CHANGELOG for hadolint hook, 4 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-136.md b/docs/pull-requests/pr-136.md index 665167c4..53889de6 100644 --- a/docs/pull-requests/pr-136.md +++ b/docs/pull-requests/pr-136.md @@ -12,9 +12,8 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none merged: 2026-02-21T17:59:44Z -synced: 2026-02-22T04:23:36.310Z +synced: 2026-03-14T04:17:56.945Z --- # [PR 136](https://github.com/vig-os/devcontainer/pull/136) chore: install tmux in Containerfile for worktree session persistence @@ -98,9 +97,9 @@ Refs: #130 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/136#issuecomment-3938553077) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/136#issuecomment-3938553077) by [@gerchowl](https://github.com/gerchowl) _Posted on February 21, 2026 at 10:11 AM_ diff --git a/docs/pull-requests/pr-166.md b/docs/pull-requests/pr-166.md index 02d62d57..9a142831 100644 --- a/docs/pull-requests/pr-166.md +++ b/docs/pull-requests/pr-166.md @@ -3,7 +3,7 @@ type: pull_request state: open branch: feature/70-remote-devc-orchestration → dev created: 2026-02-23T23:17:15Z -updated: 2026-03-09T17:00:02Z +updated: 2026-03-12T17:21:11Z author: gerchowl author_url: https://github.com/gerchowl url: https://github.com/vig-os/devcontainer/pull/166 @@ -12,62 +12,124 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-03-10T04:14:50.139Z +synced: 2026-03-14T04:17:43.765Z --- # [PR 166](https://github.com/vig-os/devcontainer/pull/166) feat(scripts): remote devcontainer orchestration via just recipe (#70) ## Description -Implements a `just devc-remote` recipe that orchestrates starting a devcontainer on a remote host and connecting Cursor/VS Code to it in a single command. This enables developers to spin up their devcontainer on powerful remote machines (GPU servers, cloud VMs) from their local terminal without manual SSH and compose steps. +Implements remote devcontainer orchestration: a single command (`just remote-devc ` or `devc-remote.sh`) that provisions a devcontainer on a remote host and connects your IDE to it. This enables developers to spin up devcontainers on powerful remote machines (GPU servers, cloud VMs) from their local terminal without manual SSH and compose steps. -The implementation includes: -- `scripts/devc-remote.sh`: Bash orchestrator with SSH connectivity checks, remote pre-flight validation, container state detection, and compose lifecycle management -- `scripts/devc_remote_uri.py`: Python helper for constructing Cursor/VS Code nested authority URIs with hex-encoded devcontainer specs -- `justfile.base` recipe: Convenient wrapper for the script -- Comprehensive BATS and pytest test suites +### Key capabilities -## Type of Change +- **Core orchestration** — SSH preflight, container state detection (fresh/running/stopped), compose lifecycle, IDE launch +- **`gh:org/repo[:branch]` targets** — Clone a GitHub repo on the remote host and start its devcontainer in one command +- **`--bootstrap` flag** — One-time remote host setup (config file, GHCR auth, image build) +- **`--force` flag** — Auto-push unpushed commits before deploying; guards against deploying stale code +- **`--open ssh|cursor|code|none`** — IDE-agnostic connection modes with auto-detection +- **Tailscale SSH integration** — Ephemeral auth key generation via OAuth API, TUN device injection, peer wait polling +- **Claude Code CLI injection** — Subscription OAuth token forwarding for AI-assisted development in containers +- **Container lifecycle execution** — Runs post-create/post-start scripts inside the container after compose up +- **Compose file parsing** — Reads `dockerComposeFile` from devcontainer.json, builds correct `-f` flags -- [x] `feat` -- New feature +### Implementation -## Testing +| Component | Purpose | +|-----------|---------| +| `scripts/devc-remote.sh` | Bash orchestrator: parse_args, check_ssh, remote_preflight, inject_tailscale_key, inject_claude_auth, remote_compose_up, run_container_lifecycle, open_editor | +| `scripts/devc_remote_uri.py` | Python helper for Cursor/VS Code nested authority URIs (hex-encoded devcontainer specs) | +| `justfile.base` | `remote-devc` recipe wrapping devc-remote.sh with local git state auto-detection | +| `setup-tailscale.sh` | Opt-in Tailscale SSH daemon (install/start subcommands, lifecycle hooks) | +| `setup-claude.sh` | Opt-in Claude Code CLI (install/start subcommands, lifecycle hooks) | -- [x] Tests pass locally (`just test`) -- [x] Manual integration testing performed on ksb-meatgrinder (#243 — closed) +## Type of Change -### Integration Test Results (#243) +- [x] `feat` -- New feature -36/39 items verified on a real remote host (ksb-meatgrinder). Remaining 3 edge cases covered by unit tests. +## Issues -| Section | Result | -|---------|--------| -| Core orchestration | 7/7 | -| Tailscale SSH | 4/4 | -| Claude Code CLI | 5/5 | -| Container lifecycle | 3/3 | -| Bootstrap | 4/4 | -| gh:org/repo target | 5/5 | -| Compose file parsing | 3/3 | -| Edge cases | 1/4 (3 need special hosts, covered by unit tests) | +Closes #152, #153, #221, #230, #231, #232, #235, #236, #243 +Refs: #70, #208, #246 -**Bugs found and fixed during testing:** +## Testing + +- [x] Unit tests pass locally (`just test`) +- [x] Manual integration testing on real remote host (ksb-meatgrinder, #243) + +### Manual Integration Test Results (#243) + +36/39 items verified on a real remote host. Remaining 3 edge cases (low disk, missing compose, missing runtime) covered by unit tests. + +
+Full test matrix (click to expand) + +#### 1. Core orchestration +- [x] `devc-remote.sh myserver:~/Projects/fd5` — SSH, preflight, compose up +- [x] Re-run with container already running — skips compose up, opens editor +- [x] Re-run with container stopped — restarts and opens +- [x] `--open none` — infra only, no IDE launch +- [x] `--open ssh` — waits for Tailscale, prints hostname +- [x] `--open code` — opens VS Code instead of Cursor +- [x] `--yes` flag — auto-accepts prompts (reuse running container) + +#### 2. Tailscale SSH integration (#208, #230) +- [x] With `TS_CLIENT_ID` + `TS_CLIENT_SECRET` set — generates ephemeral key, injects into remote compose +- [x] Container joins tailnet after compose up +- [x] `--open ssh` mode — polls `tailscale status`, prints hostname when ready +- [x] Without TS env vars — silently skips (no error) + +#### 3. Claude Code CLI (#70) +- [x] With `CLAUDE_CODE_OAUTH_TOKEN` set — injects token into remote compose +- [x] `setup-claude.sh install` inside container — installs CLI, creates `claude` user +- [x] `claude` wrapper auto-switches to non-root user when run as root +- [x] `setup-claude.sh start` — refreshes workspace permissions +- [x] Without token — silently skips (no error) + +#### 4. Container lifecycle +- [x] Fresh container — runs `post-create.sh` then `post-start.sh` inside container +- [x] Existing running container — runs only `post-start.sh` (skips post-create) +- [x] Lifecycle scripts not present — skips gracefully with log message + +#### 5. `--bootstrap` (#235) +- [x] First run on clean host — prompts for `projects_dir`, creates config, forwards GHCR auth, clones devcontainer repo, builds image +- [x] `--bootstrap --yes` — uses defaults without prompting +- [x] Re-run — reads existing config, skips prompts, pulls latest, rebuilds +- [x] GHCR auth forwarding — podman credentials or `GHCR_TOKEN` copied to remote + +#### 6. `gh:org/repo[:branch]` (#236) +- [x] `devc-remote.sh myserver gh:vig-os/fd5` — clones to `~/Projects/fd5`, starts devcontainer +- [x] `devc-remote.sh myserver gh:vig-os/fd5:feature/my-branch` — clones and checks out branch +- [x] Re-run with repo already cloned — fetches, doesn't re-clone +- [x] `devc-remote.sh myserver:~/custom/path gh:vig-os/fd5` — overrides clone location +- [x] Branch switch on existing clone — checks out new branch + +#### 7. Compose file parsing +- [x] `read_compose_files()` correctly reads `dockerComposeFile` array from devcontainer.json +- [x] `compose_cmd_with_files()` builds correct `-f` flags +- [x] Works with single file string and multi-file array + +#### 8. Edge cases +- [ ] Low disk space warning (<2GB) — requires special host (covered by unit test) +- [ ] Remote host without compose — requires special host (covered by unit test) +- [ ] Remote host without container runtime — requires special host (covered by unit test) +- [x] SSH connection failure — clear error message +- [ ] macOS remote host — not tested (covered by unit test) + +
+ +### Bugs found and fixed during testing - SSH drops empty args / expands `~` in `remote_clone_project` — fixed with sentinel values (17ca79f) - GHCR auth forwarding moved from bootstrap-only to every deploy (9280224) +- Tailscale SSH required real TUN device, not userspace networking (c209f1d) +- Tailscale key regenerated on every deploy to avoid expired ephemeral keys (0b0bcef) +- `~/.local/bin` added to PATH for SSH compose commands (15120fb) +- Reverted unnecessary podman-compose preference logic (ea3af49) +- Pre-flight check for stale local Tailscale daemon (49c7a4e) ## Changelog Entry -### Added - -- **devc-remote.sh — bash orchestrator for remote devcontainer** ([#152](https://github.com/vig-os/devcontainer/issues/152)) - - `scripts/devc-remote.sh`: parse_args, detect_editor_cli, check_ssh, remote_preflight, remote_compose_up, open_editor - - `scripts/devc_remote_uri.py`: stub for URI construction (sibling sub-issue) - - BATS unit tests with mocked commands -- **devc_remote_uri.py — Cursor URI construction for remote devcontainers** ([#153](https://github.com/vig-os/devcontainer/issues/153)) - - Standalone Python module with `hex_encode()` and `build_uri()` for vscode-remote URIs - - CLI: `devc_remote_uri.py ` prints URI to stdout - - Stdlib only (json, argparse); called by devc-remote.sh (sibling sub-issue) +See `CHANGELOG.md` `## Unreleased` section — fully up to date. ## Checklist @@ -80,16 +142,13 @@ The implementation includes: - [x] New and existing unit tests pass locally with my changes - [x] Manual integration tests pass (#243) -Closes #221, #230, #231, #232, #235, #236, #243 -Refs: #70 - --- --- -## Comments (4) +# Comments (4) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3957440870) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3957440870) by [@c-vigo](https://github.com/c-vigo) _Posted on February 25, 2026 at 07:48 AM_ @@ -97,7 +156,7 @@ This PR requires an update to `assets/workspace/.devcontainer/README.md` explain --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3958500345) by [@c-vigo](https://github.com/c-vigo) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3958500345) by [@c-vigo](https://github.com/c-vigo) _Posted on February 25, 2026 at 11:07 AM_ @@ -109,7 +168,7 @@ Planned fix in #202: isolate PATH with an empty temp dir and invoke `/bin/bash` --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3959615754) by [@c-vigo](https://github.com/c-vigo) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3959615754) by [@c-vigo](https://github.com/c-vigo) _Posted on February 25, 2026 at 02:23 PM_ @@ -123,7 +182,7 @@ Fixed in #205 --- -### [Comment #4](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3959633793) by [@c-vigo](https://github.com/c-vigo) +## [Comment #4](https://github.com/vig-os/devcontainer/pull/166#issuecomment-3959633793) by [@c-vigo](https://github.com/c-vigo) _Posted on February 25, 2026 at 02:26 PM_ diff --git a/docs/pull-requests/pr-180.md b/docs/pull-requests/pr-180.md index 91120680..4ea38abf 100644 --- a/docs/pull-requests/pr-180.md +++ b/docs/pull-requests/pr-180.md @@ -12,9 +12,8 @@ labels: none assignees: none milestone: none projects: none -relationship: none merged: 2026-03-06T11:49:08Z -synced: 2026-03-07T04:05:47.485Z +synced: 2026-03-14T04:17:38.884Z --- # [PR 180](https://github.com/vig-os/devcontainer/pull/180) fix(deps): use pyproject.toml as SSoT for devcontainer tools @@ -39,9 +38,9 @@ Closes #159 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/180#issuecomment-3966180028) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/180#issuecomment-3966180028) by [@c-vigo](https://github.com/c-vigo) _Posted on February 26, 2026 at 12:01 PM_ diff --git a/docs/pull-requests/pr-188.md b/docs/pull-requests/pr-188.md index 80f6bfac..761621ec 100644 --- a/docs/pull-requests/pr-188.md +++ b/docs/pull-requests/pr-188.md @@ -12,8 +12,7 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-03-05T04:18:25.689Z +synced: 2026-03-14T04:17:36.314Z --- # [PR 188](https://github.com/vig-os/devcontainer/pull/188) fix: container image missing bandit and check-skill-names for pre-commit (#186) @@ -102,9 +101,9 @@ Refs: #186 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/188#issuecomment-3997161024) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/188#issuecomment-3997161024) by [@c-vigo](https://github.com/c-vigo) _Posted on March 4, 2026 at 12:11 PM_ diff --git a/docs/pull-requests/pr-201.md b/docs/pull-requests/pr-201.md index 98854cb5..ee41327b 100644 --- a/docs/pull-requests/pr-201.md +++ b/docs/pull-requests/pr-201.md @@ -1,9 +1,9 @@ --- type: pull_request -state: open +state: closed (merged) branch: bugfix/197-install-sh-idempotent → dev created: 2026-02-25T10:35:44Z -updated: 2026-02-25T10:35:45Z +updated: 2026-03-03T07:06:07Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/devcontainer/pull/201 @@ -12,8 +12,8 @@ labels: none assignees: c-vigo milestone: none projects: none -relationship: none -synced: 2026-02-26T04:22:31.492Z +merged: 2026-03-03T07:06:05Z +synced: 2026-03-14T04:17:29.897Z --- # [PR 201](https://github.com/vig-os/devcontainer/pull/201) fix(workspace): make install.sh idempotent for template_project rename (#197) @@ -79,3 +79,21 @@ N/A Refs: #197 + + +--- +--- + +## Commits + +### Commit 1: [1881dc8](https://github.com/vig-os/devcontainer/commit/1881dc8832b367f91ce5df60585815c45842bbae) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:31 AM +test: add BATS test for idempotent rename guard in init-workspace.sh, 9 files modified (tests/bats/init-workspace.bats) + +### Commit 2: [7493f0e](https://github.com/vig-os/devcontainer/commit/7493f0e1990178d40132bed43b08e9fa027d3091) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:31 AM +fix(workspace): make init-workspace.sh idempotent for template_project rename, 11 files modified (assets/init-workspace.sh, tests/bats/init-workspace.bats) + +### Commit 3: [6ae99d1](https://github.com/vig-os/devcontainer/commit/6ae99d14ea10382d99622280c985fa44b25f7613) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:32 AM +docs(changelog): add entry for install.sh idempotency fix, 2 files modified (CHANGELOG.md) + +### Commit 4: [59dae4c](https://github.com/vig-os/devcontainer/commit/59dae4c1ddf4f8b458f6ab3864112fdceb33e4f3) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:34 AM +fix(test): suppress SC2016 in init-workspace BATS test, 1 file modified (tests/bats/init-workspace.bats) diff --git a/docs/pull-requests/pr-203.md b/docs/pull-requests/pr-203.md index 5a431b6c..079b95f9 100644 --- a/docs/pull-requests/pr-203.md +++ b/docs/pull-requests/pr-203.md @@ -12,9 +12,8 @@ labels: none assignees: none milestone: none projects: none -relationship: none merged: 2026-02-25T17:46:59Z -synced: 2026-02-26T04:22:29.128Z +synced: 2026-03-14T04:17:29.202Z --- # [PR 203](https://github.com/vig-os/devcontainer/pull/203) fix: make detect_editor_cli no-editor case deterministic @@ -85,9 +84,9 @@ Refs: #202 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/203#issuecomment-3958820127) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/203#issuecomment-3958820127) by [@c-vigo](https://github.com/c-vigo) _Posted on February 25, 2026 at 12:06 PM_ diff --git a/docs/pull-requests/pr-210.md b/docs/pull-requests/pr-210.md index 41b24412..c94e9536 100644 --- a/docs/pull-requests/pr-210.md +++ b/docs/pull-requests/pr-210.md @@ -1,19 +1,19 @@ --- type: pull_request -state: open +state: closed (merged) branch: feature/170-bootstrap-smoke-test-repo → dev created: 2026-02-26T12:09:36Z -updated: 2026-02-26T12:26:56Z +updated: 2026-03-03T09:00:07Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/devcontainer/pull/210 -comments: 3 +comments: 4 labels: none assignees: c-vigo milestone: none projects: none -relationship: none -synced: 2026-02-27T04:19:09.913Z +merged: 2026-03-03T09:00:05Z +synced: 2026-03-14T04:17:26.554Z --- # [PR 210](https://github.com/vig-os/devcontainer/pull/210) feat: bootstrap smoke-test repo with bare-runner CI @@ -108,9 +108,9 @@ Refs: #161, #170, #186 --- --- -## Comments (1) +# Comments (2) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/210#issuecomment-3966270423) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/210#issuecomment-3966270423) by [@c-vigo](https://github.com/c-vigo) _Posted on February 26, 2026 at 12:20 PM_ @@ -138,6 +138,14 @@ transforms = [ This is necessary to run CI in the smoke-test repo, will be fixed with #171. If that's fine, approve & merge. +--- + +## [Comment #2](https://github.com/vig-os/devcontainer/pull/210#issuecomment-3989117503) by [@c-vigo](https://github.com/c-vigo) + +_Posted on March 3, 2026 at 07:05 AM_ + +@gerchowl reminder + --- --- @@ -181,5 +189,74 @@ Conversation: The design is as intended + --- +--- + +## Commits + +### Commit 1: [87d725c](https://github.com/vig-os/devcontainer/commit/87d725cd6a469bce6dc3bb0973b3a58dc12e9b5c) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 01:10 PM +test(setup): add coverage for post-create repo security setup, 38 files modified (tests/bats/setup-gh-repo.bats) + +### Commit 2: [3419aa2](https://github.com/vig-os/devcontainer/commit/3419aa291433aa8231aeab0cfed397cfabb2d48e) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 01:11 PM +feat(setup): detach org default security config in post-create setup, 30 files modified (assets/workspace/.devcontainer/scripts/setup-gh-repo.sh) + +### Commit 3: [863f80e](https://github.com/vig-os/devcontainer/commit/863f80e146b8dfce2b853ad5b05ce676d83c6cbf) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 02:49 PM +test(image): add failing bandit image tool check, 11 files modified (tests/test_image.py) + +### Commit 4: [8d5e177](https://github.com/vig-os/devcontainer/commit/8d5e177fd30d367874b9b27cd496716c9e93fef1) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 02:54 PM +chore: update docs with just recipes, 38 files modified (CONTRIBUTE.md, README.md) + +### Commit 5: [8b2825c](https://github.com/vig-os/devcontainer/commit/8b2825cbda46f5d0eaa0fb8291e3d90a8d922ecd) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 02:55 PM +feat(image): install bandit tooling in container, 1 file modified (Containerfile) + +### Commit 6: [08643f4](https://github.com/vig-os/devcontainer/commit/08643f4d1e27a9257457c839c5e0ce26500cf93c) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 02:59 PM +chore: update sync-manifest config to exclude 'check-skill-names' pre-commit hook, 2 files modified (scripts/manifest.toml) + +### Commit 7: [cd7a8b1](https://github.com/vig-os/devcontainer/commit/cd7a8b13fb60f95a6bc556b04b3281906ff3729d) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 03:03 PM +chore: add sync-manifest hook to pre-commit configuration, 185 files modified + +### Commit 8: [5a4a788](https://github.com/vig-os/devcontainer/commit/5a4a78856f8e2085888b8eed6467cffa400abc24) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 03:27 PM +chore: update sync-manifest config to exclude 'sync-manifest' pre-commit hook, 11 files modified (assets/workspace/.pre-commit-config.yaml, scripts/manifest.toml) + +### Commit 9: [5753eb2](https://github.com/vig-os/devcontainer/commit/5753eb2aafda99268a199ea22de344345cacacff) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 03:31 PM +fix: avoid silent failure of pre-commit hooks if tool is not available, 6 files modified (.pre-commit-config.yaml) + +### Commit 10: [7ff3bbd](https://github.com/vig-os/devcontainer/commit/7ff3bbd4ea5dd7a04b6072e2e2b12cc30b8341a0) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 03:44 PM +chore: clean up old PR draft, 76 files modified (.github/pr-draft-128-into-dev.md) + +### Commit 11: [a3f0cf4](https://github.com/vig-os/devcontainer/commit/a3f0cf4ff046c28985996d392af554ba1d6c8a20) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:02 PM +chore: remove setup-env action from sync manifest, 3 files modified (scripts/manifest.toml) + +### Commit 12: [66992ed](https://github.com/vig-os/devcontainer/commit/66992ed2283b7666877b1056c3b8bdb03d36d417) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:10 PM +feat(image): install pre-commit in setup action, 9 files modified (assets/workspace/.github/actions/setup-env/action.yml) + +### Commit 13: [da116bb](https://github.com/vig-os/devcontainer/commit/da116bb99e5b60723a7baeb768c5ae62b1f391c6) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:56 PM +fix: remove empty repo blocks for all repo types in RemovePrecommitHooks, 4 files modified (scripts/transforms.py) + +### Commit 14: [2030a53](https://github.com/vig-os/devcontainer/commit/2030a5381665efa6a4fdfd28298dfc6563aa0ab1) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:57 PM +chore: remove actions from sync manifest, 74 files modified (assets/workspace/.pre-commit-config.yaml, scripts/manifest.toml) + +### Commit 15: [5873401](https://github.com/vig-os/devcontainer/commit/5873401958a418ce76f17f8a410fedfc483163f6) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 05:13 PM +test: add red test to verify deployed package is installed during inialization, 30 files modified (tests/conftest.py, tests/test_integration.py) + +### Commit 16: [9266aed](https://github.com/vig-os/devcontainer/commit/9266aeda1d83af20657cd7cefb0b6ce61551fae2) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 07:58 AM +feat: run just sync during workspace initialization, 18 files modified (Containerfile, assets/init-workspace.sh, assets/workspace/.devcontainer/docker-compose.yml, assets/workspace/.devcontainer/justfile.base, justfile.base) + +### Commit 17: [93a1042](https://github.com/vig-os/devcontainer/commit/93a104216eec5c4dfd1a77fa8db110eb514a42b1) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 08:09 AM +test: align tests with Containerfile ENV changes, 34 files modified (tests/test_image.py, tests/test_integration.py) + +### Commit 18: [7e1575b](https://github.com/vig-os/devcontainer/commit/7e1575b306684d7d4d47f8536b467506e8b1e52c) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 08:24 AM +fix: run sync recipe in update target, 4 files modified (assets/workspace/.devcontainer/justfile.base, justfile.base) + +### Commit 19: [d52c673](https://github.com/vig-os/devcontainer/commit/d52c6731b8ce9560fc719a5050a76ba079a267ff) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 09:25 AM +chore: add README.md and CHANGELOG.md to preserve files in workspace initialization, 2 files modified (assets/init-workspace.sh) + +### Commit 20: [e4b6050](https://github.com/vig-os/devcontainer/commit/e4b605079f3473f98deeefffc413328d403270b8) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 09:37 AM +chore: update CHANGELOG, 25 files modified (CHANGELOG.md) + +### Commit 21: [9ebd789](https://github.com/vig-os/devcontainer/commit/9ebd7898af8b665009dc3e137b1a472b25cc2d9d) by [c-vigo](https://github.com/c-vigo) on February 26, 2026 at 12:15 PM +Merge branch 'dev' into feature/170-bootstrap-smoke-test-repo, 5282 files modified +### Commit 22: [5f1a21b](https://github.com/vig-os/devcontainer/commit/5f1a21b98ae679e32092422dad17ebd94591caf9) by [c-vigo](https://github.com/c-vigo) on February 26, 2026 at 12:17 PM +chore: sync asset files, 43 files modified (assets/workspace/.devcontainer/justfile.gh, assets/workspace/.devcontainer/justfile.worktree, assets/workspace/.devcontainer/scripts/gh_issues.py) diff --git a/docs/pull-requests/pr-211.md b/docs/pull-requests/pr-211.md index 2c5a41a8..5922fa58 100644 --- a/docs/pull-requests/pr-211.md +++ b/docs/pull-requests/pr-211.md @@ -12,9 +12,8 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none merged: 2026-03-07T06:22:53Z -synced: 2026-03-08T04:17:40.703Z +synced: 2026-03-14T04:17:25.368Z --- # [PR 211](https://github.com/vig-os/devcontainer/pull/211) feat: add opt-in Tailscale SSH support to devcontainer (#208) @@ -97,9 +96,9 @@ Refs: #208 --- --- -## Comments (3) +# Comments (3) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/211#issuecomment-3989133680) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/211#issuecomment-3989133680) by [@c-vigo](https://github.com/c-vigo) _Posted on March 3, 2026 at 07:08 AM_ @@ -107,7 +106,7 @@ Would it make sense to install tailscale in the image instead of at the host, an --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/211#issuecomment-3989603771) by [@gerchowl](https://github.com/gerchowl) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/211#issuecomment-3989603771) by [@gerchowl](https://github.com/gerchowl) _Posted on March 3, 2026 at 08:58 AM_ @@ -118,7 +117,7 @@ just wiring up the ssh between them via tailscale, you could even check what the --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/211#issuecomment-4012305630) by [@c-vigo](https://github.com/c-vigo) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/211#issuecomment-4012305630) by [@c-vigo](https://github.com/c-vigo) _Posted on March 6, 2026 at 03:09 PM_ diff --git a/docs/pull-requests/pr-214.md b/docs/pull-requests/pr-214.md new file mode 100644 index 00000000..17d1a49f --- /dev/null +++ b/docs/pull-requests/pr-214.md @@ -0,0 +1,135 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/212-preserve-files-without-rsync → dev +created: 2026-03-03T08:21:38Z +updated: 2026-03-03T09:12:05Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/214 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-03T09:12:04Z +synced: 2026-03-14T04:17:24.372Z +--- + +# [PR 214](https://github.com/vig-os/devcontainer/pull/214) fix: preserve user-authored files on force upgrade + +## Description + +This PR fixes `init-workspace --force` so user-authored files are preserved during workspace upgrades and are not overwritten by the copy path. It also adds `rsync` to the image and regression coverage to ensure the preservation behavior remains stable. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [x] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [x] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/init-workspace.sh` + - Keeps `--force` upgrades on the rsync-based copy path so excluded files are preserved + - Extends excluded/preserved files handling for user-authored project files +- `Containerfile` + - Installs `rsync` in the devcontainer image +- `tests/bats/init-workspace.bats` + - Adds regression checks for preserve guard behavior during force upgrades +- `tests/test_image.py` + - Adds image-level tests to verify `rsync` is available +- `CHANGELOG.md` + - Adds/updates `## Unreleased` entries tied to issue #212 + +## Changelog Entry + +### Added + +- **Image tools** ([[#212](https://github.com/vig-os/devcontainer/issues/212)]) + - Install rsync +- **Preserve user-authored files during `--force` workspace upgrades** ([#212](https://github.com/vig-os/devcontainer/issues/212)) + - `init-workspace --force` no longer overwrites `README.md`, `CHANGELOG.md`, `LICENSE`, `.github/CODEOWNERS`, or `justfile.project` + +### Fixed + +- **--force preserves excluded files by relying on rsync-only copy path** ([#212](https://github.com/vig-os/devcontainer/issues/212)) + - Remove the `cp` fallback path that could overwrite preserved files during force upgrades + +## Testing + +- [x] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Verified in `devcontainer-smoke-test` coverage for the devcontainer workflow scope + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #212 + + + +--- +--- + +## Commits + +### Commit 1: [369a46d](https://github.com/vig-os/devcontainer/commit/369a46d6942bf015689a053ea7355c30abead8e5) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:35 AM +test(image): add red test for rsync installed, 14 files modified (tests/test_image.py) + +### Commit 2: [2155c28](https://github.com/vig-os/devcontainer/commit/2155c28704fb2acfa98c9db75a656302e15224a8) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:36 AM +test(workspace): add rsync and preserve guard regressions, 15 files modified (tests/bats/init-workspace.bats) + +### Commit 3: [96f1dc4](https://github.com/vig-os/devcontainer/commit/96f1dc4ee3def24e7624839e8dd62441176803c6) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:53 AM +feat: install rsync in the container image, 3 files modified (CHANGELOG.md, Containerfile) + +### Commit 4: [b1c0233](https://github.com/vig-os/devcontainer/commit/b1c02330f2ec8e3e78a3caf6a11e55ad9c13c0f5) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:57 AM +fix(image): ensure init_workspace.sh --force preserves excluded files by using rsync only, 19 files modified (CHANGELOG.md, assets/init-workspace.sh) + +### Commit 5: [c2b75df](https://github.com/vig-os/devcontainer/commit/c2b75dfd7cb71d57b24a0b72c2da17f65ee16d34) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:07 AM +feat(image): preserve additional user-authored files on --force upgrade, 7 files modified (CHANGELOG.md, assets/init-workspace.sh) + +### Commit 6: [c956b8a](https://github.com/vig-os/devcontainer/commit/c956b8aa2286f3915471d1a8a78ebf49ad4c9d3a) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:10 AM +docs: normalize CHANGELOG section headers and ordering, 159 files modified (CHANGELOG.md) + +### Commit 7: [9f2ba4b](https://github.com/vig-os/devcontainer/commit/9f2ba4b60aa1b2d6389d68ee2091cad24a7a1ab4) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:28 AM +fix: suppress SC2016 for literal assertion patterns, 3 files modified (tests/bats/init-workspace.bats) + +### Commit 8: [a28f18a](https://github.com/vig-os/devcontainer/commit/a28f18a1ff6d509c7230ab011c3ea1fcb03b9e11) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:37 AM +fix(ci): pin Trivy version in security workflows, 12 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml, .pre-commit-config.yaml, CHANGELOG.md) + +### Commit 9: [30e47a2](https://github.com/vig-os/devcontainer/commit/30e47a24943915c7548be99bf1f9c82c3da38661) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:51 AM +chore: trigger status re-evaluation + +### Commit 10: [2069c73](https://github.com/vig-os/devcontainer/commit/2069c73608948edd43f5010578a1efb284ee8998) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:02 AM +fix(image): resolve cargo-binstall version without api.github, 12 files modified (CHANGELOG.md, Containerfile) + +### Commit 11: [aafda81](https://github.com/vig-os/devcontainer/commit/aafda815b7509b2bce3b24a41f9462c9db4403dd) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:05 AM +Merge branch 'dev' into bugfix/212-preserve-files-without-rsync, 552 files modified diff --git a/docs/pull-requests/pr-215.md b/docs/pull-requests/pr-215.md new file mode 100644 index 00000000..d112456b --- /dev/null +++ b/docs/pull-requests/pr-215.md @@ -0,0 +1,117 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/213-update-python-deps → dev +created: 2026-03-03T09:39:57Z +updated: 2026-03-03T09:53:14Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/215 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-03T09:53:13Z +synced: 2026-03-14T04:17:23.476Z +--- + +# [PR 215](https://github.com/vig-os/devcontainer/pull/215) build: refresh pinned python image digest and patch vulnerable transitive deps + +## Description + +Refreshes the pinned `python:3.12-slim-bookworm` base image digest, patches vulnerable transitive Python dependencies by adding uv constraints and regenerating the lockfile, and fixes the CI Security Scan `safety` install pin so dependency resolution succeeds. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [x] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `Containerfile` + - Updated digest pin for `python:3.12-slim-bookworm` to latest upstream digest +- `pyproject.toml` + - Added `[tool.uv].constraint-dependencies` for `urllib3`, `filelock`, and `virtualenv` minimum safe versions +- `uv.lock` + - Regenerated lockfile with constrained versions (`urllib3 2.6.3`, `filelock 3.25.0`, `virtualenv 21.1.0`) + - Included new transitive package `python-discovery` +- `CHANGELOG.md` + - Added Unreleased entries in `Changed`, `Fixed`, and `Security` categories for this PR scope +- `.github/workflows/ci.yml` + - Bumped `safety` pin from `3.2.11` to `3.7.0` in Python Security Scan job +- `assets/workspace/.github/workflows/ci.yml` + - Bumped `safety` pin from `3.2.11` to `3.7.0` in workspace-template Security Scan job + +## Changelog Entry + +### Changed + +- **Refresh pinned Python base image digest** ([#213](https://github.com/vig-os/devcontainer/issues/213)) + - Update `python:3.12-slim-bookworm` pinned digest in `Containerfile` to the latest upstream value while keeping the same tag + +### Security + +- **Update vulnerable Python dependencies** ([#88](https://github.com/vig-os/devcontainer/issues/88)) + - Add uv constraints for transitive dependencies: `urllib3>=2.6.3`, `filelock>=3.20.3`, and `virtualenv>=20.36.1` + - Regenerate `uv.lock` with patched resolutions (`urllib3 2.6.3`, `filelock 3.25.0`, `virtualenv 21.1.0`) + +### Fixed + +- **CI python security scan fails on unsatisfiable safety pin** ([#213](https://github.com/vig-os/devcontainer/issues/213)) + - Bump workflow `safety` install pin from `3.2.11` to `3.7.0` in both root and workspace-template CI workflows + +## Testing + +- [x] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +Ran `just test` locally by author; no additional manual testing. + +## Checklist + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [ ] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #213, #88 + + + +--- +--- + +## Commits + +### Commit 1: [9504161](https://github.com/vig-os/devcontainer/commit/9504161b79b8bcbdef30415f30c2e7dc2a2040c2) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:30 AM +chore(setup): patch vulnerable transitive Python dependencies, 49 files modified (CHANGELOG.md, pyproject.toml, uv.lock) + +### Commit 2: [d2744e0](https://github.com/vig-os/devcontainer/commit/d2744e0b04b8de729f8c0c06fb425e508ab99481) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:36 AM +build(image): refresh pinned python base image digest, 4 files modified (CHANGELOG.md, Containerfile) + +### Commit 3: [50e3a2d](https://github.com/vig-os/devcontainer/commit/50e3a2d3c3a2ad5d8e809ca8607138cd234ef0ea) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:46 AM +fix(ci): bump safety pin to restore security scan install, 6 files modified (.github/workflows/ci.yml, CHANGELOG.md, assets/workspace/.github/workflows/ci.yml) diff --git a/docs/pull-requests/pr-216.md b/docs/pull-requests/pr-216.md new file mode 100644 index 00000000..d9169ef0 --- /dev/null +++ b/docs/pull-requests/pr-216.md @@ -0,0 +1,114 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/171-container-based-ci-workflow-smoke-test-repo → dev +created: 2026-03-03T20:11:20Z +updated: 2026-03-03T20:17:40Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/216 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-03T20:17:39Z +synced: 2026-03-14T04:17:22.688Z +--- + +# [PR 216](https://github.com/vig-os/devcontainer/pull/216) ci: validate workspace container CI via smoke test + +## Description + +Validates and documents the container CI workflow in workspace assets, centered on `assets/workspace/.github/workflows/ci-container.yml` and its smoke-test verification. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/workspace/.github/workflows/ci-container.yml` + - Adds the container-focused CI workflow validated in the smoke-test repository. +- `assets/workspace/docs/container-ci-quirks.md` + - Documents CI container behavior differences and maintainer troubleshooting notes. +- `scripts/transforms.py`, `scripts/manifest.toml`, `tests/test_transforms.py` + - Auxiliary updates to keep sync/transform behavior aligned with workflow-related config changes. +- `.pre-commit-config.yaml`, `assets/workspace/.pre-commit-config.yaml` + - Updates synced pre-commit config generated alongside the workflow-focused changes. + +## Changelog Entry + +### Changed + +- **Pre-commit hook removal transform preserves section comments** ([#171](https://github.com/vig-os/devcontainer/issues/171)) + - `scripts/transforms.py` keeps section comments intact while removing configured hooks during manifest sync + - `scripts/manifest.toml` and related sync/test updates keep workspace pre-commit outputs aligned with container CI workflow changes + +## Testing + +- [x] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- External validation: smoke-test repo PR with container workflow validation: [vig-os/devcontainer-smoke-test PR #12](https://github.com/vig-os/devcontainer-smoke-test/pull/12). + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [ ] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +- Smoke-test repo validation reference: [vig-os/devcontainer-smoke-test PR #12](https://github.com/vig-os/devcontainer-smoke-test/pull/12), where `assets/workspace/.github/workflows/ci-container.yml` was tested and validated. + +Refs: #171 + + + +--- +--- + +## Commits + +### Commit 1: [ecf92c3](https://github.com/vig-os/devcontainer/commit/ecf92c3eab8be24064ac9a9c9d61bd8b8ec78014) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 01:33 PM +chore: add sync-manifest pre-commit hook, 10 files modified (.pre-commit-config.yaml) + +### Commit 2: [231aa74](https://github.com/vig-os/devcontainer/commit/231aa74139291a1e7fbb96e54a869257633ef868) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:14 PM +test: add RemovePrecommitHooks comment preservation test, 32 files modified (tests/test_transforms.py) + +### Commit 3: [8c0e4da](https://github.com/vig-os/devcontainer/commit/8c0e4da5c1b3b69b3221b3eab6a5d2c14effbf1d) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:14 PM +fix(setup): preserve section comments when removing pre-commit hooks, 17 files modified (scripts/transforms.py) + +### Commit 4: [f3555dc](https://github.com/vig-os/devcontainer/commit/f3555dcc9f2b48e134c3923786b303119dc87a6f) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:24 PM +build: switch to RemovePrecommitHooks and add CI/link helpers, 262 files modified + +### Commit 5: [7ecb74c](https://github.com/vig-os/devcontainer/commit/7ecb74cf115abade3ed8c0644575e4b01f88616a) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:32 PM +ci: add container-based CI workflow and quirks documentation, 206 files modified (assets/workspace/.github/workflows/ci-container.yml, assets/workspace/docs/container-ci-quirks.md) + +### Commit 6: [6256225](https://github.com/vig-os/devcontainer/commit/6256225460668bb4056de107d87c2db37f315275) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:55 PM +chore: merge dev into feature/171, 2680 files modified + +### Commit 7: [fbfc202](https://github.com/vig-os/devcontainer/commit/fbfc202fed709e7a0f9e4c3cc1c20cd112dc8c81) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:10 PM +docs: update CHANGELOG, 3 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-218.md b/docs/pull-requests/pr-218.md index 13ee4f5f..a1afda3c 100644 --- a/docs/pull-requests/pr-218.md +++ b/docs/pull-requests/pr-218.md @@ -12,9 +12,8 @@ labels: none assignees: c-vigo milestone: none projects: none -relationship: none merged: 2026-03-07T21:05:12Z -synced: 2026-03-08T04:17:34.426Z +synced: 2026-03-14T04:17:21.790Z --- # [PR 218](https://github.com/vig-os/devcontainer/pull/218) refactor(vigutils): migrate shared scripts into vig-utils package entrypoints @@ -129,9 +128,9 @@ Refs: #217, #161, #179, #185 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/218#issuecomment-4015792348) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/218#issuecomment-4015792348) by [@c-vigo](https://github.com/c-vigo) _Posted on March 7, 2026 at 07:07 AM_ diff --git a/docs/pull-requests/pr-22.md b/docs/pull-requests/pr-22.md index c1513412..5a18890f 100644 --- a/docs/pull-requests/pr-22.md +++ b/docs/pull-requests/pr-22.md @@ -12,9 +12,8 @@ labels: none assignees: c-vigo milestone: 0.2 projects: none -relationship: none merged: 2026-01-06T15:43:25Z -synced: 2026-02-18T08:57:09.101Z +synced: 2026-03-14T04:18:44.274Z --- # [PR 22](https://github.com/vig-os/devcontainer/pull/22) Release Candidate 0.2.0 @@ -119,9 +118,9 @@ just push version=0.2.0 --- --- -## Comments (5) +# Comments (5) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3714921500) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3714921500) by [@gerchowl](https://github.com/gerchowl) _Posted on January 6, 2026 at 02:30 PM_ @@ -138,7 +137,7 @@ _Posted on January 6, 2026 at 02:30 PM_ --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3714999431) by [@c-vigo](https://github.com/c-vigo) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3714999431) by [@c-vigo](https://github.com/c-vigo) _Posted on January 6, 2026 at 02:51 PM_ @@ -146,7 +145,7 @@ _Posted on January 6, 2026 at 02:51 PM_ --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715007259) by [@c-vigo](https://github.com/c-vigo) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715007259) by [@c-vigo](https://github.com/c-vigo) _Posted on January 6, 2026 at 02:53 PM_ @@ -161,7 +160,7 @@ We will release that script through GitHub packages or pages? Or point towards t --- -### [Comment #4](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715050651) by [@gerchowl](https://github.com/gerchowl) +## [Comment #4](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715050651) by [@gerchowl](https://github.com/gerchowl) _Posted on January 6, 2026 at 03:05 PM_ @@ -187,7 +186,7 @@ curl -sSf https://raw.githubusercontent.com/vig-os/devcontainer/main/install.sh --- -### [Comment #5](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715144246) by [@gerchowl](https://github.com/gerchowl) +## [Comment #5](https://github.com/vig-os/devcontainer/pull/22#issuecomment-3715144246) by [@gerchowl](https://github.com/gerchowl) _Posted on January 6, 2026 at 03:31 PM_ diff --git a/docs/pull-requests/pr-222.md b/docs/pull-requests/pr-222.md index 8edc1b6c..0c4e02a8 100644 --- a/docs/pull-requests/pr-222.md +++ b/docs/pull-requests/pr-222.md @@ -12,9 +12,8 @@ labels: none assignees: c-vigo milestone: none projects: none -relationship: none merged: 2026-03-05T14:53:51Z -synced: 2026-03-06T04:14:33.544Z +synced: 2026-03-14T04:17:19.946Z --- # [PR 222](https://github.com/vig-os/devcontainer/pull/222) feat: add hadolint support across image and CI @@ -103,9 +102,9 @@ Refs: #122 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/222#issuecomment-4005470891) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/222#issuecomment-4005470891) by [@c-vigo](https://github.com/c-vigo) _Posted on March 5, 2026 at 02:29 PM_ diff --git a/docs/pull-requests/pr-228.md b/docs/pull-requests/pr-228.md index 041c01ec..a1271783 100644 --- a/docs/pull-requests/pr-228.md +++ b/docs/pull-requests/pr-228.md @@ -12,9 +12,8 @@ labels: none assignees: none milestone: none projects: none -relationship: none merged: 2026-03-07T13:16:40Z -synced: 2026-03-12T07:59:39.957Z +synced: 2026-03-14T04:17:17.588Z --- # [PR 228](https://github.com/vig-os/devcontainer/pull/228) chore: add Nix flake devShell for reproducible host tooling @@ -38,9 +37,9 @@ Refs: #227 --- --- -## Comments (4) +# Comments (4) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4016509038) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4016509038) by [@gerchowl](https://github.com/gerchowl) _Posted on March 7, 2026 at 01:16 PM_ @@ -48,7 +47,7 @@ _Posted on March 7, 2026 at 01:16 PM_ --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4024727145) by [@c-vigo](https://github.com/c-vigo) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4024727145) by [@c-vigo](https://github.com/c-vigo) _Posted on March 9, 2026 at 03:42 PM_ @@ -58,7 +57,7 @@ How am I supposed to test? DO I need Nix available locally? --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4029681291) by [@gerchowl](https://github.com/gerchowl) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4029681291) by [@gerchowl](https://github.com/gerchowl) _Posted on March 10, 2026 at 08:50 AM_ @@ -66,7 +65,7 @@ y, Nix required --- -### [Comment #4](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4037123255) by [@c-vigo](https://github.com/c-vigo) +## [Comment #4](https://github.com/vig-os/devcontainer/pull/228#issuecomment-4037123255) by [@c-vigo](https://github.com/c-vigo) _Posted on March 11, 2026 at 07:37 AM_ diff --git a/docs/pull-requests/pr-229.md b/docs/pull-requests/pr-229.md index 22d85558..4686d33f 100644 --- a/docs/pull-requests/pr-229.md +++ b/docs/pull-requests/pr-229.md @@ -12,9 +12,8 @@ labels: none assignees: none milestone: none projects: none -relationship: none merged: 2026-03-07T06:53:55Z -synced: 2026-03-08T04:17:39.624Z +synced: 2026-03-14T04:17:16.865Z --- # [PR 229](https://github.com/vig-os/devcontainer/pull/229) fix(setup): switch taplo pre-commit from source compilation to system binary @@ -46,9 +45,9 @@ Refs: #226 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/229#issuecomment-4015740490) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/229#issuecomment-4015740490) by [@c-vigo](https://github.com/c-vigo) _Posted on March 7, 2026 at 06:38 AM_ diff --git a/docs/pull-requests/pr-237.md b/docs/pull-requests/pr-237.md index 385e8d58..f7b0f99c 100644 --- a/docs/pull-requests/pr-237.md +++ b/docs/pull-requests/pr-237.md @@ -12,8 +12,7 @@ labels: none assignees: none milestone: none projects: none -relationship: none -synced: 2026-03-10T04:14:55.237Z +synced: 2026-03-14T04:17:14.340Z --- # [PR 237](https://github.com/vig-os/devcontainer/pull/237) feat(remote): add --bootstrap for one-time remote host setup @@ -38,9 +37,9 @@ Refs: #235 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/237#issuecomment-4022440220) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/237#issuecomment-4022440220) by [@gerchowl](https://github.com/gerchowl) _Posted on March 9, 2026 at 09:39 AM_ diff --git a/docs/pull-requests/pr-242.md b/docs/pull-requests/pr-242.md index 1460c43b..46264391 100644 --- a/docs/pull-requests/pr-242.md +++ b/docs/pull-requests/pr-242.md @@ -12,8 +12,7 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-03-10T04:14:54.160Z +synced: 2026-03-14T04:17:11.732Z --- # [PR 242](https://github.com/vig-os/devcontainer/pull/242) feat(remote): add gh:org/repo[:branch] clone target @@ -87,9 +86,9 @@ Refs: #236 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/242#issuecomment-4022440426) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/242#issuecomment-4022440426) by [@gerchowl](https://github.com/gerchowl) _Posted on March 9, 2026 at 09:39 AM_ diff --git a/docs/pull-requests/pr-270.md b/docs/pull-requests/pr-270.md new file mode 100644 index 00000000..d7fafbdb --- /dev/null +++ b/docs/pull-requests/pr-270.md @@ -0,0 +1,1259 @@ +--- +type: pull_request +state: closed (merged) +branch: release/0.3.0 → main +created: 2026-03-12T12:07:22Z +updated: 2026-03-13T16:26:11Z +author: vig-os-release-app[bot] +author_url: https://github.com/vig-os-release-app[bot] +url: https://github.com/vig-os/devcontainer/pull/270 +comments: 0 +labels: none +assignees: c-vigo +milestone: 0.3 +projects: none +merged: 2026-03-13T16:26:08Z +synced: 2026-03-14T04:16:59.621Z +--- + +# [PR 270](https://github.com/vig-os/devcontainer/pull/270) chore: release 0.3.0 + +# [Release 0.3.0](https://github.com/vig-os/devcontainer/releases/tag/0.3.0) - 2026-03-13 + +This PR prepares release 0.3.0 for merge to main. + +## Release Content + +### Added + +- **Image tools** ([[#212](https://github.com/vig-os/devcontainer/issues/212)]) + - Install rsync +- **Preserve user-authored files during `--force` workspace upgrades** ([#212](https://github.com/vig-os/devcontainer/issues/212)) + - `init-workspace --force` no longer overwrites `README.md`, `CHANGELOG.md`, `LICENSE`, `.github/CODEOWNERS`, or `justfile.project` +- **Devcontainer and git recipes in justfile.base** ([#71](https://github.com/vig-os/devcontainer/issues/71)) + - Devcontainer group (host-side only): `up`, `down`, `status`, `logs`, `shell`, `restart`, `open` + - Auto-detect podman/docker compose; graceful failure if run inside container + - Git group: `log` (pretty one-line, last 20), `branch` (current + recent) +- **CI status column in just gh-issues PR table** ([#143](https://github.com/vig-os/devcontainer/issues/143)) + - PR table shows CI column with pass/fail/pending summary (✓ 6/6, ⏳ 3/6, ✗ 5/6) + - Failed check names visible when checks fail + - CI cell links to GitHub PR checks page +- **Config-driven model tier assignments for agent skills** ([#103](https://github.com/vig-os/devcontainer/issues/103)) + - Extended `.cursor/agent-models.toml` with `standard` tier (sonnet-4.5) and `[skill-tiers]` mapping for skill categories (data-gathering, formatting, review, orchestration) + - New rule `.cursor/rules/subagent-delegation.mdc` documenting when and how to delegate mechanical sub-steps to lightweight subagents via the Task tool + - Added `## Delegation` sections to 12 skills identifying steps that should spawn lightweight/standard-tier subagents to reduce token consumption on the primary autonomous model + - Skills updated: `worktree_solve-and-pr`, `worktree_brainstorm`, `worktree_plan`, `worktree_execute`, `worktree_verify`, `worktree_pr`, `worktree_ci-check`, `worktree_ci-fix`, `code_review`, `issue_triage`, `pr_post-merge`, `ci_check` +- **hadolint pre-commit hook for Containerfile linting** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Add `hadolint` hook to `.pre-commit-config.yaml`, pinned by SHA (v2.9.3) + - Enforce Dockerfile best practices: pinned base image tags, consolidated `RUN` layers, shellcheck for inline scripts + - Fix `tests/fixtures/sidecar.Containerfile` to pass hadolint with no warnings +- **tmux installed in container image for worktree session persistence** ([#130](https://github.com/vig-os/devcontainer/issues/130)) + - Add `tmux` to the Containerfile `apt-get install` block + - Enables autonomous worktree agents to survive Cursor session disconnects +- **pr_solve skill — diagnose PR failures, plan fixes, execute** ([#133](https://github.com/vig-os/devcontainer/issues/133)) + - Single entry point that gathers CI failures, review feedback, and merge state into a consolidated diagnosis + - Presents diagnosis for approval before any fixes, plans fixes using design_plan conventions, executes with TDD discipline + - Pre-commit hook `check-skill-names` enforces `[a-z0-9][a-z0-9_-]*` naming for skill directories + - BATS test suite with canary test that injects a bad name into the real repo + - TDD scenario checklist expanded with canary, idempotency, and concurrency categories +- **Optional reviewer parameter for autonomous worktree pipeline** ([#102](https://github.com/vig-os/devcontainer/issues/102)) + - Support `reviewer` parameter in `just worktree-start` + - Propagate `PR_REVIEWER` via tmux environment to the autonomous agent + - Update `worktree_pr` skill to automatically request review when `PR_REVIEWER` is set +- **Inception skill family for pre-development product thinking** ([#90](https://github.com/vig-os/devcontainer/issues/90)) + - Four-phase pipeline: `inception_explore` (divergent problem understanding), `inception_scope` (convergent scoping), `inception_architect` (pattern-validated design), `inception_plan` (decomposition into GitHub issues) + - Document templates: `docs/templates/RFC.md` (Problem Statement, Proposed Solution, Alternatives, Impact, Phasing) and `docs/templates/DESIGN.md` (Architecture, Components, Data Flow, Technology Stack, Testing) + - Document directories: `docs/rfcs/` and `docs/designs/` for durable artifacts + - Certified architecture reference repos embedded in `inception_architect` skill: ByteByteGoHq/system-design-101, donnemartin/system-design-primer, karanpratapsingh/system-design, binhnguyennus/awesome-scalability, mehdihadeli/awesome-software-architecture + - Fills the gap between "I have an idea" and "I have issues ready for design" +- **Automatic update notifications on devcontainer attach** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Wire `version-check.sh` into `post-attach.sh` for automatic update checks + - Silent, throttled checks (24-hour interval by default) + - Graceful failure - never disrupts the attach process +- **Host-side devcontainer upgrade recipe** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - New `just devcontainer-upgrade` recipe for convenient upgrades from host + - Container detection - prevents accidental execution inside devcontainer + - Clear error messages with instructions when run from wrong context +- **`just check` recipe for version management** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Expose version-check.sh subcommands: `just check`, `just check config`, `just check on/off`, `just check 7d` + - User-friendly interface for managing update notifications +- **Cursor worktree support for parallel agent development** ([#64](https://github.com/vig-os/devcontainer/issues/64)) + - `.cursor/worktrees.json` for native Cursor worktree initialization (macOS/Linux local) + - `justfile.worktree` with tmux + cursor-agent CLI recipes (`worktree-start`, `worktree-list`, `worktree-attach`, `worktree-stop`, `worktree-clean`) for devcontainer environments + - Autonomous worktree skills: `worktree_brainstorm`, `worktree_plan`, `worktree_execute`, `worktree_verify`, `worktree_pr`, `worktree_ask`, `worktree_solve-and-pr` + - Sync manifest updated to propagate worktree config and recipes to downstream projects +- **GitHub issue and PR dashboard recipe** ([#84](https://github.com/vig-os/devcontainer/issues/84)) + - `just gh-issues` displays open issues grouped by milestone in rich tables with columns for type, title, assignee, linked branch, priority, scope, effort, and semver + - Open pull requests section with author, branch, review status, and diff delta + - Linked branches fetched via a single GraphQL call + - Ships to downstream workspaces via sync manifest (`.devcontainer/justfile.gh` + `.devcontainer/scripts/gh_issues.py`) +- **Issue triage agent skill** ([#81](https://github.com/vig-os/devcontainer/issues/81)) + - Cursor skill at `.cursor/skills/issue_triage/` for triaging open issues across priority, area, effort, SemVer impact, dependencies, and release readiness + - Decision matrix groups issues into parent/sub-issue clusters with milestone suggestions + - Predefined label taxonomy (`label-taxonomy.md`) for priority, area, effort, and SemVer dimensions + - Sync manifest updated to propagate skill to workspace template +- **Cursor commands and rules for agent-driven development workflows** ([#63](https://github.com/vig-os/devcontainer/issues/63)) + - Always-on rules: `coding-principles.mdc` (YAGNI, minimal diff, DRY, no secrets, traceability, single responsibility) and `tdd.mdc` (RED-GREEN-REFACTOR discipline) + - Tier 1 commands: `start-issue.md`, `create-issue.md`, `brainstorm.md`, `tdd.md`, `review.md`, `verify.md` + - Tier 2 commands: `check-ci.md`, `fix-ci.md` + - Tier 3 commands: `plan.md`, `execute-plan.md`, `debug.md` +- **Agent-friendly issue templates, changelog rule, and PR template enhancements** ([#61](https://github.com/vig-os/devcontainer/issues/61)) + - Cursor rule `.cursor/rules/changelog.mdc` (always applied) guiding agents on when, where, and how to update CHANGELOG.md + - Changelog Category dropdown added to `bug_report.yml`, `feature_request.yml`, and `task.yml` issue templates + - New issue templates: `refactor.yml` (scope/invariants), `documentation.yml` (docs/templates workflow), `ci_build.yml` (target workflows/triggers/release impact) + - Template chooser `config.yml` disabling blank issues and linking to project docs + - PR template enhanced with explicit Changelog Entry section, CI/Build change type, and updated checklist referencing `docs/templates/` and `just docs` +- **GitHub issue and PR templates in workspace template** ([#63](https://github.com/vig-os/devcontainer/issues/63)) + - Pull request template, issue templates, Dependabot config, and `.gitmessage` synced to `assets/workspace/` + - Ground truth lives in repo root; `assets/workspace/` is generated output +- **cursor-agent CLI pre-installed in devcontainer image** ([#108](https://github.com/vig-os/devcontainer/issues/108)) + - Enables `just worktree-start` to work out of the box without manual installation +- **Automatic merge commit message compliance** ([#79](https://github.com/vig-os/devcontainer/issues/79)) + - `setup-gh-repo.sh` configures repo merge settings via `gh api` (`merge_commit_title=PR_TITLE`, `merge_commit_message=PR_BODY`, `allow_auto_merge=true`) + - Wired into `post-create.sh` so downstream devcontainer projects get compliant merge commits automatically + - `--subject-only` flag for `validate-commit-msg` to validate PR titles without requiring body or Refs + - `pr-title-check.yml` CI workflow enforces commit message standard on PR titles + - PR body template includes `Refs: #` placeholder for merge commit traceability +- **Smoke-test repo bootstrap validation** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Downstream smoke coverage that bootstraps a workspace from the template and verifies `ci.yml` passes on a real GitHub-hosted runner +- **`bandit` pre-installed in devcontainer image** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - `bandit[toml]` added to the system Python install in the Containerfile +- **`pre-commit` pre-installed in CI `setup-env` action** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Workspace `setup-env` composite action now installs `pre-commit` as a mandatory step so hooks are available in bare-runner CI without a devcontainer +- **`setup-gh-repo.sh` detaches org default code security configuration** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - On post-create, detach any org-level default security config from the repo to avoid conflicts with the security workflows shipped in the workspace template + - Graceful fallback when repo ID cannot be resolved or permissions are insufficient +- **`init-workspace.sh` runs `just sync` after placeholder replacement** ([#170](https://github.com/vig-os/devcontainer/issues/170)) + - Resolves the `uv.lock` for the new project name and installs the project package into the venv during workspace bootstrap +- **Candidate publishing mode in release workflow** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `release.yml` now supports `release-kind=candidate` (default) and infers the next available `X.Y.Z-rcN` tag automatically + - Candidate runs create and push Git tags, publish candidate manifests, and keep candidate tags after final release + - Final runs remain available via `release-kind=final` and are exposed by `just finalize-release` +- **PR-based dev sync after release** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `sync-main-to-dev.yml` replaces `post-release.yml` — syncs main into dev via PR instead of direct push, satisfying branch protection rules + - Detects merge conflicts, labels `merge-conflict` with resolution instructions + - Auto-merge enabled for conflict-free PRs; stale sync branches cleaned up automatically +- **hadolint installed and wired into CI tooling** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Install `hadolint` in the devcontainer image with SHA-256 checksum verification + - Add image test coverage to verify `hadolint` is available in the built image + - Configure pre-commit to use the local `hadolint` binary and install it in `setup-env`/`test-project` workflows +- **Taplo TOML linting in pre-commit** ([#181](https://github.com/vig-os/devcontainer/issues/181)) + - Add SHA-pinned `taplo-format` and `taplo-lint` hooks to enforce TOML formatting and schema-aware validation + - Add `.taplo.toml` configuration (local to this repository, not synced downstream) +- **Add `--smoke-test` flag to deploy smoke-test-specific assets** ([#250](https://github.com/vig-os/devcontainer/issues/250)) + - `init-workspace.sh --smoke-test` deploys files from `assets/smoke-test/` (currently `repository-dispatch.yml` and `README.md`) + - `install.sh` forwards `--smoke-test` flag to `init-workspace.sh` + - Smoke mode implies `--force --no-prompts` for unattended use + - Refactor `initialized_workspace` fixture into reusable `_init_workspace()` with `smoke_test` parameter +- **Root `.vig-os` config file as devcontainer version SSoT** ([#257](https://github.com/vig-os/devcontainer/issues/257)) + - Add committed `assets/workspace/.vig-os` key/value config with `DEVCONTAINER_VERSION` as the canonical version source + - Update `docker-compose.yml`, `initialize.sh`, and `version-check.sh` to consume `.vig-os`-driven version flow + - Extend integration/image tests for `.vig-os` presence and graceful handling when `.vig-os` is missing +- **VS Code settings synced via manifest** + - Added `.vscode/settings.json` to `scripts/manifest.toml` to keep editor settings consistent across root repo and workspace template +- **Cross-repo smoke-test dispatch on RC publish** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - RC candidate publishes now trigger `repository_dispatch` in `vig-os/devcontainer-smoke-test` with the RC tag payload + - Release process now includes a documented manual smoke gate before running final publish +- **Automated RC deploy-and-test via PR in smoke-test repo** ([#258](https://github.com/vig-os/devcontainer/issues/258)) + - Dispatch workflow now deploys the tag, creates a signed commit on `chore/deploy-`, and opens a PR to `dev` + - CI workflows (`ci.yml`, `ci-container.yml`) trigger on the deploy PR, and auto-merge is enabled when checks pass + - Stale deploy PRs are closed before each new deployment + - The smoke-test repo keeps audit trail through deploy PRs and merge history instead of a local changelog + - Dispatch payload tag validation now enforces semver format `X.Y.Z` or `X.Y.Z-rcN` before using the tag in refs/URLs + - CI security scan now includes a time-bounded exception for `CVE-2026-31812` in `uv`/`uvx` pending upstream dependency patch release + +### Changed + +- **Release CHANGELOG flow redesigned** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - `prepare-release.yml` now freezes CHANGELOG on dev (Unreleased → [X.Y.Z] - TBD + fresh empty Unreleased), then forks release branch and strips the empty Unreleased section + - Dev never enters a state without `## Unreleased`; both branches share the [X.Y.Z] section for clean merges + - Candidate releases skip CHANGELOG changes; only final releases set the date + - No CHANGELOG reset needed during post-release sync +- **Release automation now uses dedicated GitHub App identities** ([#172](https://github.com/vig-os/devcontainer/issues/172)) + - Replaced deprecated `APP_SYNC_ISSUES_*` secrets with `RELEASE_APP_*` for release and preparation workflows + - `sync-issues.yml` now uses `COMMIT_APP_*`; `sync-main-to-dev.yml` uses both apps (commit app for refs, release app for PR operations) + - Removed automatic `sync-issues` trigger from `sync-main-to-dev.yml` and documented the app permission model in `docs/RELEASE_CYCLE.md` +- **Container CI defaults image tag from `.vig-os`** ([#264](https://github.com/vig-os/devcontainer/issues/264)) + - `ci.yml` and `ci-container.yml` now run only on `pull_request` and `workflow_dispatch` after removing unused `workflow_call` triggers + - `ci-container.yml` now resolves `DEVCONTAINER_VERSION` from `.vig-os` before container jobs start + - Manual `workflow_dispatch` runs can still override the image via `image-tag`; fallback remains `latest` when no version is available + - Added an early manifest check in `resolve-image` so workflows fail fast if the resolved image tag is unavailable or inaccessible + +- **worktree-clean: add filter mode for stopped-only vs all** ([#158](https://github.com/vig-os/devcontainer/issues/158)) + - Default `just worktree-clean` (no args) now cleans only stopped worktrees, skips running tmux sessions + - `just worktree-clean all` retains previous behavior (clean all worktrees) with warning + - Summary output shows cleaned vs skipped worktrees + - `just wt-clean` alias unchanged +- **Consolidate sync_manifest.py and utils.py into manifest-as-config architecture** ([#89](https://github.com/vig-os/devcontainer/issues/89)) + - Extract transform classes (Sed, RemoveLines, etc.) to `scripts/transforms.py` + - Unify sed logic: `substitute_in_file()` in utils shared by sed_inplace and Sed transform + - Convert MANIFEST from Python code to declarative `scripts/manifest.toml` +- **justfile.base is canonical at repo root, synced via manifest** ([#71](https://github.com/vig-os/devcontainer/issues/71)) + - Root `justfile.base` is now the single source of truth; synced to `assets/workspace/.devcontainer/justfile.base` via `sync_manifest.py` + - `just sync-workspace` and prepare-build keep workspace template in sync +- **Autonomous PR skills use pull request template** ([#147](https://github.com/vig-os/devcontainer/issues/147)) + - `pr_create` and `worktree_pr` now read `.github/pull_request_template.md` and fill each section from available context + - Explicit read-then-fill procedure with section-by-section mapping (Description, Type of Change, Changelog Entry, Testing, Checklist, Refs) + - Ensures autonomous PRs match manual PR structure and include all checklist items +- **Rename skill namespace separator from colon to underscore** ([#128](https://github.com/vig-os/devcontainer/issues/128)) + - All skill directories under `.cursor/skills/` and `assets/workspace/.cursor/skills/` renamed (e.g. `issue:create` → `issue_create`) + - All internal cross-references, frontmatter, prose, `CLAUDE.md` command table, and label taxonomy updated + - `issue_create` skill enhanced: gathers context via `just gh-issues` before drafting, suggests parent/child relationships and milestones + - `issue_create` skill now includes TDD acceptance criterion for testable issue types + - Remaining `sync-issues` workflow trigger references removed from skills + - `tdd.mdc` expanded with test scenario checklist and test type guidance; switched from always-on to glob-triggered on source/test files + - `code_tdd`, `code_execute`, and `worktree_execute` skills now reference `tdd.mdc` explicitly +- **Clickable issue and PR numbers in gh-issues table** ([#104](https://github.com/vig-os/devcontainer/issues/104)) + - `#` column in issue and PR tables now uses Rich OSC 8 hyperlinks to GitHub URLs + - Clicking an issue or PR number opens it in the browser (or Cursor's integrated terminal) +- **PR template aligned with canonical commit types** ([#115](https://github.com/vig-os/devcontainer/issues/115)) + - Replace ad-hoc Type of Change checkboxes with the 10 canonical commit types + - Move breaking change from type to a separate modifier checkbox + - Add release-branch hint to Related Issues section +- **Updated update notification message** ([#73](https://github.com/vig-os/devcontainer/issues/73)) + - Fixed misleading `just update` instruction (Python deps, not devcontainer upgrade) + - Show correct upgrade instructions: `just devcontainer-upgrade` and curl fallback + - Clarify that upgrade must run from host terminal, not inside container + - Add reminder to rebuild container in VS Code after upgrade +- **Declarative Python sync manifest** ([#67](https://github.com/vig-os/devcontainer/issues/67)) + - Replaced `sync-manifest.txt` + bash function and `sync-workspace.sh` with `scripts/sync_manifest.py` + - Single source of truth for which files to sync and what transformations to apply + - `prepare-build.sh` and `just sync-workspace` both call the same manifest +- **Namespace-prefixed Cursor skill names** ([#67](https://github.com/vig-os/devcontainer/issues/67)) + - Renamed all 15 skills with colon-separated namespace prefixes (`issue:`, `design:`, `code:`, `git:`, `ci:`, `pr:`) + - Enables filtering by namespace when invoking skills (e.g., typing `code:` shows implementation skills) +- **`--org` flag for install script** ([#33](https://github.com/vig-os/devcontainer/issues/33)) + - Allows overriding the default organization name (default: `vigOS`) + - Passes `ORG_NAME` as environment variable to the container + - Usage: `curl -sSf ... | bash -s --org MyOrg -- ~/my-project` + - Unit tests for `--org` flag in help, default value, and custom override +- **Virtual environment prompt renaming** ([#34](https://github.com/vig-os/devcontainer/issues/34)) + - Post-create script updates venv prompt from "template-project" to project short name + - Integration test verifies venv activate script does not contain "template-project" +- **BATS (Bash Automated Testing System) shell testing framework** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - npm dependencies for bats, bats-support, bats-assert, and bats-file + - `test-bats` justfile task and requirements configuration + - `test_helper.bash` supporting both local (node_modules) and CI (BATS_LIB_PATH) library resolution + - CI integration in setup-env and test-project actions with conditional parallel execution via GNU parallel + - Comprehensive BATS test suites for build, clean, init, install, and prepare-build scripts + - Tests verify script structure, argument parsing, function definitions, error handling, and OS/runtime detection patterns +- **Post-install user configuration step** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Automatically call copy-host-user-conf.sh after workspace initialization + - `run_user_conf()` helper for host-side setup (git, ssh, gh) + - Integration tests for .devcontainer/.conf/ directory creation and expected config files +- **Git repository initialization in install script** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - `setup_git_repo()` function to initialize git if missing + - Creates initial commit "chore: initial project scaffold" for new repos + - Automatically creates main and dev branches + - `test-install` justfile recipe for running install tests + - Integration tests for git repo initialization, branches, and initial commit +- **Commit message standardization** ([#36](https://github.com/vig-os/devcontainer/issues/36)) + - Commit message format: `type(scope)!: subject` with mandatory `Refs: #` line + - Documentation: `docs/COMMIT_MESSAGE_STANDARD.md` defining format, approved types (feat, fix, docs, chore, refactor, test, ci, build, revert, style), and traceability requirements + - Validation script: `scripts/validate_commit_msg.py` enforcing the standard + - Commit-msg hook: `.githooks/commit-msg` runs validation on every commit + - Pre-commit integration: commit-msg stage hook in `.pre-commit-config.yaml` + - Git commit template: `.gitmessage` with format placeholder + - Cursor integration: `.cursor/rules/commit-messages.mdc` and `.cursor/commands/commit-msg.md` for AI-assisted commit messages + - Workspace template: all commit message tooling included in `assets/workspace/` for new projects + - Tests: `tests/test_validate_commit_msg.py` with comprehensive validation test cases +- **nano text editor** in devcontainer image ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Chore Refs exemption** in commit message standard ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - `chore` commits may omit the `Refs:` line when no issue or PR is directly related + - Validator updated with `REFS_OPTIONAL_TYPES` to accept chore commits without Refs +- **Dependency review allowlist entry** for debug@0.6.0 ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Added GHSA-9vvw-cc9w-f27h exception to `.github/dependency-review-allow.txt` + - Addresses ReDoS vulnerability in transitive test dependency (bats-assert → verbose → debug) + - High risk severity but isolated to CI/development environment with expiration 2026-11-17 +|- **Dependency review exception for legacy test vulnerabilities** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Comprehensive acceptance register for 9 transitive vulnerabilities in unmaintained BATS test framework dependencies + - All 9 vulnerabilities are isolated to CI/development environment (engine.io, debug, node-uuid, qs, tough-cookie, ws, xmlhttprequest, form-data) + - Formal risk assessments and mitigations documented in `SECURITY.md` and `.github/dependency-review-allow.txt` + - Expiration-enforced exceptions with 2026-11-17 expiration date to force periodic re-evaluation + +- **Bandit and Safety security scanning** ([#37](https://github.com/vig-os/devcontainer/issues/37), [#50](https://github.com/vig-os/devcontainer/issues/50)) + - Bandit pre-commit hook for medium/high/critical severity Python code analysis + - CI pipeline job with Bandit static analysis and Safety dependency vulnerability scanning + - Reports uploaded as artifacts (30-day retention) with job summary integration +- **Scheduled weekly security scan workflow** (`security-scan.yml`) ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Full Trivy vulnerability scan (all severities) against `dev` branch every Monday 06:00 UTC + - SBOM generation (CycloneDX) and SARIF upload to GitHub Security tab + - Non-blocking: catches newly published CVEs between pull requests +- **Non-blocking unfixed vulnerability reporting in CI** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Additional CI scan step reports unfixed HIGH/CRITICAL CVEs for awareness without blocking the pipeline +- **Comprehensive `.trivyignore` vulnerability acceptance register** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Formal medtech-compliant register (IEC 62304 / ISO 13485) documenting 10 accepted CVEs + - Each entry includes risk assessment, exploitability justification, fix status, and mitigation + - 6-month expiration dates enforce periodic re-evaluation +- **Expiration-enforced dependency-review exceptions** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Allow GHSA-wvrr-2x4r-394v (bats-file false positive) via `.github/dependency-review-allow.txt` + - CI validation step parses expiration dates and fails the pipeline when exceptions expire, forcing periodic review +- **Branch name enforcement as a pre-commit hook** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - New `branch-name` hook enforcing `/-` convention (e.g. `feature/38-standardize-branching-strategy-enforcement`) + - Pre-commit configuration updated in repo and in workspace assets (`.pre-commit-config.yaml`, `assets/workspace/.pre-commit-config.yaml`) + - Integration tests added for valid and invalid branch names +- **Cursor rules for branch naming and issue workflow** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - `.cursor/rules/branch-naming.mdc`: topic branch naming format, branch types, workflow for creating/linking branches via `gh issue develop` + - Guidelines for inferring branch type from issue labels and deriving short summary from issue title +- **Release cycle documentation** ([#38](https://github.com/vig-os/devcontainer/issues/38), [#48](https://github.com/vig-os/devcontainer/issues/48)) + - `docs/RELEASE_CYCLE.md` with complete release workflow, branching strategy, and CI/CD integration + - Cursor commands: `after-pr-merge.md`, `submit-pr.md` +- **pip-licenses** installed system-wide with version verification test ([#43](https://github.com/vig-os/devcontainer/issues/43)) +- **just-lsp** language server and VS Code extension for Just files ([#44](https://github.com/vig-os/devcontainer/issues/44)) +- **Automated release cycle** ([#48](https://github.com/vig-os/devcontainer/issues/48)) + - `prepare-release` and `finalize-release` justfile commands triggering GitHub Actions workflows + - `prepare-changelog.py` script with prepare, validate, reset, and finalize commands for CHANGELOG automation + - `reset-changelog` justfile command for post-merge CHANGELOG cleanup + - `prepare-release.yml` GitHub Actions workflow: validates semantic version, creates release branch, prepares CHANGELOG + - Unified `release.yml` pipeline: validate → finalize → build/test → publish → rollback + - Comprehensive test suite in `tests/test_release_cycle.py` +- **CI testing infrastructure** ([#48](https://github.com/vig-os/devcontainer/issues/48)) + - `ci.yml` workflow replacing `test.yml` with streamlined project checks (lint, changelog validation, utility and release-cycle tests) + - Reusable composite actions: `setup-env`, `build-image`, `test-image`, `test-integration`, `test-project` + - Artifact transfer between jobs for consistent image testing + - Retry logic across all CI operations for transient failure handling +- **GitHub Actions SHA pinning enforcement** ([#50](https://github.com/vig-os/devcontainer/issues/50)) + - `scripts/check_action_pins.py` pre-commit hook and CI check ensuring all GitHub Actions and Docker actions reference commit SHAs + - Comprehensive test suite in `tests/test_check_action_pins.py` +- **CODEOWNERS** for automated review assignment ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **SECURITY.md** with vulnerability reporting procedures and supported version policy ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **OpenSSF Scorecard workflow** (`scorecard.yml`) for supply chain security scoring ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **CodeQL analysis workflow** (`codeql.yml`) for automated static security analysis ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Dependabot configuration** for automated dependency update PRs with license compliance monitoring ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Vulnerability scanning and dependency review** in CI pipeline with non-blocking MEDIUM severity reporting ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **SBOM generation, container signing, and provenance attestation** in release and CI pipelines ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Edge case tests** for changelog validation, action SHA pinning, and install script ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **`vig-utils` reusable CLI utilities package** ([#51](https://github.com/vig-os/devcontainer/issues/51)) + - Python package in `packages/vig-utils/` for shared validation and build utilities + - `validate_commit_msg` module: enforces commit message format and references standards + - Configurable commit scopes validation: scope list can be customized per project + - Scopes are optional by default; if used, must be in the approved list + - Support for multiple scopes, comma-separated (e.g., `feat(api, cli): add feature`) + - Support for GitHub auto-linked issue references (e.g., PR cross-repo links) + - Comprehensive test suite with edge case coverage for PR and cross-repo issue links + - `prepare_changelog` module: CHANGELOG management and validation + - `check_action_pins` module: GitHub Actions SHA pinning enforcement + - Integrated into CI/CD pipeline and pre-commit hooks as standard Python package + - Package version tests verify installation and correct versioning +- **Code coverage reporting in CI** ([#52](https://github.com/vig-os/devcontainer/issues/52)) + - Code coverage measurement integrated into test action workflow + - Coverage threshold raised to 50% for unit tests + - Expanded unit tests to improve overall test coverage +- **File duplication detection and elimination** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Build-time manifest system detects and removes duplicated workspace assets + - Replaces duplicated files with sync manifest entries, reducing redundancy + - Workspace assets now synchronized from central manifest during build preparation + - GitHub workflow templates for devcontainer projects included in sync manifest + - Automated npm dependency management with centralized version pinning in `.github/package.json` + - Extract build preparation into dedicated `prepare-build.sh` script with manifest sync + - SHA-256 checksum verification tests for synced files via `parse_manifest` fixture and `test_manifest_files` +- **GitHub workflow templates for devcontainer projects** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Reusable workflow templates for continuous integration and deployment + - Support for projects using devcontainer-based development environments +- **Centralized `@devcontainers/cli` version management** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Version pinned in `.github/package.json` for consistent behavior across workflows and builds + - Ensures reproducibility across build and setup environments +- **`--require-scope` flag for `validate-commit-msg`** ([#58](https://github.com/vig-os/devcontainer/issues/58)) + - New CLI flag to mandate that all commits include at least one scope (e.g. `feat(api): ...`) + - When enabled, scopeless commits (e.g. `feat: ...`) are rejected at the commit-msg stage + - Comprehensive tests added to `test_validate_commit_msg.py` +- **`post-start.sh` devcontainer lifecycle script** ([#60](https://github.com/vig-os/devcontainer/issues/60)) + - New script runs on every container start (create + restart) + - Handles Docker socket permissions and dependency sync via `just sync` + - Replaces inline `postStartCommand` in `devcontainer.json` +- **Dependency sync delegated to `just sync` across all lifecycle hooks** ([#60](https://github.com/vig-os/devcontainer/issues/60)) + - `post-create.sh`, `post-start.sh`, and `post-attach.sh` now call `just sync` instead of `uv sync` directly + - `justfile.base` `sync` recipe updated with `--all-extras --no-install-project` flags and `pyproject.toml` guard + - Abstracts toolchain details so future dependency managers only need a recipe change + +- **Git initialization default branch** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Updated git initialization to set the default branch to 'main' instead of 'master' + - Consolidated Podman installation with other apt commands in Containerfile +- **CI release workflow uses GitHub API** ([#35](https://github.com/vig-os/devcontainer/issues/35)) + - Replace local git operations with GitHub API in prepare-release workflow + - Use commit-action for CHANGELOG updates instead of local git + - Replace git operations with GitHub API in release finalization flow + - Simplify rollback and tag deletion to use gh api + - Add sync-dependencies input to setup-env action (default: false) + - Remove checkout step from setup-env; callers must checkout explicitly + - Update all workflow callers to pass sync-dependencies input + - Update CI security job to use uv with setup-env action +- **Commit message guidelines** - updated documentation ([#36](https://github.com/vig-os/devcontainer/issues/37)) +- **Expected version checks** - updated ruff and pre-commit versions in test suite ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Bumped `actions/create-github-app-token`** from v1 to v2 across workflows with updated SHA pins ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Pinned `@devcontainers/cli`** to version 0.81.1 in CI for consistent behavior ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **CI and release Trivy scans gate on fixable CVEs only** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Added `ignore-unfixed: true` to blocking scan steps in `ci.yml` and `release.yml` + - Unfixable CVEs no longer block the pipeline; documented in `.trivyignore` with risk assessments +- **Updated pre-commit hook configuration in the devcontainer** ([#38](https://github.com/vig-os/devcontainer/issues/38)) + - Exclude issue and template docs from .github_data + - Autofix shellcheck + - Autofix pymarkdown + - Add license compliance check +- **Renamed `publish-container-image.yml` to `release.yml`** and expanded into unified release pipeline ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Merged `prepare-build.sh` into `build.sh`** — consolidated directory preparation, asset copying, placeholder replacement, and README updates into a single entry point ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Consolidated test files by domain** — reorganized from 6 files to 4 (`test_image.py`, `test_integration.py`, `test_utils.py`, `test_release_cycle.py`) ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Replaced `setup-python-uv` with flexible `setup-env` composite action** supporting optional inputs for podman, Node.js, and devcontainer CLI ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Reduced `sync-issues` workflow triggers** — removed `edited` event type from issues and pull_request triggers ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Release workflow pushes tested images** instead of rebuilding after tests pass ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Updated CONTRIBUTE.md** release workflow documentation to match automated process ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **CodeQL Action v3 → v4 upgrade** + - Updated all CodeQL Action references from v3 (deprecated Dec 2026) to v4.32.2 + - Updated in `.github/workflows/codeql.yml`, `security-scan.yml`, and `ci.yml` + - Uses commit hash `45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2` for integrity +- **Sync-issues workflow output directory** ([#53](https://github.com/vig-os/devcontainer/issues/53)) + - Changed output directory from '.github_data' to 'docs' for better project structure alignment +- **Workspace `validate-commit-msg` hook configured strict-by-default** ([#58](https://github.com/vig-os/devcontainer/issues/58)) + - `assets/workspace/.pre-commit-config.yaml` now ships with explicit `args` instead of commented-out examples + - Default args enable type enforcement, scope enforcement with `--require-scope`, and `chore` refs exemption + - Link to `vig-utils` README added as a comment above the hook for discoverability +- **Refresh pinned Python base image digest** ([#213](https://github.com/vig-os/devcontainer/issues/213)) + - Update `python:3.12-slim-bookworm` pinned digest in `Containerfile` to the latest upstream value while keeping the same tag +- **Pre-commit hook removal transform preserves section comments** ([#171](https://github.com/vig-os/devcontainer/issues/171)) + - `scripts/transforms.py` keeps section comments intact while removing configured hooks during manifest sync + - `scripts/manifest.toml` and related sync/test updates keep workspace pre-commit outputs aligned with container CI workflow changes +- **Migrate shared scripts into `vig-utils` package entrypoints** ([#217](https://github.com/vig-os/devcontainer/issues/217), [#161](https://github.com/vig-os/devcontainer/issues/161), [#179](https://github.com/vig-os/devcontainer/issues/179)) + - Shell scripts (`check-skill-names.sh`, `derive-branch-summary.sh`, `resolve-branch.sh`, `setup-labels.sh`) bundled inside `vig_utils.shell` and exposed as `vig-` CLI entrypoints + - Python scripts (`gh_issues.py`, `check-agent-identity.py`, `check-pr-agent-fingerprints.py`, `prepare-commit-msg-strip-trailers.py`) migrated into `vig-utils` modules with entrypoints + - Agent fingerprint helpers consolidated into shared `vig_utils.utils` module + - Callers (justfiles, pre-commit hooks, CI workflows) switched from direct script paths to `vig-utils` entrypoints +- **Restructure workspace justfile into devc/project split** ([#219](https://github.com/vig-os/devcontainer/issues/219)) + - Rename `justfile.base` to `justfile.devc` and keep devcontainer lifecycle recipes there + - Move project-level recipes (`lint`, `format`, `precommit`, `test`, `sync`, `update`, `clean-artifacts`, `log`, `branch`) into `justfile.project` + - Add tracked `justfile.local` template for personal recipes while keeping it ignored in downstream workspaces, and update workspace imports/manifests to the new structure +- **Update base Python image and GitHub Actions dependencies** ([#240](https://github.com/vig-os/devcontainer/issues/240)) + - Containerfile: pin `python:3.12-slim-bookworm` to latest digest + - Bump trivy CLI v0.69.2 → v0.69.3, trivy-action v0.33.1 → v0.35.0 + - Update astral-sh/setup-uv, taiki-e/install-action, docker/build-push-action, github/codeql-action, actions/dependency-review-action, actions/attest-build-provenance +- **Bump GitHub CLI to 2.88.x** + - Update expected `gh` version in image tests from 2.87 to 2.88 +- **Manifest sync includes `sync-main-to-dev` workflow** ([#278](https://github.com/vig-os/devcontainer/issues/278)) + - Add `.github/workflows/sync-main-to-dev.yml` to `scripts/manifest.toml` so workspace sync includes the release-to-dev PR automation workflow + + +### Removed + +- **`post-release.yml`** — replaced by `sync-main-to-dev.yml` ([#172](https://github.com/vig-os/devcontainer/issues/172)) +- **`scripts/prepare-build.sh`** — merged into `build.sh` ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **`scripts/sync-prs-issues.sh`** — deprecated sync script ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **`test.yml` workflow** — replaced by `ci.yml` ([#48](https://github.com/vig-os/devcontainer/issues/48)) +- **Stale `.github_data/` directory** — 98 files superseded by `docs/issues/` and `docs/pull-requests/` ([#91](https://github.com/vig-os/devcontainer/issues/91)) +- **Legacy standalone script copies** ([#217](https://github.com/vig-os/devcontainer/issues/217)) + - Removed `scripts/check-agent-identity.py`, `scripts/check-skill-names.sh`, `scripts/derive-branch-summary.sh`, `scripts/resolve-branch.sh` — now in `vig-utils` + - Removed `assets/workspace/.devcontainer/scripts/gh_issues.py`, `check-pr-agent-fingerprints.py`, `prepare-commit-msg-strip-trailers.py` — now in `vig-utils` + - Removed `scripts/utils.py` shim — superseded by `vig_utils.utils` + +### Fixed + +- **`just` default recipe hidden by lint recipe** ([#254](https://github.com/vig-os/devcontainer/issues/254)) + - The `default` recipe must appear before any other recipe in the justfile; `lint` was placed first, shadowing the recipe listing + - Moved `default` recipe above `lint` to restore `just` with no arguments showing available recipes +- **Broken `gh-issues --help` guard in justfile recipe** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - `gh-issues` CLI has no `--help` flag, so the availability check always failed even when the binary was installed + - Removed the broken guard; binary availability is now verified by the image test suite +- **Smoke-test redeploy preserves synced docs directories** ([#262](https://github.com/vig-os/devcontainer/issues/262)) + - `init-workspace.sh --smoke-test` now excludes `docs/issues/` and `docs/pull-requests/` from `rsync --delete` + - Re-deploying smoke assets no longer removes docs synced by `sync-issues` +- **Prepare-release uses scoped app tokens for protected branch writes** ([#268](https://github.com/vig-os/devcontainer/issues/268)) + - `prepare-release.yml` now uses `COMMIT_APP_*` for git/ref and `commit-action` operations that touch `dev` and release refs + - Draft PR creation in prepare-release now uses `RELEASE_APP_*` token scope for pull-request operations +- **generate-docs picks up unreleased TBD version on release branches** ([#271](https://github.com/vig-os/devcontainer/issues/271)) + - `get_version_from_changelog()` and `get_release_date_from_changelog()` now skip entries without a concrete release date +- **PR fingerprint check false positives on plain-text AI tool mentions** ([#274](https://github.com/vig-os/devcontainer/issues/274)) + - `contains_agent_fingerprint` now restricts name matching to attribution-context lines (e.g. "generated by", "authored by") instead of scanning the entire content + - Wire up `allow_patterns` from `agent-blocklist.toml` to strip known-safe text (dotfile paths, doc filenames) before checking +- **Release candidate publish retags loaded images before push** ([#281](https://github.com/vig-os/devcontainer/issues/281)) + - `release.yml` now tags `ghcr.io/vig-os/devcontainer:X.Y.Z-arch` artifacts as `X.Y.Z-rcN-arch` before `docker push` in candidate runs + - Prevents publish failures caused by pushing candidate tags that were never created locally after `docker load` +- **Pinned commit-action to the malformed path fix release** ([#286](https://github.com/vig-os/devcontainer/issues/286)) + - Updated smoke-test and release-related workflows to `vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646` (`v0.1.5`) + - Resolves failures when commit-action receives `FILE_PATHS: .` and accidentally includes invalid `.git/*` tree paths +- **Smoke-test deploy commit no longer references non-local issue IDs** ([#284](https://github.com/vig-os/devcontainer/issues/284)) + - `assets/smoke-test/.github/workflows/repository-dispatch.yml` no longer injects `Refs: #258` into automated `chore: deploy ` commits in the smoke-test repository + - Added maintainer note that workflow-template changes require manual redeploy to `vig-os/devcontainer-smoke-test` and promotion through PRs to `main` +- **Install name sanitization trims invalid package boundaries** ([#291](https://github.com/vig-os/devcontainer/issues/291)) + - `install.sh` now normalizes sanitized project names to ensure they start/end with alphanumeric characters before passing `SHORT_NAME` + - `init-workspace.sh` mirrors the same normalization so generated `pyproject.toml` names cannot end with separators like `_` + +### Security + +- **Eliminated 13 transitive vulnerabilities in BATS test dependencies** ([#37](https://github.com/vig-os/devcontainer/issues/37)) + - Bumped bats-assert from v2.1.0 to v2.2.0, which dropped a bogus runtime dependency on the `verbose` npm package + - Removed entire transitive dependency tree: engine.io, debug, node-uuid, qs, tough-cookie, ws, xmlhttprequest, form-data, request, sockjs, and others (50+ packages reduced to 5) + - Cleaned 13 now-unnecessary GHSA exceptions from `.github/dependency-review-allow.txt` +- **Go stdlib CVEs from gh binary accepted and documented** ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- CVE-2025-68121, CVE-2025-61726, CVE-2025-61728, CVE-2025-61730 added to `.trivyignore` +- Vulnerabilities embedded in statically-linked GitHub CLI binary; low exploitability in devcontainer context +- Each entry includes risk assessment, justification, and 3-month expiration date to force re-review +- Awaiting upstream `gh` release with Go 1.25.7 or later +- **GHSA-wvrr-2x4r-394v (bats-file false positive) accepted in dependency review** ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- Added to `.github/dependency-review-allow.txt` with 6-month expiration date enforced by CI +- **Upgraded pip** in Containerfile to fix CVE-2025-8869 (symbolic link extraction vulnerability) ([#37](https://github.com/vig-os/devcontainer/issues/37)) +- **Digest-pinned base image** (`python:3.12-slim-bookworm`) with SHA256 checksum verification for all downloaded binaries and `.trivyignore` risk-assessment policy in Containerfile ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Minisign signature verification** for cargo-binstall downloads ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **GitHub Actions and Docker actions pinned to commit SHAs** across all workflows ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Pre-commit hook repos pinned to commit SHAs** ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Workflow permissions hardened** with least-privilege principle and explicit token scoping ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Input sanitization** — inline expression interpolation replaced with environment variables in workflow run blocks to prevent injection ([#50](https://github.com/vig-os/devcontainer/issues/50)) +- **Update vulnerable Python dependencies** ([#88](https://github.com/vig-os/devcontainer/issues/88)) + - Add uv constraints for transitive dependencies: `urllib3>=2.6.3`, `filelock>=3.20.3`, and `virtualenv>=20.36.1` + - Regenerate `uv.lock` with patched resolutions (`urllib3 2.6.3`, `filelock 3.25.0`, `virtualenv 21.1.0`) +- **Temporary Trivy exception for CVE-2025-15558 in gh binary** ([#122](https://github.com/vig-os/devcontainer/issues/122)) + - Added `CVE-2025-15558` to `.trivyignore` with risk assessment, upstream dependency context, and an expiration date + - Keeps CI vulnerability scan unblocked while waiting for an upstream `gh` release that includes the patched `github.com/docker/cli` dependency + + +--- +--- + +## Commits + +### Commit 1: [3ec1f64](https://github.com/vig-os/devcontainer/commit/3ec1f648850f256b9d7f6b9789524d600ed21b2a) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 03:53 PM +fix(worktree): surface derive-branch-summary errors and retry with standard model, 31 files modified (CHANGELOG.md, assets/workspace/.devcontainer/justfile.worktree, justfile.worktree, scripts/derive-branch-summary.sh) + +### Commit 2: [a3f0cf4](https://github.com/vig-os/devcontainer/commit/a3f0cf4ff046c28985996d392af554ba1d6c8a20) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:02 PM +chore: remove setup-env action from sync manifest, 3 files modified (scripts/manifest.toml) + +### Commit 3: [66992ed](https://github.com/vig-os/devcontainer/commit/66992ed2283b7666877b1056c3b8bdb03d36d417) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:10 PM +feat(image): install pre-commit in setup action, 9 files modified (assets/workspace/.github/actions/setup-env/action.yml) + +### Commit 4: [a2f73ed](https://github.com/vig-os/devcontainer/commit/a2f73edf49df9e3011836e4c4856164695b29fe9) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:40 PM +fix(worktree): surface derive-branch-summary errors and retry with standard model (#184), 38 files modified (CHANGELOG.md, assets/workspace/.devcontainer/justfile.worktree, justfile.worktree, scripts/derive-branch-summary.sh, tests/bats/worktree.bats) + +### Commit 5: [db8fc1c](https://github.com/vig-os/devcontainer/commit/db8fc1cf55de58e88a98d202c25b4d6cd15f13a0) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:41 PM +fix(gh-issues): make issue numbers in PR table clickable hyperlinks (#182), 58 files modified (CHANGELOG.md, scripts/gh_issues.py, tests/test_gh_issues.py) + +### Commit 6: [cbe2e88](https://github.com/vig-os/devcontainer/commit/cbe2e8833afd1ce79e19025a83111c62c824b063) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:42 PM +Merge branch 'dev' into bugfix/176-gh-issues-ci-dedup, 96 files modified (CHANGELOG.md, assets/workspace/.devcontainer/justfile.worktree, justfile.worktree, scripts/derive-branch-summary.sh, scripts/gh_issues.py, tests/bats/worktree.bats, tests/test_gh_issues.py) + +### Commit 7: [e03a80c](https://github.com/vig-os/devcontainer/commit/e03a80c87aeae71ad916ac41c54b1322b19ab47a) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:42 PM +fix(gh-issues): deduplicate CI status by check name (#176) (#177), 67 files modified (.github/actions/test-project/action.yml, CHANGELOG.md, scripts/gh_issues.py, tests/test_gh_issues.py) + +### Commit 8: [cb9eeca](https://github.com/vig-os/devcontainer/commit/cb9eeca160c04013370465aaf4bfdb40d19ce4d0) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:43 PM +test: add regression test for just check path resolution, 22 files modified (tests/test_integration.py) + +### Commit 9: [27782bd](https://github.com/vig-os/devcontainer/commit/27782bd4bf42061bbc48e0fcefa039893e57f29e) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:46 PM +fix(justfile): use source_directory() for check recipe path resolution, 9 files modified (assets/workspace/.devcontainer/justfile.base, justfile.base, tests/test_integration.py) + +### Commit 10: [c32304c](https://github.com/vig-os/devcontainer/commit/c32304c303e0df9aae26743cef375d50e6cbb419) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 04:51 PM +docs(changelog): add entry for just check path fix, 3 files modified (CHANGELOG.md) + +### Commit 11: [da116bb](https://github.com/vig-os/devcontainer/commit/da116bb99e5b60723a7baeb768c5ae62b1f391c6) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:56 PM +fix: remove empty repo blocks for all repo types in RemovePrecommitHooks, 4 files modified (scripts/transforms.py) + +### Commit 12: [743c84f](https://github.com/vig-os/devcontainer/commit/743c84fd8d0f277f4d1d336c2952b3a9b9fe3ae4) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:01 PM +Merge branch 'dev' into bugfix/187-fix-just-check-path, 125 files modified (.github/actions/test-project/action.yml, CHANGELOG.md, scripts/gh_issues.py, tests/test_gh_issues.py) + +### Commit 13: [2030a53](https://github.com/vig-os/devcontainer/commit/2030a5381665efa6a4fdfd28298dfc6563aa0ab1) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 04:57 PM +chore: remove actions from sync manifest, 74 files modified (assets/workspace/.pre-commit-config.yaml, scripts/manifest.toml) + +### Commit 14: [56b4d73](https://github.com/vig-os/devcontainer/commit/56b4d73f3abeb75543e750ab73df55b04e1fcaa7) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:04 PM +fix(workspace): sync scripts referenced by justfiles into manifest (#190), 122 files modified + +### Commit 15: [32a71ad](https://github.com/vig-os/devcontainer/commit/32a71adf6598c7fceaeacd137c1cde5502012e66) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:26 PM +fix(workspace): sync scripts referenced by justfiles into manifest (#190) (#191), 122 files modified + +### Commit 16: [fbe7eb8](https://github.com/vig-os/devcontainer/commit/fbe7eb89402c59f8347247018a748c4942765c35) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:27 PM +fix: just check uses wrong path (#187) (#189), 30 files modified (CHANGELOG.md, assets/workspace/.devcontainer/justfile.base, justfile.base, tests/test_integration.py) + +### Commit 17: [17dfbea](https://github.com/vig-os/devcontainer/commit/17dfbea17c56d7800c4584dd909955470dda071b) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:29 PM +refactor(skills): add pull-latest-base-before-pr to PR-creating skills, 66 files modified + +### Commit 18: [a47dbb0](https://github.com/vig-os/devcontainer/commit/a47dbb0f86fd684141b2eabb50e6fff64cd5c57f) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 05:49 PM +feat(skills): add pull-latest-base-before-pr to PR-creating skills (#192) (#193), 66 files modified + +### Commit 19: [d4bbf46](https://github.com/vig-os/devcontainer/commit/d4bbf467aa0667b61802580c2e17bd1b7d26357f) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 06:00 PM +fix(vigutils): AI agent identity enforcement — blocklist, prepare-commit-msg, author check, PR body scan, 681 files modified + +### Commit 20: [305dd75](https://github.com/vig-os/devcontainer/commit/305dd75a9133df7fff7b420fedd307758b57f711) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 06:10 PM +fix(deps): use pyproject.toml as SSoT for devcontainer tools (#159), 46 files modified (CHANGELOG.md, Containerfile, assets/workspace/.devcontainer/justfile.gh, assets/workspace/pyproject.toml, justfile.gh, pyproject.toml, scripts/prepare-build.sh, uv.lock) + +### Commit 21: [8358523](https://github.com/vig-os/devcontainer/commit/83585239408198ab8d47a35f7725d0c2dce16449) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 06:26 PM +Merge branch 'dev' into bugfix/159-rich-missing-dependency, 373 files modified + +### Commit 22: [04ffa33](https://github.com/vig-os/devcontainer/commit/04ffa33d9b56056e938984339a2b961849acf481) by [gerchowl](https://github.com/gerchowl) on February 24, 2026 at 06:39 PM +fix(container): copy vig-utils before uv export to resolve workspace member, 8 files modified (CHANGELOG.md, Containerfile) + +### Commit 23: [37efa33](https://github.com/vig-os/devcontainer/commit/37efa33c6069a54fca3033b64abc1742ef7bee07) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on February 25, 2026 at 04:26 AM +chore: sync issues and PRs, 2616 files modified + +### Commit 24: [5873401](https://github.com/vig-os/devcontainer/commit/5873401958a418ce76f17f8a410fedfc483163f6) by [c-vigo](https://github.com/c-vigo) on February 24, 2026 at 05:13 PM +test: add red test to verify deployed package is installed during inialization, 30 files modified (tests/conftest.py, tests/test_integration.py) + +### Commit 25: [9266aed](https://github.com/vig-os/devcontainer/commit/9266aeda1d83af20657cd7cefb0b6ce61551fae2) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 07:58 AM +feat: run just sync during workspace initialization, 18 files modified (Containerfile, assets/init-workspace.sh, assets/workspace/.devcontainer/docker-compose.yml, assets/workspace/.devcontainer/justfile.base, justfile.base) + +### Commit 26: [93a1042](https://github.com/vig-os/devcontainer/commit/93a104216eec5c4dfd1a77fa8db110eb514a42b1) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 08:09 AM +test: align tests with Containerfile ENV changes, 34 files modified (tests/test_image.py, tests/test_integration.py) + +### Commit 27: [7e1575b](https://github.com/vig-os/devcontainer/commit/7e1575b306684d7d4d47f8536b467506e8b1e52c) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 08:24 AM +fix: run sync recipe in update target, 4 files modified (assets/workspace/.devcontainer/justfile.base, justfile.base) + +### Commit 28: [d52c673](https://github.com/vig-os/devcontainer/commit/d52c6731b8ce9560fc719a5050a76ba079a267ff) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 09:25 AM +chore: add README.md and CHANGELOG.md to preserve files in workspace initialization, 2 files modified (assets/init-workspace.sh) + +### Commit 29: [e4b6050](https://github.com/vig-os/devcontainer/commit/e4b605079f3473f98deeefffc413328d403270b8) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 09:37 AM +chore: update CHANGELOG, 25 files modified (CHANGELOG.md) + +### Commit 30: [da794f9](https://github.com/vig-os/devcontainer/commit/da794f92c8da2ec311ae0f8d5fcd25a1e9351030) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 09:52 AM +chore(setup): add missing host prerequisites to setup requirements, 97 files modified (CHANGELOG.md, CONTRIBUTE.md, README.md, scripts/requirements.yaml) + +### Commit 31: [94d7c06](https://github.com/vig-os/devcontainer/commit/94d7c06a4059f433462f83f539c6b6007335b1af) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:05 AM +chore: declare worktree prerequisites in setup requirements (#198), 97 files modified (CHANGELOG.md, CONTRIBUTE.md, README.md, scripts/requirements.yaml) + +### Commit 32: [85ba3af](https://github.com/vig-os/devcontainer/commit/85ba3af1fc2a5412b73350526193dc4df1e29339) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:16 AM +fix(vigutils): AI agent identity enforcement (#163) (#194), 681 files modified + +### Commit 33: [6d9dca5](https://github.com/vig-os/devcontainer/commit/6d9dca53783365eec753b4a99ec4128488083ac4) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:25 AM +refactor: move alias definitions to source justfiles and add file associations, 64 files modified (.vscode/settings.json, justfile, justfile.gh, justfile.podman, justfile.worktree) + +### Commit 34: [1881dc8](https://github.com/vig-os/devcontainer/commit/1881dc8832b367f91ce5df60585815c45842bbae) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:31 AM +test: add BATS test for idempotent rename guard in init-workspace.sh, 9 files modified (tests/bats/init-workspace.bats) + +### Commit 35: [7493f0e](https://github.com/vig-os/devcontainer/commit/7493f0e1990178d40132bed43b08e9fa027d3091) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:31 AM +fix(workspace): make init-workspace.sh idempotent for template_project rename, 11 files modified (assets/init-workspace.sh, tests/bats/init-workspace.bats) + +### Commit 36: [6ae99d1](https://github.com/vig-os/devcontainer/commit/6ae99d14ea10382d99622280c985fa44b25f7613) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:32 AM +docs(changelog): add entry for install.sh idempotency fix, 2 files modified (CHANGELOG.md) + +### Commit 37: [59dae4c](https://github.com/vig-os/devcontainer/commit/59dae4c1ddf4f8b458f6ab3864112fdceb33e4f3) by [c-vigo](https://github.com/c-vigo) on February 25, 2026 at 10:34 AM +fix(test): suppress SC2016 in init-workspace BATS test, 1 file modified (tests/bats/init-workspace.bats) + +### Commit 38: [91bc824](https://github.com/vig-os/devcontainer/commit/91bc8241cfc74566f2e574ce7f2c8b4b61819759) by [gerchowl](https://github.com/gerchowl) on February 25, 2026 at 05:49 PM +refactor: move alias definitions to source justfiles and add file associations (#195) (#200), 64 files modified (.vscode/settings.json, justfile, justfile.gh, justfile.podman, justfile.worktree) + +### Commit 39: [a20c3d6](https://github.com/vig-os/devcontainer/commit/a20c3d6dc95e2d98019e64d754f19c43ff131d82) by [gerchowl](https://github.com/gerchowl) on February 25, 2026 at 05:55 PM +chore(ci): omit untested CLI scripts from coverage to meet 50% threshold, 46 files modified (CONTRIBUTE.md, README.md, pyproject.toml) + +### Commit 40: [397f176](https://github.com/vig-os/devcontainer/commit/397f176f25fe373ad4bc2fa5111d4bf85f8fc7da) by [gerchowl](https://github.com/gerchowl) on February 25, 2026 at 06:08 PM +fix(ci): resolve hadolint DL3045 and ruff version test, 3 files modified (Containerfile, tests/test_image.py) + +### Commit 41: [9d490dc](https://github.com/vig-os/devcontainer/commit/9d490dc8b6c9ed7c10792071835099a6dcec322a) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on February 26, 2026 at 04:22 AM +chore: sync issues and PRs, 1183 files modified + +### Commit 42: [9ebd789](https://github.com/vig-os/devcontainer/commit/9ebd7898af8b665009dc3e137b1a472b25cc2d9d) by [c-vigo](https://github.com/c-vigo) on February 26, 2026 at 12:15 PM +Merge branch 'dev' into feature/170-bootstrap-smoke-test-repo, 5282 files modified + +### Commit 43: [5f1a21b](https://github.com/vig-os/devcontainer/commit/5f1a21b98ae679e32092422dad17ebd94591caf9) by [c-vigo](https://github.com/c-vigo) on February 26, 2026 at 12:17 PM +chore: sync asset files, 43 files modified (assets/workspace/.devcontainer/justfile.gh, assets/workspace/.devcontainer/justfile.worktree, assets/workspace/.devcontainer/scripts/gh_issues.py) + +### Commit 44: [03519fb](https://github.com/vig-os/devcontainer/commit/03519fb94056909ea74936cfb720942b94c3eb5b) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on February 27, 2026 at 04:19 AM +chore: sync issues and PRs, 789 files modified + +### Commit 45: [16b1763](https://github.com/vig-os/devcontainer/commit/16b1763fe2a171b45c49666192b38ce9912324df) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 1, 2026 at 04:27 AM +chore: sync issues and PRs, 29 files modified (docs/issues/issue-27.md) + +### Commit 46: [850792f](https://github.com/vig-os/devcontainer/commit/850792fe6ab874342ce553743be51c0d560c0702) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:06 AM +fix(workspace): make install.sh idempotent for template_project rename (#197) (#201), 21 files modified (CHANGELOG.md, assets/init-workspace.sh, tests/bats/init-workspace.bats) + +### Commit 47: [369a46d](https://github.com/vig-os/devcontainer/commit/369a46d6942bf015689a053ea7355c30abead8e5) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:35 AM +test(image): add red test for rsync installed, 14 files modified (tests/test_image.py) + +### Commit 48: [2155c28](https://github.com/vig-os/devcontainer/commit/2155c28704fb2acfa98c9db75a656302e15224a8) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:36 AM +test(workspace): add rsync and preserve guard regressions, 15 files modified (tests/bats/init-workspace.bats) + +### Commit 49: [96f1dc4](https://github.com/vig-os/devcontainer/commit/96f1dc4ee3def24e7624839e8dd62441176803c6) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:53 AM +feat: install rsync in the container image, 3 files modified (CHANGELOG.md, Containerfile) + +### Commit 50: [b1c0233](https://github.com/vig-os/devcontainer/commit/b1c02330f2ec8e3e78a3caf6a11e55ad9c13c0f5) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 07:57 AM +fix(image): ensure init_workspace.sh --force preserves excluded files by using rsync only, 19 files modified (CHANGELOG.md, assets/init-workspace.sh) + +### Commit 51: [c2b75df](https://github.com/vig-os/devcontainer/commit/c2b75dfd7cb71d57b24a0b72c2da17f65ee16d34) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:07 AM +feat(image): preserve additional user-authored files on --force upgrade, 7 files modified (CHANGELOG.md, assets/init-workspace.sh) + +### Commit 52: [c956b8a](https://github.com/vig-os/devcontainer/commit/c956b8aa2286f3915471d1a8a78ebf49ad4c9d3a) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:10 AM +docs: normalize CHANGELOG section headers and ordering, 159 files modified (CHANGELOG.md) + +### Commit 53: [9f2ba4b](https://github.com/vig-os/devcontainer/commit/9f2ba4b60aa1b2d6389d68ee2091cad24a7a1ab4) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:28 AM +fix: suppress SC2016 for literal assertion patterns, 3 files modified (tests/bats/init-workspace.bats) + +### Commit 54: [a28f18a](https://github.com/vig-os/devcontainer/commit/a28f18a1ff6d509c7230ab011c3ea1fcb03b9e11) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:37 AM +fix(ci): pin Trivy version in security workflows, 12 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml, .pre-commit-config.yaml, CHANGELOG.md) + +### Commit 55: [30e47a2](https://github.com/vig-os/devcontainer/commit/30e47a24943915c7548be99bf1f9c82c3da38661) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:51 AM +chore: trigger status re-evaluation + +### Commit 56: [70dd926](https://github.com/vig-os/devcontainer/commit/70dd926751d2e6f57da4bc24d6a6762b7f6c71ed) by [gerchowl](https://github.com/gerchowl) on March 3, 2026 at 09:00 AM +feat: bootstrap smoke-test repo with bare-runner CI (#210), 568 files modified + +### Commit 57: [2069c73](https://github.com/vig-os/devcontainer/commit/2069c73608948edd43f5010578a1efb284ee8998) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:02 AM +fix(image): resolve cargo-binstall version without api.github, 12 files modified (CHANGELOG.md, Containerfile) + +### Commit 58: [aafda81](https://github.com/vig-os/devcontainer/commit/aafda815b7509b2bce3b24a41f9462c9db4403dd) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:05 AM +Merge branch 'dev' into bugfix/212-preserve-files-without-rsync, 552 files modified + +### Commit 59: [6d03e00](https://github.com/vig-os/devcontainer/commit/6d03e0061b013d691219d8326195e8d10ce87b4d) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:12 AM +fix: preserve user-authored files on force upgrade (#214), 252 files modified + +### Commit 60: [9504161](https://github.com/vig-os/devcontainer/commit/9504161b79b8bcbdef30415f30c2e7dc2a2040c2) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:30 AM +chore(setup): patch vulnerable transitive Python dependencies, 49 files modified (CHANGELOG.md, pyproject.toml, uv.lock) + +### Commit 61: [d2744e0](https://github.com/vig-os/devcontainer/commit/d2744e0b04b8de729f8c0c06fb425e508ab99481) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:36 AM +build(image): refresh pinned python base image digest, 4 files modified (CHANGELOG.md, Containerfile) + +### Commit 62: [50e3a2d](https://github.com/vig-os/devcontainer/commit/50e3a2d3c3a2ad5d8e809ca8607138cd234ef0ea) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:46 AM +fix(ci): bump safety pin to restore security scan install, 6 files modified (.github/workflows/ci.yml, CHANGELOG.md, assets/workspace/.github/workflows/ci.yml) + +### Commit 63: [267cb25](https://github.com/vig-os/devcontainer/commit/267cb251a4ad9c48413d088ec3cfc7f28ab8aa8e) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 09:53 AM +build: refresh pinned python image digest and patch vulnerable transitive deps (#215), 59 files modified (.github/workflows/ci.yml, CHANGELOG.md, Containerfile, assets/workspace/.github/workflows/ci.yml, pyproject.toml, uv.lock) + +### Commit 64: [ecf92c3](https://github.com/vig-os/devcontainer/commit/ecf92c3eab8be24064ac9a9c9d61bd8b8ec78014) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 01:33 PM +chore: add sync-manifest pre-commit hook, 10 files modified (.pre-commit-config.yaml) + +### Commit 65: [231aa74](https://github.com/vig-os/devcontainer/commit/231aa74139291a1e7fbb96e54a869257633ef868) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:14 PM +test: add RemovePrecommitHooks comment preservation test, 32 files modified (tests/test_transforms.py) + +### Commit 66: [8c0e4da](https://github.com/vig-os/devcontainer/commit/8c0e4da5c1b3b69b3221b3eab6a5d2c14effbf1d) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:14 PM +fix(setup): preserve section comments when removing pre-commit hooks, 17 files modified (scripts/transforms.py) + +### Commit 67: [f3555dc](https://github.com/vig-os/devcontainer/commit/f3555dcc9f2b48e134c3923786b303119dc87a6f) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:24 PM +build: switch to RemovePrecommitHooks and add CI/link helpers, 262 files modified + +### Commit 68: [7ecb74c](https://github.com/vig-os/devcontainer/commit/7ecb74cf115abade3ed8c0644575e4b01f88616a) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:32 PM +ci: add container-based CI workflow and quirks documentation, 206 files modified (assets/workspace/.github/workflows/ci-container.yml, assets/workspace/docs/container-ci-quirks.md) + +### Commit 69: [6256225](https://github.com/vig-os/devcontainer/commit/6256225460668bb4056de107d87c2db37f315275) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 02:55 PM +chore: merge dev into feature/171, 2680 files modified + +### Commit 70: [fbfc202](https://github.com/vig-os/devcontainer/commit/fbfc202fed709e7a0f9e4c3cc1c20cd112dc8c81) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:10 PM +docs: update CHANGELOG, 3 files modified (CHANGELOG.md) + +### Commit 71: [b4a33fc](https://github.com/vig-os/devcontainer/commit/b4a33fc4d99179c0a8e82a79e736bd2d6693f56b) by [c-vigo](https://github.com/c-vigo) on March 3, 2026 at 08:17 PM +ci: validate workspace container CI via smoke test (#216), 269 files modified + +### Commit 72: [0bea943](https://github.com/vig-os/devcontainer/commit/0bea943e514f44e9011b13bb22677a693b37d5bb) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 10:36 AM +test(vigutils): add red tests for migrated scripts, 671 files modified (packages/vig-utils/tests/test_gh_issues.py, packages/vig-utils/tests/test_shell_entrypoints.py) + +### Commit 73: [a352a68](https://github.com/vig-os/devcontainer/commit/a352a6836e1c6ff19e625b24632c94f1b36dabaa) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 10:37 AM +feat(vigutils): migrate shared scripts into package entrypoints, 1299 files modified + +### Commit 74: [bd9958c](https://github.com/vig-os/devcontainer/commit/bd9958c7f2f7152e40867a0d38adf586aae304e9) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 10:40 AM +refactor(setup): switch callers to vig-utils entrypoints, 407 files modified + +### Commit 75: [d131449](https://github.com/vig-os/devcontainer/commit/d131449a2176a9738d8581bbf9e21cdf9634ef45) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 10:45 AM +chore(setup): remove legacy script copies and superseded tests, 2695 files modified + +### Commit 76: [f3eb543](https://github.com/vig-os/devcontainer/commit/f3eb543915f09962aeb90cfd156dfb18b555d060) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 12:02 PM +refactor(vigutils): consolidate agent fingerprint helpers into shared utils, 1696 files modified + +### Commit 77: [a8d8957](https://github.com/vig-os/devcontainer/commit/a8d89573e874c0fcc4328e86de532366f977bebf) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 12:03 PM +chore(setup): remove legacy scripts/utils.py shim, 33 files modified (.github/CODEOWNERS, scripts/prepare-build.sh, scripts/utils.py, tests/bats/prepare-build.bats) + +### Commit 78: [e5a1316](https://github.com/vig-os/devcontainer/commit/e5a13167aa9899298d2615015c1b35b96566b8bd) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 12:14 PM +docs: update CHANGELOG, 9 files modified (CHANGELOG.md) + +### Commit 79: [0ca279c](https://github.com/vig-os/devcontainer/commit/0ca279c24505144f0c636df2b5c5c7bcf24fd773) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 01:57 PM +feat(ci): add candidate release mode with inferred RC tags, 231 files modified (.github/workflows/release.yml, CHANGELOG.md, CONTRIBUTE.md, README.md, docs/RELEASE_CYCLE.md, justfile) + +### Commit 80: [17f6e48](https://github.com/vig-os/devcontainer/commit/17f6e4857cedf9466de179c92f27e5f0938d5bc8) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 02:28 PM +test: add case for inline ## markers truncating changelog extraction, 29 files modified (packages/vig-utils/tests/test_prepare_changelog.py) + +### Commit 81: [5bbc6b3](https://github.com/vig-os/devcontainer/commit/5bbc6b330c0af1a1af038fcdac280155d6ebe8a8) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 02:28 PM +fix: anchor changelog regex to line starts to prevent truncation, 8 files modified (packages/vig-utils/src/vig_utils/prepare_changelog.py) + +### Commit 82: [f894d60](https://github.com/vig-os/devcontainer/commit/f894d60e9a6ee2e5c1e743b019bdcc9993b18150) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 02:40 PM +feat: redesign release CHANGELOG flow and replace post-release with PR-based sync, 731 files modified + +### Commit 83: [569d1af](https://github.com/vig-os/devcontainer/commit/569d1af97f5a37722396b838546a67e06bc02206) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 06:42 PM +ci: migrate workflow tokens to RELEASE_APP and COMMIT_APP, 98 files modified + +### Commit 84: [0bbe81e](https://github.com/vig-os/devcontainer/commit/0bbe81e85c9bb858788cca1d49d39414d1aa48c1) by [c-vigo](https://github.com/c-vigo) on March 4, 2026 at 08:52 PM +feat(ci): add candidate release mode and PR-based dev sync (#220), 1049 files modified + +### Commit 85: [f443f9b](https://github.com/vig-os/devcontainer/commit/f443f9b00c52c06c054f91091ddaf50e90132eea) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 5, 2026 at 04:18 AM +chore: sync issues and PRs, 772 files modified + +### Commit 86: [af4a25a](https://github.com/vig-os/devcontainer/commit/af4a25ac63bc81df9fa9cee6db7efbb5c1a123cd) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:05 PM +fix(setup): enhance parse_requirements to support multiline install commands, 45 files modified (scripts/init.sh) + +### Commit 87: [3632ef8](https://github.com/vig-os/devcontainer/commit/3632ef8de7ce251b16a475775a277239710bcb14) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:09 PM +fix(setup): update `just` installation instructions to use `sudo`, 4 files modified (CONTRIBUTE.md, scripts/requirements.yaml) + +### Commit 88: [10695f5](https://github.com/vig-os/devcontainer/commit/10695f5ba2dfc3a03846353ef9b3d2dbe03abedf) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:10 PM +feat(setup): add hadolint for Containerfile/Dockerfile linting, 53 files modified (CONTRIBUTE.md, scripts/requirements.yaml) + +### Commit 89: [067c0b3](https://github.com/vig-os/devcontainer/commit/067c0b37cd6ac377ed66c69e774b7d5585a68712) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:12 PM +feat(setup): update hadolint configuration to use local binary, 51 files modified (.github/actions/setup-env/action.yml, .github/actions/test-project/action.yml, .pre-commit-config.yaml, scripts/manifest.toml) + +### Commit 90: [2e3f96a](https://github.com/vig-os/devcontainer/commit/2e3f96a064cf27fd089e2ea2ab54ba6485009184) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:15 PM +test(image): add hadolint tool verification tests, 16 files modified (tests/test_image.py) + +### Commit 91: [acfc46c](https://github.com/vig-os/devcontainer/commit/acfc46c3e50e94dd3d0fac6659a8340ad3621f65) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:21 PM +feat(image): install hadolint with checksum verification, 19 files modified (Containerfile) + +### Commit 92: [a4a7de0](https://github.com/vig-os/devcontainer/commit/a4a7de0d94e7fd340d753f85a0921f96225e1839) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:23 PM +docs: update CHANGELOG, 7 files modified (CHANGELOG.md) + +### Commit 93: [79fe0fc](https://github.com/vig-os/devcontainer/commit/79fe0fc19a0e5725cd3524ba1b44aced18986ae1) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:39 PM +test: fix init installer-key checks with parser mappings, 10 files modified (tests/bats/init.bats) + +### Commit 94: [3e44277](https://github.com/vig-os/devcontainer/commit/3e442775975a45dd001c30acacf8f9e7d9eebf4c) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:45 PM +chore: update .trivyignore to include CVE-2025-15558 exception, 12 files modified (.trivyignore) + +### Commit 95: [e36aa05](https://github.com/vig-os/devcontainer/commit/e36aa05e016c7cca3195f1780b111792afb8e592) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:46 PM +docs: update CHANGELOG, 3 files modified (CHANGELOG.md) + +### Commit 96: [d2cd3fe](https://github.com/vig-os/devcontainer/commit/d2cd3febc1af19a29c3ef4f44e865e00cc7e9997) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 02:53 PM +feat: add hadolint support across image and CI (#222), 220 files modified + +### Commit 97: [6f17eb9](https://github.com/vig-os/devcontainer/commit/6f17eb9537ed63cd120383d85071f167ae3a7f5e) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 03:12 PM +feat: add taplo TOML linting pre-commit hooks, 42 files modified (.pre-commit-config.yaml, .taplo.toml, CHANGELOG.md, assets/workspace/.pre-commit-config.yaml, assets/workspace/.taplo.toml, scripts/manifest.toml) + +### Commit 98: [fd602c4](https://github.com/vig-os/devcontainer/commit/fd602c4bbcd938c30e6d949fdff59dbaba237e9c) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 03:13 PM +chore: run taplo TOML linting and formatting, 156 files modified + +### Commit 99: [a18bdf9](https://github.com/vig-os/devcontainer/commit/a18bdf9f23af389aa2f65e37382fb37c6301d914) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 03:21 PM +chore: exclude .taplo.toml from downstream sync, 14 files modified (CHANGELOG.md, assets/workspace/.taplo.toml, scripts/manifest.toml) + +### Commit 100: [f77cd71](https://github.com/vig-os/devcontainer/commit/f77cd717b00490a41d8810983f59cdad45537c0e) by [c-vigo](https://github.com/c-vigo) on March 5, 2026 at 03:30 PM +feat: add taplo TOML linting pre-commit hooks (#223), 186 files modified + +### Commit 101: [0982aaa](https://github.com/vig-os/devcontainer/commit/0982aaa7dec9b14e44e445cad9849e3fed4339ab) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 6, 2026 at 04:14 AM +chore: sync issues and PRs, 265 files modified (docs/issues/issue-122.md, docs/pull-requests/pr-222.md, docs/pull-requests/pr-223.md) + +### Commit 102: [325ed5a](https://github.com/vig-os/devcontainer/commit/325ed5a3533691695e6eaa9a99bb0e40279878b9) by [c-vigo](https://github.com/c-vigo) on March 6, 2026 at 11:28 AM +chore: merge 'dev' into bugfix/159-rich-missing-dependency, 8606 files modified + +### Commit 103: [c621b31](https://github.com/vig-os/devcontainer/commit/c621b31ffe9b854bbb614056a6561b83ac87aab9) by [c-vigo](https://github.com/c-vigo) on March 6, 2026 at 11:42 AM +chore: update package versions in uv.lock with 'just update', 208 files modified (uv.lock) + +### Commit 104: [79f6315](https://github.com/vig-os/devcontainer/commit/79f63153e9e475592935c7d4336b230f5129ea14) by [c-vigo](https://github.com/c-vigo) on March 6, 2026 at 11:49 AM +fix(deps): use pyproject.toml as SSoT for devcontainer tools (#180), 274 files modified (CHANGELOG.md, Containerfile, assets/workspace/.devcontainer/justfile.gh, assets/workspace/pyproject.toml, justfile.gh, pyproject.toml, scripts/prepare-build.sh, uv.lock) + +### Commit 105: [693eb82](https://github.com/vig-os/devcontainer/commit/693eb82f27ac6d316f162345adb20e4037f10e1f) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 07:05 PM +chore: add Claude Code integration with project commands, 195 files modified + +### Commit 106: [7dcc1e0](https://github.com/vig-os/devcontainer/commit/7dcc1e09f9a52e73162ec0f3d62500aa51bb3562) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 07:13 PM +chore: add Nix flake devShell for reproducible host tooling, 114 files modified (.envrc, .gitignore, .pre-commit-config.yaml, assets/workspace/.pre-commit-config.yaml, flake.lock, flake.nix) + +### Commit 107: [dc9f996](https://github.com/vig-os/devcontainer/commit/dc9f99611253bf5bc7dce9ad002c990a100c9de9) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 07:50 PM +chore: add .direnv/ to gitignore, 3 files modified (.gitignore) + +### Commit 108: [68fcdc6](https://github.com/vig-os/devcontainer/commit/68fcdc69c111b869740e1c3d178ce74224c2ee2f) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 7, 2026 at 04:05 AM +chore: sync issues and PRs, 1181 files modified + +### Commit 109: [bbef96a](https://github.com/vig-os/devcontainer/commit/bbef96a55a19732df688524c276e8cd5d400dfab) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 07:14 PM +fix(setup): switch taplo pre-commit from source compilation to system binary, 89 files modified (.pre-commit-config.yaml, CONTRIBUTE.md, Containerfile, assets/workspace/.pre-commit-config.yaml, scripts/requirements.yaml) + +### Commit 110: [b820ba5](https://github.com/vig-os/devcontainer/commit/b820ba59b32531e0691d88570565bb83e64df1e0) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 07:55 PM +fix(setup): correct taplo release URL and use latest version, 32 files modified (CONTRIBUTE.md, Containerfile, scripts/requirements.yaml) + +### Commit 111: [33ae417](https://github.com/vig-os/devcontainer/commit/33ae417cc9d475a1dd1342331973283e620ec40a) by [gerchowl](https://github.com/gerchowl) on March 6, 2026 at 08:01 PM +fix(ci): add taplo install to CI setup-env action, 33 files modified (.github/actions/setup-env/action.yml, .github/actions/test-project/action.yml) + +### Commit 112: [7430db1](https://github.com/vig-os/devcontainer/commit/7430db171ec963fb5f18dc6785b9627177ce16ef) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 06:41 AM +test: add taplo installation and version verification, 15 files modified (tests/test_image.py) + +### Commit 113: [2dd7720](https://github.com/vig-os/devcontainer/commit/2dd7720c9bd6cab663d7b15543751241f28202e1) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 06:43 AM +docs: update CHANGELOG, 4 files modified (CHANGELOG.md) + +### Commit 114: [25031ad](https://github.com/vig-os/devcontainer/commit/25031add2e12bac3f10eb5a5b2ebec654f6756e9) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 06:53 AM +fix(setup): switch taplo pre-commit from source compilation to system binary (#229), 141 files modified + +### Commit 115: [1814b28](https://github.com/vig-os/devcontainer/commit/1814b283697d459479e3cc99eff2e71923794523) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 07:06 AM +chore: merge branch 'dev' into feature/217-reorganize-scripts-vig-utils, 4063 files modified + +### Commit 116: [9b7f20f](https://github.com/vig-os/devcontainer/commit/9b7f20facae656489da35bdc588f3a0a021bfd6b) by [gerchowl](https://github.com/gerchowl) on March 7, 2026 at 12:55 PM +fix(ci): allow dotfile paths and doc filenames in agent blocklist, 27 files modified (.claude/settings.local.json, .github/agent-blocklist.toml, assets/workspace/.github/agent-blocklist.toml, packages/vig-utils/src/vig_utils/agent_blocklist.py) + +### Commit 117: [9ec59b5](https://github.com/vig-os/devcontainer/commit/9ec59b52cb4d5a607797bfbbf0a39b4ac162855e) by [gerchowl](https://github.com/gerchowl) on March 7, 2026 at 01:16 PM +chore: add CC integration with project commands (#225), 222 files modified + +### Commit 118: [91f1441](https://github.com/vig-os/devcontainer/commit/91f1441c4b8da31a4dafcf5df038b9f67c22bae7) by [gerchowl](https://github.com/gerchowl) on March 7, 2026 at 01:16 PM +chore: add Nix flake devShell for reproducible host tooling (#228), 117 files modified (.envrc, .gitignore, .pre-commit-config.yaml, assets/workspace/.pre-commit-config.yaml, flake.lock, flake.nix) + +### Commit 119: [e9c9da4](https://github.com/vig-os/devcontainer/commit/e9c9da4f830b406cafbc3403223b71c3e14da4ca) by [gerchowl](https://github.com/gerchowl) on March 7, 2026 at 03:56 PM +test: improve coverage for agent blocklist and shell entrypoints, 20 files modified + +### Commit 120: [5936a9e](https://github.com/vig-os/devcontainer/commit/5936a9e07a56b3016066a626e70d97762574ce45) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 08:58 PM +chore: merge branch 'dev' into feature/217-reorganize-scripts-vig-utils, 335 files modified + +### Commit 121: [4d4b923](https://github.com/vig-os/devcontainer/commit/4d4b923b60fc8e8e892f53c3441826fe1ba0e906) by [c-vigo](https://github.com/c-vigo) on March 7, 2026 at 09:05 PM +refactor(vigutils): migrate shared scripts into vig-utils package entrypoints (#218), 3513 files modified + +### Commit 122: [c9b1186](https://github.com/vig-os/devcontainer/commit/c9b11862f4ac3f95fe97715ebb0452b19d5d5acd) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 8, 2026 at 04:17 AM +chore: sync issues and PRs, 282 files modified + +### Commit 123: [bd71692](https://github.com/vig-os/devcontainer/commit/bd716929c6ad3023db1ab0586b3d324d4fcec56a) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 9, 2026 at 04:23 AM +chore: sync issues and PRs, 209 files modified (docs/issues/issue-236.md, docs/issues/issue-70.md, docs/pull-requests/pr-166.md, docs/pull-requests/pr-237.md) + +### Commit 124: [f897a3d](https://github.com/vig-os/devcontainer/commit/f897a3d36e44709ec52a6fe86da70716d6f91de1) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:00 AM +test: add failing tests for IN_CONTAINER guard in workspace githooks, 86 files modified (tests/bats/githooks.bats) + +### Commit 125: [ae383c5](https://github.com/vig-os/devcontainer/commit/ae383c5706a00091c44b22b9a2fc2ccf808cef8f) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:01 AM +fix: use != "true" for IN_CONTAINER guard in workspace githooks, 6 files modified (assets/workspace/.githooks/commit-msg, assets/workspace/.githooks/pre-commit, assets/workspace/.githooks/prepare-commit-msg) + +### Commit 126: [bcda78d](https://github.com/vig-os/devcontainer/commit/bcda78d6bc14e1eb63d03eccd97ddc0e69bbe81d) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:01 AM +docs: add changelog entry for IN_CONTAINER guard fix, 3 files modified (CHANGELOG.md) + +### Commit 127: [97f46ff](https://github.com/vig-os/devcontainer/commit/97f46ff69e6b68dc52e4b555ba66ab2e994a3935) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:12 AM +fix: correct IN_CONTAINER guard in workspace githooks (#239), 95 files modified (CHANGELOG.md, assets/workspace/.githooks/commit-msg, assets/workspace/.githooks/pre-commit, assets/workspace/.githooks/prepare-commit-msg, tests/bats/githooks.bats) + +### Commit 128: [88e66c4](https://github.com/vig-os/devcontainer/commit/88e66c4f9fc10f1545da20885c60f2c15cb99deb) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:37 AM +chore: update base image digest and trivy version, 20 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml, Containerfile) + +### Commit 129: [070f00e](https://github.com/vig-os/devcontainer/commit/070f00e819e248c9a88bc91150d9165937041597) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:44 AM +chore: update GitHub Actions to latest digest-pinned versions, 46 files modified + +### Commit 130: [28f0037](https://github.com/vig-os/devcontainer/commit/28f00377dd22f5343f66573bdbf0ef075d165d1a) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:45 AM +docs: add CHANGELOG entry for dependency updates, 7 files modified (CHANGELOG.md) + +### Commit 131: [f758b0c](https://github.com/vig-os/devcontainer/commit/f758b0c83e896d5bb6f598fcc814fd77f5bf562f) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 08:55 AM +chore: update base Python image and GitHub Actions dependencies (#240) (#241), 73 files modified + +### Commit 132: [2c5da5d](https://github.com/vig-os/devcontainer/commit/2c5da5dc1debeff588e539991d50dc29f4f808a1) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:28 AM +fix: correct justfile file association glob in vscode settings, 2 files modified (.vscode/settings.json) + +### Commit 133: [20b1a82](https://github.com/vig-os/devcontainer/commit/20b1a8235a8f4608ca90466067284f368fb34f03) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:30 AM +test: add failing tests for justfile devc project split, 39 files modified (tests/test_integration.py) + +### Commit 134: [a078b77](https://github.com/vig-os/devcontainer/commit/a078b7782a4e82d3507042c8722f959db1a836bf) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:32 AM +refactor: rename justfile base to devc and split project recipes, 694 files modified + +### Commit 135: [1455f37](https://github.com/vig-os/devcontainer/commit/1455f37ea238b31839b6805397e4a400bfc1598e) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:33 AM +docs: update changelog for justfile restructure, 4 files modified (CHANGELOG.md) + +### Commit 136: [ee722b6](https://github.com/vig-os/devcontainer/commit/ee722b605a3ab196850eace13f98cf88ed4ca2a6) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:34 AM +chore: merge 'dev' into feature/..., 73 files modified + +### Commit 137: [6cabd43](https://github.com/vig-os/devcontainer/commit/6cabd4370aa64205576ed3627f509f73dcee35be) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:42 AM +refactor(image): track justfile.local template in repository, 7 files modified (CHANGELOG.md, assets/workspace/justfile.local) + +### Commit 138: [7d7a843](https://github.com/vig-os/devcontainer/commit/7d7a843e19adcd364c768ae7dda7bbd2da71e954) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 09:49 AM +refactor: split workspace justfiles into devc and project layers (#244), 744 files modified + +### Commit 139: [c82dd44](https://github.com/vig-os/devcontainer/commit/c82dd4480ef52e0ca43f33f4494813007cf4bd9a) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 10:00 AM +fix(sync): exclude taplo hooks from workspace pre-commit, 16 files modified (assets/workspace/.pre-commit-config.yaml, scripts/manifest.toml) + +### Commit 140: [3b816af](https://github.com/vig-os/devcontainer/commit/3b816afe11145a02c946e7fb0a257b3754f493e9) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 10:29 AM +ci: bump pinned GitHub Actions revisions, 52 files modified + +### Commit 141: [c111834](https://github.com/vig-os/devcontainer/commit/c111834c7c18cd2819e91faa22b0c047d53fffa7) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 10:35 AM +ci(image): enable workflow_call and configurable container image tag, 12 files modified (assets/workspace/.github/workflows/ci-container.yml, assets/workspace/.github/workflows/ci.yml) + +### Commit 142: [22b58ec](https://github.com/vig-os/devcontainer/commit/22b58ec1487f55259cc54e51ad9112f2f2e4705c) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 12:37 PM +fix(workspace): harden helper CLI checks and sidecar runtime fallback, 81 files modified + +### Commit 143: [5e3de93](https://github.com/vig-os/devcontainer/commit/5e3de93151770023bc0eae0c9ded81af804d035f) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 12:49 PM +feat(image): update sync command to include all groups for dependency management, 2 files modified (assets/workspace/justfile.project) + +### Commit 144: [acba58c](https://github.com/vig-os/devcontainer/commit/acba58c4a3aaf3af600259141c4b003aa4b9aefa) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 12:54 PM +chore(manifest): sync VS Code settings to workspace template, 29 files modified (CHANGELOG.md, assets/workspace/.vscode/settings.json, scripts/manifest.toml) + +### Commit 145: [69149c0](https://github.com/vig-os/devcontainer/commit/69149c0648a46839ef6fa81b08465b01a6a0efe7) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 02:14 PM +fix(image): sync venv path to container location in settings.json, 5 files modified (assets/workspace/.vscode/settings.json, scripts/manifest.toml) + +### Commit 146: [74bec10](https://github.com/vig-os/devcontainer/commit/74bec10d90f0280a7195bff7657719ef9fe55e00) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 02:24 PM +fix(ci): add pull-requests: write to ci.yml top-level permissions, 1 file modified (assets/workspace/.github/workflows/ci.yml) + +### Commit 147: [b90ab7c](https://github.com/vig-os/devcontainer/commit/b90ab7cb504e515bf3faf6dd06ebc0797916ab82) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 03:33 PM +fix(ci): remove CodeQL PR path filter to satisfy merge protection, 6 files modified (.github/workflows/codeql.yml, CHANGELOG.md, assets/workspace/.github/workflows/codeql.yml) + +### Commit 148: [2cbcb93](https://github.com/vig-os/devcontainer/commit/2cbcb93d0b5ef5d8ea7e25f7fe31dae9647788bc) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 03:48 PM +fix: remove CodeQL PR path filter to satisfy merge protection (#248), 6 files modified (.github/workflows/codeql.yml, CHANGELOG.md, assets/workspace/.github/workflows/codeql.yml) + +### Commit 149: [6983994](https://github.com/vig-os/devcontainer/commit/69839947631a917ba4eddaca010866afa9dc7b23) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 05:53 PM +feat(ci): wire RC smoke-test dispatch and gate docs, 87 files modified (.github/workflows/release.yml, CHANGELOG.md, docs/RELEASE_CYCLE.md) + +### Commit 150: [0a338a8](https://github.com/vig-os/devcontainer/commit/0a338a835945942730bdb75e88a14c53448d4da2) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 06:39 PM +test(image): add gh-issues to callable CLI checks and remove broken integration smoke test, 83 files modified (tests/test_image.py, tests/test_integration.py) + +### Commit 151: [72d93a8](https://github.com/vig-os/devcontainer/commit/72d93a8452a4cdf1b4308be7baeab0cab968b482) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 06:39 PM +fix(workspace): remove broken gh-issues --help guard from justfile recipe, 10 files modified (assets/workspace/.devcontainer/justfile.gh, justfile.gh) + +### Commit 152: [9acdc46](https://github.com/vig-os/devcontainer/commit/9acdc464e238932857dc30008fd828a61129f993) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 06:40 PM +docs: add changelog entry for gh-issues guard fix, 6 files modified (CHANGELOG.md) + +### Commit 153: [6e3c193](https://github.com/vig-os/devcontainer/commit/6e3c193d8b1cd0a2a4efa3a9e92f3c68b61ec475) by [c-vigo](https://github.com/c-vigo) on March 9, 2026 at 07:54 PM +docs(ci): explain why pull-requests write is at workflow level, 2 files modified (assets/workspace/.github/workflows/ci.yml) + +### Commit 154: [959a151](https://github.com/vig-os/devcontainer/commit/959a1515931767ae1d683e1f2d9325d5bb422e03) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 10, 2026 at 04:15 AM +chore: sync issues and PRs, 1655 files modified + +### Commit 155: [ed2287a](https://github.com/vig-os/devcontainer/commit/ed2287a94e01af13e5bad718e27b515bbf5cc1c9) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 10:42 AM +feat(workspace): bundle smoke-test assets in image, 152 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml, assets/smoke-test/README.md) + +### Commit 156: [80a37b8](https://github.com/vig-os/devcontainer/commit/80a37b87f64e70e666d116affab6aeadb5e3370f) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 10:42 AM +test: add tests for --smoke-test flag, 679 files modified (tests/bats/init-workspace.bats, tests/bats/install.bats, tests/conftest.py, tests/test_install_script.py, tests/test_integration.py) + +### Commit 157: [c1c2f61](https://github.com/vig-os/devcontainer/commit/c1c2f6125c6ba54f09706c9f41dbf4fda7977a7a) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 10:42 AM +feat(workspace): add --smoke-test flag to init-workspace.sh and install.sh, 39 files modified (assets/init-workspace.sh, install.sh) + +### Commit 158: [551d417](https://github.com/vig-os/devcontainer/commit/551d417502daf5076079a94ae0c22c630c1e47d9) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 10:43 AM +docs: update changelog for --smoke-test flag, 5 files modified (CHANGELOG.md) + +### Commit 159: [80e8a38](https://github.com/vig-os/devcontainer/commit/80e8a384f8e10a0bdb71de70c038aaa21a066e53) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 12:51 PM +test: add failing test for dry-run output quoting, 7 files modified (tests/bats/install.bats) + +### Commit 160: [9167231](https://github.com/vig-os/devcontainer/commit/9167231e886daa2fe042fed9472e309da6f6576c) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 12:53 PM +fix(install): quote project path and image in dry-run output, 2 files modified (install.sh) + +### Commit 161: [4bb9dad](https://github.com/vig-os/devcontainer/commit/4bb9dadf3a7994922f78403bb10d2928aa27ee57) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 12:59 PM +test: add failing test for smoke mode rsync --delete, 5 files modified (tests/bats/init-workspace.bats) + +### Commit 162: [43471d3](https://github.com/vig-os/devcontainer/commit/43471d3ad976109474dae6d3871ecce2f5cce18b) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 01:00 PM +fix(workspace): use rsync --delete in smoke mode for clean deploy, 23 files modified (assets/init-workspace.sh) + +### Commit 163: [6db022c](https://github.com/vig-os/devcontainer/commit/6db022c29fa108019d1c62f5fae74cc2dba61655) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 01:08 PM +feat: add smoke-test asset deployment workflow (#251), 910 files modified + +### Commit 164: [6d18e22](https://github.com/vig-os/devcontainer/commit/6d18e2205d6833ad0d1375b5242241eca17ed3b9) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 02:39 PM +test: fix bats warnings in githooks and tmux crash in worktree tests, 13 files modified (tests/bats/githooks.bats, tests/bats/worktree.bats) + +### Commit 165: [983997b](https://github.com/vig-os/devcontainer/commit/983997b45de5edabc00459a012483b15408ba22d) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 03:19 PM +test: fix bats warnings in githooks and tmux crash in worktree tests (#253), 13 files modified (tests/bats/githooks.bats, tests/bats/worktree.bats) + +### Commit 166: [f979a7a](https://github.com/vig-os/devcontainer/commit/f979a7a2c53effe2a3ed8740265f440b8aa01b7a) by [c-vigo](https://github.com/c-vigo) on March 10, 2026 at 03:48 PM +chore: merge branch 'dev' into feature/173-wire-cross-repo-dispatch-release-gate, 2578 files modified + +### Commit 167: [d3de4c0](https://github.com/vig-os/devcontainer/commit/d3de4c0cdb6148c7ecd8f6466bc226e792b6685f) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 11, 2026 at 04:15 AM +chore: sync issues and PRs, 539 files modified (docs/issues/issue-250.md, docs/issues/issue-252.md, docs/pull-requests/pr-228.md, docs/pull-requests/pr-251.md, docs/pull-requests/pr-253.md) + +### Commit 168: [c969810](https://github.com/vig-os/devcontainer/commit/c969810f89234c98f88ca5b3ab2a24676291780e) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:14 AM +test(setup): add regression test for just default recipe listing, 16 files modified (tests/bats/justfile.bats) + +### Commit 169: [21000fa](https://github.com/vig-os/devcontainer/commit/21000facc0d14b27fc01eb12eaafa9e210f999a7) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:16 AM +fix(setup): move default recipe before lint to restore recipe listing, 38 files modified (justfile, tests/bats/just.bats) + +### Commit 170: [d7c8974](https://github.com/vig-os/devcontainer/commit/d7c8974ebc1ed58b9ae047ee7138a5dd68e58127) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:25 AM +docs: add changelog entry for just default recipe fix, 3 files modified (CHANGELOG.md) + +### Commit 171: [d7fc36c](https://github.com/vig-os/devcontainer/commit/d7fc36ca113c851ac8b5e67ad5eec1cddc17f479) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:39 AM +chore: bump expected gh version 2.87 → 2.88, 4 files modified (CHANGELOG.md, tests/test_image.py) + +### Commit 172: [23ca217](https://github.com/vig-os/devcontainer/commit/23ca21715c341177e608b8be60912dcd9edb1cc4) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:43 AM +fix(setup): restore just default recipe listing (#256), 61 files modified (CHANGELOG.md, justfile, tests/bats/just.bats, tests/test_image.py) + +### Commit 173: [f6e0ac4](https://github.com/vig-os/devcontainer/commit/f6e0ac45635cfca3feb9668d864cb918b258edef) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:23 AM +test(setup): add vig-os version flow test coverage, 95 files modified (tests/test_image.py, tests/test_integration.py) + +### Commit 174: [25a048a](https://github.com/vig-os/devcontainer/commit/25a048a07b994e0335d964b01ab12e3665e4213c) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:25 AM +feat(setup): add .vig-os as devcontainer version source of truth, 45 files modified (assets/workspace/.devcontainer/docker-compose.yml, assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh, assets/workspace/.vig-os) + +### Commit 175: [ccc5e2d](https://github.com/vig-os/devcontainer/commit/ccc5e2d2d47b81b56a51946b37650fec8622891f) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:27 AM +docs(setup): record vig-os version source in changelog, 4 files modified (CHANGELOG.md) + +### Commit 176: [a6c3bf0](https://github.com/vig-os/devcontainer/commit/a6c3bf0189da37a8de004386804c06a64ef7151f) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 12:16 PM +feat(setup): add .vig-os as devcontainer version source of truth (#259), 144 files modified + +### Commit 177: [9fdf45d](https://github.com/vig-os/devcontainer/commit/9fdf45dd59916b4606fc18e1f81a9c3a8906a612) by [gerchowl](https://github.com/gerchowl) on March 11, 2026 at 01:16 PM +feat(ci): wire cross-repo smoke dispatch and release gate (#249), 575 files modified + +### Commit 178: [d86e6d1](https://github.com/vig-os/devcontainer/commit/d86e6d1eaf774858ed02441b692d068e0735c870) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 06:18 PM +feat(ci): automate deploy-and-test via PR in smoke-test workflow, 205 files modified (CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml, assets/smoke-test/README.md) + +### Commit 179: [e8f2712](https://github.com/vig-os/devcontainer/commit/e8f271200fe7c71b49d28df4dc1d75ecc7aa0645) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 06:19 PM +chore(setup): merge origin/dev into issue 258 branch, 539 files modified (docs/issues/issue-250.md, docs/issues/issue-252.md, docs/pull-requests/pr-228.md, docs/pull-requests/pr-251.md, docs/pull-requests/pr-253.md) + +### Commit 180: [c33ba60](https://github.com/vig-os/devcontainer/commit/c33ba608c2538d4b0da9edf8fb0fecc23bd9bc60) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 07:57 PM +ci: harden dispatch tag validation, 7 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 181: [99d70f0](https://github.com/vig-os/devcontainer/commit/99d70f086b6dc63da82e6241f14a5ea151ee4788) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 07:57 PM +chore: add CVE-2026-31812 to trivyignore, 10 files modified (.trivyignore) + +### Commit 182: [b8fdc4f](https://github.com/vig-os/devcontainer/commit/b8fdc4fa3d9fcb64449391c4e5d34c5dfc2fbee9) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:08 PM +docs(ci): update unreleased notes for PR 260 follow-ups, 2 files modified (CHANGELOG.md) + +### Commit 183: [3832ba1](https://github.com/vig-os/devcontainer/commit/3832ba1891a018cf2ea071b731d8873fc09fcaa3) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 08:16 PM +feat(ci): automate deploy-and-test via PR in smoke-test workflow (#260), 222 files modified (.trivyignore, CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml, assets/smoke-test/README.md) + +### Commit 184: [b533324](https://github.com/vig-os/devcontainer/commit/b533324fddbb1a5d99d1c8d7ca9f3694b229d04f) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:22 PM +fix(ci): align smoke dispatch payload key and tag validation, 6 files modified (.github/workflows/release.yml, assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 185: [61eaef5](https://github.com/vig-os/devcontainer/commit/61eaef58dfdbfad67218f1d37b1156172c889fc7) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:22 PM +docs(ci): align release dispatch payload and RC tag notation, 4 files modified (CHANGELOG.md, docs/RELEASE_CYCLE.md) + +### Commit 186: [0f8aed3](https://github.com/vig-os/devcontainer/commit/0f8aed38aa7511b1831dfa6942440dde0f761626) by [c-vigo](https://github.com/c-vigo) on March 11, 2026 at 09:40 PM +docs: consolidate duplicate changelog headings in unreleased section, 60 files modified (CHANGELOG.md) + +### Commit 187: [4749ef2](https://github.com/vig-os/devcontainer/commit/4749ef2cf1ad307f91c8fdf607daec9e88b4f3fc) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:52 AM +fix(ci): align smoke dispatch payload key and tag validation (#261), 68 files modified (.github/workflows/release.yml, CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml, docs/RELEASE_CYCLE.md) + +### Commit 188: [fe8a7f4](https://github.com/vig-os/devcontainer/commit/fe8a7f4d4221ae87c80ea7828c3cda2220f5b6fd) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 07:11 AM +test(setup): add smoke redeploy docs preservation regression tests, 43 files modified (tests/bats/init-workspace.bats, tests/test_integration.py) + +### Commit 189: [c4e4158](https://github.com/vig-os/devcontainer/commit/c4e41583d7a18ee17c64708a277cfcf7833e57cb) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 07:12 AM +fix(setup): preserve synced docs directories in smoke redeploy, 5 files modified (CHANGELOG.md, assets/init-workspace.sh) + +### Commit 190: [9195ddc](https://github.com/vig-os/devcontainer/commit/9195ddc4dd205ce276232662b3c217dd75d96fa0) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 07:22 AM +fix(setup): preserve docs sync directories in smoke redeploy (#263), 48 files modified (CHANGELOG.md, assets/init-workspace.sh, tests/bats/init-workspace.bats, tests/test_integration.py) + +### Commit 191: [4a3bb25](https://github.com/vig-os/devcontainer/commit/4a3bb25924f51070b4779988121d44289991be9e) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 12, 2026 at 07:59 AM +chore: sync issues and PRs, 1361 files modified + +### Commit 192: [ac53fb3](https://github.com/vig-os/devcontainer/commit/ac53fb32b133eb79b505cdfc77e1af51cac84d0f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:10 AM +ci(ci): remove unused workflow_call entry points, 6 files modified (assets/workspace/.github/workflows/ci-container.yml, assets/workspace/.github/workflows/ci.yml) + +### Commit 193: [992314e](https://github.com/vig-os/devcontainer/commit/992314ed757ad5b79b03bc08a789fd6ce206056f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:10 AM +ci(ci): resolve image tag from .vig-os with early validation, 51 files modified (assets/workspace/.github/workflows/ci-container.yml) + +### Commit 194: [022ddae](https://github.com/vig-os/devcontainer/commit/022ddae1c1c2188fb5bda08cbe70e7aebce06028) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:11 AM +docs: record CI workflow changes for #264, 6 files modified (CHANGELOG.md) + +### Commit 195: [73fb287](https://github.com/vig-os/devcontainer/commit/73fb287eb719430d38ad441abcb9c85960207f66) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:12 AM +chore(ci): merge origin/dev into feature/264 branch, 1361 files modified + +### Commit 196: [faca273](https://github.com/vig-os/devcontainer/commit/faca273421a883beecf32ecc354d53ed3d952a1a) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:27 AM +ci(ci): harden resolve-image checkout and summary gating, 7 files modified (assets/workspace/.github/workflows/ci-container.yml) + +### Commit 197: [6c9e2cd](https://github.com/vig-os/devcontainer/commit/6c9e2cd70f6c814f65f36d7206ed8e04bff4f1a2) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 08:34 AM +ci: remove workflow_call triggers and resolve container image from .vig-os (#265), 70 files modified (CHANGELOG.md, assets/workspace/.github/workflows/ci-container.yml, assets/workspace/.github/workflows/ci.yml) + +### Commit 198: [06541a0](https://github.com/vig-os/devcontainer/commit/06541a00926e86fee26d58fcea5d31ec2b01a541) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 09:43 AM +test(ci): add dispatch ref regression checks, 15 files modified (tests/bats/just.bats) + +### Commit 199: [a438270](https://github.com/vig-os/devcontainer/commit/a4382700601a43a1824901ad06215ab48603fb3f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 09:44 AM +fix(ci): add branch ref defaults for release dispatch, 36 files modified (CONTRIBUTE.md, README.md, justfile) + +### Commit 200: [d82e238](https://github.com/vig-os/devcontainer/commit/d82e2389967e452b2b1494fadd85b8d755bd09ab) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 09:44 AM +test(ci): add prepare-release rollback assertions, 10 files modified (tests/bats/just.bats) + +### Commit 201: [c528736](https://github.com/vig-os/devcontainer/commit/c528736bb02e51cefe01d752cdecae568f722e30) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 09:44 AM +fix(ci): rollback failed prepare-release side effects, 72 files modified (.github/workflows/prepare-release.yml) + +### Commit 202: [1e609b8](https://github.com/vig-os/devcontainer/commit/1e609b8b2887fb9c964365b8161efa45eff1a78c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 09:45 AM +docs(ci): document branch refs and prepare rollback, 15 files modified (CHANGELOG.md, docs/RELEASE_CYCLE.md) + +### Commit 203: [3e0e714](https://github.com/vig-os/devcontainer/commit/3e0e71449ad4b8da08ab5fc99cedbba704479701) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 10:10 AM +fix(ci): harden prepare-release rollback safeguards, 22 files modified (.github/workflows/prepare-release.yml, CHANGELOG.md) + +### Commit 204: [e6228f4](https://github.com/vig-os/devcontainer/commit/e6228f44db9f7696ffdb88c1075fb9206fc1f565) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 10:26 AM +fix(ci): pin release workflow refs and harden prepare-release rollback (#267), 168 files modified (.github/workflows/prepare-release.yml, CHANGELOG.md, CONTRIBUTE.md, README.md, docs/RELEASE_CYCLE.md, justfile, tests/bats/just.bats) + +### Commit 205: [bdabb10](https://github.com/vig-os/devcontainer/commit/bdabb10aa4d61b1d2bb8a2f5e8cc128bbda87136) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 11:31 AM +fix(ci): split prepare-release app tokens by scope, 28 files modified (.github/workflows/prepare-release.yml, CHANGELOG.md) + +### Commit 206: [df6cd08](https://github.com/vig-os/devcontainer/commit/df6cd08f0e6ca17a78b1056d2f11b5fc70b90eba) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 11:41 AM +ci: reduce prepare-release GITHUB_TOKEN permissions, 1 file modified (.github/workflows/prepare-release.yml) + +### Commit 207: [a85589b](https://github.com/vig-os/devcontainer/commit/a85589b4057da9cf69bb0dd52484ce06422cd39c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 11:48 AM +fix(ci): split prepare-release app tokens by scope (#269), 29 files modified (.github/workflows/prepare-release.yml, CHANGELOG.md) + +### Commit 208: [0e88612](https://github.com/vig-os/devcontainer/commit/0e8861206a53fb3c216ac18559b0d51bbbe7d546) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 12, 2026 at 12:05 PM +chore: sync issues and PRs, 1140 files modified (docs/issues/issue-169.md, docs/issues/issue-266.md, docs/issues/issue-268.md, docs/pull-requests/pr-265.md, docs/pull-requests/pr-267.md, docs/pull-requests/pr-269.md) + +### Commit 209: [462eec5](https://github.com/vig-os/devcontainer/commit/462eec547dbcdce7206d82669358e4e1c6689a51) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 12, 2026 at 12:07 PM +chore: freeze changelog for release 0.3.0, 167 files modified (CHANGELOG.md) + +### Commit 210: [b4b3736](https://github.com/vig-os/devcontainer/commit/b4b37368d09676557124d57ba9d3da561f1d6321) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 12, 2026 at 12:07 PM +chore: prepare release 0.3.0, 14 files modified (CHANGELOG.md) + +### Commit 211: [8f28283](https://github.com/vig-os/devcontainer/commit/8f28283d1ccca1e684abb99b867a3ef47186490b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:22 PM +test(setup): add regression tests for TBD changelog entries, 40 files modified (tests/test_utils.py) + +### Commit 212: [09e6f51](https://github.com/vig-os/devcontainer/commit/09e6f51891ede39df10de02d9008842448ba308c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:23 PM +fix(setup): skip TBD changelog entries in doc version parsing, 9 files modified (docs/generate.py) + +### Commit 213: [cea91db](https://github.com/vig-os/devcontainer/commit/cea91db648ec318016aafe99b1070047f5990d3d) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:24 PM +docs(setup): record changelog parser release-branch fix, 2 files modified (CHANGELOG.md) + +### Commit 214: [0600907](https://github.com/vig-os/devcontainer/commit/0600907a4d5ac92cb1b799bebe3201247dc7c60c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:47 PM +fix: skip TBD changelog entries in docs generator (#273), 51 files modified (CHANGELOG.md, docs/generate.py, tests/test_utils.py) + +### Commit 215: [8b7edcf](https://github.com/vig-os/devcontainer/commit/8b7edcf96da106ca3dadc78477845e39ae5dbeb7) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:01 PM +fix(setup): remove unsafe github.com substring assertions, 22 files modified (tests/test_integration.py) + +### Commit 216: [4ae86b5](https://github.com/vig-os/devcontainer/commit/4ae86b596552e745cb92d7e0816a501961aafa10) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:12 PM +fix: remove unsafe github.com substring assertions (#275), 22 files modified (tests/test_integration.py) + +### Commit 217: [8172fd7](https://github.com/vig-os/devcontainer/commit/8172fd77c6565c0db14c7dd3305f054801501c03) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:23 PM +ci: use compliant title for release PRs, 2 files modified (.github/workflows/prepare-release.yml) + +### Commit 218: [e377fd6](https://github.com/vig-os/devcontainer/commit/e377fd6926961146a9d46d6a85ac32f6bb2c3239) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:25 PM +ci: use compliant title for release PRs (#277), 2 files modified (.github/workflows/prepare-release.yml) + +### Commit 219: [e99f882](https://github.com/vig-os/devcontainer/commit/e99f882990bb74dbe60123a19ed90b831894de9f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:43 PM +test: add failing tests for fingerprint plain-prose false positive, 107 files modified (packages/vig-utils/tests/test_utils.py) + +### Commit 220: [892162e](https://github.com/vig-os/devcontainer/commit/892162ef25abdfc2aca392955f401b3e7d742545) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:46 PM +fix: restrict fingerprint name matching to attribution contexts, 43 files modified (CHANGELOG.md, packages/vig-utils/src/vig_utils/utils.py) + +### Commit 221: [e884ca0](https://github.com/vig-os/devcontainer/commit/e884ca064faefc8999049991e032004674929244) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:55 PM +ci: pass blocklist TOML to validate-commit-msg in PR title check, 3 files modified (.github/workflows/pr-title-check.yml) + +### Commit 222: [22d3422](https://github.com/vig-os/devcontainer/commit/22d34222f242d3b6e06ee084af639f7cf9e98350) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:01 PM +fix: drop bare bot/agent/assistant from attribution context regex, 17 files modified (packages/vig-utils/src/vig_utils/utils.py, packages/vig-utils/tests/test_utils.py) + +### Commit 223: [382fc6c](https://github.com/vig-os/devcontainer/commit/382fc6cd6adbc1d98ff9614414aa605bd427877f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:02 PM +fix: PR fingerprint check blocks plain-text mentions of Cursor/Copilot (#279), 166 files modified (.github/workflows/pr-title-check.yml, CHANGELOG.md, packages/vig-utils/src/vig_utils/utils.py, packages/vig-utils/tests/test_utils.py) + +### Commit 224: [7ac7d5d](https://github.com/vig-os/devcontainer/commit/7ac7d5dd46fc9591b249365ccfd9d4e154fc919b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:06 PM +chore(ci): sync sync-main-to-dev workflow in manifest, 241 files modified (CHANGELOG.md, assets/workspace/.github/workflows/sync-main-to-dev.yml, scripts/manifest.toml) + +### Commit 225: [2e9ee18](https://github.com/vig-os/devcontainer/commit/2e9ee182008a7745ef1fc0b559649c7f56039730) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:12 PM +chore(ci): sync sync-main-to-dev workflow in manifest (#280), 241 files modified (CHANGELOG.md, assets/workspace/.github/workflows/sync-main-to-dev.yml, scripts/manifest.toml) + +### Commit 226: [d7e20fb](https://github.com/vig-os/devcontainer/commit/d7e20fbfe899fa06a31399bd5d4e2de071643c54) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:31 PM +fix(ci): retag loaded candidate images before push, 5 files modified (.github/workflows/release.yml, CHANGELOG.md) + +### Commit 227: [619cc84](https://github.com/vig-os/devcontainer/commit/619cc840538e069f7561c06a984db6e681e31282) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:38 PM +fix(ci): retag loaded candidate images before push (#282), 5 files modified (.github/workflows/release.yml, CHANGELOG.md) + +### Commit 228: [2daaa78](https://github.com/vig-os/devcontainer/commit/2daaa78a93d0efaceb2e606d06eff5644202fc50) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:35 AM +test: add regression coverage for safe vig-os parsing, 84 files modified (tests/test_integration.py) + +### Commit 229: [15f3baf](https://github.com/vig-os/devcontainer/commit/15f3bafca7a1944316567a0fad56190912619846) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:38 AM +refactor: parse vig-os as data in init and version check, 57 files modified (assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh) + +### Commit 230: [b333e9d](https://github.com/vig-os/devcontainer/commit/b333e9de2ac93e8a34ac49418cfbb6ba1edd4522) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:12 AM +test(setup): restore .vig-os after mutation regression tests, 138 files modified (tests/test_integration.py) + +### Commit 231: [4e1eb91](https://github.com/vig-os/devcontainer/commit/4e1eb91292c509d337f49a8bcf1e6e078c36acd4) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:27 AM +test: make IN_CONTAINER=true hook tests deterministic, 6 files modified (tests/bats/githooks.bats) + +### Commit 232: [b876687](https://github.com/vig-os/devcontainer/commit/b876687c1a46bada939af32908c72cb68ea85ede) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:16 AM +fix: guard unset line in initialize vig-os parser loop, 2 files modified (assets/workspace/.devcontainer/scripts/initialize.sh) + +### Commit 233: [bf0cab0](https://github.com/vig-os/devcontainer/commit/bf0cab08d12c27d9004ad599aa788dcf804d2d90) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:22 AM +refactor: parse .vig-os config as data (#287), 169 files modified (assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh, tests/bats/githooks.bats, tests/test_integration.py) + +### Commit 234: [d9785c4](https://github.com/vig-os/devcontainer/commit/d9785c421bcad28ab66b1853937b0e0cf1375d69) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:36 AM +chore(ci): bump commit-action pins to v0.1.5, 19 files modified + +### Commit 235: [8632134](https://github.com/vig-os/devcontainer/commit/8632134138b02735752671cba72cd956404462e4) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:47 AM +chore(ci): bump commit-action pins to v0.1.5 (#288), 19 files modified + +### Commit 236: [8e0ddd5](https://github.com/vig-os/devcontainer/commit/8e0ddd543d5fe3d8ab88e0f88d13ef8a8dcf787a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:57 AM +fix(ci): remove invalid issue refs from smoke deploy commit, 5 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 237: [9c1f401](https://github.com/vig-os/devcontainer/commit/9c1f4013ff4353dfda368ae7e6a6c899dea03cc8) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 11:02 AM +docs(ci): record smoke-test dispatch reference fix, 3 files modified (CHANGELOG.md) + +### Commit 238: [2ead98c](https://github.com/vig-os/devcontainer/commit/2ead98c1ec8d379c3ee2f86f9084e42e532694a3) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 11:17 AM +fix: remove invalid issue refs from smoke-test deploy commit (#290), 8 files modified (CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 239: [377c7f4](https://github.com/vig-os/devcontainer/commit/377c7f4f549f484115cedbca4792aea85ee41469) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +test: add regression for trailing separator sanitization, 24 files modified (tests/test_install_script.py) + +### Commit 240: [fe7de78](https://github.com/vig-os/devcontainer/commit/fe7de786bd28b017edc50e9784836fb724538003) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +fix(setup): normalize short name boundaries for package validity, 8 files modified (assets/init-workspace.sh, install.sh) + +### Commit 241: [942cabf](https://github.com/vig-os/devcontainer/commit/942cabfe1d032aeb5498f98783b1402ca31b1dd7) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +docs: record short-name sanitization fix in changelog, 3 files modified (CHANGELOG.md) + +### Commit 242: [be28810](https://github.com/vig-os/devcontainer/commit/be28810b3e69ad38232f1c270ffa28d64feca69f) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:21 PM +fix(setup): normalize short name boundaries for package validity (#292), 35 files modified (CHANGELOG.md, assets/init-workspace.sh, install.sh, tests/test_install_script.py) + +### Commit 243: [2df8539](https://github.com/vig-os/devcontainer/commit/2df853986b45704aa0bf79e0639dec95d6f11aff) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:59 PM +fix(ci): send typed force flag for ref reset, 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 244: [5163a01](https://github.com/vig-os/devcontainer/commit/5163a0158275e861295e07221777b4df12a8c7ea) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:05 PM +fix(ci): send typed force flag for smoke-test ref reset (#294), 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 245: [10fb6e8](https://github.com/vig-os/devcontainer/commit/10fb6e8806ab74d38a0fb394cbdbbcfdb5aea437) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:32 PM +fix(ci): use publish version tag across release artifacts, 16 files modified (.github/workflows/release.yml) + +### Commit 246: [b7ea79b](https://github.com/vig-os/devcontainer/commit/b7ea79bda807c75d7e82ba9903cd31bc69a47d4a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:37 PM +chore(ci): align sync-main-to-dev checkout pin with repository standard, 8 files modified (.github/workflows/sync-main-to-dev.yml, assets/workspace/.github/workflows/sync-main-to-dev.yml) + +### Commit 247: [fe3489b](https://github.com/vig-os/devcontainer/commit/fe3489b7074fc0a10a5211c6b4ee94c471011a1b) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:42 PM +fix(ci): preserve RC publish tag across release artifacts (#297), 16 files modified (.github/workflows/release.yml) + +### Commit 248: [b21036f](https://github.com/vig-os/devcontainer/commit/b21036f855b2bac40eef8b19712f2ff68043d601) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:47 PM +chore(ci): align sync-main-to-dev checkout pin with repository standard (#298), 8 files modified (.github/workflows/sync-main-to-dev.yml, assets/workspace/.github/workflows/sync-main-to-dev.yml) + +### Commit 249: [3656e13](https://github.com/vig-os/devcontainer/commit/3656e13cec1bd1e162a5df22080620193df3f1dd) by [vig-os-release-app[bot]](https://github.com/apps/vig-os-release-app) on March 13, 2026 at 03:32 PM +chore: finalize release 0.3.0, 2 files modified (CHANGELOG.md) + +### Commit 250: [0217d7d](https://github.com/vig-os/devcontainer/commit/0217d7d1b25d82b9ab412e93b24d2d9d8f2b858a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 04:12 PM +chore: update release links for 0.3.0, 4 files modified (CHANGELOG.md, README.md) diff --git a/docs/pull-requests/pr-273.md b/docs/pull-requests/pr-273.md new file mode 100644 index 00000000..f0606f4e --- /dev/null +++ b/docs/pull-requests/pr-273.md @@ -0,0 +1,102 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/271-generate-docs-release-version-date → release/0.3.0 +created: 2026-03-12T12:41:29Z +updated: 2026-03-12T12:47:30Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/273 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-12T12:47:28Z +synced: 2026-03-14T04:16:56.213Z +--- + +# [PR 273](https://github.com/vig-os/devcontainer/pull/273) fix: skip TBD changelog entries in docs generator + +## Description + +Fixes release-branch CI failures where `generate-docs` rewrote `README.md` to an unreleased `TBD` version after `prepare-release`. +The changelog parser in `docs/generate.py` now selects the latest dated release entry, and regression tests ensure `TBD` entries are skipped. + +## Type of Change + +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `docs/generate.py` + - Added date-aware filtering for changelog headings so version/date extraction ignores `TBD` entries. + - Updated `get_version_from_changelog()` and `get_release_date_from_changelog()` to pick the first heading containing a concrete `YYYY-MM-DD` date. +- `tests/test_utils.py` + - Added regression tests in both changelog parser test classes to verify `## [x.y.z] - TBD` headings are skipped. + - Added a test helper to point parser functions at a temporary changelog fixture. +- `CHANGELOG.md` + - Added a `### Fixed` entry in `## [0.3.0] - TBD` for issue `#271`. + +## Changelog Entry + +### Fixed +- **generate-docs picks up unreleased TBD version on release branches** ([#271](https://github.com/vig-os/devcontainer/issues/271)) + - `get_version_from_changelog()` and `get_release_date_from_changelog()` now skip entries without a concrete release date + +## Testing + +- [ ] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- `uv run pytest tests/test_utils.py -k "skips_tbd_entry"` (RED before fix; two failures reproduced) +- `uv run pytest tests/test_utils.py -k "changelog"` (GREEN after fix; passing) +- `uv run pre-commit run generate-docs --all-files` (docs regenerated without remaining diffs) + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +This branch targets `release/0.3.0` to unblock the release pipeline affected by PR #270 checks. + +Refs: #271 + + + +--- +--- + +## Commits + +### Commit 1: [8f28283](https://github.com/vig-os/devcontainer/commit/8f28283d1ccca1e684abb99b867a3ef47186490b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:22 PM +test(setup): add regression tests for TBD changelog entries, 40 files modified (tests/test_utils.py) + +### Commit 2: [09e6f51](https://github.com/vig-os/devcontainer/commit/09e6f51891ede39df10de02d9008842448ba308c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:23 PM +fix(setup): skip TBD changelog entries in doc version parsing, 9 files modified (docs/generate.py) + +### Commit 3: [cea91db](https://github.com/vig-os/devcontainer/commit/cea91db648ec318016aafe99b1070047f5990d3d) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:24 PM +docs(setup): record changelog parser release-branch fix, 2 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-275.md b/docs/pull-requests/pr-275.md new file mode 100644 index 00000000..c26b58c6 --- /dev/null +++ b/docs/pull-requests/pr-275.md @@ -0,0 +1,167 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/272-codeql-url-sanitization → release/0.3.0 +created: 2026-03-12T13:04:59Z +updated: 2026-03-12T13:15:13Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/275 +comments: 3 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-12T13:12:45Z +synced: 2026-03-14T04:16:55.496Z +--- + +# [PR 275](https://github.com/vig-os/devcontainer/pull/275) fix: remove unsafe github.com substring assertions + +## Description + +Fix CodeQL `py/incomplete-url-substring-sanitization` findings in integration tests by removing unsafe `github.com` substring checks and replacing them with deterministic SSH/GitHub auth output assertions. + +## Type of Change + +- [x] `fix` -- Bug fix +- [ ] `feat` -- New feature +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `tests/test_integration.py` + - Replace `github.com` substring assertions in SSH auth checks with safer output-based checks. + - Ensure `Permission denied` path is validated as auth failure, not hostname/DNS failure. + - Replace `gh auth status` assertion with generic success markers (`Logged in to`, `✓ Logged in`). + +## Changelog Entry + +No changelog needed: this change only adjusts integration-test assertions to satisfy static analysis and does not change shipped runtime behavior. + +## Testing + +- [ ] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- `uv run pytest tests/test_integration.py -k "ssh or gh_auth" -v` (3 selected tests passed) +- `uv run pre-commit run --all-files` (all hooks passed after formatter adjustment) + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +This PR targets `release/0.3.0` per issue requirements. + +Refs: #272 + + + +--- +--- + +## Review Threads (3) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 12, 2026 at 01:15 PM_ + +_File: [`tests/test_integration.py (line 1373 RIGHT)`](https://github.com/vig-os/devcontainer/pull/275#discussion_r2924551972)_ + +```diff +@@ -1366,8 +1366,11 @@ def test_ssh_github_authentication(self, devcontainer_up): + # Check if it's a permission denied (keys not authorized) vs connection error + if "Permission denied" in result.stderr: + # Keys exist but aren't authorized - this is acceptable for testing +- # The important thing is that SSH is configured +- assert "github.com" in result.stderr, ( ++ # Ensure this is an auth failure, not a connectivity/hostname failure. ++ assert ( ++ "Could not resolve hostname" not in result.stderr ++ and "Name or service not known" not in result.stderr ++ ), ( +``` + +This assertion is effectively redundant in this branch because it only runs when `\"Permission denied\" in result.stderr` is already true, which (in practice) won’t coincide with hostname-resolution failures. To keep the test logic simpler and more deterministic, consider removing this extra negative-assertion and instead assert on a more specific permission-denied pattern (e.g., the SSH publickey denial text) if you want additional signal beyond the existing `if` condition. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 12, 2026 at 01:15 PM_ + +_File: [`tests/test_integration.py (line 1387 RIGHT)`](https://github.com/vig-os/devcontainer/pull/275#discussion_r2924552011)_ + +```diff +@@ -1381,7 +1384,12 @@ def test_ssh_github_authentication(self, devcontainer_up): + ) + elif result.returncode == 1: + # Success - GitHub responded (exit 1 is normal for test connections) +- assert "Hi" in result.stdout or "github.com" in result.stderr, ( ++ output = result.stdout + result.stderr +``` + +Concatenating `stdout` and `stderr` without a separator can create artificial substring matches across the boundary (end of stdout + start of stderr). Prefer joining with a delimiter (e.g., newline) to avoid accidental matches and make debugging output comparisons more reliable. +```suggestion + output = result.stdout + "\n" + result.stderr +``` + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 12, 2026 at 01:15 PM_ + +_File: [`tests/test_integration.py (line 1647 RIGHT)`](https://github.com/vig-os/devcontainer/pull/275#discussion_r2924552038)_ + +```diff +@@ -1632,15 +1640,11 @@ def test_github_cli_authentication(self, devcontainer_up): + + # Verify we got a successful authentication response + output = result.stdout + result.stderr +- assert ( +- "Logged in to github.com" in output +- or "✓ Logged in" in output +- or "github.com" in output +- ), ( ++ assert "Logged in to " in output or "✓ Logged in" in output, ( + f"GitHub CLI authentication status unclear\n" + f"stdout: {result.stdout}\n" + f"stderr: {result.stderr}\n" +- f"Expected 'Logged in to github.com' or similar in output" ++ f"Expected a successful gh auth status message in output" +``` + +The check `\"Logged in to \" in output` is quite broad and may be brittle across `gh` versions/locales (message wording can vary). If possible, make the assertion more structured (e.g., assert on `result.returncode` for success and then check for a smaller set of stable tokens like `\"Logged in\"` plus an `\"as\"`/account indicator, or parse the specific host block if your test setup is host-targeted). This tends to reduce test flakiness while still avoiding `github.com` substring matching. + + +--- +--- + +## Commits + +### Commit 1: [8b7edcf](https://github.com/vig-os/devcontainer/commit/8b7edcf96da106ca3dadc78477845e39ae5dbeb7) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:01 PM +fix(setup): remove unsafe github.com substring assertions, 22 files modified (tests/test_integration.py) diff --git a/docs/pull-requests/pr-277.md b/docs/pull-requests/pr-277.md new file mode 100644 index 00000000..bc28c26d --- /dev/null +++ b/docs/pull-requests/pr-277.md @@ -0,0 +1,40 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/276-release-pr-title-format → release/0.3.0 +created: 2026-03-12T13:24:38Z +updated: 2026-03-12T13:25:38Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/277 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-12T13:25:35Z +synced: 2026-03-14T04:16:54.684Z +--- + +# [PR 277](https://github.com/vig-os/devcontainer/pull/277) ci: use compliant title for release PRs + +## Summary +- update `prepare-release` workflow to create release draft PRs with title format `chore: release X.Y.Z` +- align generated release PR titles with the repository commit-message/PR-title validation rules +- resolve issue #276 without expanding allowed commit types + +## Test plan +- [x] Run `uv run validate-commit-msg` locally with `--subject-only` for `chore: release 0.3.0` +- [x] Retitle release PR #270 to `chore: release 0.3.0` +- [ ] Confirm `Validate PR Title` passes on PR #270 once unrelated blocker #274 is resolved + +Refs: #276 + + +--- +--- + +## Commits + +### Commit 1: [8172fd7](https://github.com/vig-os/devcontainer/commit/8172fd77c6565c0db14c7dd3305f054801501c03) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:23 PM +ci: use compliant title for release PRs, 2 files modified (.github/workflows/prepare-release.yml) diff --git a/docs/pull-requests/pr-279.md b/docs/pull-requests/pr-279.md new file mode 100644 index 00000000..e4deddef --- /dev/null +++ b/docs/pull-requests/pr-279.md @@ -0,0 +1,198 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/274-ai-fingerprint-block → release/0.3.0 +created: 2026-03-12T13:50:33Z +updated: 2026-03-12T14:02:51Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/279 +comments: 5 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-12T14:02:49Z +synced: 2026-03-14T04:16:54.000Z +--- + +# [PR 279](https://github.com/vig-os/devcontainer/pull/279) fix: PR fingerprint check blocks plain-text mentions of Cursor/Copilot + +## Description + +The PR fingerprint check (`check-pr-agent-fingerprints`) produces false positives when normal prose mentions AI tool names like "Cursor" or "Copilot". The root cause is case-insensitive substring matching of `names` against the entire PR title+body. + +This PR narrows name matching to attribution-context lines only and wires up the previously unused `allow_patterns` from `agent-blocklist.toml`. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- **`packages/vig-utils/src/vig_utils/utils.py`** + - `load_blocklist`: now loads `allow_patterns` from TOML as compiled regexes + - `contains_agent_fingerprint`: strips `allow_patterns` matches from content before checking; restricts `names` matching to lines containing attribution-context phrases (`authored by`, `generated by`, `powered by`, `bot`, `agent`, etc.) + - New module-level `_ATTRIBUTION_CONTEXT_RE` compiled regex +- **`packages/vig-utils/tests/test_utils.py`** + - 8 new tests in `TestAgentBlocklistHelpers`: allow_patterns loading, plain-prose pass-through, attribution-context blocking, allow_patterns stripping, combined stripping+attribution, email unconditional matching +- **`CHANGELOG.md`** + - Added entry under `### Fixed` in `[0.3.0]` + +## Changelog Entry + +### Fixed + +- **PR fingerprint check false positives on plain-text AI tool mentions** ([#274](https://github.com/vig-os/devcontainer/issues/274)) + - `contains_agent_fingerprint` now restricts name matching to attribution-context lines (e.g. "generated by", "authored by") instead of scanning the entire content + - Wire up `allow_patterns` from `agent-blocklist.toml` to strip known-safe text (dotfile paths, doc filenames) before checking + +## Testing + +- [x] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [x] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #274 + + + +--- +--- + +## Review Threads (2) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 12, 2026 at 01:54 PM_ + +_File: [`packages/vig-utils/src/vig_utils/utils.py (line 176 RIGHT)`](https://github.com/vig-os/devcontainer/pull/279#discussion_r2924820683)_ + +```diff +@@ -163,9 +166,17 @@ def load_blocklist(path: Path) -> dict: + "trailers": [re.compile(p) for p in patterns.get("trailers", [])], + "names": [s.lower() for s in patterns.get("names", [])], + "emails": [s.lower() for s in patterns.get("emails", [])], ++ "allow_patterns": [re.compile(p) for p in patterns.get("allow_patterns", [])], + } + + ++_ATTRIBUTION_CONTEXT_RE = re.compile( ++ r"(?:authored|generated|created|produced|written|assisted|made|built|suggested|signed|powered)[\s-]+by\b" ++ r"|\b(?:bot|agent|assistant)\b", ++ re.IGNORECASE, ++) +``` + +`_ATTRIBUTION_CONTEXT_RE` treats any occurrence of the standalone words "agent" or "assistant" as attribution context. This can still produce false positives in ordinary prose lines that mention a blocked name alongside those generic terms (e.g. "copilot agent integration"), which undermines the goal of avoiding plain-text mentions triggering the check. Consider tightening the context regex to require explicit attribution phrasing (like " by") and/or more specific markers (e.g. "AI agent", "bot account"), rather than matching bare "agent"/"assistant" anywhere. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 12, 2026 at 01:58 PM — [link](https://github.com/vig-os/devcontainer/pull/279#discussion_r2924842952) + + Low risk — the example "copilot agent integration" references the Copilot agent as an entity, not a generic concept. Truly generic prose rarely combines a blocked AI tool name with "agent"/"assistant" on the same line. Tightening to "AI agent" / "bot account" would miss legitimate patterns like "Cursor agent" or "Copilot bot". + +If a real-world false positive surfaces, we can revisit. Leaving as-is for now. + +- **[@c-vigo](https://github.com/c-vigo)** on March 12, 2026 at 02:02 PM — [link](https://github.com/vig-os/devcontainer/pull/279#discussion_r2924873825) + + Revisited — you were right. The bare `\b(?:bot|agent|assistant)\b` match triggered on "agent" inside `check-pr-agent-fingerprints` in the PR body itself. Dropped those terms from the context regex in 22d3422; now relying solely on explicit "X by" attribution phrases. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 12, 2026 at 01:54 PM_ + +_File: [`packages/vig-utils/src/vig_utils/utils.py (line 204 RIGHT)`](https://github.com/vig-os/devcontainer/pull/279#discussion_r2924820725)_ + +```diff +@@ -174,17 +185,32 @@ def contains_agent_fingerprint( + ) -> str | None: + """Check if content contains any blocklisted pattern. + ++ Names are only matched on lines that contain an attribution-context phrase ++ (e.g. "generated by", "authored by", "bot", "agent") to avoid false ++ positives from plain-prose mentions of AI tool names (Refs: #274). ++ + Returns the first matching pattern string if found, else None. + """ +- content_lower = content.lower() +- for name in blocklist.get("names", []): +- if name in content_lower: +- return name ++ cleaned = content ++ for allow_re in blocklist.get("allow_patterns", []): ++ cleaned = allow_re.sub("", cleaned) ++ ++ names = blocklist.get("names", []) ++ if names: ++ for line in cleaned.splitlines(): ++ if _ATTRIBUTION_CONTEXT_RE.search(line): ++ line_lower = line.lower() ++ for name in names: ++ if name in line_lower: ++ return name +``` + +`contains_agent_fingerprint` now only matches `names` on attribution-context lines, which changes behavior for all callers (not just the PR-title/body check). In particular, `validate-commit-msg --blocked-patterns` uses this helper, so commit messages that mention a blocked name in non-attribution prose would now pass where they previously failed. If this narrowing is intended only for PR text, consider adding a parameter/mode (or a separate helper) so commit-message validation can continue scanning the full content for `names` when using the TOML blocklist. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 12, 2026 at 01:58 PM — [link](https://github.com/vig-os/devcontainer/pull/279#discussion_r2924843200) + + The behavior change is intentional for all callers. Commit messages should also be able to mention tool names in non-attribution context (e.g. `fix: update cursor config path`). The old broad substring matching was equally problematic for commits. + +Notably the `validate_commit_msg` hardcoded fallback was already more nuanced — it used `cursoragent` and `cursor\.com` (not bare `cursor`), and `\bcopilot\b` with word boundaries. The TOML-based path was the more aggressive one; this fix brings it in line. + +Adding a separate mode would be over-engineering for a case where the same fix is desirable in both contexts. + + +--- +--- + +## Commits + +### Commit 1: [e99f882](https://github.com/vig-os/devcontainer/commit/e99f882990bb74dbe60123a19ed90b831894de9f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:43 PM +test: add failing tests for fingerprint plain-prose false positive, 107 files modified (packages/vig-utils/tests/test_utils.py) + +### Commit 2: [892162e](https://github.com/vig-os/devcontainer/commit/892162ef25abdfc2aca392955f401b3e7d742545) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:46 PM +fix: restrict fingerprint name matching to attribution contexts, 43 files modified (CHANGELOG.md, packages/vig-utils/src/vig_utils/utils.py) + +### Commit 3: [e884ca0](https://github.com/vig-os/devcontainer/commit/e884ca064faefc8999049991e032004674929244) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:55 PM +ci: pass blocklist TOML to validate-commit-msg in PR title check, 3 files modified (.github/workflows/pr-title-check.yml) + +### Commit 4: [22d3422](https://github.com/vig-os/devcontainer/commit/22d34222f242d3b6e06ee084af639f7cf9e98350) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:01 PM +fix: drop bare bot/agent/assistant from attribution context regex, 17 files modified (packages/vig-utils/src/vig_utils/utils.py, packages/vig-utils/tests/test_utils.py) diff --git a/docs/pull-requests/pr-280.md b/docs/pull-requests/pr-280.md new file mode 100644 index 00000000..9f201d7d --- /dev/null +++ b/docs/pull-requests/pr-280.md @@ -0,0 +1,95 @@ +--- +type: pull_request +state: closed (merged) +branch: release/278-sync-main-to-dev-manifest → release/0.3.0 +created: 2026-03-12T14:09:34Z +updated: 2026-03-12T14:12:09Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/280 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-12T14:12:07Z +synced: 2026-03-14T04:16:53.171Z +--- + +# [PR 280](https://github.com/vig-os/devcontainer/pull/280) chore(ci): sync sync-main-to-dev workflow in manifest + +## Description + +Sync `sync-main-to-dev` workflow into the manifest-driven workspace sync pipeline so release-to-dev automation is shipped consistently in generated workspace assets. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [x] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `scripts/manifest.toml` + - Add `[[entries]]` for `.github/workflows/sync-main-to-dev.yml` +- `assets/workspace/.github/workflows/sync-main-to-dev.yml` + - Add synced workspace copy generated by manifest sync +- `CHANGELOG.md` + - Add `0.3.0` -> `Changed` entry for issue `#278` + +## Changelog Entry + +### Changed +- **Manifest sync includes `sync-main-to-dev` workflow** ([#278](https://github.com/vig-os/devcontainer/issues/278)) + - Add `.github/workflows/sync-main-to-dev.yml` to `scripts/manifest.toml` so workspace sync includes the release-to-dev PR automation workflow + +## Testing + +- [x] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `uv run python scripts/sync_manifest.py list` and verified: + - `.github/workflows/sync-issues.yml` + - `.github/workflows/sync-main-to-dev.yml` + - `.github/workflows/codeql.yml` + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #278 + + + +--- +--- + +## Commits + +### Commit 1: [7ac7d5d](https://github.com/vig-os/devcontainer/commit/7ac7d5dd46fc9591b249365ccfd9d4e154fc919b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:06 PM +chore(ci): sync sync-main-to-dev workflow in manifest, 241 files modified (CHANGELOG.md, assets/workspace/.github/workflows/sync-main-to-dev.yml, scripts/manifest.toml) diff --git a/docs/pull-requests/pr-282.md b/docs/pull-requests/pr-282.md new file mode 100644 index 00000000..1eea8720 --- /dev/null +++ b/docs/pull-requests/pr-282.md @@ -0,0 +1,94 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/281-release-candidate-publish-fix → release/0.3.0 +created: 2026-03-12T17:33:52Z +updated: 2026-03-12T17:38:30Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/282 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-12T17:38:27Z +synced: 2026-03-14T04:16:52.455Z +--- + +# [PR 282](https://github.com/vig-os/devcontainer/pull/282) fix(ci): retag loaded candidate images before push + +## Description + +Fix the candidate release publish failure where `release.yml` loads images tagged with base version (`X.Y.Z-arch`) but attempts to push candidate tags (`X.Y.Z-rcN-arch`) that do not exist locally. + +This change retags each loaded architecture image to the candidate tag before push and documents the fix in the `0.3.0` changelog section. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/release.yml` + - Add `SOURCE_IMAGE_TAG="$REPO:$BASE_VERSION-$arch"` in `Load and push images` + - Retag loaded image with `docker tag "$SOURCE_IMAGE_TAG" "$IMAGE_TAG"` before `docker push` +- `CHANGELOG.md` + - Add a `### Fixed` entry under `## [0.3.0] - TBD` describing the candidate retag fix for `#281` + +## Changelog Entry + +### Fixed +- **Release candidate publish retags loaded images before push** ([#281](https://github.com/vig-os/devcontainer/issues/281)) + - `release.yml` now tags `ghcr.io/vig-os/devcontainer:X.Y.Z-arch` artifacts as `X.Y.Z-rcN-arch` before `docker push` in candidate runs + - Prevents publish failures caused by pushing candidate tags that were never created locally after `docker load` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `just precommit` successfully (includes YAML/workflow checks and sync-manifest checks). +- Triggered one candidate release run during validation by mistake, then cancelled it before publish completion. + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +Target branch for this PR is `release/0.3.0` (release hotfix flow for issue `#281`). + +Refs: #281 + + +--- +--- + +## Commits + +### Commit 1: [d7e20fb](https://github.com/vig-os/devcontainer/commit/d7e20fbfe899fa06a31399bd5d4e2de071643c54) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:31 PM +fix(ci): retag loaded candidate images before push, 5 files modified (.github/workflows/release.yml, CHANGELOG.md) diff --git a/docs/pull-requests/pr-287.md b/docs/pull-requests/pr-287.md new file mode 100644 index 00000000..0833962b --- /dev/null +++ b/docs/pull-requests/pr-287.md @@ -0,0 +1,153 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/285-parse-vig-os-config-safely → release/0.3.0 +created: 2026-03-13T09:38:55Z +updated: 2026-03-13T10:22:50Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/287 +comments: 2 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-13T10:22:48Z +synced: 2026-03-14T04:16:51.739Z +--- + +# [PR 287](https://github.com/vig-os/devcontainer/pull/287) refactor: parse .vig-os config as data + +## Description + +Replaces executable `.vig-os` loading with data-only parsing in `initialize.sh` and `version-check.sh` so unexpected shell content cannot execute. + +Adds regression integration coverage proving shell payloads in `.vig-os` are not executed while `DEVCONTAINER_VERSION` is still read and used. + +Includes a follow-up test hardening commit to restore `.vig-os` after mutation-based tests so later integration tests are not impacted by test-side config changes. + +Adds a final test stabilization commit so `IN_CONTAINER=true` hook-path BATS checks are deterministic and no longer depend on host hook return behavior. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [x] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/workspace/.devcontainer/scripts/initialize.sh` + - Replaced `source "$config_file"` in `load_vig_os_config` with line-by-line key/value parsing for `DEVCONTAINER_VERSION` + - Preserved existing `.env` update behavior and Darwin/Linux `sed` handling +- `assets/workspace/.devcontainer/scripts/version-check.sh` + - Replaced `source "$config_file"` in `get_current_version` with data-only parsing for `DEVCONTAINER_VERSION` + - Preserved existing pinned-version filtering (`dev`, `latest`, empty) +- `tests/test_integration.py` + - Added regression test for `initialize.sh` to ensure shell payloads in `.vig-os` are not executed + - Added regression test for `version-check.sh config` to ensure shell payloads in `.vig-os` are not executed + - Added restoration of `.vig-os` after mutation tests to prevent side effects on later tests +- `tests/bats/githooks.bats` + - Made `IN_CONTAINER=true` guard tests deterministic for `pre-commit`, `prepare-commit-msg`, and `commit-msg` + - Adjusted expectations so tests validate guard behavior without flaky exit-code assumptions + +## Changelog Entry + +No changelog needed. Issue `#285` explicitly marks changelog category as "No changelog needed", and this PR keeps behavior intact while hardening implementation details. + +## Testing + +- [x] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +Issue references a security hardening concern flagged during smoke-test review; this PR keeps scope limited to the two script functions and corresponding tests. + +Refs: #285 + + + +--- +--- + +## Review Threads (1) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 13, 2026 at 09:43 AM_ + +_File: [`assets/workspace/.devcontainer/scripts/initialize.sh`](https://github.com/vig-os/devcontainer/pull/287#discussion_r2930123463)_ + +```diff +@@ -17,26 +17,44 @@ DEVCONTAINER_DIR="$(dirname "$SCRIPT_DIR")" + load_vig_os_config() { + local config_file="$DEVCONTAINER_DIR/../.vig-os" + local env_file="$DEVCONTAINER_DIR/.env" ++ local devcontainer_version="" + + if [[ ! -f "$config_file" ]]; then + return 0 + fi + +- # shellcheck source=/dev/null +- source "$config_file" +- +- if [[ -z "${DEVCONTAINER_VERSION:-}" ]]; then ++ while IFS= read -r line || [[ -n "$line" ]]; do ++ [[ -z "${line//[[:space:]]/}" ]] && continue ++ [[ "$line" =~ ^[[:space:]]*# ]] && continue +``` + +`load_vig_os_config` runs with `set -u`, but the `while IFS= read -r line || [[ -n "$line" ]]; do` condition can reference `line` before it has ever been set (e.g., if `.vig-os` exists but is empty, or if the first `read` fails). That will trigger an “unbound variable” error and abort initialization. Declare/initialize `line` as a local (e.g., `local line=""`) before the loop, or use a safe expansion like `${line:-}` in the condition. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 13, 2026 at 10:17 AM — [link](https://github.com/vig-os/devcontainer/pull/287#discussion_r2930286125) + + Great catch — fixed in commit b876687 by changing the loop condition to use `${line:-}` under `set -u` (`[[ -n "${line:-}" ]]`). I also re-ran focused integration tests (`initialize_writes_devcontainer_version_to_env` and `initialize_does_not_execute_vig_os_shell_content`), both passing. + + +--- +--- + +## Commits + +### Commit 1: [2daaa78](https://github.com/vig-os/devcontainer/commit/2daaa78a93d0efaceb2e606d06eff5644202fc50) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:35 AM +test: add regression coverage for safe vig-os parsing, 84 files modified (tests/test_integration.py) + +### Commit 2: [15f3baf](https://github.com/vig-os/devcontainer/commit/15f3bafca7a1944316567a0fad56190912619846) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:38 AM +refactor: parse vig-os as data in init and version check, 57 files modified (assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh) + +### Commit 3: [b333e9d](https://github.com/vig-os/devcontainer/commit/b333e9de2ac93e8a34ac49418cfbb6ba1edd4522) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:12 AM +test(setup): restore .vig-os after mutation regression tests, 138 files modified (tests/test_integration.py) + +### Commit 4: [4e1eb91](https://github.com/vig-os/devcontainer/commit/4e1eb91292c509d337f49a8bcf1e6e078c36acd4) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:27 AM +test: make IN_CONTAINER=true hook tests deterministic, 6 files modified (tests/bats/githooks.bats) + +### Commit 5: [b876687](https://github.com/vig-os/devcontainer/commit/b876687c1a46bada939af32908c72cb68ea85ede) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:16 AM +fix: guard unset line in initialize vig-os parser loop, 2 files modified (assets/workspace/.devcontainer/scripts/initialize.sh) diff --git a/docs/pull-requests/pr-288.md b/docs/pull-requests/pr-288.md new file mode 100644 index 00000000..06af1e2d --- /dev/null +++ b/docs/pull-requests/pr-288.md @@ -0,0 +1,38 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/286-update-smoke-test-commit-action-pin-release-0-3-0 → release/0.3.0 +created: 2026-03-13T10:39:44Z +updated: 2026-03-13T10:47:02Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/288 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-13T10:47:00Z +synced: 2026-03-14T04:16:50.898Z +--- + +# [PR 288](https://github.com/vig-os/devcontainer/pull/288) chore(ci): bump commit-action pins to v0.1.5 + +## Summary +- Update all `vig-os/commit-action` workflow pins used by release/sync/smoke-test flows to `c0024cbad0e501764127cccab732c6cd465b4646` (`v0.1.5`). +- Align workspace template workflow pins with root workflow pin updates. +- Add a `CHANGELOG.md` entry under `0.3.0` → `Fixed` for issue #286. + +## Test plan +- [x] Confirm old `commit-action` SHAs no longer exist in repo workflow files. +- [x] Run pre-commit hooks via local commit (all checks passed). +- [ ] Validate end-to-end smoke-test repository dispatch run after merge. + + +--- +--- + +## Commits + +### Commit 1: [d9785c4](https://github.com/vig-os/devcontainer/commit/d9785c421bcad28ab66b1853937b0e0cf1375d69) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:36 AM +chore(ci): bump commit-action pins to v0.1.5, 19 files modified diff --git a/docs/pull-requests/pr-290.md b/docs/pull-requests/pr-290.md new file mode 100644 index 00000000..90c9ea25 --- /dev/null +++ b/docs/pull-requests/pr-290.md @@ -0,0 +1,97 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/284-smoke-test-dispatch-tag → release/0.3.0 +created: 2026-03-13T11:05:56Z +updated: 2026-03-13T11:17:20Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/290 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-13T11:17:18Z +synced: 2026-03-14T04:16:50.110Z +--- + +# [PR 290](https://github.com/vig-os/devcontainer/pull/290) fix: remove invalid issue refs from smoke-test deploy commit + +## Description + +Fix smoke-test deploy commit traceability by removing an invalid local issue reference from automated `chore: deploy ` commits generated in `vig-os/devcontainer-smoke-test`. +Also add a maintainer note in the template workflow that changes in this file require manual redeploy to the smoke-test repository and promotion through PRs to `main`. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Remove `Refs: #258` from the automated deploy commit body (invalid in smoke-test repo context) + - Add a template note about manual redeploy/sync expectations for this workflow file +- `CHANGELOG.md` + - Add a `0.3.0` `### Fixed` entry for issue `#284` + +## Changelog Entry + +### Fixed + +- **Smoke-test deploy commit no longer references non-local issue IDs** ([#284](https://github.com/vig-os/devcontainer/issues/284)) + - `assets/smoke-test/.github/workflows/repository-dispatch.yml` no longer injects `Refs: #258` into automated `chore: deploy ` commits in the smoke-test repository + - Added maintainer note that workflow-template changes require manual redeploy to `vig-os/devcontainer-smoke-test` and promotion through PRs to `main` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +Follow-up enhancement tracked in `#289` for richer dispatch payload metadata and downstream completion reporting. + +Refs: #284 + + + +--- +--- + +## Commits + +### Commit 1: [8e0ddd5](https://github.com/vig-os/devcontainer/commit/8e0ddd543d5fe3d8ab88e0f88d13ef8a8dcf787a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:57 AM +fix(ci): remove invalid issue refs from smoke deploy commit, 5 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 2: [9c1f401](https://github.com/vig-os/devcontainer/commit/9c1f4013ff4353dfda368ae7e6a6c899dea03cc8) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 11:02 AM +docs(ci): record smoke-test dispatch reference fix, 3 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-292.md b/docs/pull-requests/pr-292.md new file mode 100644 index 00000000..303fa0e6 --- /dev/null +++ b/docs/pull-requests/pr-292.md @@ -0,0 +1,98 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/291-release-failed-automatic-rollback → release/0.3.0 +created: 2026-03-13T12:13:38Z +updated: 2026-03-13T12:21:11Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/292 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-13T12:21:09Z +synced: 2026-03-14T04:16:49.277Z +--- + +# [PR 292](https://github.com/vig-os/devcontainer/pull/292) fix(setup): normalize short name boundaries for package validity + +## Description + +Fixes a release-blocking edge case where sanitized project names could end with `_`, producing an invalid `pyproject.toml` package name during install/integration flows. Adds a regression test, applies boundary-safe normalization in both install paths, and records the fix in the changelog. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `tests/test_install_script.py` + - Added `test_dry_run_name_sanitization_trims_trailing_separator`. + - Verifies `--name "Install-Test-Project-"` becomes `install_test_project` (no trailing underscore). +- `install.sh` + - Updated `sanitize_name()` to collapse duplicate underscores, trim non-alphanumeric boundaries, and fallback to `project` when empty. +- `assets/init-workspace.sh` + - Mirrored the same short-name normalization to keep runtime initialization behavior consistent with install-time behavior. +- `CHANGELOG.md` + - Added a `### Fixed` entry for issue `#291` under the active `0.3.0` section. + +## Changelog Entry + +### Fixed +- **Install name sanitization trims invalid package boundaries** ([#291](https://github.com/vig-os/devcontainer/issues/291)) + - `install.sh` now normalizes sanitized project names to ensure they start/end with alphanumeric characters before passing `SHORT_NAME` + - `init-workspace.sh` mirrors the same normalization so generated `pyproject.toml` names cannot end with separators like `_` + +## Testing + +- [x] Tests pass locally (`just test`) + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +- Release failure reference: [run 23049118093](https://github.com/vig-os/devcontainer/actions/runs/23049118093), [job 66945611261](https://github.com/vig-os/devcontainer/actions/runs/23049118093/job/66945611261) +- Intended base branch for this fix PR: `release/0.3.0` + +Refs: #291 + + + +--- +--- + +## Commits + +### Commit 1: [377c7f4](https://github.com/vig-os/devcontainer/commit/377c7f4f549f484115cedbca4792aea85ee41469) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +test: add regression for trailing separator sanitization, 24 files modified (tests/test_install_script.py) + +### Commit 2: [fe7de78](https://github.com/vig-os/devcontainer/commit/fe7de786bd28b017edc50e9784836fb724538003) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +fix(setup): normalize short name boundaries for package validity, 8 files modified (assets/init-workspace.sh, install.sh) + +### Commit 3: [942cabf](https://github.com/vig-os/devcontainer/commit/942cabfe1d032aeb5498f98783b1402ca31b1dd7) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +docs: record short-name sanitization fix in changelog, 3 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-294.md b/docs/pull-requests/pr-294.md new file mode 100644 index 00000000..0e38a2b9 --- /dev/null +++ b/docs/pull-requests/pr-294.md @@ -0,0 +1,89 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/293-fix-dispatch-branch-reset → release/0.3.0 +created: 2026-03-13T13:01:34Z +updated: 2026-03-13T13:05:42Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/294 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-13T13:05:40Z +synced: 2026-03-14T04:16:48.492Z +--- + +# [PR 294](https://github.com/vig-os/devcontainer/pull/294) fix(ci): send typed force flag for smoke-test ref reset + +## Description + +Fixes the smoke-test dispatch redeploy failure when resetting an existing `chore/deploy-` branch. + +The GitHub API PATCH request in `repository-dispatch.yml` previously sent `force` as a string (`-f force=true`), which returns `HTTP 422` in the smoke-test repository. This updates the call to use a typed boolean flag. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Changed `gh api` PATCH argument from `-f force=true` to `-F force=true` + - Ensures `force` is sent as a boolean, matching GitHub API schema for ref updates + +## Changelog Entry + +No changelog needed. This is an internal CI workflow fix on a release bugfix branch, and issue `#293` explicitly sets changelog category to "No changelog needed". + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +Repro/error reference: https://github.com/vig-os/devcontainer-smoke-test/actions/runs/23051686417/job/66954252175 + +Refs: #293 + + + +--- +--- + +## Commits + +### Commit 1: [2df8539](https://github.com/vig-os/devcontainer/commit/2df853986b45704aa0bf79e0639dec95d6f11aff) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:59 PM +fix(ci): send typed force flag for ref reset, 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) diff --git a/docs/pull-requests/pr-297.md b/docs/pull-requests/pr-297.md new file mode 100644 index 00000000..268541be --- /dev/null +++ b/docs/pull-requests/pr-297.md @@ -0,0 +1,41 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/296-rc-tag-propagation → release/0.3.0 +created: 2026-03-13T13:33:23Z +updated: 2026-03-13T13:42:13Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/297 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-13T13:42:11Z +synced: 2026-03-14T04:16:47.737Z +--- + +# [PR 297](https://github.com/vig-os/devcontainer/pull/297) fix(ci): preserve RC publish tag across release artifacts + +## Summary +- Use `publish_version` (not base `version`) for build, test, and artifact naming in release candidate/final release image flow. +- Align downloaded artifact patterns and source image tags in publish step to the same `publish_version` value. +- Prevent RC smoke-test deploys from falling back to stable-only tags when validating container image availability. + +## Test plan +- [x] Pre-commit hooks pass for the workflow change. +- [ ] Run release workflow in `candidate` mode for `0.3.0` and verify generated smoke-test PR writes `.vig-os` with `DEVCONTAINER_VERSION=`. +- [ ] Verify smoke-test `CI (Container)` resolves and validates `ghcr.io/vig-os/devcontainer:` successfully. +- [ ] Run final release workflow and verify stable release path remains unchanged. + +Refs: #296 + + +--- +--- + +## Commits + +### Commit 1: [10fb6e8](https://github.com/vig-os/devcontainer/commit/10fb6e8806ab74d38a0fb394cbdbbcfdb5aea437) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:32 PM +fix(ci): use publish version tag across release artifacts, 16 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-298.md b/docs/pull-requests/pr-298.md new file mode 100644 index 00000000..b75c85a4 --- /dev/null +++ b/docs/pull-requests/pr-298.md @@ -0,0 +1,89 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/295-align-sync-main-to-dev-checkout-pin → release/0.3.0 +created: 2026-03-13T13:39:06Z +updated: 2026-03-13T13:47:17Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/298 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-13T13:47:15Z +synced: 2026-03-14T04:16:47.000Z +--- + +# [PR 298](https://github.com/vig-os/devcontainer/pull/298) chore(ci): align sync-main-to-dev checkout pin with repository standard + +## Description + +Align `actions/checkout` pin in the sync-main-to-dev workflow to the repository-standard SHA (`v6.0.2`) to keep CI action pinning consistent. +This updates both the source workflow and its mirrored workspace asset with no behavioral refactor. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [x] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/sync-main-to-dev.yml` + - Updated both `actions/checkout` steps from `34e114876b0b11c390a56381ad16ebd13914f8d5 # v4` to `de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`. +- `assets/workspace/.github/workflows/sync-main-to-dev.yml` + - Mirrored the same two pin updates to keep generated/workspace assets aligned. + +## Changelog Entry + +No changelog needed: this is an internal `chore(ci)` pin-alignment change with no user-facing impact. + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #295 + + + +--- +--- + +## Commits + +### Commit 1: [b7ea79b](https://github.com/vig-os/devcontainer/commit/b7ea79bda807c75d7e82ba9903cd31bc69a47d4a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:37 PM +chore(ci): align sync-main-to-dev checkout pin with repository standard, 8 files modified (.github/workflows/sync-main-to-dev.yml, assets/workspace/.github/workflows/sync-main-to-dev.yml) diff --git a/docs/pull-requests/pr-301.md b/docs/pull-requests/pr-301.md new file mode 100644 index 00000000..3c602d9b --- /dev/null +++ b/docs/pull-requests/pr-301.md @@ -0,0 +1,166 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/sync-main-to-dev-1-1 → dev +created: 2026-03-13T16:26:36Z +updated: 2026-03-13T16:33:38Z +author: vig-os-release-app[bot] +author_url: https://github.com/vig-os-release-app[bot] +url: https://github.com/vig-os/devcontainer/pull/301 +comments: 0 +labels: merge-conflict +assignees: none +milestone: none +projects: none +merged: 2026-03-13T16:33:36Z +synced: 2026-03-14T04:16:46.342Z +--- + +# [PR 301](https://github.com/vig-os/devcontainer/pull/301) chore: sync dev with main (conflicts) + +Automated sync of `main` to `dev` found **merge conflicts** that require manual resolution. + +### How to resolve + +```bash +git fetch origin chore/sync-main-to-dev-1-1:chore/sync-main-to-dev-1-1 +git checkout chore/sync-main-to-dev-1-1 +git merge origin/dev +# resolve conflicts +git commit -S +git push origin chore/sync-main-to-dev-1-1 +``` + +Once pushed, this PR will update and become mergeable. + + +--- +--- + +## Commits + +### Commit 1: [b4b3736](https://github.com/vig-os/devcontainer/commit/b4b37368d09676557124d57ba9d3da561f1d6321) by [commit-action-bot[bot]](https://github.com/apps/commit-action-bot) on March 12, 2026 at 12:07 PM +chore: prepare release 0.3.0, 14 files modified (CHANGELOG.md) + +### Commit 2: [8f28283](https://github.com/vig-os/devcontainer/commit/8f28283d1ccca1e684abb99b867a3ef47186490b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:22 PM +test(setup): add regression tests for TBD changelog entries, 40 files modified (tests/test_utils.py) + +### Commit 3: [09e6f51](https://github.com/vig-os/devcontainer/commit/09e6f51891ede39df10de02d9008842448ba308c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:23 PM +fix(setup): skip TBD changelog entries in doc version parsing, 9 files modified (docs/generate.py) + +### Commit 4: [cea91db](https://github.com/vig-os/devcontainer/commit/cea91db648ec318016aafe99b1070047f5990d3d) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:24 PM +docs(setup): record changelog parser release-branch fix, 2 files modified (CHANGELOG.md) + +### Commit 5: [0600907](https://github.com/vig-os/devcontainer/commit/0600907a4d5ac92cb1b799bebe3201247dc7c60c) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 12:47 PM +fix: skip TBD changelog entries in docs generator (#273), 51 files modified (CHANGELOG.md, docs/generate.py, tests/test_utils.py) + +### Commit 6: [8b7edcf](https://github.com/vig-os/devcontainer/commit/8b7edcf96da106ca3dadc78477845e39ae5dbeb7) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:01 PM +fix(setup): remove unsafe github.com substring assertions, 22 files modified (tests/test_integration.py) + +### Commit 7: [4ae86b5](https://github.com/vig-os/devcontainer/commit/4ae86b596552e745cb92d7e0816a501961aafa10) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:12 PM +fix: remove unsafe github.com substring assertions (#275), 22 files modified (tests/test_integration.py) + +### Commit 8: [8172fd7](https://github.com/vig-os/devcontainer/commit/8172fd77c6565c0db14c7dd3305f054801501c03) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:23 PM +ci: use compliant title for release PRs, 2 files modified (.github/workflows/prepare-release.yml) + +### Commit 9: [e377fd6](https://github.com/vig-os/devcontainer/commit/e377fd6926961146a9d46d6a85ac32f6bb2c3239) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:25 PM +ci: use compliant title for release PRs (#277), 2 files modified (.github/workflows/prepare-release.yml) + +### Commit 10: [e99f882](https://github.com/vig-os/devcontainer/commit/e99f882990bb74dbe60123a19ed90b831894de9f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:43 PM +test: add failing tests for fingerprint plain-prose false positive, 107 files modified (packages/vig-utils/tests/test_utils.py) + +### Commit 11: [892162e](https://github.com/vig-os/devcontainer/commit/892162ef25abdfc2aca392955f401b3e7d742545) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:46 PM +fix: restrict fingerprint name matching to attribution contexts, 43 files modified (CHANGELOG.md, packages/vig-utils/src/vig_utils/utils.py) + +### Commit 12: [e884ca0](https://github.com/vig-os/devcontainer/commit/e884ca064faefc8999049991e032004674929244) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 01:55 PM +ci: pass blocklist TOML to validate-commit-msg in PR title check, 3 files modified (.github/workflows/pr-title-check.yml) + +### Commit 13: [22d3422](https://github.com/vig-os/devcontainer/commit/22d34222f242d3b6e06ee084af639f7cf9e98350) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:01 PM +fix: drop bare bot/agent/assistant from attribution context regex, 17 files modified (packages/vig-utils/src/vig_utils/utils.py, packages/vig-utils/tests/test_utils.py) + +### Commit 14: [382fc6c](https://github.com/vig-os/devcontainer/commit/382fc6cd6adbc1d98ff9614414aa605bd427877f) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:02 PM +fix: PR fingerprint check blocks plain-text mentions of Cursor/Copilot (#279), 166 files modified (.github/workflows/pr-title-check.yml, CHANGELOG.md, packages/vig-utils/src/vig_utils/utils.py, packages/vig-utils/tests/test_utils.py) + +### Commit 15: [7ac7d5d](https://github.com/vig-os/devcontainer/commit/7ac7d5dd46fc9591b249365ccfd9d4e154fc919b) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:06 PM +chore(ci): sync sync-main-to-dev workflow in manifest, 241 files modified (CHANGELOG.md, assets/workspace/.github/workflows/sync-main-to-dev.yml, scripts/manifest.toml) + +### Commit 16: [2e9ee18](https://github.com/vig-os/devcontainer/commit/2e9ee182008a7745ef1fc0b559649c7f56039730) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 02:12 PM +chore(ci): sync sync-main-to-dev workflow in manifest (#280), 241 files modified (CHANGELOG.md, assets/workspace/.github/workflows/sync-main-to-dev.yml, scripts/manifest.toml) + +### Commit 17: [d7e20fb](https://github.com/vig-os/devcontainer/commit/d7e20fbfe899fa06a31399bd5d4e2de071643c54) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:31 PM +fix(ci): retag loaded candidate images before push, 5 files modified (.github/workflows/release.yml, CHANGELOG.md) + +### Commit 18: [619cc84](https://github.com/vig-os/devcontainer/commit/619cc840538e069f7561c06a984db6e681e31282) by [c-vigo](https://github.com/c-vigo) on March 12, 2026 at 05:38 PM +fix(ci): retag loaded candidate images before push (#282), 5 files modified (.github/workflows/release.yml, CHANGELOG.md) + +### Commit 19: [2daaa78](https://github.com/vig-os/devcontainer/commit/2daaa78a93d0efaceb2e606d06eff5644202fc50) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:35 AM +test: add regression coverage for safe vig-os parsing, 84 files modified (tests/test_integration.py) + +### Commit 20: [15f3baf](https://github.com/vig-os/devcontainer/commit/15f3bafca7a1944316567a0fad56190912619846) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 08:38 AM +refactor: parse vig-os as data in init and version check, 57 files modified (assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh) + +### Commit 21: [b333e9d](https://github.com/vig-os/devcontainer/commit/b333e9de2ac93e8a34ac49418cfbb6ba1edd4522) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:12 AM +test(setup): restore .vig-os after mutation regression tests, 138 files modified (tests/test_integration.py) + +### Commit 22: [4e1eb91](https://github.com/vig-os/devcontainer/commit/4e1eb91292c509d337f49a8bcf1e6e078c36acd4) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 09:27 AM +test: make IN_CONTAINER=true hook tests deterministic, 6 files modified (tests/bats/githooks.bats) + +### Commit 23: [b876687](https://github.com/vig-os/devcontainer/commit/b876687c1a46bada939af32908c72cb68ea85ede) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:16 AM +fix: guard unset line in initialize vig-os parser loop, 2 files modified (assets/workspace/.devcontainer/scripts/initialize.sh) + +### Commit 24: [bf0cab0](https://github.com/vig-os/devcontainer/commit/bf0cab08d12c27d9004ad599aa788dcf804d2d90) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:22 AM +refactor: parse .vig-os config as data (#287), 169 files modified (assets/workspace/.devcontainer/scripts/initialize.sh, assets/workspace/.devcontainer/scripts/version-check.sh, tests/bats/githooks.bats, tests/test_integration.py) + +### Commit 25: [d9785c4](https://github.com/vig-os/devcontainer/commit/d9785c421bcad28ab66b1853937b0e0cf1375d69) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:36 AM +chore(ci): bump commit-action pins to v0.1.5, 19 files modified + +### Commit 26: [8632134](https://github.com/vig-os/devcontainer/commit/8632134138b02735752671cba72cd956404462e4) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:47 AM +chore(ci): bump commit-action pins to v0.1.5 (#288), 19 files modified + +### Commit 27: [8e0ddd5](https://github.com/vig-os/devcontainer/commit/8e0ddd543d5fe3d8ab88e0f88d13ef8a8dcf787a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 10:57 AM +fix(ci): remove invalid issue refs from smoke deploy commit, 5 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 28: [9c1f401](https://github.com/vig-os/devcontainer/commit/9c1f4013ff4353dfda368ae7e6a6c899dea03cc8) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 11:02 AM +docs(ci): record smoke-test dispatch reference fix, 3 files modified (CHANGELOG.md) + +### Commit 29: [2ead98c](https://github.com/vig-os/devcontainer/commit/2ead98c1ec8d379c3ee2f86f9084e42e532694a3) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 11:17 AM +fix: remove invalid issue refs from smoke-test deploy commit (#290), 8 files modified (CHANGELOG.md, assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 30: [377c7f4](https://github.com/vig-os/devcontainer/commit/377c7f4f549f484115cedbca4792aea85ee41469) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +test: add regression for trailing separator sanitization, 24 files modified (tests/test_install_script.py) + +### Commit 31: [fe7de78](https://github.com/vig-os/devcontainer/commit/fe7de786bd28b017edc50e9784836fb724538003) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +fix(setup): normalize short name boundaries for package validity, 8 files modified (assets/init-workspace.sh, install.sh) + +### Commit 32: [942cabf](https://github.com/vig-os/devcontainer/commit/942cabfe1d032aeb5498f98783b1402ca31b1dd7) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:05 PM +docs: record short-name sanitization fix in changelog, 3 files modified (CHANGELOG.md) + +### Commit 33: [be28810](https://github.com/vig-os/devcontainer/commit/be28810b3e69ad38232f1c270ffa28d64feca69f) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:21 PM +fix(setup): normalize short name boundaries for package validity (#292), 35 files modified (CHANGELOG.md, assets/init-workspace.sh, install.sh, tests/test_install_script.py) + +### Commit 34: [2df8539](https://github.com/vig-os/devcontainer/commit/2df853986b45704aa0bf79e0639dec95d6f11aff) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 12:59 PM +fix(ci): send typed force flag for ref reset, 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 35: [5163a01](https://github.com/vig-os/devcontainer/commit/5163a0158275e861295e07221777b4df12a8c7ea) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:05 PM +fix(ci): send typed force flag for smoke-test ref reset (#294), 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 36: [10fb6e8](https://github.com/vig-os/devcontainer/commit/10fb6e8806ab74d38a0fb394cbdbbcfdb5aea437) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:32 PM +fix(ci): use publish version tag across release artifacts, 16 files modified (.github/workflows/release.yml) + +### Commit 37: [b7ea79b](https://github.com/vig-os/devcontainer/commit/b7ea79bda807c75d7e82ba9903cd31bc69a47d4a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:37 PM +chore(ci): align sync-main-to-dev checkout pin with repository standard, 8 files modified (.github/workflows/sync-main-to-dev.yml, assets/workspace/.github/workflows/sync-main-to-dev.yml) + +### Commit 38: [fe3489b](https://github.com/vig-os/devcontainer/commit/fe3489b7074fc0a10a5211c6b4ee94c471011a1b) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:42 PM +fix(ci): preserve RC publish tag across release artifacts (#297), 16 files modified (.github/workflows/release.yml) + +### Commit 39: [b21036f](https://github.com/vig-os/devcontainer/commit/b21036f855b2bac40eef8b19712f2ff68043d601) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 01:47 PM +chore(ci): align sync-main-to-dev checkout pin with repository standard (#298), 8 files modified (.github/workflows/sync-main-to-dev.yml, assets/workspace/.github/workflows/sync-main-to-dev.yml) + +### Commit 40: [3656e13](https://github.com/vig-os/devcontainer/commit/3656e13cec1bd1e162a5df22080620193df3f1dd) by [vig-os-release-app[bot]](https://github.com/apps/vig-os-release-app) on March 13, 2026 at 03:32 PM +chore: finalize release 0.3.0, 2 files modified (CHANGELOG.md) + +### Commit 41: [0217d7d](https://github.com/vig-os/devcontainer/commit/0217d7d1b25d82b9ab412e93b24d2d9d8f2b858a) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 04:12 PM +chore: update release links for 0.3.0, 4 files modified (CHANGELOG.md, README.md) + +### Commit 42: [6c8eb6b](https://github.com/vig-os/devcontainer/commit/6c8eb6b8b2dbb95b35c2ad91a005f0fc8b3a8ea5) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 04:26 PM +chore: release 0.3.0 (#270), 67538 files modified diff --git a/docs/pull-requests/pr-302.md b/docs/pull-requests/pr-302.md new file mode 100644 index 00000000..a589d10e --- /dev/null +++ b/docs/pull-requests/pr-302.md @@ -0,0 +1,106 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/npm_and_yarn/dev/devcontainers/cli-0.84.0 → dev +created: 2026-03-13T16:27:10Z +updated: 2026-03-13T17:27:10Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/302 +comments: 0 +labels: dependencies, javascript +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:44.501Z +--- + +# [PR 302](https://github.com/vig-os/devcontainer/pull/302) build(deps): bump @devcontainers/cli from 0.81.1 to 0.84.0 + +Bumps [@devcontainers/cli](https://github.com/devcontainers/cli) from 0.81.1 to 0.84.0. +
+Changelog +

Sourced from @​devcontainers/cli's changelog.

+
+

[0.84.0]

+ +

February 2026

+

[0.83.3]

+ +

[0.83.2]

+ +

[0.83.1]

+ +

[0.83.0]

+ +

January 2026

+

[0.82.0]

+
    +
  • devcontainer commands now use current directory as default workspace folder when not specified (devcontainers/cli#1104)
  • +
+
+
+
+Commits + +
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@devcontainers/cli&package-manager=npm_and_yarn&previous-version=0.81.1&new-version=0.84.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [70b38f9](https://github.com/vig-os/devcontainer/commit/70b38f91c51e1e850d7da0dd699ba1d4b764eaa7) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:27 PM +build(deps): bump @devcontainers/cli from 0.81.1 to 0.84.0, 12 files modified (package-lock.json, package.json) diff --git a/docs/pull-requests/pr-303.md b/docs/pull-requests/pr-303.md new file mode 100644 index 00000000..80365f4b --- /dev/null +++ b/docs/pull-requests/pr-303.md @@ -0,0 +1,100 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/npm_and_yarn/dev/bats-assert-v2.2.4 → dev +created: 2026-03-13T16:27:20Z +updated: 2026-03-13T17:27:11Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/303 +comments: 0 +labels: dependencies, javascript +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:43.787Z +--- + +# [PR 303](https://github.com/vig-os/devcontainer/pull/303) build(deps): bump bats-assert from v2.2.0 to v2.2.4 + +Bumps [bats-assert](https://github.com/bats-core/bats-assert) from v2.2.0 to v2.2.4. +
+Release notes +

Sourced from bats-assert's releases.

+
+

v2.2.4

+

What's Changed

+ +

Full Changelog: https://github.com/bats-core/bats-assert/compare/v2.2.3...v2.2.4

+

v2.2.3

+

What's Changed

+ +

Full Changelog: https://github.com/bats-core/bats-assert/compare/v2.2.2...v2.2.3

+

v2.2.2

+

What's Changed

+ +

Full Changelog: https://github.com/bats-core/bats-assert/compare/v2.2.1...v2.2.2

+

v2.2.1

+

What's Changed

+ +

Full Changelog: https://github.com/bats-core/bats-assert/compare/v2.2.0...v2.2.1

+
+
+
+Commits +
    +
  • f1e9280 2.2.4
  • +
  • 6257ebe Merge pull request #89 from bats-core/fix/make-invalid-regex-tests-less-picky
  • +
  • a392cf6 fix: too brittle invalid regex tests under bash 5.3
  • +
  • c6ccf04 2.2.3
  • +
  • fdb153e Merge pull request #86 from bats-core/version-docs
  • +
  • 21916fb Move files for humans to non-hidden docs/ dir
  • +
  • 36603d3 Improve the release process documentation
  • +
  • 1ba47b9 Prepare v2.2.2
  • +
  • 1ffa732 Merge pull request #85 from bats-core/fix/test-bash-version
  • +
  • d1e608f fix syntax error on bash <5.2
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [3db503c](https://github.com/vig-os/devcontainer/commit/3db503c018b19cd3f83caaab9df80d865a73db37) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:27 PM +build(deps): bump bats-assert from v2.2.0 to v2.2.4, 9 files modified (package-lock.json, package.json) diff --git a/docs/pull-requests/pr-304.md b/docs/pull-requests/pr-304.md new file mode 100644 index 00000000..9fe4f458 --- /dev/null +++ b/docs/pull-requests/pr-304.md @@ -0,0 +1,239 @@ +--- +type: pull_request +state: closed +branch: dependabot/github_actions/dev/actions-minor-patch-e9ddfccc4a → dev +created: 2026-03-13T16:28:20Z +updated: 2026-03-13T16:36:07Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/304 +comments: 1 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +synced: 2026-03-14T04:16:43.073Z +--- + +# [PR 304](https://github.com/vig-os/devcontainer/pull/304) ci(deps): bump the actions-minor-patch group with 3 updates + +Bumps the actions-minor-patch group with 3 updates: [vig-os/commit-action](https://github.com/vig-os/commit-action), [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) and [anchore/sbom-action](https://github.com/anchore/sbom-action). + +Updates `vig-os/commit-action` from 0.1.3 to 0.1.5 +
+Release notes +

Sourced from vig-os/commit-action's releases.

+
+

v0.1.5

+

Fixed

+
    +
  • Excluded .git metadata paths during FILE_PATHS directory expansion to prevent malformed Git tree paths (issue #15).
  • +
+

v0.1.4

+

Added

+
    +
  • Added ALLOW_EMPTY environment variable support to allow creating signed empty commits when no file changes are detected
  • +
  • Added unit test coverage for empty commit behavior in commitViaAPI() and commit-runner flow handling
  • +
+

Changed

+
    +
  • Updated npm dependency overrides to force patched minimatch versions across transitive dependency trees.
  • +
  • Updated commitViaAPI() to support empty commits by reusing the parent tree SHA when ALLOW_EMPTY=true
  • +
  • Updated runner behavior to preserve default no-op behavior when no files are detected, unless ALLOW_EMPTY=true
  • +
  • Updated README usage examples and environment variable documentation for ALLOW_EMPTY
  • +
  • Replaced process.exit(0) with early return in runner for improved testability
  • +
+

Fixed

+
    +
  • Fixed missing ALLOW_EMPTY in commit-runner environment variable documentation
  • +
+

Security

+
    +
  • Fixed minimatch ReDoS vulnerabilities (CVE-2026-27903 / GHSA-7r86-cg39-jmmj) by pinning safe transitive versions via npm overrides.
  • +
+
+
+
+Changelog +

Sourced from vig-os/commit-action's changelog.

+
+

Changelog

+

All notable changes to this project will be documented in this file.

+

The format is based on Keep a Changelog, +and this project adheres to Semantic Versioning.

+

v0.1.5 - 2026-03-13

+

Fixed

+
    +
  • Excluded .git metadata paths during FILE_PATHS directory expansion to prevent malformed Git tree paths (issue #15).
  • +
+

v0.1.4 - 2026-03-11

+

Added

+
    +
  • Added ALLOW_EMPTY environment variable support to allow creating signed empty commits when no file changes are detected
  • +
  • Added unit test coverage for empty commit behavior in commitViaAPI() and commit-runner flow handling
  • +
+

Changed

+
    +
  • Updated npm dependency overrides to force patched minimatch versions across transitive dependency trees.
  • +
  • Updated commitViaAPI() to support empty commits by reusing the parent tree SHA when ALLOW_EMPTY=true
  • +
  • Updated runner behavior to preserve default no-op behavior when no files are detected, unless ALLOW_EMPTY=true
  • +
  • Updated README usage examples and environment variable documentation for ALLOW_EMPTY
  • +
  • Replaced process.exit(0) with early return in runner for improved testability
  • +
+

Fixed

+
    +
  • Fixed missing ALLOW_EMPTY in commit-runner environment variable documentation
  • +
+

Security

+
    +
  • Fixed minimatch ReDoS vulnerabilities (CVE-2026-27903 / GHSA-7r86-cg39-jmmj) by pinning safe transitive versions via npm overrides.
  • +
+

v0.1.3 - 2026-01-28

+

Added

+
    +
  • Added TARGET_BRANCH environment variable support to avoid conflicts with GitHub's built-in GITHUB_REF
  • +
  • Added normalizeBranch() and resolveBranch() exported functions for branch resolution logic
  • +
  • Added comprehensive test suite for branch normalization and resolution (commit-runner.test.ts)
  • +
+

Changed

+
    +
  • Improved branch resolution logic with explicit priority: TARGET_BRANCH > GITHUB_REF (if different from context) > workflow context
  • +
  • Refactored branch resolution into testable exported functions
  • +
+

Fixed

+ +
+

... (truncated)

+
+
+Commits +
    +
  • c0024cb release: Release 0.1.5
  • +
  • 3d6eb3a chore: bump action version to 0.1.5
  • +
  • 0e52494 chore: prepare CHANGELOG for release 0.1.5
  • +
  • ef2addf fix: exclude .git paths when expanding FILE_PATHS directories
  • +
  • 873cb5f build(dist): refresh compiled commit-runner test artifacts
  • +
  • f2baddf docs(readme): clarify direct .git FILE_PATHS entries are ignored
  • +
  • a93945c test(commit-runner): cover direct .git FILE_PATHS and remove require usage
  • +
  • 0cc2e65 build(dist): re-bundle artifacts for issue 15 fix
  • +
  • 7974cb6 docs(changelog): document issue 15 .git exclusion behavior
  • +
  • 74d2d5f fix(commit-runner): skip .git metadata during FILE_PATHS expansion
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ +Updates `sigstore/cosign-installer` from 4.0.0 to 4.1.0 +
+Release notes +

Sourced from sigstore/cosign-installer's releases.

+
+

v4.1.0

+

What's Changed

+

We recommend updating as soon as possible as this includes bug fixes for Cosign. We also recommend removing with: cosign-release and strongly discourage using cosign-release unless you have a specific reason to use an older version of Cosign.

+ +

Full Changelog: https://github.com/sigstore/cosign-installer/compare/v4.0.0...v4.1.0

+
+
+
+Commits + +
+
+ +Updates `anchore/sbom-action` from 0.22.2 to 0.23.1 +
+Release notes +

Sourced from anchore/sbom-action's releases.

+
+

v0.23.1

+

⬆️ Dependencies

+ +

v0.23.0

+ +
+
+
+Commits +
    +
  • 57aae52 chore(deps): update Syft to v1.42.2 (#607)
  • +
  • c29e913 chore(deps): bump fast-xml-parser and other deps (#604)
  • +
  • 17ae174 chore(deps/test): move to es modules, node:test, single dist file (#595)
  • +
  • 6d473d3 chore(deps): update Syft to v1.42.1 (#599)
  • +
  • 60619e7 fix tests and bump fast-xml-parser (#598)
  • +
  • e2bd58a chore(deps-dev): bump the dev-dependencies group with 3 updates (#592)
  • +
  • d032d7d ci(syft auto update): npm ci, not npm install (#597)
  • +
  • 2d09430 fix(dev): switch to esbuild (#590)
  • +
  • 74c5ce9 chore(deps): update Syft to v1.42.0 (#589)
  • +
  • 77fae5a chore(deps-dev): bump the dev-dependencies group with 4 updates (#583)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) +- `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) +- `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) +- `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency +- `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions + + +
+ + +--- +--- + +# Comments (1) + +## [Comment #1](https://github.com/vig-os/devcontainer/pull/304#issuecomment-4056431054) by [@dependabot[bot]](https://github.com/apps/dependabot) + +_Posted on March 13, 2026 at 04:35 PM_ + +Looks like these dependencies are updatable in another way, so this is no longer needed. + +--- +--- + +## Commits + +### Commit 1: [447435a](https://github.com/vig-os/devcontainer/commit/447435aaec38cc3b2c993a3188bd2d278c909b68) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:28 PM +ci(deps): bump the actions-minor-patch group with 3 updates, 14 files modified (.github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/sync-issues.yml) diff --git a/docs/pull-requests/pr-305.md b/docs/pull-requests/pr-305.md new file mode 100644 index 00000000..6dee981d --- /dev/null +++ b/docs/pull-requests/pr-305.md @@ -0,0 +1,124 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/download-artifact-8.0.1 → dev +created: 2026-03-13T16:28:26Z +updated: 2026-03-13T17:27:10Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/305 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:42.077Z +--- + +# [PR 305](https://github.com/vig-os/devcontainer/pull/305) ci(deps): bump actions/download-artifact from 4.3.0 to 8.0.1 + +Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1. +
+Release notes +

Sourced from actions/download-artifact's releases.

+
+

v8.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/actions/download-artifact/compare/v8...v8.0.1

+

v8.0.0

+

v8 - What's new

+
+

[!IMPORTANT] +actions/download-artifact@v8 has been migrated to an ESM module. This should be transparent to the caller but forks might need to make significant changes.

+
+
+

[!IMPORTANT] +Hash mismatches will now error by default. Users can override this behavior with a setting change (see below).

+
+

Direct downloads

+

To support direct uploads in actions/upload-artifact, the action will no longer attempt to unzip all downloaded files. Instead, the action checks the Content-Type header ahead of unzipping and skips non-zipped files. Callers wishing to download a zipped file as-is can also set the new skip-decompress parameter to true.

+

Enforced checks (breaking)

+

A previous release introduced digest checks on the download. If a download hash didn't match the expected hash from the server, the action would log a warning. Callers can now configure the behavior on mismatch with the digest-mismatch parameter. To be secure by default, we are now defaulting the behavior to error which will fail the workflow run.

+

ESM

+

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

+

What's Changed

+ +

Full Changelog: https://github.com/actions/download-artifact/compare/v7...v8.0.0

+

v7.0.0

+

v7 - What's new

+
+

[!IMPORTANT] +actions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

+
+

Node.js 24

+

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

+

What's Changed

+ + +
+

... (truncated)

+
+
+Commits +
    +
  • 3e5f45b Add regression tests for CJK characters (#471)
  • +
  • e6d03f6 Add a regression test for artifact name + content-type mismatches (#472)
  • +
  • 70fc10c Merge pull request #461 from actions/danwkennedy/digest-mismatch-behavior
  • +
  • f258da9 Add change docs
  • +
  • ccc058e Fix linting issues
  • +
  • bd7976b Add a setting to specify what to do on hash mismatch and default it to error
  • +
  • ac21fcf Merge pull request #460 from actions/danwkennedy/download-no-unzip
  • +
  • 15999bf Add note about package bumps
  • +
  • 974686e Bump the version to v8 and add release notes
  • +
  • fbe48b1 Update test names to make it clearer what they do
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=4.3.0&new-version=8.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [eda87b5](https://github.com/vig-os/devcontainer/commit/eda87b52c1de1d79053c7263d878274886bbce5f) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:34 PM +ci(deps): bump actions/download-artifact from 4.3.0 to 8.0.1, 10 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) diff --git a/docs/pull-requests/pr-306.md b/docs/pull-requests/pr-306.md new file mode 100644 index 00000000..0845c79a --- /dev/null +++ b/docs/pull-requests/pr-306.md @@ -0,0 +1,92 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/github-script-8.0.0 → dev +created: 2026-03-13T16:28:31Z +updated: 2026-03-13T17:27:11Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/306 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:41.296Z +--- + +# [PR 306](https://github.com/vig-os/devcontainer/pull/306) ci(deps): bump actions/github-script from 7.1.0 to 8.0.0 + +Bumps [actions/github-script](https://github.com/actions/github-script) from 7.1.0 to 8.0.0. +
+Release notes +

Sourced from actions/github-script's releases.

+
+

v8.0.0

+

What's Changed

+ +

⚠️ Minimum Compatible Runner Version

+

v2.327.1
+Release Notes

+

Make sure your runner is updated to this version or newer to use this release.

+

New Contributors

+ +

Full Changelog: https://github.com/actions/github-script/compare/v7.1.0...v8.0.0

+
+
+
+Commits +
    +
  • ed59741 Merge pull request #653 from actions/sneha-krip/readme-for-v8
  • +
  • 2dc352e Bold minimum Actions Runner version in README
  • +
  • 01e118c Update README for Node 24 runtime requirements
  • +
  • 8b222ac Apply suggestion from @​salmanmkc
  • +
  • adc0eea README for updating actions/github-script from v7 to v8
  • +
  • 20fe497 Merge pull request #637 from actions/node24
  • +
  • e7b7f22 update licenses
  • +
  • 2c81ba0 Update Node.js version support to 24.x
  • +
  • See full diff in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=7.1.0&new-version=8.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [b862228](https://github.com/vig-os/devcontainer/commit/b862228df898d2a8b2e9f0ad3c9dfcb6a023cdf0) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:28 PM +ci(deps): bump actions/github-script from 7.1.0 to 8.0.0, 2 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-307.md b/docs/pull-requests/pr-307.md new file mode 100644 index 00000000..fb5a393a --- /dev/null +++ b/docs/pull-requests/pr-307.md @@ -0,0 +1,131 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/attest-build-provenance-4.1.0 → dev +created: 2026-03-13T16:28:35Z +updated: 2026-03-13T17:27:11Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/307 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:40.615Z +--- + +# [PR 307](https://github.com/vig-os/devcontainer/pull/307) ci(deps): bump actions/attest-build-provenance from 3.0.0 to 4.1.0 + +Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3.0.0 to 4.1.0. +
+Release notes +

Sourced from actions/attest-build-provenance's releases.

+
+

v4.1.0

+
+

[!NOTE] +As of version 4, actions/attest-build-provenance is simply a wrapper on top of actions/attest.

+

Existing applications may continue to use the attest-build-provenance action, but new implementations should use actions/attest instead.

+
+

What's Changed

+ +

Full Changelog: https://github.com/actions/attest-build-provenance/compare/v4.0.0...v4.1.0

+

v4.0.0

+
+

[!NOTE] +As of version 4, actions/attest-build-provenance is simply a wrapper on top of actions/attest.

+

Existing applications may continue to use the attest-build-provenance action, but new implementations should use actions/attest instead.

+
+

What's Changed

+ +

Full Changelog: https://github.com/actions/attest-build-provenance/compare/v3.2.0...v4.0.0

+

v3.2.0

+

What's Changed

+ +

Full Changelog: https://github.com/actions/attest-build-provenance/compare/v3.1.0...v3.2.0

+

v3.1.0

+

What's Changed

+ +

New Contributors

+ +
+

... (truncated)

+
+
+Commits +
    +
  • a2bbfa2 bump actions/attest from 4.0.0 to 4.1.0 (#838)
  • +
  • 0856891 update RELEASE.md docs (#836)
  • +
  • e4d4f7c prepare v4 release (#835)
  • +
  • 02a49bd Bump github/codeql-action in the actions-minor group (#824)
  • +
  • 7c757df Bump the npm-development group with 2 updates (#825)
  • +
  • c44148e Bump github/codeql-action in the actions-minor group (#818)
  • +
  • 3234352 Bump @​types/node from 25.0.10 to 25.2.0 in the npm-development group (#819)
  • +
  • 18db129 Bump tar from 7.5.6 to 7.5.7 (#816)
  • +
  • 90fadfa Bump @​actions/core from 2.0.1 to 2.0.2 in the npm-production group (#799)
  • +
  • 57db8ba Bump the npm-development group across 1 directory with 3 updates (#808)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/attest-build-provenance&package-manager=github_actions&previous-version=3.0.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [50debdc](https://github.com/vig-os/devcontainer/commit/50debdccec76fecc4b3014d8bc8f258b7604efee) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:28 PM +ci(deps): bump actions/attest-build-provenance from 3.0.0 to 4.1.0, 2 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-308.md b/docs/pull-requests/pr-308.md new file mode 100644 index 00000000..a72f189f --- /dev/null +++ b/docs/pull-requests/pr-308.md @@ -0,0 +1,192 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/checkout-6.0.2 → dev +created: 2026-03-13T16:28:48Z +updated: 2026-03-13T17:27:10Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/308 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:39.878Z +--- + +# [PR 308](https://github.com/vig-os/devcontainer/pull/308) ci(deps): bump actions/checkout from 4.3.1 to 6.0.2 + +Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.1 to 6.0.2. +
+Release notes +

Sourced from actions/checkout's releases.

+
+

v6.0.2

+

What's Changed

+ +

Full Changelog: https://github.com/actions/checkout/compare/v6.0.1...v6.0.2

+

v6.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.1

+

v6.0.0

+

What's Changed

+ +

Full Changelog: https://github.com/actions/checkout/compare/v5.0.0...v6.0.0

+

v6-beta

+

What's Changed

+

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

+

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

+

v5.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/actions/checkout/compare/v5...v5.0.1

+

v5.0.0

+

What's Changed

+ +

⚠️ Minimum Compatible Runner Version

+

v2.327.1
+Release Notes

+ +
+

... (truncated)

+
+
+Changelog +

Sourced from actions/checkout's changelog.

+
+

Changelog

+

v6.0.2

+ +

v6.0.1

+ +

v6.0.0

+ +

v5.0.1

+ +

v5.0.0

+ +

v4.3.1

+ +

v4.3.0

+ +

v4.2.2

+ +

v4.2.1

+ +

v4.2.0

+ +

v4.1.7

+ +

v4.1.6

+ + +
+

... (truncated)

+
+
+Commits + +
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.3.1&new-version=6.0.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [d292e0a](https://github.com/vig-os/devcontainer/commit/d292e0aa90d5560df616069392999d9b13ff8f10) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:34 PM +ci(deps): bump actions/checkout from 4.3.1 to 6.0.2, 32 files modified (.github/workflows/ci.yml, .github/workflows/pr-title-check.yml, .github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) diff --git a/docs/pull-requests/pr-309.md b/docs/pull-requests/pr-309.md new file mode 100644 index 00000000..f8071135 --- /dev/null +++ b/docs/pull-requests/pr-309.md @@ -0,0 +1,125 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions-minor-patch-29bb5d88da → dev +created: 2026-03-13T16:36:03Z +updated: 2026-03-13T17:27:10Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/309 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:09Z +synced: 2026-03-14T04:16:39.086Z +--- + +# [PR 309](https://github.com/vig-os/devcontainer/pull/309) ci(deps): bump the actions-minor-patch group across 1 directory with 2 updates + +Bumps the actions-minor-patch group with 2 updates in the / directory: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) and [anchore/sbom-action](https://github.com/anchore/sbom-action). + +Updates `sigstore/cosign-installer` from 4.0.0 to 4.1.0 +
+Release notes +

Sourced from sigstore/cosign-installer's releases.

+
+

v4.1.0

+

What's Changed

+

We recommend updating as soon as possible as this includes bug fixes for Cosign. We also recommend removing with: cosign-release and strongly discourage using cosign-release unless you have a specific reason to use an older version of Cosign.

+ +

Full Changelog: https://github.com/sigstore/cosign-installer/compare/v4.0.0...v4.1.0

+
+
+
+Commits + +
+
+ +Updates `anchore/sbom-action` from 0.22.2 to 0.23.1 +
+Release notes +

Sourced from anchore/sbom-action's releases.

+
+

v0.23.1

+

⬆️ Dependencies

+ +

v0.23.0

+ +
+
+
+Commits +
    +
  • 57aae52 chore(deps): update Syft to v1.42.2 (#607)
  • +
  • c29e913 chore(deps): bump fast-xml-parser and other deps (#604)
  • +
  • 17ae174 chore(deps/test): move to es modules, node:test, single dist file (#595)
  • +
  • 6d473d3 chore(deps): update Syft to v1.42.1 (#599)
  • +
  • 60619e7 fix tests and bump fast-xml-parser (#598)
  • +
  • e2bd58a chore(deps-dev): bump the dev-dependencies group with 3 updates (#592)
  • +
  • d032d7d ci(syft auto update): npm ci, not npm install (#597)
  • +
  • 2d09430 fix(dev): switch to esbuild (#590)
  • +
  • 74c5ce9 chore(deps): update Syft to v1.42.0 (#589)
  • +
  • 77fae5a chore(deps-dev): bump the dev-dependencies group with 4 updates (#583)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) +- `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) +- `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) +- `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency +- `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions + + +
+ + +--- +--- + +## Commits + +### Commit 1: [348d5d9](https://github.com/vig-os/devcontainer/commit/348d5d9e69aa363760c33cece82d121557eb993b) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:36 PM +ci(deps): bump the actions-minor-patch group across 1 directory with 2 updates, 4 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-311.md b/docs/pull-requests/pr-311.md new file mode 100644 index 00000000..06b4a16f --- /dev/null +++ b/docs/pull-requests/pr-311.md @@ -0,0 +1,30 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/prepare-changelog → dev +created: 2026-03-13T16:42:19Z +updated: 2026-03-13T16:48:47Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/311 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-13T16:48:46Z +synced: 2026-03-14T04:16:38.373Z +--- + +# [PR 311](https://github.com/vig-os/devcontainer/pull/311) chore: add empty unreleased section to CHANGELOG + +Merging from `main` removed the `## Unreleased` section. + + +--- +--- + +## Commits + +### Commit 1: [037f3ac](https://github.com/vig-os/devcontainer/commit/037f3accd2cb703c07a7b7630ba5c3b493abbaeb) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 04:41 PM +chore: add empty unreleased section to CHANGELOG, 14 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-312.md b/docs/pull-requests/pr-312.md new file mode 100644 index 00000000..dfa5b801 --- /dev/null +++ b/docs/pull-requests/pr-312.md @@ -0,0 +1,88 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/dependabot-updates → dev +created: 2026-03-13T17:19:29Z +updated: 2026-03-13T17:27:03Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/312 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-13T17:27:01Z +synced: 2026-03-14T04:16:37.582Z +--- + +# [PR 312](https://github.com/vig-os/devcontainer/pull/312) chore: consolidate dependabot updates + +## Summary +- Consolidate Dependabot dependency updates from open PRs #302, #303, #305, #306, #307, #308, and #309 into a single branch based on `dev` +- Update `CHANGELOG.md` (`## Unreleased` -> `### Changed`) with one grouped entry referencing all merged Dependabot PRs +- Keep closed PR #304 out of scope because its actionable updates are already covered by #309/current `dev` state + +## Validation +- Ran `just build no_cache && just test` +- Result: success (command exited 0) + +## Includes +- #302 https://github.com/vig-os/devcontainer/pull/302 +- #303 https://github.com/vig-os/devcontainer/pull/303 +- #305 https://github.com/vig-os/devcontainer/pull/305 +- #306 https://github.com/vig-os/devcontainer/pull/306 +- #307 https://github.com/vig-os/devcontainer/pull/307 +- #308 https://github.com/vig-os/devcontainer/pull/308 +- #309 https://github.com/vig-os/devcontainer/pull/309 + + +--- +--- + +## Commits + +### Commit 1: [70b38f9](https://github.com/vig-os/devcontainer/commit/70b38f91c51e1e850d7da0dd699ba1d4b764eaa7) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:27 PM +build(deps): bump @devcontainers/cli from 0.81.1 to 0.84.0, 12 files modified (package-lock.json, package.json) + +### Commit 2: [3db503c](https://github.com/vig-os/devcontainer/commit/3db503c018b19cd3f83caaab9df80d865a73db37) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:27 PM +build(deps): bump bats-assert from v2.2.0 to v2.2.4, 9 files modified (package-lock.json, package.json) + +### Commit 3: [b862228](https://github.com/vig-os/devcontainer/commit/b862228df898d2a8b2e9f0ad3c9dfcb6a023cdf0) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:28 PM +ci(deps): bump actions/github-script from 7.1.0 to 8.0.0, 2 files modified (.github/workflows/release.yml) + +### Commit 4: [50debdc](https://github.com/vig-os/devcontainer/commit/50debdccec76fecc4b3014d8bc8f258b7604efee) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:28 PM +ci(deps): bump actions/attest-build-provenance from 3.0.0 to 4.1.0, 2 files modified (.github/workflows/release.yml) + +### Commit 5: [eda87b5](https://github.com/vig-os/devcontainer/commit/eda87b52c1de1d79053c7263d878274886bbce5f) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:34 PM +ci(deps): bump actions/download-artifact from 4.3.0 to 8.0.1, 10 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 6: [d292e0a](https://github.com/vig-os/devcontainer/commit/d292e0aa90d5560df616069392999d9b13ff8f10) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:34 PM +ci(deps): bump actions/checkout from 4.3.1 to 6.0.2, 32 files modified (.github/workflows/ci.yml, .github/workflows/pr-title-check.yml, .github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 7: [348d5d9](https://github.com/vig-os/devcontainer/commit/348d5d9e69aa363760c33cece82d121557eb993b) by [dependabot[bot]](https://github.com/apps/dependabot) on March 13, 2026 at 04:36 PM +ci(deps): bump the actions-minor-patch group across 1 directory with 2 updates, 4 files modified (.github/workflows/release.yml) + +### Commit 8: [87867a1](https://github.com/vig-os/devcontainer/commit/87867a1022315634f87c0054716dd954bf3121f5) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:08 PM +chore: merge dependabot pr 302, 12 files modified (package-lock.json, package.json) + +### Commit 9: [2beaf8e](https://github.com/vig-os/devcontainer/commit/2beaf8e6ceb434cea1ed77336173d0a327cb7cc6) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:08 PM +chore: merge dependabot pr 303, 9 files modified (package-lock.json, package.json) + +### Commit 10: [a1b9006](https://github.com/vig-os/devcontainer/commit/a1b90062926fecd36651c336bc7cf378ac34acae) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:08 PM +chore: merge dependabot pr 305, 10 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 11: [a219251](https://github.com/vig-os/devcontainer/commit/a219251f09a1f6e1cac45819213ead399e90d925) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:08 PM +chore: merge dependabot pr 306, 2 files modified (.github/workflows/release.yml) + +### Commit 12: [b380568](https://github.com/vig-os/devcontainer/commit/b380568cf6ec064d2f4044f6226cc5c7447d6619) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:09 PM +chore: merge dependabot pr 307, 2 files modified (.github/workflows/release.yml) + +### Commit 13: [486c43e](https://github.com/vig-os/devcontainer/commit/486c43ee523d85671edb89a6e2635bc0b2dcee0e) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:09 PM +chore: merge dependabot pr 308, 32 files modified (.github/workflows/ci.yml, .github/workflows/pr-title-check.yml, .github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 14: [e05b29a](https://github.com/vig-os/devcontainer/commit/e05b29a44c6c217642fdebfc8fd4ae3f71326270) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:09 PM +chore: merge dependabot pr 309, 4 files modified (.github/workflows/release.yml) + +### Commit 15: [2c1f4bb](https://github.com/vig-os/devcontainer/commit/2c1f4bbf5a675e259bcc59a263f01a76229b3df9) by [c-vigo](https://github.com/c-vigo) on March 13, 2026 at 05:17 PM +chore: update changelog for dependabot batch, 5 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-314.md b/docs/pull-requests/pr-314.md new file mode 100644 index 00000000..40f45dfe --- /dev/null +++ b/docs/pull-requests/pr-314.md @@ -0,0 +1,89 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/attest-sbom-4.0.0 → dev +created: 2026-03-16T02:28:42Z +updated: 2026-03-16T07:57:34Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/314 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-16T07:57:32Z +synced: 2026-03-17T04:24:23.066Z +--- + +# [PR 314](https://github.com/vig-os/devcontainer/pull/314) ci(deps): bump actions/attest-sbom from 3.0.0 to 4.0.0 + +Bumps [actions/attest-sbom](https://github.com/actions/attest-sbom) from 3.0.0 to 4.0.0. +
+Release notes +

Sourced from actions/attest-sbom's releases.

+
+

v4.0.0

+
+

[!WARNING] +As of version 4.0.0 this action is being deprecated in favor of actions/attest. actions/attest-sbom will continue to function as a wrapper on top of actions/attest for some period of time, but applications should make plans to migrate.

+

All of the existing action inputs are compatible with the actions/attest interface.

+
+

What's Changed

+ +

Full Changelog: https://github.com/actions/attest-sbom/compare/v3...v4.0.0

+
+
+
+Commits +
    +
  • 07e74fc perpare v4 release (#253)
  • +
  • b74e951 Bump the actions-minor group with 2 updates (#247)
  • +
  • 7d9b9d6 Bump the npm-development group across 1 directory with 4 updates (#245)
  • +
  • 35d5f43 Bump @​actions/core from 2.0.1 to 2.0.2 in the npm-production group (#243)
  • +
  • 876bb5f Bump the actions-minor group across 1 directory with 3 updates (#246)
  • +
  • 6cf30ca Bump the npm-development group with 2 updates (#241)
  • +
  • e395115 Bump the actions-minor group with 2 updates (#239)
  • +
  • afc801d Bump the npm-development group with 3 updates (#240)
  • +
  • 6ec0860 Bump @​actions/core from 1.11.1 to 2.0.1 (#237)
  • +
  • 532af8a Bump github/codeql-action in the actions-minor group (#233)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/attest-sbom&package-manager=github_actions&previous-version=3.0.0&new-version=4.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [af06db6](https://github.com/vig-os/devcontainer/commit/af06db64b3cb989e358a85e1000c28ee89a10ebd) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/attest-sbom from 3.0.0 to 4.0.0, 2 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-315.md b/docs/pull-requests/pr-315.md new file mode 100644 index 00000000..5d05cf71 --- /dev/null +++ b/docs/pull-requests/pr-315.md @@ -0,0 +1,123 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/upload-artifact-7.0.0 → dev +created: 2026-03-16T02:28:48Z +updated: 2026-03-16T07:57:34Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/315 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-16T07:57:32Z +synced: 2026-03-17T04:24:21.984Z +--- + +# [PR 315](https://github.com/vig-os/devcontainer/pull/315) ci(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0 + +Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.0. +
+Release notes +

Sourced from actions/upload-artifact's releases.

+
+

v7.0.0

+

v7 What's new

+

Direct Uploads

+

Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

+

ESM

+

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

+

What's Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/actions/upload-artifact/compare/v6...v7.0.0

+

v6.0.0

+

v6 - What's new

+
+

[!IMPORTANT] +actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

+
+

Node.js 24

+

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

+

What's Changed

+ +

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0

+

v5.0.0

+

What's Changed

+

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

+ + +
+

... (truncated)

+
+
+Commits +
    +
  • bbbca2d Support direct file uploads (#764)
  • +
  • 589182c Upgrade the module to ESM and bump dependencies (#762)
  • +
  • 47309c9 Merge pull request #754 from actions/Link-/add-proxy-integration-tests
  • +
  • 02a8460 Add proxy integration test
  • +
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • +
  • e516bc8 docs: correct description of Node.js 24 support in README
  • +
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • +
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • +
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • +
  • 38d4c79 chore: rebuild dist
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4.6.2&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [c2afaee](https://github.com/vig-os/devcontainer/commit/c2afaee87448f656c50e1995c787b7b6dbeafac4) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0, 14 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) diff --git a/docs/pull-requests/pr-316.md b/docs/pull-requests/pr-316.md new file mode 100644 index 00000000..27738dbd --- /dev/null +++ b/docs/pull-requests/pr-316.md @@ -0,0 +1,122 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/actions/create-github-app-token-3.0.0 → dev +created: 2026-03-16T02:28:54Z +updated: 2026-03-16T07:57:34Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/316 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-16T07:57:32Z +synced: 2026-03-17T04:24:20.835Z +--- + +# [PR 316](https://github.com/vig-os/devcontainer/pull/316) ci(deps): bump actions/create-github-app-token from 2.2.1 to 3.0.0 + +Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.2.1 to 3.0.0. +
+Release notes +

Sourced from actions/create-github-app-token's releases.

+
+

v3.0.0

+

3.0.0 (2026-03-14)

+
    +
  • feat!: node 24 support (#275) (2e564a0)
  • +
  • fix!: require NODE_USE_ENV_PROXY for proxy support (#342) (4451bcb)
  • +
+

Bug Fixes

+ +

BREAKING CHANGES

+
    +
  • Custom proxy handling has been removed. If you use HTTP_PROXY or HTTPS_PROXY, you must now also set NODE_USE_ENV_PROXY=1 on the action step.
  • +
  • Requires Actions Runner v2.327.1 or later if you are using a self-hosted runner.
  • +
+

v3.0.0-beta.6

+

3.0.0-beta.6 (2026-03-13)

+

Bug Fixes

+
    +
  • deps: bump @​actions/core from 1.11.1 to 3.0.0 (#337) (b044133)
  • +
  • deps: bump minimatch from 9.0.5 to 9.0.9 (#335) (5cbc656)
  • +
  • deps: bump the production-dependencies group with 4 updates (#336) (6bda5bc)
  • +
  • deps: bump undici from 7.16.0 to 7.18.2 (#323) (b4f638f)
  • +
+

v3.0.0-beta.5

+

3.0.0-beta.5 (2026-03-13)

+
    +
  • fix!: require NODE_USE_ENV_PROXY for proxy support (#342) (d53a1cd)
  • +
+

BREAKING CHANGES

+
    +
  • Custom proxy handling has been removed. If you use HTTP_PROXY or HTTPS_PROXY, you must now also set NODE_USE_ENV_PROXY=1 on the action step.
  • +
+

v3.0.0-beta.4

+

3.0.0-beta.4 (2026-03-13)

+

Bug Fixes

+
    +
  • deps: bump @​octokit/auth-app from 7.2.1 to 8.0.1 (#257) (bef1eaf)
  • +
  • deps: bump @​octokit/request from 9.2.3 to 10.0.2 (#256) (5d7307b)
  • +
  • deps: bump glob from 10.4.5 to 10.5.0 (#305) (5480f43)
  • +
  • deps: bump p-retry from 6.2.1 to 7.1.0 (#294) (dce3be8)
  • +
+ +
+

... (truncated)

+
+
+Commits +
    +
  • f8d387b build(release): 3.0.0 [skip ci]
  • +
  • d2129bd style: remove extra blank line in release workflow
  • +
  • 77b94ef build: refresh generated artifacts
  • +
  • 3ab4c66 chore: move undici to devDependencies
  • +
  • 739cf66 docs: update README action versions
  • +
  • db40289 build(deps): bump actions versions in test.yml
  • +
  • 496a7ac test: migrate from AVA to Node.js native test runner (#346)
  • +
  • 3870dc3 Rename end-to-end proxy job in test workflow
  • +
  • 4451bcb fix!: require NODE_USE_ENV_PROXY for proxy support (#342)
  • +
  • dce0ab0 fix: remove custom proxy handling (#143)
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/create-github-app-token&package-manager=github_actions&previous-version=2.2.1&new-version=3.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [13e93b1](https://github.com/vig-os/devcontainer/commit/13e93b1a074c3143221f9f0918706f57fbb1695b) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/create-github-app-token from 2.2.1 to 3.0.0, 18 files modified (.github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/sync-issues.yml, .github/workflows/sync-main-to-dev.yml) diff --git a/docs/pull-requests/pr-317.md b/docs/pull-requests/pr-317.md new file mode 100644 index 00000000..ed02334c --- /dev/null +++ b/docs/pull-requests/pr-317.md @@ -0,0 +1,90 @@ +--- +type: pull_request +state: closed (merged) +branch: dependabot/github_actions/dev/docker/login-action-4.0.0 → dev +created: 2026-03-16T02:28:59Z +updated: 2026-03-16T07:57:34Z +author: dependabot[bot] +author_url: https://github.com/dependabot[bot] +url: https://github.com/vig-os/devcontainer/pull/317 +comments: 0 +labels: dependencies, github_actions +assignees: none +milestone: none +projects: none +merged: 2026-03-16T07:57:32Z +synced: 2026-03-17T04:24:19.736Z +--- + +# [PR 317](https://github.com/vig-os/devcontainer/pull/317) ci(deps): bump docker/login-action from 3.7.0 to 4.0.0 + +Bumps [docker/login-action](https://github.com/docker/login-action) from 3.7.0 to 4.0.0. +
+Release notes +

Sourced from docker/login-action's releases.

+
+

v4.0.0

+ +

Full Changelog: https://github.com/docker/login-action/compare/v3.7.0...v4.0.0

+
+
+
+Commits +
    +
  • b45d80f Merge pull request #929 from crazy-max/node24
  • +
  • 176cb9c node 24 as default runtime
  • +
  • cad8984 Merge pull request #920 from docker/dependabot/npm_and_yarn/aws-sdk-dependenc...
  • +
  • 92cbcb2 chore: update generated content
  • +
  • 5a2d6a7 build(deps): bump the aws-sdk-dependencies group with 2 updates
  • +
  • 44512b6 Merge pull request #928 from docker/dependabot/npm_and_yarn/docker/actions-to...
  • +
  • 28737a5 chore: update generated content
  • +
  • dac0793 build(deps): bump @​docker/actions-toolkit from 0.76.0 to 0.77.0
  • +
  • 62029f3 Merge pull request #919 from docker/dependabot/npm_and_yarn/actions/core-3.0.0
  • +
  • 08c8f06 chore: update generated content
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/login-action&package-manager=github_actions&previous-version=3.7.0&new-version=4.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) + + +
+ + +--- +--- + +## Commits + +### Commit 1: [34d7538](https://github.com/vig-os/devcontainer/commit/34d75380a971d11c21cc0a5fdcb45babbe5b67fa) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump docker/login-action from 3.7.0 to 4.0.0, 2 files modified (.github/workflows/release.yml) diff --git a/docs/pull-requests/pr-318.md b/docs/pull-requests/pr-318.md new file mode 100644 index 00000000..13a7a831 --- /dev/null +++ b/docs/pull-requests/pr-318.md @@ -0,0 +1,121 @@ +--- +type: pull_request +state: closed (merged) +branch: chore/dependabot-updates → dev +created: 2026-03-16T07:40:11Z +updated: 2026-03-16T07:57:31Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/318 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-16T07:57:30Z +synced: 2026-03-17T04:24:24.419Z +--- + +# [PR 318](https://github.com/vig-os/devcontainer/pull/318) chore(ci): batch Dependabot workflow updates + +## Description + +Batch Dependabot workflow dependency updates into a single chore PR targeting `dev`, while preserving provenance from each original Dependabot PR. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [x] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [ ] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- Merged original Dependabot PR branches `#314`, `#315`, `#316`, and `#317` into `chore/dependabot-updates` +- Updated GitHub Actions workflow dependencies: + - `actions/attest-sbom` `3.0.0` -> `4.0.0` + - `actions/upload-artifact` `4.6.2` -> `7.0.0` + - `actions/create-github-app-token` `2.2.1` -> `3.0.0` + - `docker/login-action` `3.7.0` -> `4.0.0` +- Updated mirrored workspace workflow files via repository sync-manifest hooks +- Added matching `## Unreleased` changelog entry for this Dependabot batch + +## Changelog Entry + +### Changed +- **Dependabot dependency update batch** ([#314](https://github.com/vig-os/devcontainer/pull/314), [#315](https://github.com/vig-os/devcontainer/pull/315), [#316](https://github.com/vig-os/devcontainer/pull/316), [#317](https://github.com/vig-os/devcontainer/pull/317)) + - Bump GitHub Actions: `actions/attest-sbom` (`3.0.0` -> `4.0.0`), `actions/upload-artifact` (`4.6.2` -> `7.0.0`), `actions/create-github-app-token` (`2.2.1` -> `3.0.0`) + - Bump `docker/login-action` from `3.7.0` to `4.0.0` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +This PR consolidates open Dependabot workflow updates to reduce merge overhead while keeping each source PR traceable. + +Refs: #314, #315, #316, #317 + + +--- +--- + +## Commits + +### Commit 1: [af06db6](https://github.com/vig-os/devcontainer/commit/af06db64b3cb989e358a85e1000c28ee89a10ebd) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/attest-sbom from 3.0.0 to 4.0.0, 2 files modified (.github/workflows/release.yml) + +### Commit 2: [c2afaee](https://github.com/vig-os/devcontainer/commit/c2afaee87448f656c50e1995c787b7b6dbeafac4) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0, 14 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 3: [13e93b1](https://github.com/vig-os/devcontainer/commit/13e93b1a074c3143221f9f0918706f57fbb1695b) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump actions/create-github-app-token from 2.2.1 to 3.0.0, 18 files modified (.github/workflows/prepare-release.yml, .github/workflows/release.yml, .github/workflows/sync-issues.yml, .github/workflows/sync-main-to-dev.yml) + +### Commit 4: [34d7538](https://github.com/vig-os/devcontainer/commit/34d75380a971d11c21cc0a5fdcb45babbe5b67fa) by [dependabot[bot]](https://github.com/apps/dependabot) on March 16, 2026 at 02:28 AM +ci(deps): bump docker/login-action from 3.7.0 to 4.0.0, 2 files modified (.github/workflows/release.yml) + +### Commit 5: [5b5c4e9](https://github.com/vig-os/devcontainer/commit/5b5c4e9d9621604ad4bafe6c8655b8f33b7bc179) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:35 AM +chore(ci): merge dependabot PR 314, 2 files modified (.github/workflows/release.yml) + +### Commit 6: [32badd4](https://github.com/vig-os/devcontainer/commit/32badd4d4ebf65fc1fec8112faef76cfd48a2da4) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:35 AM +chore(ci): merge dependabot PR 315, 14 files modified (.github/workflows/ci.yml, .github/workflows/release.yml, .github/workflows/security-scan.yml) + +### Commit 7: [f8f045a](https://github.com/vig-os/devcontainer/commit/f8f045ab3281333e5f6c1c2958dde71f22eb046f) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:36 AM +chore(ci): merge dependabot PR 316, 24 files modified + +### Commit 8: [3e1320b](https://github.com/vig-os/devcontainer/commit/3e1320b76612f31bf800631db4deb3f56a8e96f1) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:36 AM +chore(ci): merge dependabot PR 317, 2 files modified (.github/workflows/release.yml) + +### Commit 9: [b37094c](https://github.com/vig-os/devcontainer/commit/b37094c74bf017546045dcd1acf5777e03eee8ca) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:38 AM +chore: update changelog for dependabot batch, 3 files modified (CHANGELOG.md) + +### Commit 10: [381d9da](https://github.com/vig-os/devcontainer/commit/381d9da5deb46f1f4de3567170b6b74d5edf1e33) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 07:47 AM +chore: update just version in test_image.py to 1.47., 3 files modified (CHANGELOG.md, tests/test_image.py) diff --git a/docs/pull-requests/pr-319.md b/docs/pull-requests/pr-319.md new file mode 100644 index 00000000..b9772667 --- /dev/null +++ b/docs/pull-requests/pr-319.md @@ -0,0 +1,226 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/300-finalize-release-docs-ci → dev +created: 2026-03-16T08:17:53Z +updated: 2026-03-16T08:40:12Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/319 +comments: 4 +labels: none +assignees: none +milestone: none +projects: none +merged: 2026-03-16T08:40:11Z +synced: 2026-03-17T04:24:18.591Z +--- + +# [PR 319](https://github.com/vig-os/devcontainer/pull/319) fix(ci): finalize release docs and refresh release PR body + +## Description + +Fixes release finalization regressions from issue #300 by ensuring final releases commit all generated docs required by pre-commit and by refreshing release PR body content from finalized `CHANGELOG.md`. + +The release PR body now follows the established release format (linked release heading with date and release-content section), while checklist/related sections are removed from persistent release PR body text. + +## Type of Change + +- [ ] `feat` -- New feature +- [x] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [x] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/release.yml` + - Regenerate docs during final release finalization (`docs/generate.py`) + - Collect dynamic tracked changed files and pass them to `commit-action` instead of hardcoding `CHANGELOG.md` + - Add guardrails to fail if finalization produces no tracked changes or misses expected generated docs + - Refresh release PR body from finalized `CHANGELOG.md` with the release heading format used in previous release PRs +- `.github/workflows/prepare-release.yml` + - Remove persistent `Testing Checklist`, `When Ready to Release`, and `Related` sections from draft release PR body +- `tests/bats/just.bats` + - Add regressions for docs regeneration and dynamic finalization file-path commit behavior + - Add regression asserting release PR body section cleanup in prepare workflow +- `CHANGELOG.md` + - Add `Unreleased` `### Fixed` entry documenting issue #300 behavior change + +## Changelog Entry + +### Fixed + +- **Release finalization now commits generated docs and refreshes PR content** ([#300](https://github.com/vig-os/devcontainer/issues/300)) + - Final release automation regenerates docs before committing so pre-commit `generate-docs` does not fail CI with tracked file diffs + - Release PR body is refreshed from finalized `CHANGELOG.md` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `npx bats tests/bats/just.bats` +- Result: all tests passed (`9/9`) + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +The release PR heading/body format aligns with prior release PR style for consistency (release link + date heading, then `## Release Content`). + +Refs: #300 + + + +--- +--- + +## Review Threads (2) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 08:25 AM_ + +_File: [`.github/workflows/release.yml`](https://github.com/vig-os/devcontainer/pull/319#discussion_r2938833900)_ + +```diff +@@ -381,6 +381,40 @@ jobs: + uv run prepare-changelog finalize "$VERSION" "$RELEASE_DATE" + echo "✓ Release date set in CHANGELOG.md" + ++ - name: Regenerate docs for finalized release ++ if: needs.validate.outputs.release_kind == 'final' ++ run: | ++ set -euo pipefail ++ uv run python docs/generate.py ++ echo "✓ Regenerated docs from finalized CHANGELOG.md" ++ ++ - name: Collect finalization files ++ if: needs.validate.outputs.release_kind == 'final' ++ id: finalize-files ++ run: | ++ set -euo pipefail ++ ++ CHANGED_FILES="$(git diff --name-only)" ++ if [ -z "$CHANGED_FILES" ]; then ++ echo "ERROR: Finalization produced no tracked file changes." ++ echo "Expected CHANGELOG.md and possibly generated docs to change." ++ exit 1 ++ fi ++ ++ FILE_PATHS="$(echo "$CHANGED_FILES" | tr '\n' ' ' | sed 's/[[:space:]]*$//')" ++ echo "file_paths=$FILE_PATHS" >> "$GITHUB_OUTPUT" ++ echo "Finalization files: $FILE_PATHS" ++ ++ for expected_doc in README.md CONTRIBUTE.md TESTING.md docs/SKILL_PIPELINE.md; do ++ if git diff --quiet -- "$expected_doc"; then ++ continue ++ fi ++ if ! echo "$FILE_PATHS" | grep -Eq "(^|[[:space:]])$expected_doc([[:space:]]|$)"; then ++ echo "ERROR: Expected modified doc '$expected_doc' missing from commit file list." ++ exit 1 ++ fi +``` + +The membership check uses `grep -Eq` with `$expected_doc` interpolated directly into a regex. Since filenames like `README.md` contain `.` (regex wildcard) and could contain other regex metacharacters, this can produce false matches and weaken the guardrail. Use a non-regex comparison (e.g., `case " $FILE_PATHS " in *" $expected_doc "* )` or split `CHANGED_FILES` and compare exact strings) or escape the filename before using it in a regex. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 08:33 AM — [link](https://github.com/vig-os/devcontainer/pull/319#discussion_r2938866530) + + Thanks, good catch. Updated in 067c7a2 to use exact line-based matching against CHANGED_FILES (`printf ... | grep -Fxq -- "$expected_doc"`) so filename metacharacters no longer affect matching. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 08:25 AM_ + +_File: [`.github/workflows/release.yml (line 527 RIGHT)`](https://github.com/vig-os/devcontainer/pull/319#discussion_r2938833930)_ + +```diff +@@ -463,6 +497,35 @@ jobs: + git reset --hard "origin/release/$VERSION" + echo "✓ Synced with remote release branch" + ++ - name: Refresh release PR body from finalized changelog ++ if: needs.validate.outputs.release_kind == 'final' ++ env: ++ GH_TOKEN: ${{ steps.app-token.outputs.token }} ++ VERSION: ${{ needs.validate.outputs.version }} ++ RELEASE_DATE: ${{ needs.validate.outputs.release_date }} ++ PR_NUMBER: ${{ needs.validate.outputs.pr_number }} ++ run: | ++ set -euo pipefail ++ CHANGELOG_CONTENT=$(sed -n "/## \[$VERSION\]/,/^## \[/p" CHANGELOG.md | sed '$d') ++ ++ if [ -z "$CHANGELOG_CONTENT" ]; then ++ echo "ERROR: Could not extract release section for version $VERSION from CHANGELOG.md" ++ exit 1 ++ fi ++ ++ cat > /tmp/release-pr-body.md < Changed` entry for issue `#321` + +## Changelog Entry + +### Changed +- **Node24-ready GitHub Actions pin refresh for shared composite actions** ([#321](https://github.com/vig-os/devcontainer/issues/321)) + - Update Docker build path pins in `build-image` (`docker/setup-buildx-action`, `docker/metadata-action`, `docker/build-push-action`) to Node24-compatible releases + - Set `setup-env` default Node runtime to `24` and upgrade `actions/setup-node` + - Align test composite actions with newer pins (`actions/checkout`, `actions/cache`, `actions/upload-artifact`) + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +- Related issue: https://github.com/vig-os/sync-issues-action/issues/77 +- Validation completed with pre-commit checks on modified files, including `check-action-pins`, `check yaml`, and `yamllint`. + +Refs: #321 + + + +--- +--- + +## Commits + +### Commit 1: [e0a7b97](https://github.com/vig-os/devcontainer/commit/e0a7b97c834e75c0158417ab8989ade6917729f0) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 08:49 AM +ci(ci): upgrade build-image docker action pins, 8 files modified (.github/actions/build-image/action.yml) + +### Commit 2: [bcd9fa0](https://github.com/vig-os/devcontainer/commit/bcd9fa04644e30b6291b3504c677331997a23dac) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 08:49 AM +ci(setup): move setup-env default node version to 24, 6 files modified (.github/actions/setup-env/action.yml) + +### Commit 3: [37bd38e](https://github.com/vig-os/devcontainer/commit/37bd38efe9f90a97d3573dce45e3083b48e1cfbb) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 08:49 AM +ci(ci): align test composite action pins with node24-ready releases, 10 files modified (.github/actions/test-image/action.yml, .github/actions/test-integration/action.yml, .github/actions/test-project/action.yml) + +### Commit 4: [e2028d3](https://github.com/vig-os/devcontainer/commit/e2028d35de8471c3d6063557f6d219f16df90c06) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 08:50 AM +docs(ci): record node24 action pin migration in changelog, 4 files modified (CHANGELOG.md) diff --git a/docs/pull-requests/pr-323.md b/docs/pull-requests/pr-323.md new file mode 100644 index 00000000..1d4d4cd6 --- /dev/null +++ b/docs/pull-requests/pr-323.md @@ -0,0 +1,88 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/320-security-scan-artifact-handoff → dev +created: 2026-03-16T09:13:37Z +updated: 2026-03-16T09:21:26Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/323 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-16T09:21:25Z +synced: 2026-03-17T04:24:15.759Z +--- + +# [PR 323](https://github.com/vig-os/devcontainer/pull/323) ci: fix scheduled security scan artifact handoff + +## Description + +Fix the scheduled security scan workflow by uploading the built container image artifact in `build-image` so `security-scan` can download it reliably. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/security-scan.yml` + - Add `Upload image artifact` step after image build in `build-image` + - Upload `/tmp/image.tar` as `container-image-${{ steps.version.outputs.version }}-amd64` + - Keep artifact settings aligned with CI workflow (`retention-days: 1`, `compression-level: 0`) + +## Changelog Entry + +No changelog needed: issue `#320` specifies "No changelog needed" and this is an internal CI workflow reliability fix. + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A (workflow change; validate via next scheduled or manual workflow run) + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #320 + + + +--- +--- + +## Commits + +### Commit 1: [7ed1d12](https://github.com/vig-os/devcontainer/commit/7ed1d123afa402bfeae900148e0db066c227731b) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 09:11 AM +fix(ci): upload scheduled scan image artifact, 8 files modified (.github/workflows/security-scan.yml) diff --git a/docs/pull-requests/pr-324.md b/docs/pull-requests/pr-324.md new file mode 100644 index 00000000..f305ebc8 --- /dev/null +++ b/docs/pull-requests/pr-324.md @@ -0,0 +1,156 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/289-source-run-metadata-dispatch → dev +created: 2026-03-16T09:42:55Z +updated: 2026-03-16T09:56:18Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/324 +comments: 2 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-16T09:56:17Z +synced: 2026-03-17T04:24:14.651Z +--- + +# [PR 324](https://github.com/vig-os/devcontainer/pull/324) ci: add smoke dispatch source metadata contract + +## Description + +Add traceability metadata to candidate `repository_dispatch` events sent to `vig-os/devcontainer-smoke-test`, and make the smoke-test receiver summarize source context with backward-compatible parsing. This also documents the dispatch payload contract and future callback direction for `publish-candidate`. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/release.yml` + - Extend candidate dispatch payload with `event_type`, `source_repo`, `source_workflow`, `source_run_id`, `source_run_url`, `source_sha`, and deterministic `correlation_id` + - Keep `client_payload[tag]` as required contract key +- `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - Parse optional source metadata fields and expose them as validate job outputs + - Derive `source_run_url` when absent but `source_repo` and `source_run_id` are present + - Write normalized source context to `$GITHUB_STEP_SUMMARY` +- `tests/bats/just.bats` + - Add regression guards for sender payload keys and receiver summary/fallback behavior +- `docs/RELEASE_CYCLE.md` + - Document required/optional dispatch payload schema, compatibility behavior, and future callback path +- `CHANGELOG.md` + - Add Unreleased entry for issue #289 + +## Changelog Entry + +### Changed + +- **Smoke-test dispatch payload now carries source run traceability metadata** ([#289](https://github.com/vig-os/devcontainer/issues/289)) + - Candidate release dispatches now include source repo/workflow/run/SHA metadata plus a deterministic `correlation_id` + - Smoke-test dispatch receiver logs normalized source context, derives source run URL when possible, and writes it to workflow summary output + - Release-cycle docs now define required vs optional dispatch payload keys and the future callback contract path for `publish-candidate` + +## Testing + +- [ ] Tests pass locally (`just test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- `npx bats tests/bats/just.bats` +- Verified all 12 BATS tests pass, including new dispatch metadata regression guards + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [x] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +No gating callback is implemented in this PR; callback wiring is intentionally documented-only for a follow-up that can report smoke-test completion back to `publish-candidate`. + +Refs: #289 + + +--- +--- + +## Review Threads (1) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 09:46 AM_ + +_File: [`assets/smoke-test/.github/workflows/repository-dispatch.yml`](https://github.com/vig-os/devcontainer/pull/324#discussion_r2939236838)_ + +```diff +@@ -191,6 +222,28 @@ jobs: + needs: [validate, deploy] + if: always() + steps: ++ - name: Write source context summary ++ env: ++ TAG: ${{ needs.validate.outputs.tag }} ++ SOURCE_REPO: ${{ needs.validate.outputs.source_repo }} ++ SOURCE_WORKFLOW: ${{ needs.validate.outputs.source_workflow }} ++ SOURCE_RUN_ID: ${{ needs.validate.outputs.source_run_id }} ++ SOURCE_RUN_URL: ${{ needs.validate.outputs.source_run_url }} ++ SOURCE_SHA: ${{ needs.validate.outputs.source_sha }} ++ CORRELATION_ID: ${{ needs.validate.outputs.correlation_id }} ++ run: | ++ { ++ echo "## Source Dispatch Context" ++ echo "" ++ echo "- Tag: ${TAG}" ++ echo "- Source Repo: ${SOURCE_REPO:-n/a}" ++ echo "- Source Workflow: ${SOURCE_WORKFLOW:-n/a}" ++ echo "- Source Run ID: ${SOURCE_RUN_ID:-n/a}" ++ echo "- Source Run URL: ${SOURCE_RUN_URL:-n/a}" +``` + +In the summary block, the Tag line uses `${TAG}` without a fallback, while the other fields use `${VAR:-n/a}`. If `validate` fails (or outputs are unset), this can render an empty Tag line in the step summary; consider using the same `:-n/a` fallback for consistency and clearer diagnostics. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 09:49 AM — [link](https://github.com/vig-os/devcontainer/pull/324#discussion_r2939257483) + + Good catch — updated the summary line to use the same fallback style: `Tag` now renders as `${TAG:-n/a}` for consistency when outputs are missing. Implemented in commit `7d9aede`, and `npx bats tests/bats/just.bats` still passes (12/12). Thanks! + + +--- +--- + +## Commits + +### Commit 1: [f4dc1df](https://github.com/vig-os/devcontainer/commit/f4dc1dfc0b88b245568645c8544bfa7da03df9fa) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 09:39 AM +ci(ci): add smoke dispatch source metadata, 70 files modified (.github/workflows/release.yml, assets/smoke-test/.github/workflows/repository-dispatch.yml) + +### Commit 2: [9b0cce8](https://github.com/vig-os/devcontainer/commit/9b0cce8e8c267ae68d70edb2b9ab57d05ec1a720) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 09:39 AM +test(ci): add dispatch metadata regression guards, 10 files modified (tests/bats/just.bats) + +### Commit 3: [135fb2d](https://github.com/vig-os/devcontainer/commit/135fb2d5c0b3f4602c4f6ec8244190888a44e645) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 09:39 AM +docs(ci): document smoke dispatch metadata contract, 28 files modified (CHANGELOG.md, docs/RELEASE_CYCLE.md) + +### Commit 4: [7d9aede](https://github.com/vig-os/devcontainer/commit/7d9aeded461da2f8154b576e0186fc92110e71b0) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 09:49 AM +fix(ci): add summary fallback for dispatch tag, 2 files modified (assets/smoke-test/.github/workflows/repository-dispatch.yml) diff --git a/docs/pull-requests/pr-325.md b/docs/pull-requests/pr-325.md new file mode 100644 index 00000000..b99a17b5 --- /dev/null +++ b/docs/pull-requests/pr-325.md @@ -0,0 +1,190 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/313-trigger-smoke-test-existing-tag → dev +created: 2026-03-16T10:13:18Z +updated: 2026-03-16T10:26:18Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/325 +comments: 6 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-16T10:26:17Z +synced: 2026-03-17T04:24:13.382Z +--- + +# [PR 325](https://github.com/vig-os/devcontainer/pull/325) ci: trigger smoke dispatch for final releases + +## Description + +Enable smoke-test cross-repository dispatch for final releases in addition to candidate releases, while keeping the existing non-blocking behavior and payload contract. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/release.yml` + - Expand smoke-test token generation and dispatch step guards from candidate-only to candidate-or-final release kinds. + - Keep dispatch non-blocking via `continue-on-error: true`. + - Generalize dispatch variable/log wording from RC-specific (`RC_TAG`) to release-agnostic (`RELEASE_TAG`) while preserving `client_payload[tag]`. + - Update release summary next steps to include smoke-dispatch verification for both release modes. +- `docs/RELEASE_CYCLE.md` + - Document that both candidate and final publish paths emit `repository_dispatch` to the smoke-test repository. + - Update GitHub App permission note and publish job behavior notes accordingly. +- `CHANGELOG.md` + - Add Unreleased `### Changed` entry for issue `#173` describing final-release dispatch behavior. + +## Changelog Entry + +### Changed +- **Smoke-test repository dispatch now runs for final releases too** ([#173](https://github.com/vig-os/devcontainer/issues/173)) + - `release.yml` now triggers the existing smoke-test dispatch contract for both `candidate` and `final` release kinds + - Final release summaries and release-cycle documentation now reflect dispatch behavior for both release modes + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #313 + + + +--- +--- + +## Review Threads (3) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:16 AM_ + +_File: [`.github/workflows/release.yml (line 922 RIGHT)`](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939406283)_ + +```diff +@@ -914,15 +914,15 @@ jobs: + echo " - SBOM (SPDX) generated and attested" + echo "" + echo "Next steps:" ++ echo " 1. Verify smoke-test repo dispatch run status" + if [ "$RELEASE_KIND" = "candidate" ]; then +- echo " 1. Verify smoke-test repo dispatch run status before final release" + echo " 2. If smoke tests pass, run: just finalize-release $BASE_VERSION" + else +- echo " 1. Merge release PR to main (triggers sync-main-to-dev workflow automatically)" ++ echo " 2. Merge release PR to main (triggers sync-main-to-dev workflow automatically)" + fi +``` + +The publish-job "Summary" step prints "Next steps: 1. Verify smoke-test repo dispatch run status" before the dispatch token generation + dispatch steps actually run (they occur later in the job). This can mislead operators into checking for a dispatch run while it may not exist yet. Consider moving the Summary step to after the dispatch steps, or adjust the wording to clarify the dispatch is triggered later (and may be skipped if token generation fails). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 10:19 AM — [link](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939422151) + + Fixed in 553c81b. Updated publish summary wording to clarify dispatch is triggered later in the publish job. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:16 AM_ + +_File: [`docs/RELEASE_CYCLE.md (line 365 RIGHT)`](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939406327)_ + +```diff +@@ -359,8 +359,8 @@ The `release.yml` workflow performs the entire remaining release process. Behavi + + 4. ✅ **Publish** job (runs only if all builds/tests pass) + - Candidate mode: infers next `rcN`, creates annotated tag `X.Y.Z-rcN`, publishes candidate manifests +- - Candidate mode: triggers `repository_dispatch` to `vig-os/devcontainer-smoke-test` with `client_payload[tag]` plus source metadata (`source_repo`, `source_workflow`, `source_run_id`, `source_run_url`, `source_sha`, `correlation_id`) + - Final mode: creates annotated tag `X.Y.Z`, publishes final manifests ++ - Candidate and final modes: trigger `repository_dispatch` to `vig-os/devcontainer-smoke-test` with `client_payload[tag]` plus source metadata (`source_repo`, `source_workflow`, `source_run_id`, `source_run_url`, `source_sha`, `correlation_id`) + - Pushes tag to origin + - Downloads tested images from artifacts + - Logs in to GitHub Container Registry +``` + +This bullet list reads like an ordered sequence, but in `release.yml` the tag is created/pushed early in the publish job and the smoke-test `repository_dispatch` is triggered near the end (after publishing/attestations). To avoid operational confusion, either reorder these bullets to match the workflow’s execution order or rephrase so it’s clear the dispatch happens later in the job. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 10:19 AM — [link](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939422343) + + Fixed in 553c81b. Reordered the publish bullet list so dispatch is documented after manifest verification, matching execution order. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:16 AM_ + +_File: [`docs/RELEASE_CYCLE.md (line 659 RIGHT)`](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939406354)_ + +```diff +@@ -654,6 +655,7 @@ gh workflow run prepare-release.yml --ref dev -f "version=1.0.0" -f "dry-run=tru + 4. **publish** (runs if all builds/tests pass) - Creates tag and publishes + - Candidate mode creates and pushes `X.Y.Z-rcN` (next available `N`) + - Final mode creates and pushes `X.Y.Z` ++ - Candidate and final modes trigger smoke-test `repository_dispatch` with `client_payload[tag]=` + - Pushes tag + - Downloads tested images from artifacts +``` + +In this later summary of the publish job, the bullets imply smoke-test dispatch happens before "Pushes tag" / "Pushes images". In the actual workflow, the dispatch is triggered near the end of the publish job. Please reorder or reword these bullets so the documented sequence matches what operators will see in the logs. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 10:19 AM — [link](https://github.com/vig-os/devcontainer/pull/325#discussion_r2939422500) + + Fixed in 553c81b. Reordered the later publish summary bullets so dispatch appears at the end of publish steps, consistent with runtime logs. + + +--- +--- + +## Commits + +### Commit 1: [335c0d1](https://github.com/vig-os/devcontainer/commit/335c0d16d6ca5f9c78d9295d8f28fb60db80ffb3) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 10:08 AM +ci(ci): trigger smoke dispatch for final releases, 25 files modified (.github/workflows/release.yml, CHANGELOG.md, docs/RELEASE_CYCLE.md) + +### Commit 2: [553c81b](https://github.com/vig-os/devcontainer/commit/553c81be80ac839bb3aa4019ec5dd905c4cf9bc6) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 10:19 AM +ci(ci): clarify smoke dispatch timing in release docs, 6 files modified (.github/workflows/release.yml, docs/RELEASE_CYCLE.md) diff --git a/docs/pull-requests/pr-328.md b/docs/pull-requests/pr-328.md new file mode 100644 index 00000000..3d2d9ae3 --- /dev/null +++ b/docs/pull-requests/pr-328.md @@ -0,0 +1,229 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/327-consolidate-workspace-ci → dev +created: 2026-03-16T12:34:22Z +updated: 2026-03-16T13:09:47Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/328 +comments: 4 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-16T13:09:45Z +synced: 2026-03-17T04:24:12.111Z +--- + +# [PR 328](https://github.com/vig-os/devcontainer/pull/328) ci: consolidate workspace CI and reuse image resolver + +## Description + +Consolidates workspace CI templates to a single container-based `ci.yml` workflow, removes obsolete workspace CI assets, and aligns downstream smoke-test/release-cycle documentation with the new single-workflow contract. + +Follow-up review fixes are included to keep workflow behavior consistent: a reusable `resolve-image` composite action is introduced and reused by both workspace `ci.yml` and `release.yml`, and workspace release tests now run in the containerized model. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/workspace/.github/workflows/ci.yml` + - Replaced legacy/bare-runner flow with the canonical container-based CI flow + - Switched inline image-tag resolution to `./.github/actions/resolve-image` + - Kept lint/test container execution and summary aggregation; adjusted header comment to match actual test behavior +- `assets/workspace/.github/actions/resolve-image/action.yml` + - Added reusable composite action to resolve image tag from `image-tag` input, `.vig-os` `DEVCONTAINER_VERSION`, or `latest` + - Added image accessibility validation via `docker manifest inspect` +- `assets/workspace/.github/workflows/release.yml` + - Added image resolution in `validate` job and exposed it through outputs + - Converted finalized `test` job to run inside `ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }}` + - Removed bare-runner `setup-python`/`setup-uv` bootstrap and added `safe.directory` fix for container checkout +- Removed obsolete workspace CI assets: + - `assets/workspace/.github/workflows/ci-container.yml` + - `assets/workspace/.github/actions/setup-env/action.yml` +- Updated downstream docs/templates to reference single CI workflow: + - `assets/smoke-test/.github/workflows/repository-dispatch.yml` + - `assets/smoke-test/README.md` + - `assets/workspace/docs/container-ci-quirks.md` + - `docs/RELEASE_CYCLE.md` +- Updated `CHANGELOG.md` Unreleased entry for #327 to match final implementation details + +## Changelog Entry + +### Changed + +- **Workspace CI templates now use a single container-based workflow** ([#327](https://github.com/vig-os/devcontainer/issues/327)) + - Consolidate `assets/workspace/.github/workflows/ci.yml` as the canonical CI workflow and remove the obsolete `ci-container.yml` template + - Extract reusable `assets/workspace/.github/actions/resolve-image` and run workspace release tests in the same containerized workflow model + - Update smoke-test and release-cycle documentation to reference the single CI workflow contract + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +N/A + +Refs: #327 + + + +--- +--- + +## Review Threads (2) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 12:41 PM_ + +_File: [`assets/workspace/docs/container-ci-quirks.md`](https://github.com/vig-os/devcontainer/pull/328#discussion_r2940132117)_ + +```diff +@@ -36,29 +36,19 @@ The container job does not have access to a Docker or Podman daemon. + Jobs that require building or running containers (e.g. integration tests + using `devcontainer up`) are not supported in this workflow. + +-## No dedicated security job ++## Security scope + +-The bare-runner CI runs a standalone security job that invokes `bandit` +-and `safety` independently, uploads JSON reports as artifacts, and posts +-findings to the step summary. `bandit` still runs inside the container +-via the `pre-commit` lint hook (`uv run bandit`), but `safety` is not +-available in the image. The dedicated security job with artifact uploads +-remains bare-runner-only until `safety` is added to a future image +-release. ++`bandit` can still run via the `pre-commit` lint hook (`uv run bandit`), but ++there is no separate CI security-report job with JSON artifact uploads. + +-## No dependency-review job ++## Dependency review scope + +-The bare-runner CI includes an `actions/dependency-review-action` job +-that checks for dependency vulnerabilities on pull requests. This is a +-marketplace action that inspects the GitHub dependency graph and does not +-need to run inside the container. It is omitted from the container +-workflow to keep it focused on validating tools shipped in the image. ++The CI workflow does not include a dedicated `actions/dependency-review-action` ++job; it focuses on validating code quality and tests inside the image. + + ## No coverage artifact upload + +-The bare-runner test job produces an XML coverage report +-(`--cov-report=xml`) and uploads it via `actions/upload-artifact`. The +-container test job only prints coverage to the terminal ++The test job prints coverage to the terminal + (`--cov-report=term-missing`) and does not upload artifacts. +``` + +The doc claims the CI test job prints coverage (`--cov-report=term-missing`), but the updated workflow runs `just test`, and the template `justfile.project` defines `test` as plain `pytest` (no coverage). Update this doc to match current behavior, or change the workflow to run the coverage recipe (e.g., `just test-cov`) if coverage output is still desired. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 12:56 PM — [link](https://github.com/vig-os/devcontainer/pull/328#discussion_r2940210695) + + Good catch — fixed. I updated `assets/workspace/docs/container-ci-quirks.md` to match the current workflow behavior: the test job now documents `just test` (plain pytest) and explicitly states no coverage artifacts are uploaded. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 12:41 PM_ + +_File: [`assets/workspace/.github/actions/resolve-image/action.yml`](https://github.com/vig-os/devcontainer/pull/328#discussion_r2940132160)_ + +```diff +@@ -0,0 +1,53 @@ ++name: Resolve devcontainer image ++description: Resolve and validate the devcontainer image tag for CI jobs ++ ++inputs: ++ image-tag: ++ description: Optional image tag override ++ required: false ++ default: '' ++ ++outputs: ++ image-tag: ++ description: Resolved image tag ++ value: ${{ steps.resolve.outputs.tag }} ++ ++runs: ++ using: composite ++ steps: ++ - name: Resolve image tag ++ id: resolve ++ shell: bash ++ env: ++ INPUT_IMAGE_TAG: ${{ inputs.image-tag }} ++ run: | ++ set -euo pipefail ++ ++ if [[ -n "$INPUT_IMAGE_TAG" ]]; then ++ echo "tag=$INPUT_IMAGE_TAG" >> "$GITHUB_OUTPUT" ++ exit 0 ++ fi ++ ++ if [[ -f .vig-os ]]; then ++ VERSION="$(grep '^DEVCONTAINER_VERSION=' .vig-os | cut -d= -f2)" +``` + +`.vig-os` parsing here uses a simple `grep | cut`, which doesn’t trim whitespace/quotes and will include any trailing inline comments in the value. Elsewhere in the workspace assets (e.g., initialize/version-check scripts) the same key is parsed more defensively. Consider reusing that parsing approach so the resolved tag is consistently normalized before being used in `docker manifest inspect`. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 12:56 PM — [link](https://github.com/vig-os/devcontainer/pull/328#discussion_r2940210850) + + Agreed — updated. I replaced the `grep | cut` parsing with defensive `.vig-os` parsing aligned with the workspace scripts pattern: skip blank/comment lines, trim whitespace, and unquote quoted values before emitting the resolved tag. + + +--- +--- + +## Commits + +### Commit 1: [1f25c08](https://github.com/vig-os/devcontainer/commit/1f25c08b8b61d83fe33121644434b20dc939967e) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 12:23 PM +ci(ci): remove legacy workspace CI templates and references, 432 files modified + +### Commit 2: [26d7ee5](https://github.com/vig-os/devcontainer/commit/26d7ee53d3c6396072dd7b458ec063aada84cb45) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 12:23 PM +fix(ci): extract image resolver and reuse it in workspace workflows, 258 files modified (CHANGELOG.md, assets/workspace/.github/actions/resolve-image/action.yml, assets/workspace/.github/workflows/ci.yml, assets/workspace/.github/workflows/release.yml) + +### Commit 3: [20590bf](https://github.com/vig-os/devcontainer/commit/20590bfec7b216461ec21051e553c39b59688a19) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 01:02 PM +fix(ci): harden image tag parsing and align ci notes, 26 files modified (assets/workspace/.github/actions/resolve-image/action.yml, assets/workspace/docs/container-ci-quirks.md) diff --git a/docs/pull-requests/pr-329.md b/docs/pull-requests/pr-329.md new file mode 100644 index 00000000..e3ee0a68 --- /dev/null +++ b/docs/pull-requests/pr-329.md @@ -0,0 +1,2344 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/326-reusable-release-cycle-workflows → dev +created: 2026-03-16T16:09:37Z +updated: 2026-03-16T16:45:11Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/329 +comments: 24 +labels: none +assignees: c-vigo +milestone: none +projects: none +merged: 2026-03-16T16:45:08Z +synced: 2026-03-17T04:24:10.853Z +--- + +# [PR 329](https://github.com/vig-os/devcontainer/pull/329) ci: split downstream release workflow into reusable stages + +## Description + +Split downstream release execution into reusable local workflows with a project-owned extension hook, add a release contract validator, and introduce a dedicated `prepare-release` workflow for branch preparation. This also documents the downstream contract and candidate/final release behavior. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `assets/workspace/.github/workflows/release.yml` + - Replaced monolithic release flow with orchestration over `release-core`, `release-extension`, and `release-publish` + - Added `release-kind` dispatch input and propagated mapped inputs into reusable workflows + - Kept rollback handling in the orchestrator with outputs from `core` +- `assets/workspace/.github/workflows/release-core.yml` + - Added core validation/finalization/test stages behind `workflow_call` + - Added `release_kind` support for candidate (`X.Y.Z-rcN`) and final (`X.Y.Z`) behavior + - Added publish tag computation and candidate sequencing logic +- `assets/workspace/.github/workflows/release-extension.yml` + - Added default no-op extension contract for downstream project-owned release steps +- `assets/workspace/.github/workflows/release-publish.yml` + - Added isolated publish phase that tags and creates pre-release/final GitHub releases +- `assets/workspace/.github/actions/validate-contract/action.yml` + - Added shared contract version validation to enforce compatible reusable workflow interfaces +- `assets/workspace/.github/workflows/prepare-release.yml` + - Added standalone release preparation workflow (`workflow_dispatch`) for release branch setup and draft PR creation +- `assets/workspace/justfile.project` + - Added commented release helper examples for dispatching prepare/finalize/candidate workflows +- `assets/init-workspace.sh` + - Preserved downstream `release-extension.yml` on `--force` workspace upgrades +- `docs/DOWNSTREAM_RELEASE.md`, `docs/RELEASE_CYCLE.md`, `CHANGELOG.md` + - Documented downstream release contract, extension model, and release mode behavior + +## Changelog Entry + +### Added + +- **Split downstream release workflow with project-owned extension hook** ([#326](https://github.com/vig-os/devcontainer/issues/326)) + - Add local `workflow_call` release phases (`release-core.yml`, `release-publish.yml`) and a lightweight `release.yml` orchestrator in `assets/workspace/.github/workflows/` + - Add `release_kind` support with candidate mode (`X.Y.Z-rcN`) and final mode (`X.Y.Z`) in downstream release workflows + - Candidate mode now auto-computes the next RC tag, skips CHANGELOG finalization/sync-issues, and publishes a GitHub pre-release + - Add project-owned `release-extension.yml` stub and preserve it during `init-workspace.sh --force` upgrades + - Add `validate-contract` composite action for single-source contract version validation + - Add downstream release contract documentation and GHCR extension example in `docs/DOWNSTREAM_RELEASE.md` + +## Testing + +- [x] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [x] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +The branch is split into three atomic commits to keep review boundaries clear (workflow architecture, prepare-release flow, docs/changelog). + +Refs: #326 + + + +--- +--- + +## Review Threads (12) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/release-core.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439671)_ + +```diff +@@ -0,0 +1,454 @@ ++name: Release Core ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" ++ required: false ++ default: "candidate" ++ type: string ++ dry_run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git_user_name: ++ description: "Git user name for release commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ version: ++ description: "Resolved release version" ++ value: ${{ jobs.validate.outputs.version }} ++ pr_number: ++ description: "Release PR number" ++ value: ${{ jobs.validate.outputs.pr_number }} ++ release_date: ++ description: "UTC release date used for finalization" ++ value: ${{ jobs.validate.outputs.release_date }} ++ pre_finalize_sha: ++ description: "Release branch SHA before finalization" ++ value: ${{ jobs.validate.outputs.pre_finalize_sha }} ++ release_kind: ++ description: "Release kind (candidate or final)" ++ value: ${{ jobs.validate.outputs.release_kind }} ++ publish_version: ++ description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ value: ${{ jobs.validate.outputs.publish_version }} ++ finalize_sha: ++ description: "Release branch SHA after finalization" ++ value: ${{ jobs.finalize.outputs.finalize_sha }} ++ image_tag: ++ description: "Resolved devcontainer image tag" ++ value: ${{ jobs.validate.outputs.image_tag }} ++ ++concurrency: ++ group: release-core ++ cancel-in-progress: false ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Core ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ pr_number: ${{ steps.pr.outputs.pr_number }} ++ release_date: ${{ steps.vars.outputs.release_date }} ++ pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} ++ release_kind: ${{ steps.vars.outputs.release_kind }} ++ publish_version: ${{ steps.publish_meta.outputs.publish_version }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ INPUT_RELEASE_KIND: ${{ inputs.release_kind }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ RELEASE_KIND="$INPUT_RELEASE_KIND" ++ ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then ++ echo "ERROR: Invalid release-kind '$RELEASE_KIND'" ++ echo "release-kind must be one of: candidate, final" ++ exit 1 ++ fi ++ ++ RELEASE_DATE=$(date -u +%Y-%m-%d) ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" ++ echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ steps.vars.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ echo "Validating image availability: $IMAGE" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ echo "Check whether the tag exists and whether this workflow has access to GHCR." ++ exit 1 ++ fi ++ ++ - name: Record pre-finalization SHA ++ id: pre_sha ++ run: | ++ PRE_FINALIZE_SHA=$(git rev-parse HEAD) ++ echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" ++ echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" ++ ++ - name: Verify CHANGELOG has TBD entry ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ if ! grep -q "## \[$VERSION\] - TBD" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" ++ exit 1 ++ fi ++ ++ - name: Compute publish version ++ id: publish_meta ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} ++ run: | ++ set -euo pipefail ++ NEXT_RC="" ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ TAG_PATTERN="${VERSION}-rc*" ++ EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" | awk '{print $2}' | sed 's#refs/tags/##') ++ ++ MAX_RC=0 ++ if [ -n "$EXISTING_TAGS" ]; then ++ while IFS= read -r tag; do ++ [ -z "$tag" ] && continue ++ if ! echo "$tag" | grep -qE "^${VERSION}-rc[0-9]+$"; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 ++ fi ++ rc_num="${tag##*-rc}" ++ if [ "$rc_num" -gt "$MAX_RC" ]; then ++ MAX_RC="$rc_num" ++ fi ++ done <<< "$EXISTING_TAGS" ++ fi ++ ++ NEXT_RC=$((MAX_RC + 1)) ++ PUBLISH_VERSION="${VERSION}-rc${NEXT_RC}" ++ else ++ PUBLISH_VERSION="$VERSION" ++ fi ++ echo "publish_version=$PUBLISH_VERSION" >> "$GITHUB_OUTPUT" ++ echo "next_rc=$NEXT_RC" >> "$GITHUB_OUTPUT" ++ ++ - name: Verify publish tag does not exist ++ env: ++ PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} ++ run: | ++ if git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then +``` + +The remote tag check uses `grep -q "refs/tags/$PUBLISH_VERSION$"` where `$PUBLISH_VERSION` contains dots/hyphens and is treated as a regex. This can match the wrong tag (e.g., `1x2x3`). Use `grep -F`/`grep -Fx` on the ref name, or avoid grep entirely (e.g., `git ls-remote --exit-code --tags --refs origin "refs/tags/$PUBLISH_VERSION"`). + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:21 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941483334) + + Addressed in d2742bb: switched to exact remote tag ref checks using git ls-remote --exit-code --tags --refs. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/release-core.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439746)_ + +```diff +@@ -0,0 +1,454 @@ ++name: Release Core ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" ++ required: false ++ default: "candidate" ++ type: string ++ dry_run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git_user_name: ++ description: "Git user name for release commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ version: ++ description: "Resolved release version" ++ value: ${{ jobs.validate.outputs.version }} ++ pr_number: ++ description: "Release PR number" ++ value: ${{ jobs.validate.outputs.pr_number }} ++ release_date: ++ description: "UTC release date used for finalization" ++ value: ${{ jobs.validate.outputs.release_date }} ++ pre_finalize_sha: ++ description: "Release branch SHA before finalization" ++ value: ${{ jobs.validate.outputs.pre_finalize_sha }} ++ release_kind: ++ description: "Release kind (candidate or final)" ++ value: ${{ jobs.validate.outputs.release_kind }} ++ publish_version: ++ description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ value: ${{ jobs.validate.outputs.publish_version }} ++ finalize_sha: ++ description: "Release branch SHA after finalization" ++ value: ${{ jobs.finalize.outputs.finalize_sha }} ++ image_tag: ++ description: "Resolved devcontainer image tag" ++ value: ${{ jobs.validate.outputs.image_tag }} ++ ++concurrency: ++ group: release-core ++ cancel-in-progress: false ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Core ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ pr_number: ${{ steps.pr.outputs.pr_number }} ++ release_date: ${{ steps.vars.outputs.release_date }} ++ pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} ++ release_kind: ${{ steps.vars.outputs.release_kind }} ++ publish_version: ${{ steps.publish_meta.outputs.publish_version }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ INPUT_RELEASE_KIND: ${{ inputs.release_kind }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ RELEASE_KIND="$INPUT_RELEASE_KIND" ++ ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then ++ echo "ERROR: Invalid release-kind '$RELEASE_KIND'" ++ echo "release-kind must be one of: candidate, final" ++ exit 1 ++ fi ++ ++ RELEASE_DATE=$(date -u +%Y-%m-%d) ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" ++ echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ steps.vars.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ echo "Validating image availability: $IMAGE" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ echo "Check whether the tag exists and whether this workflow has access to GHCR." ++ exit 1 ++ fi ++ ++ - name: Record pre-finalization SHA ++ id: pre_sha ++ run: | ++ PRE_FINALIZE_SHA=$(git rev-parse HEAD) ++ echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" ++ echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" ++ ++ - name: Verify CHANGELOG has TBD entry ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ if ! grep -q "## \[$VERSION\] - TBD" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" ++ exit 1 ++ fi ++ ++ - name: Compute publish version ++ id: publish_meta ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} ++ run: | ++ set -euo pipefail ++ NEXT_RC="" ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ TAG_PATTERN="${VERSION}-rc*" ++ EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" | awk '{print $2}' | sed 's#refs/tags/##') ++ ++ MAX_RC=0 ++ if [ -n "$EXISTING_TAGS" ]; then ++ while IFS= read -r tag; do ++ [ -z "$tag" ] && continue ++ if ! echo "$tag" | grep -qE "^${VERSION}-rc[0-9]+$"; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 +``` + +The candidate tag format validation uses `grep -qE "^${VERSION}-rc[0-9]+$"`; since `${VERSION}` contains dots, they act as regex wildcards and can incorrectly accept tags for other versions. Escape `${VERSION}` for regex use (or compare with string/pattern matching that treats dots literally). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484235) + + Addressed in d2742bb: candidate tag parsing now uses prefix extraction plus explicit numeric suffix validation, avoiding VERSION regex interpolation. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/prepare-release.yml (line 190 RIGHT)`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439780)_ + +```diff +@@ -0,0 +1,309 @@ ++name: Prepare Release ++ ++on: # yamllint disable-line rule:truthy ++ workflow_dispatch: ++ inputs: ++ version: ++ description: "Semantic version to prepare (e.g., 1.2.3)" ++ required: true ++ type: string ++ dry-run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git-user-name: ++ description: "Git user name for commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git-user-email: ++ description: "Git user email for commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Preparation ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ release_branch: ${{ steps.vars.outputs.release_branch }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: dev ++ fetch-depth: 0 ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning format: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ RELEASE_BRANCH="release/$VERSION" ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ exit 1 ++ fi ++ ++ - name: Verify release branch does not exist ++ env: ++ RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} ++ run: | ++ set -euo pipefail ++ if git show-ref --verify --quiet "refs/heads/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists locally: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ if git show-ref --verify --quiet "refs/remotes/origin/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists on remote: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ ++ - name: Verify tag does not exist ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ set -euo pipefail ++ if git tag -l | grep -q "^$VERSION$"; then ++ echo "ERROR: Tag $VERSION already exists" ++ exit 1 ++ fi ++ ++ - name: Verify CHANGELOG has Unreleased section with content ++ run: | ++ set -euo pipefail ++ if ! grep -q "^## Unreleased" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md is missing '## Unreleased' section" ++ exit 1 ++ fi ++ ++ - name: Summary ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ DRY_RUN: ${{ inputs.dry-run }} ++ run: | ++ echo "Validation passed" ++ echo "Version: $VERSION" ++ echo "Release branch: $RELEASE_BRANCH" ++ echo "Image tag: $IMAGE_TAG" ++ echo "Dry-run: $DRY_RUN" ++ ++ prepare: ++ name: Prepare Release Branch ++ needs: validate ++ runs-on: ubuntu-22.04 ++ container: ++ image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }} ++ env: ++ UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv ++ timeout-minutes: 15 ++ if: ${{ inputs.dry-run != true }} ++ permissions: ++ contents: write ++ pull-requests: write ++ ++ steps: ++ - name: Checkout dev branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: dev ++ fetch-depth: 0 ++ ++ - name: Fix git safe.directory ++ run: git config --global --add safe.directory "$GITHUB_WORKSPACE" ++ ++ - name: Capture pre-prepare dev SHA ++ id: pre_state ++ env: ++ GH_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ PREPARE_START_SHA=$(gh api "repos/${{ github.repository }}/git/ref/heads/dev" --jq '.object.sha') ++ echo "prepare_start_sha=$PREPARE_START_SHA" >> "$GITHUB_OUTPUT" ++ ++ - name: Prepare CHANGELOG (freeze + reset) ++ env: ++ VERSION: ${{ needs.validate.outputs.version }} ++ run: | ++ set -euo pipefail ++ prepare-changelog prepare "$VERSION" CHANGELOG.md ++ ++ - name: Extract CHANGELOG content for PR body ++ id: changelog ++ env: ++ VERSION: ${{ needs.validate.outputs.version }} ++ run: | ++ set -euo pipefail ++ CHANGELOG_CONTENT=$(sed -n "/## \[$VERSION\]/,/## \[/p" CHANGELOG.md | sed '$d') +``` + +The `sed` range used to build `CHANGELOG_CONTENT` will terminate on the same header line because the end pattern `/## \[/` also matches `## [$VERSION]...`, resulting in an empty PR body section. Use a parser that starts *after* the `## [$VERSION]` header (e.g., awk like in release notes) or make the end regex match only the *next* header (e.g., `^## \[` with stateful logic). + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484427) + + Kept as-is intentionally. This sed range does produce section content correctly under GNU sed; no empty-body repro found. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/prepare-release.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439812)_ + +```diff +@@ -0,0 +1,309 @@ ++name: Prepare Release ++ ++on: # yamllint disable-line rule:truthy ++ workflow_dispatch: ++ inputs: ++ version: ++ description: "Semantic version to prepare (e.g., 1.2.3)" ++ required: true ++ type: string ++ dry-run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git-user-name: ++ description: "Git user name for commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git-user-email: ++ description: "Git user email for commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Preparation ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ release_branch: ${{ steps.vars.outputs.release_branch }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: dev ++ fetch-depth: 0 ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning format: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ RELEASE_BRANCH="release/$VERSION" ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ exit 1 ++ fi ++ ++ - name: Verify release branch does not exist ++ env: ++ RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} ++ run: | ++ set -euo pipefail ++ if git show-ref --verify --quiet "refs/heads/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists locally: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ if git show-ref --verify --quiet "refs/remotes/origin/$RELEASE_BRANCH"; then +``` + +`git show-ref --verify refs/remotes/origin/$RELEASE_BRANCH` is not a reliable way to detect whether the branch exists on the remote in Actions, because `actions/checkout` typically only fetches the checked-out ref. This can miss an existing `origin/release/*` branch and then `gh api .../git/refs` will fail later. Prefer `git ls-remote --heads origin "$RELEASE_BRANCH"` or a `gh api repos/.../branches/$RELEASE_BRANCH` check. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484586) + + Addressed in d2742bb: remote release-branch existence check now uses git ls-remote --exit-code --heads origin "$RELEASE_BRANCH". + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/prepare-release.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439838)_ + +```diff +@@ -0,0 +1,309 @@ ++name: Prepare Release ++ ++on: # yamllint disable-line rule:truthy ++ workflow_dispatch: ++ inputs: ++ version: ++ description: "Semantic version to prepare (e.g., 1.2.3)" ++ required: true ++ type: string ++ dry-run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git-user-name: ++ description: "Git user name for commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git-user-email: ++ description: "Git user email for commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Preparation ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ release_branch: ${{ steps.vars.outputs.release_branch }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: dev ++ fetch-depth: 0 ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning format: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ RELEASE_BRANCH="release/$VERSION" ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ exit 1 ++ fi ++ ++ - name: Verify release branch does not exist ++ env: ++ RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} ++ run: | ++ set -euo pipefail ++ if git show-ref --verify --quiet "refs/heads/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists locally: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ if git show-ref --verify --quiet "refs/remotes/origin/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists on remote: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ ++ - name: Verify tag does not exist ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ set -euo pipefail ++ if git tag -l | grep -q "^$VERSION$"; then ++ echo "ERROR: Tag $VERSION already exists" ++ exit 1 ++ fi +``` + +The tag existence check uses `git tag -l | grep -q "^$VERSION$"`, where dots in a SemVer (e.g., `1.2.3`) are treated as regex wildcards. This can mis-detect tags (false positives/negatives). Use an exact match (`git tag -l "$VERSION"`, `git tag -l --list "$VERSION"`, or `grep -Fx`). Also consider checking the remote with `git ls-remote --tags --refs origin "$VERSION"` rather than only local tags. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484752) + + Addressed in d2742bb: tag checks now use exact tag refs locally and remotely (rev-parse + ls-remote refs/tags/...). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/release-core.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439869)_ + +```diff +@@ -0,0 +1,454 @@ ++name: Release Core ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" ++ required: false ++ default: "candidate" ++ type: string ++ dry_run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git_user_name: ++ description: "Git user name for release commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ version: ++ description: "Resolved release version" ++ value: ${{ jobs.validate.outputs.version }} ++ pr_number: ++ description: "Release PR number" ++ value: ${{ jobs.validate.outputs.pr_number }} ++ release_date: ++ description: "UTC release date used for finalization" ++ value: ${{ jobs.validate.outputs.release_date }} ++ pre_finalize_sha: ++ description: "Release branch SHA before finalization" ++ value: ${{ jobs.validate.outputs.pre_finalize_sha }} ++ release_kind: ++ description: "Release kind (candidate or final)" ++ value: ${{ jobs.validate.outputs.release_kind }} ++ publish_version: ++ description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ value: ${{ jobs.validate.outputs.publish_version }} ++ finalize_sha: ++ description: "Release branch SHA after finalization" ++ value: ${{ jobs.finalize.outputs.finalize_sha }} ++ image_tag: ++ description: "Resolved devcontainer image tag" ++ value: ${{ jobs.validate.outputs.image_tag }} ++ ++concurrency: ++ group: release-core ++ cancel-in-progress: false ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Core ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ pr_number: ${{ steps.pr.outputs.pr_number }} ++ release_date: ${{ steps.vars.outputs.release_date }} ++ pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} ++ release_kind: ${{ steps.vars.outputs.release_kind }} ++ publish_version: ${{ steps.publish_meta.outputs.publish_version }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ INPUT_RELEASE_KIND: ${{ inputs.release_kind }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ RELEASE_KIND="$INPUT_RELEASE_KIND" ++ ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then ++ echo "ERROR: Invalid release-kind '$RELEASE_KIND'" ++ echo "release-kind must be one of: candidate, final" ++ exit 1 ++ fi ++ ++ RELEASE_DATE=$(date -u +%Y-%m-%d) ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" ++ echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ steps.vars.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ echo "Validating image availability: $IMAGE" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ echo "Check whether the tag exists and whether this workflow has access to GHCR." ++ exit 1 ++ fi ++ ++ - name: Record pre-finalization SHA ++ id: pre_sha ++ run: | ++ PRE_FINALIZE_SHA=$(git rev-parse HEAD) ++ echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" ++ echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" ++ ++ - name: Verify CHANGELOG has TBD entry ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ if ! grep -q "## \[$VERSION\] - TBD" CHANGELOG.md; then +``` + +This `grep` pattern interpolates `$VERSION` into a regular expression, so dots in SemVer act as wildcards and can match the wrong section header. Use `grep -F`/`fgrep`, or escape the version before using it in a regex. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484886) + + Addressed in d2742bb: CHANGELOG TBD validation now uses literal matching (grep -Fq). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/release-core.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439896)_ + +```diff +@@ -0,0 +1,454 @@ ++name: Release Core ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" ++ required: false ++ default: "candidate" ++ type: string ++ dry_run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git_user_name: ++ description: "Git user name for release commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ version: ++ description: "Resolved release version" ++ value: ${{ jobs.validate.outputs.version }} ++ pr_number: ++ description: "Release PR number" ++ value: ${{ jobs.validate.outputs.pr_number }} ++ release_date: ++ description: "UTC release date used for finalization" ++ value: ${{ jobs.validate.outputs.release_date }} ++ pre_finalize_sha: ++ description: "Release branch SHA before finalization" ++ value: ${{ jobs.validate.outputs.pre_finalize_sha }} ++ release_kind: ++ description: "Release kind (candidate or final)" ++ value: ${{ jobs.validate.outputs.release_kind }} ++ publish_version: ++ description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ value: ${{ jobs.validate.outputs.publish_version }} ++ finalize_sha: ++ description: "Release branch SHA after finalization" ++ value: ${{ jobs.finalize.outputs.finalize_sha }} ++ image_tag: ++ description: "Resolved devcontainer image tag" ++ value: ${{ jobs.validate.outputs.image_tag }} ++ ++concurrency: ++ group: release-core ++ cancel-in-progress: false ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Core ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ pr_number: ${{ steps.pr.outputs.pr_number }} ++ release_date: ${{ steps.vars.outputs.release_date }} ++ pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} ++ release_kind: ${{ steps.vars.outputs.release_kind }} ++ publish_version: ${{ steps.publish_meta.outputs.publish_version }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ INPUT_RELEASE_KIND: ${{ inputs.release_kind }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ RELEASE_KIND="$INPUT_RELEASE_KIND" ++ ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then ++ echo "ERROR: Invalid release-kind '$RELEASE_KIND'" ++ echo "release-kind must be one of: candidate, final" ++ exit 1 ++ fi ++ ++ RELEASE_DATE=$(date -u +%Y-%m-%d) ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" ++ echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ steps.vars.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ echo "Validating image availability: $IMAGE" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ echo "Check whether the tag exists and whether this workflow has access to GHCR." ++ exit 1 ++ fi ++ ++ - name: Record pre-finalization SHA ++ id: pre_sha ++ run: | ++ PRE_FINALIZE_SHA=$(git rev-parse HEAD) ++ echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" ++ echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" ++ ++ - name: Verify CHANGELOG has TBD entry ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ if ! grep -q "## \[$VERSION\] - TBD" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" ++ exit 1 ++ fi ++ ++ - name: Compute publish version ++ id: publish_meta ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} ++ run: | ++ set -euo pipefail ++ NEXT_RC="" ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ TAG_PATTERN="${VERSION}-rc*" ++ EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" | awk '{print $2}' | sed 's#refs/tags/##') ++ ++ MAX_RC=0 ++ if [ -n "$EXISTING_TAGS" ]; then ++ while IFS= read -r tag; do ++ [ -z "$tag" ] && continue ++ if ! echo "$tag" | grep -qE "^${VERSION}-rc[0-9]+$"; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 ++ fi ++ rc_num="${tag##*-rc}" ++ if [ "$rc_num" -gt "$MAX_RC" ]; then ++ MAX_RC="$rc_num" ++ fi ++ done <<< "$EXISTING_TAGS" ++ fi ++ ++ NEXT_RC=$((MAX_RC + 1)) ++ PUBLISH_VERSION="${VERSION}-rc${NEXT_RC}" ++ else ++ PUBLISH_VERSION="$VERSION" ++ fi ++ echo "publish_version=$PUBLISH_VERSION" >> "$GITHUB_OUTPUT" ++ echo "next_rc=$NEXT_RC" >> "$GITHUB_OUTPUT" ++ ++ - name: Verify publish tag does not exist ++ env: ++ PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} ++ run: | ++ if git ls-remote --tags --refs origin "$PUBLISH_VERSION" | grep -q "refs/tags/$PUBLISH_VERSION$"; then ++ echo "ERROR: Tag $PUBLISH_VERSION already exists" ++ exit 1 ++ fi ++ if git tag -l | grep -q "^${PUBLISH_VERSION}$"; then +``` + +`git tag -l | grep -q "^${PUBLISH_VERSION}$"` treats dots in `PUBLISH_VERSION` as regex wildcards, which can mis-detect existing tags. Use an exact match (`git tag -l --list "$PUBLISH_VERSION"` or `grep -Fx`) and avoid regex interpolation for SemVer strings. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941484990) + + Addressed in d2742bb: local publish tag validation now uses exact ref verification (refs/tags/$PUBLISH_VERSION). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:14 PM_ + +_File: [`assets/workspace/.github/workflows/release-publish.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941439917)_ + +```diff +@@ -0,0 +1,145 @@ ++name: Release Publish ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ finalize_sha: ++ description: "Finalized commit SHA to publish" ++ required: true ++ type: string ++ release_date: ++ description: "Release date used for summary output" ++ required: true ++ type: string ++ publish_version: ++ description: "Version tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release kind (candidate or final)" ++ required: true ++ type: string ++ git_user_name: ++ description: "Git user name for release tag operations" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release tag operations" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ tag: ++ description: "Published tag" ++ value: ${{ jobs.publish.outputs.tag }} ++ release_url: ++ description: "Published release URL" ++ value: ${{ jobs.publish.outputs.release_url }} ++ ++permissions: ++ contents: read ++ ++jobs: ++ publish: ++ name: Publish Release ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ permissions: ++ contents: write ++ outputs: ++ tag: ${{ steps.out.outputs.tag }} ++ release_url: ${{ steps.out.outputs.release_url }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Checkout finalized commit ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: ${{ inputs.finalize_sha }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Configure git ++ env: ++ GIT_USER_NAME: ${{ inputs.git_user_name }} ++ GIT_USER_EMAIL: ${{ inputs.git_user_email }} ++ run: | ++ git config user.name "$GIT_USER_NAME" ++ git config user.email "$GIT_USER_EMAIL" ++ ++ - name: Create and push tag ++ env: ++ PUBLISH_VERSION: ${{ inputs.publish_version }} ++ run: | ++ set -euo pipefail ++ git tag -a "$PUBLISH_VERSION" -m "Release $PUBLISH_VERSION" ++ git push origin "$PUBLISH_VERSION" ++ ++ - name: Extract release notes from CHANGELOG ++ env: ++ VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ awk '/^## \['"$VERSION"'\]/{found=1; next} /^## \[/{found=0} found' \ +``` + +The `awk` regex used to extract release notes interpolates `$VERSION` unescaped, so dots in a SemVer (e.g., `1.2.3`) act as wildcards and can match the wrong header. Escape the version for regex use, or switch to a literal match approach (e.g., `index($0, "## [" VERSION "]") == 1`). + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:22 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941485105) + + Addressed in d2742bb: release notes extraction now uses literal header matching in awk via index(). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:28 PM_ + +_File: [`assets/workspace/.github/actions/validate-contract/action.yml`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941516386)_ + +```diff +@@ -0,0 +1,27 @@ ++name: Validate release contract version ++description: Validate that the caller's contract version matches the expected version ++ ++inputs: ++ contract_version: ++ description: "Contract version received from caller" ++ required: true ++ expected_version: ++ description: "Expected contract version" ++ required: true +``` + +`expected_version` is marked `required: true` while also providing `default: "1"`, and all current callers only pass `contract_version`. This is contradictory metadata and makes the contract unclear for downstream users (and could be interpreted as requiring an explicit input despite the default). Consider making `expected_version` `required: false` (keeping the default) or removing the default and requiring callers to pass it explicitly. + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:35 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941555904) + + Addressed in 427e3e1: `expected_version` now uses `required: false` with default `"1"`, which matches current caller behavior and keeps contract metadata clear. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:28 PM_ + +_File: [`assets/workspace/.github/workflows/prepare-release.yml (line 130 RIGHT)`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941516415)_ + +```diff +@@ -0,0 +1,313 @@ ++name: Prepare Release ++ ++on: # yamllint disable-line rule:truthy ++ workflow_dispatch: ++ inputs: ++ version: ++ description: "Semantic version to prepare (e.g., 1.2.3)" ++ required: true ++ type: string ++ dry-run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git-user-name: ++ description: "Git user name for commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git-user-email: ++ description: "Git user email for commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Preparation ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ release_branch: ${{ steps.vars.outputs.release_branch }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: dev ++ fetch-depth: 0 ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning format: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ RELEASE_BRANCH="release/$VERSION" ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ exit 1 ++ fi ++ ++ - name: Verify release branch does not exist ++ env: ++ RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} ++ run: | ++ set -euo pipefail ++ if git show-ref --verify --quiet "refs/heads/$RELEASE_BRANCH"; then ++ echo "ERROR: Release branch already exists locally: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ if git ls-remote --exit-code --heads origin "$RELEASE_BRANCH" > /dev/null 2>&1; then ++ echo "ERROR: Release branch already exists on remote: $RELEASE_BRANCH" ++ exit 1 ++ fi ++ ++ - name: Verify tag does not exist ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ set -euo pipefail ++ if git rev-parse -q --verify "refs/tags/$VERSION" > /dev/null; then ++ echo "ERROR: Tag $VERSION already exists" ++ exit 1 ++ fi ++ if git ls-remote --exit-code --tags --refs origin "refs/tags/$VERSION" > /dev/null 2>&1; then ++ echo "ERROR: Tag $VERSION already exists on remote" ++ exit 1 ++ fi ++ ++ - name: Verify CHANGELOG has Unreleased section with content ++ run: | ++ set -euo pipefail ++ if ! grep -q "^## Unreleased" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md is missing '## Unreleased' section" ++ exit 1 ++ fi ++ +``` + +The step name says "Verify CHANGELOG has Unreleased section with content", but the check only verifies the header exists (`grep "^## Unreleased"`) and does not validate that the section is non-empty. Either update the step/error message to reflect what’s actually checked, or extend the check to ensure there’s at least one entry under `## Unreleased` (before the next `## [` header). + + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:35 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941556021) + + Addressed in 427e3e1: step now validates both the `## Unreleased` header and presence of at least one bullet entry before the next release section. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:28 PM_ + +_File: [`assets/workspace/.github/workflows/release-publish.yml (line 150 RIGHT)`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941516435)_ + +```diff +@@ -0,0 +1,148 @@ ++name: Release Publish ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ finalize_sha: ++ description: "Finalized commit SHA to publish" ++ required: true ++ type: string ++ release_date: ++ description: "Release date used for summary output" ++ required: true ++ type: string ++ publish_version: ++ description: "Version tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release kind (candidate or final)" ++ required: true ++ type: string ++ git_user_name: ++ description: "Git user name for release tag operations" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release tag operations" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ tag: ++ description: "Published tag" ++ value: ${{ jobs.publish.outputs.tag }} ++ release_url: ++ description: "Published release URL" ++ value: ${{ jobs.publish.outputs.release_url }} ++ ++permissions: ++ contents: read ++ ++jobs: ++ publish: ++ name: Publish Release ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ permissions: ++ contents: write ++ outputs: ++ tag: ${{ steps.out.outputs.tag }} ++ release_url: ${{ steps.out.outputs.release_url }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Checkout finalized commit ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: ${{ inputs.finalize_sha }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Configure git ++ env: ++ GIT_USER_NAME: ${{ inputs.git_user_name }} ++ GIT_USER_EMAIL: ${{ inputs.git_user_email }} ++ run: | ++ git config user.name "$GIT_USER_NAME" ++ git config user.email "$GIT_USER_EMAIL" ++ ++ - name: Create and push tag ++ env: ++ PUBLISH_VERSION: ${{ inputs.publish_version }} ++ run: | ++ set -euo pipefail ++ git tag -a "$PUBLISH_VERSION" -m "Release $PUBLISH_VERSION" ++ git push origin "$PUBLISH_VERSION" ++ ++ - name: Extract release notes from CHANGELOG ++ env: ++ VERSION: ${{ inputs.version }} ++ run: | ++ set -euo pipefail ++ awk -v version="$VERSION" ' ++ index($0, "## [" version "]") == 1 {found=1; next} ++ /^## \[/ && found {found=0} ++ found ++ ' CHANGELOG.md > /tmp/release-notes.md ++ if [ ! -s /tmp/release-notes.md ]; then ++ echo "No changelog notes found for $VERSION" > /tmp/release-notes.md ++ fi ++ ++ - name: Create GitHub Release ++ env: ++ PUBLISH_VERSION: ${{ inputs.publish_version }} ++ RELEASE_KIND: ${{ inputs.release_kind }} ++ GH_TOKEN: ${{ steps.auth.outputs.token }} ++ run: | ++ set -euo pipefail ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ gh release create "$PUBLISH_VERSION" \ ++ --title "$PUBLISH_VERSION" \ ++ --notes-file /tmp/release-notes.md \ ++ --verify-tag \ ++ --prerelease ++ else ++ gh release create "$PUBLISH_VERSION" \ ++ --title "$PUBLISH_VERSION" \ ++ --notes-file /tmp/release-notes.md \ ++ --verify-tag ++ fi +``` + +`release_kind` is treated as a boolean switch where any value other than the exact string `candidate` falls into the `else` branch and creates a non-prerelease (final) GitHub Release. Since this workflow is reusable via `workflow_call`, add an explicit validation step to reject unknown `release_kind` values (e.g., only allow `candidate` or `final`) to prevent accidental final releases from typos or unexpected inputs. + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:35 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941556139) + + Addressed in 427e3e1: added explicit `release_kind` validation in `release-publish.yml` to allow only `candidate` or `final`. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 04:28 PM_ + +_File: [`assets/workspace/.github/workflows/release-core.yml (line 395 RIGHT)`](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941516460)_ + +```diff +@@ -0,0 +1,459 @@ ++name: Release Core ++ ++on: # yamllint disable-line rule:truthy ++ workflow_call: ++ inputs: ++ version: ++ description: "Semantic version to release (e.g., 1.2.3)" ++ required: true ++ type: string ++ release_kind: ++ description: "Release mode: candidate publishes X.Y.Z-rcN, final publishes X.Y.Z" ++ required: false ++ default: "candidate" ++ type: string ++ dry_run: ++ description: "Validate without making changes" ++ required: false ++ default: false ++ type: boolean ++ git_user_name: ++ description: "Git user name for release commits" ++ required: false ++ default: "github-actions[bot]" ++ type: string ++ git_user_email: ++ description: "Git user email for release commits" ++ required: false ++ default: "41898282+github-actions[bot]@users.noreply.github.com" ++ type: string ++ contract_version: ++ description: "Release workflow contract version" ++ required: true ++ type: string ++ secrets: ++ token: ++ required: false ++ outputs: ++ version: ++ description: "Resolved release version" ++ value: ${{ jobs.validate.outputs.version }} ++ pr_number: ++ description: "Release PR number" ++ value: ${{ jobs.validate.outputs.pr_number }} ++ release_date: ++ description: "UTC release date used for finalization" ++ value: ${{ jobs.validate.outputs.release_date }} ++ pre_finalize_sha: ++ description: "Release branch SHA before finalization" ++ value: ${{ jobs.validate.outputs.pre_finalize_sha }} ++ release_kind: ++ description: "Release kind (candidate or final)" ++ value: ${{ jobs.validate.outputs.release_kind }} ++ publish_version: ++ description: "Tag to publish (X.Y.Z or X.Y.Z-rcN)" ++ value: ${{ jobs.validate.outputs.publish_version }} ++ finalize_sha: ++ description: "Release branch SHA after finalization" ++ value: ${{ jobs.finalize.outputs.finalize_sha }} ++ image_tag: ++ description: "Resolved devcontainer image tag" ++ value: ${{ jobs.validate.outputs.image_tag }} ++ ++concurrency: ++ group: release-core ++ cancel-in-progress: false ++ ++permissions: ++ contents: read ++ ++jobs: ++ validate: ++ name: Validate Release Core ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 10 ++ outputs: ++ version: ${{ steps.vars.outputs.version }} ++ pr_number: ${{ steps.pr.outputs.pr_number }} ++ release_date: ${{ steps.vars.outputs.release_date }} ++ pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} ++ release_kind: ${{ steps.vars.outputs.release_kind }} ++ publish_version: ${{ steps.publish_meta.outputs.publish_version }} ++ image_tag: ${{ steps.resolve_image.outputs.image_tag }} ++ ++ steps: ++ - name: Validate contract version ++ uses: ./.github/actions/validate-contract ++ with: ++ contract_version: ${{ inputs.contract_version }} ++ ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Validate and prepare variables ++ id: vars ++ env: ++ INPUT_VERSION: ${{ inputs.version }} ++ INPUT_RELEASE_KIND: ${{ inputs.release_kind }} ++ run: | ++ set -euo pipefail ++ VERSION="$INPUT_VERSION" ++ RELEASE_KIND="$INPUT_RELEASE_KIND" ++ ++ if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ++ echo "ERROR: Invalid version format '$VERSION'" ++ echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)" ++ exit 1 ++ fi ++ if [ "$RELEASE_KIND" != "candidate" ] && [ "$RELEASE_KIND" != "final" ]; then ++ echo "ERROR: Invalid release-kind '$RELEASE_KIND'" ++ echo "release-kind must be one of: candidate, final" ++ exit 1 ++ fi ++ ++ RELEASE_DATE=$(date -u +%Y-%m-%d) ++ echo "version=$VERSION" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$RELEASE_KIND" >> "$GITHUB_OUTPUT" ++ echo "release_date=$RELEASE_DATE" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ steps.vars.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Resolve container image tag ++ id: resolve_image ++ run: | ++ set -euo pipefail ++ TAG="" ++ if [ -f ".vig-os" ]; then ++ TAG=$(awk -F= '/^DEVCONTAINER_VERSION=/{gsub(/^[ \t"]+|[ \t"]+$/, "", $2); print $2; exit}' .vig-os || true) ++ fi ++ if [ -z "${TAG:-}" ]; then ++ TAG="latest" ++ fi ++ echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" ++ ++ - name: Validate image accessibility ++ env: ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ set -euo pipefail ++ IMAGE="ghcr.io/vig-os/devcontainer:${IMAGE_TAG}" ++ echo "Validating image availability: $IMAGE" ++ if ! docker manifest inspect "$IMAGE" > /dev/null 2>&1; then ++ echo "ERROR: Cannot access image manifest: $IMAGE" ++ echo "Check whether the tag exists and whether this workflow has access to GHCR." ++ exit 1 ++ fi ++ ++ - name: Record pre-finalization SHA ++ id: pre_sha ++ run: | ++ PRE_FINALIZE_SHA=$(git rev-parse HEAD) ++ echo "pre_finalize_sha=$PRE_FINALIZE_SHA" >> "$GITHUB_OUTPUT" ++ echo "Pre-finalization SHA: $PRE_FINALIZE_SHA" ++ ++ - name: Verify CHANGELOG has TBD entry ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ if ! grep -Fq "## [$VERSION] - TBD" CHANGELOG.md; then ++ echo "ERROR: CHANGELOG.md does not contain '## [$VERSION] - TBD'" ++ exit 1 ++ fi ++ ++ - name: Compute publish version ++ id: publish_meta ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} ++ run: | ++ set -euo pipefail ++ NEXT_RC="" ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ TAG_PATTERN="${VERSION}-rc*" ++ EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" | awk '{print $2}' | sed 's#refs/tags/##') ++ ++ MAX_RC=0 ++ if [ -n "$EXISTING_TAGS" ]; then ++ while IFS= read -r tag; do ++ [ -z "$tag" ] && continue ++ if [ "${tag#${VERSION}-rc}" = "$tag" ]; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 ++ fi ++ rc_num="${tag#${VERSION}-rc}" ++ if ! echo "$rc_num" | grep -qE '^[0-9]+$'; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 ++ fi ++ if [ "$rc_num" -gt "$MAX_RC" ]; then ++ MAX_RC="$rc_num" ++ fi ++ done <<< "$EXISTING_TAGS" ++ fi ++ ++ NEXT_RC=$((MAX_RC + 1)) ++ PUBLISH_VERSION="${VERSION}-rc${NEXT_RC}" ++ else ++ PUBLISH_VERSION="$VERSION" ++ fi ++ echo "publish_version=$PUBLISH_VERSION" >> "$GITHUB_OUTPUT" ++ echo "next_rc=$NEXT_RC" >> "$GITHUB_OUTPUT" ++ ++ - name: Verify publish tag does not exist ++ env: ++ PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} ++ run: | ++ if git ls-remote --exit-code --tags --refs origin "refs/tags/$PUBLISH_VERSION" > /dev/null 2>&1; then ++ echo "ERROR: Tag $PUBLISH_VERSION already exists" ++ exit 1 ++ fi ++ if git rev-parse -q --verify "refs/tags/$PUBLISH_VERSION" > /dev/null; then ++ echo "ERROR: Local tag $PUBLISH_VERSION already exists" ++ exit 1 ++ fi ++ ++ - name: Find and verify PR ++ id: pr ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ GH_TOKEN: ${{ steps.auth.outputs.token }} ++ run: | ++ set -euo pipefail ++ ++ PR_JSON=$(gh pr list \ ++ --head "release/$VERSION" \ ++ --base main \ ++ --json number,isDraft,reviewDecision,statusCheckRollup \ ++ --limit 1) ++ ++ PR_COUNT=$(echo "$PR_JSON" | jq 'length') ++ if [ "$PR_COUNT" != "1" ]; then ++ echo "ERROR: Expected exactly 1 PR from release/$VERSION to main, found $PR_COUNT" ++ exit 1 ++ fi ++ ++ PR_NUMBER=$(echo "$PR_JSON" | jq -r '.[0].number') ++ IS_DRAFT=$(echo "$PR_JSON" | jq -r '.[0].isDraft') ++ REVIEW_DECISION=$(echo "$PR_JSON" | jq -r '.[0].reviewDecision') ++ echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" ++ ++ if [ "$IS_DRAFT" = "true" ]; then ++ echo "ERROR: PR #$PR_NUMBER is still in draft" ++ exit 1 ++ fi ++ if [ "$REVIEW_DECISION" != "APPROVED" ]; then ++ echo "ERROR: PR #$PR_NUMBER is not approved (status: $REVIEW_DECISION)" ++ exit 1 ++ fi ++ ++ STATUS_ROLLUP=$(echo "$PR_JSON" | jq -r '.[0].statusCheckRollup // []') ++ CI_FAILED=$(echo "$STATUS_ROLLUP" | jq '[.[] | select(.conclusion == "FAILURE" or .conclusion == "ERROR")] | length') ++ if [ "$CI_FAILED" != "0" ]; then ++ echo "ERROR: PR #$PR_NUMBER has failed CI checks" ++ exit 1 ++ fi ++ ++ - name: Summary ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ RELEASE_KIND: ${{ steps.vars.outputs.release_kind }} ++ PUBLISH_VERSION: ${{ steps.publish_meta.outputs.publish_version }} ++ NEXT_RC: ${{ steps.publish_meta.outputs.next_rc }} ++ PR_NUMBER: ${{ steps.pr.outputs.pr_number }} ++ RELEASE_DATE: ${{ steps.vars.outputs.release_date }} ++ DRY_RUN: ${{ inputs.dry_run }} ++ IMAGE_TAG: ${{ steps.resolve_image.outputs.image_tag }} ++ run: | ++ echo "Validation passed" ++ echo "" ++ echo " Version: $VERSION" ++ echo " Release Kind: $RELEASE_KIND" ++ echo " Publish Tag: $PUBLISH_VERSION" ++ if [ -n "$NEXT_RC" ]; then ++ echo " Next RC: $NEXT_RC" ++ fi ++ echo " Branch: release/$VERSION" ++ echo " PR: #$PR_NUMBER" ++ echo " Release Date: $RELEASE_DATE" ++ echo " Image Tag: $IMAGE_TAG" ++ echo " Dry-run: $DRY_RUN" ++ ++ finalize: ++ name: Finalize Release Core ++ needs: validate ++ runs-on: ubuntu-22.04 ++ container: ++ image: ghcr.io/vig-os/devcontainer:${{ needs.validate.outputs.image_tag }} ++ env: ++ UV_PROJECT_ENVIRONMENT: /root/assets/workspace/.venv ++ timeout-minutes: 15 ++ if: ${{ inputs.dry_run != true }} ++ permissions: ++ contents: write ++ outputs: ++ finalize_sha: ${{ steps.finalize.outputs.finalize_sha }} ++ ++ steps: ++ - name: Resolve auth token ++ id: auth ++ env: ++ PROVIDED_TOKEN: ${{ secrets.token }} ++ FALLBACK_TOKEN: ${{ github.token }} ++ run: | ++ set -euo pipefail ++ if [ -n "${PROVIDED_TOKEN:-}" ]; then ++ echo "token=$PROVIDED_TOKEN" >> "$GITHUB_OUTPUT" ++ else ++ echo "token=$FALLBACK_TOKEN" >> "$GITHUB_OUTPUT" ++ fi ++ ++ - name: Checkout release branch ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ with: ++ ref: release/${{ needs.validate.outputs.version }} ++ fetch-depth: 0 ++ token: ${{ steps.auth.outputs.token }} ++ ++ - name: Fix git safe.directory ++ run: git config --global --add safe.directory "$GITHUB_WORKSPACE" ++ ++ - name: Finalize CHANGELOG date ++ if: ${{ inputs.release_kind == 'final' }} ++ env: ++ VERSION: ${{ needs.validate.outputs.version }} ++ RELEASE_DATE: ${{ needs.validate.outputs.release_date }} ++ run: | ++ set -euo pipefail ++ prepare-changelog finalize "$VERSION" "$RELEASE_DATE" CHANGELOG.md ++ ++ - name: Commit and push finalization changes via API ++ if: ${{ inputs.release_kind == 'final' }} ++ uses: vig-os/commit-action@c0024cbad0e501764127cccab732c6cd465b4646 # v0.1.5 ++ env: ++ GH_TOKEN: ${{ steps.auth.outputs.token }} ++ GITHUB_REPOSITORY: ${{ github.repository }} ++ TARGET_BRANCH: refs/heads/release/${{ needs.validate.outputs.version }} ++ COMMIT_MESSAGE: |- ++ chore: finalize release ${{ needs.validate.outputs.version }} ++ ++ Set release date to ${{ needs.validate.outputs.release_date }} in CHANGELOG.md ++ ++ Refs: #${{ needs.validate.outputs.pr_number }} ++ FILE_PATHS: CHANGELOG.md ++ ++ - name: Trigger sync-issues workflow ++ if: ${{ inputs.release_kind == 'final' }} ++ env: ++ GH_TOKEN: ${{ steps.auth.outputs.token }} ++ VERSION: ${{ needs.validate.outputs.version }} ++ run: | ++ set -euo pipefail ++ gh workflow run sync-issues.yml -f "target-branch=release/$VERSION" ++ ++ - name: Wait for sync-issues completion ++ if: ${{ inputs.release_kind == 'final' }} ++ env: ++ GH_TOKEN: ${{ steps.auth.outputs.token }} ++ run: | ++ set -euo pipefail ++ TIMEOUT=120 ++ ELAPSED=0 ++ INTERVAL=10 ++ sleep 5 ++ ELAPSED=5 ++ ++ while [ $ELAPSED -lt $TIMEOUT ]; do ++ RUN_STATUS=$(gh run list \ ++ --workflow sync-issues.yml \ ++ --limit 1 \ ++ --json status,conclusion \ ++ --jq '.[0].status' 2>/dev/null || echo "unknown") ++ ++ if [ "$RUN_STATUS" = "completed" ]; then ++ break ++ fi ++ ++ sleep "$INTERVAL" +``` + +The sync-issues wait loop stops when `status == completed`, but after the loop there’s no check for (a) timing out without completion or (b) a non-success conclusion. As written, the workflow can proceed to `git fetch/reset` even if sync-issues is still running or has failed, which can leave the release branch in an unexpected state. Add an explicit timeout/conclusion check after the loop (fail the job, or at least emit a clear warning and skip pulling). + +Conversation: + +- **[@c-vigo](https://github.com/c-vigo)** on March 16, 2026 at 04:35 PM — [link](https://github.com/vig-os/devcontainer/pull/329#discussion_r2941556249) + + Addressed in 427e3e1: `sync-issues` wait step now fails on timeout and also fails when completion conclusion is not `success`, preventing downstream pull/reset from continuing in bad states. + + +--- +--- + +## Commits + +### Commit 1: [00ea919](https://github.com/vig-os/devcontainer/commit/00ea919df2aeed434490fdde512eabb670228b47) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 03:55 PM +ci(ci): split release workflow into core extension and publish stages, 1164 files modified + +### Commit 2: [59a72d3](https://github.com/vig-os/devcontainer/commit/59a72d32b29d22ecc520aaf4d5fe8f3dd01bf7d4) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 03:55 PM +ci(ci): add prepare release workflow and dispatch helpers, 324 files modified (assets/workspace/.github/workflows/prepare-release.yml, assets/workspace/justfile.project) + +### Commit 3: [a09a0b7](https://github.com/vig-os/devcontainer/commit/a09a0b72dd3fa07e3db0a3b7e54095e27726a7a6) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 03:55 PM +docs(ci): document downstream release contract and release modes, 159 files modified (CHANGELOG.md, docs/DOWNSTREAM_RELEASE.md, docs/RELEASE_CYCLE.md) + +### Commit 4: [d2742bb](https://github.com/vig-os/devcontainer/commit/d2742bb2dc293509ebc253d99aaf56ff90cf7bd8) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 04:21 PM +fix(ci): harden release workflow tag and ref checks, 30 files modified (assets/workspace/.github/workflows/prepare-release.yml, assets/workspace/.github/workflows/release-core.yml, assets/workspace/.github/workflows/release-publish.yml) + +### Commit 5: [427e3e1](https://github.com/vig-os/devcontainer/commit/427e3e17f067c35601130a3fb31a5e7b2cff135d) by [c-vigo](https://github.com/c-vigo) on March 16, 2026 at 04:34 PM +fix(ci): tighten release workflow validation and sync gating, 41 files modified diff --git a/docs/pull-requests/pr-332.md b/docs/pull-requests/pr-332.md new file mode 100644 index 00000000..266c7c36 --- /dev/null +++ b/docs/pull-requests/pr-332.md @@ -0,0 +1,465 @@ +--- +type: pull_request +state: open +branch: feature/331-trigger-downstream-smoke-test-release-gate → dev +created: 2026-03-16T22:21:11Z +updated: 2026-03-16T22:26:07Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/devcontainer/pull/332 +comments: 5 +labels: none +assignees: c-vigo +milestone: none +projects: none +synced: 2026-03-17T04:24:09.435Z +--- + +# [PR 332](https://github.com/vig-os/devcontainer/pull/332) ci: enforce downstream smoke-test release gate + +## Description + +Automates downstream smoke-test release gating for the release pipeline so final release publication requires verified downstream RC pre-release state. +Also introduces a downstream repository-dispatch workflow template and aligns release documentation/changelog with the new gate behavior. + +## Type of Change + +- [ ] `feat` -- New feature +- [ ] `fix` -- Bug fix +- [ ] `docs` -- Documentation only +- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `refactor` -- Code restructuring (no behavior change) +- [ ] `test` -- Adding or updating tests +- [x] `ci` -- CI/CD pipeline changes +- [ ] `build` -- Build system or dependency changes +- [ ] `revert` -- Reverts a previous commit +- [ ] `style` -- Code style (formatting, whitespace) + +### Modifiers + +- [ ] Breaking change (`!`) -- This change breaks backward compatibility + +## Changes Made + +- `.github/workflows/release.yml` + - Add final-release validation that discovers latest RC tag and verifies downstream RC release is present as `prerelease=true` + - Move smoke-test dispatch into a dedicated `smoke-test` job and include `client_payload[release_kind]` + - Wait for downstream release creation and enforce expected pre-release/final release type by release kind + - Tighten rollback trigger condition to explicit upstream job failure states +- `assets/workspace/.github/workflows/repository-dispatch.yml` + - Add workflow for `repository_dispatch` (`smoke-test-trigger`) payload handling + - Validate payload contract and infer `release_kind` when missing for backward compatibility + - Run smoke tests and create downstream GitHub release (pre-release for candidates, full release for finals) +- `docs/DOWNSTREAM_RELEASE.md`, `docs/RELEASE_CYCLE.md` + - Document automated smoke-test gate behavior and release_kind dispatch contract + - Replace manual smoke-gate guidance with automated gate prerequisites and verification details + +## Changelog Entry + +### Changed +- **Final release now requires downstream RC pre-release gate** ([#331](https://github.com/vig-os/devcontainer/issues/331)) + - Add upstream final-release validation that requires a downstream GitHub pre-release for the latest published RC tag + - Move smoke-test dispatch to a dedicated release job and include `release_kind` in the dispatch payload + - Add downstream `repository-dispatch.yml` template that runs smoke tests and creates pre-release/final release artifacts + +## Testing + +- [ ] Tests pass locally (`just test`) +- [ ] Manual testing performed (describe below) + +### Manual Testing Details + +N/A + +## Checklist + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) +- [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) +- [ ] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Additional Notes + +Changes were prepared as three atomic commits to isolate upstream orchestrator logic, downstream workflow template, and docs/changelog updates. + +Refs: #331 + + + +--- +--- + +## Review Threads (5) + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:26 PM_ + +_File: [`assets/workspace/.github/workflows/repository-dispatch.yml (line 69 RIGHT)`](https://github.com/vig-os/devcontainer/pull/332#discussion_r2943297728)_ + +```diff +@@ -0,0 +1,126 @@ ++name: Repository Dispatch ++ ++on: # yamllint disable-line rule:truthy ++ repository_dispatch: ++ types: [smoke-test-trigger] ++ ++permissions: ++ contents: write ++ ++jobs: ++ smoke-test-release: ++ name: Smoke Test and Release ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 30 ++ env: ++ GH_TOKEN: ${{ github.token }} ++ steps: ++ - name: Resolve dispatch payload ++ id: payload ++ run: | ++ set -euo pipefail ++ ++ TAG=$(jq -r '.client_payload.tag // empty' "$GITHUB_EVENT_PATH") ++ KIND=$(jq -r '.client_payload.release_kind // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_REPO=$(jq -r '.client_payload.source_repo // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_WORKFLOW=$(jq -r '.client_payload.source_workflow // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_RUN_ID=$(jq -r '.client_payload.source_run_id // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_RUN_URL=$(jq -r '.client_payload.source_run_url // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_SHA=$(jq -r '.client_payload.source_sha // empty' "$GITHUB_EVENT_PATH") ++ CORRELATION_ID=$(jq -r '.client_payload.correlation_id // empty' "$GITHUB_EVENT_PATH") ++ ++ if [ -z "$TAG" ]; then ++ echo "ERROR: repository_dispatch payload is missing client_payload.tag" ++ exit 1 ++ fi ++ ++ if [ -z "$KIND" ]; then ++ if echo "$TAG" | grep -qE -- '-rc[0-9]+$'; then ++ KIND="candidate" ++ else ++ KIND="final" ++ fi ++ fi ++ ++ if [ "$KIND" != "candidate" ] && [ "$KIND" != "final" ]; then ++ echo "ERROR: Invalid release_kind '$KIND' (expected candidate or final)" ++ exit 1 ++ fi ++ ++ if [ -z "$SOURCE_RUN_URL" ] && [ -n "$SOURCE_REPO" ] && [ -n "$SOURCE_RUN_ID" ]; then ++ SOURCE_RUN_URL="https://github.com/${SOURCE_REPO}/actions/runs/${SOURCE_RUN_ID}" ++ fi ++ ++ echo "tag=$TAG" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$KIND" >> "$GITHUB_OUTPUT" ++ echo "source_repo=$SOURCE_REPO" >> "$GITHUB_OUTPUT" ++ echo "source_workflow=$SOURCE_WORKFLOW" >> "$GITHUB_OUTPUT" ++ echo "source_run_id=$SOURCE_RUN_ID" >> "$GITHUB_OUTPUT" ++ echo "source_run_url=$SOURCE_RUN_URL" >> "$GITHUB_OUTPUT" ++ echo "source_sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT" ++ echo "correlation_id=$CORRELATION_ID" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ ++ - name: Run smoke tests ++ run: | ++ set -euo pipefail ++ just test +``` + +`just test` is invoked on a plain `ubuntu-22.04` runner, but this workflow doesn't install `just` (or any of the project deps). In this repo, other templates run `just` inside the `ghcr.io/vig-os/devcontainer` container where it's available. Consider either running this job in the same container image (likely keyed by the dispatched tag) or adding an explicit `just` installation step before calling it. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:26 PM_ + +_File: [`assets/workspace/.github/workflows/repository-dispatch.yml (line 108 RIGHT)`](https://github.com/vig-os/devcontainer/pull/332#discussion_r2943297759)_ + +```diff +@@ -0,0 +1,126 @@ ++name: Repository Dispatch ++ ++on: # yamllint disable-line rule:truthy ++ repository_dispatch: ++ types: [smoke-test-trigger] ++ ++permissions: ++ contents: write ++ ++jobs: ++ smoke-test-release: ++ name: Smoke Test and Release ++ runs-on: ubuntu-22.04 ++ timeout-minutes: 30 ++ env: ++ GH_TOKEN: ${{ github.token }} ++ steps: ++ - name: Resolve dispatch payload ++ id: payload ++ run: | ++ set -euo pipefail ++ ++ TAG=$(jq -r '.client_payload.tag // empty' "$GITHUB_EVENT_PATH") ++ KIND=$(jq -r '.client_payload.release_kind // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_REPO=$(jq -r '.client_payload.source_repo // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_WORKFLOW=$(jq -r '.client_payload.source_workflow // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_RUN_ID=$(jq -r '.client_payload.source_run_id // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_RUN_URL=$(jq -r '.client_payload.source_run_url // empty' "$GITHUB_EVENT_PATH") ++ SOURCE_SHA=$(jq -r '.client_payload.source_sha // empty' "$GITHUB_EVENT_PATH") ++ CORRELATION_ID=$(jq -r '.client_payload.correlation_id // empty' "$GITHUB_EVENT_PATH") ++ ++ if [ -z "$TAG" ]; then ++ echo "ERROR: repository_dispatch payload is missing client_payload.tag" ++ exit 1 ++ fi ++ ++ if [ -z "$KIND" ]; then ++ if echo "$TAG" | grep -qE -- '-rc[0-9]+$'; then ++ KIND="candidate" ++ else ++ KIND="final" ++ fi ++ fi ++ ++ if [ "$KIND" != "candidate" ] && [ "$KIND" != "final" ]; then ++ echo "ERROR: Invalid release_kind '$KIND' (expected candidate or final)" ++ exit 1 ++ fi ++ ++ if [ -z "$SOURCE_RUN_URL" ] && [ -n "$SOURCE_REPO" ] && [ -n "$SOURCE_RUN_ID" ]; then ++ SOURCE_RUN_URL="https://github.com/${SOURCE_REPO}/actions/runs/${SOURCE_RUN_ID}" ++ fi ++ ++ echo "tag=$TAG" >> "$GITHUB_OUTPUT" ++ echo "release_kind=$KIND" >> "$GITHUB_OUTPUT" ++ echo "source_repo=$SOURCE_REPO" >> "$GITHUB_OUTPUT" ++ echo "source_workflow=$SOURCE_WORKFLOW" >> "$GITHUB_OUTPUT" ++ echo "source_run_id=$SOURCE_RUN_ID" >> "$GITHUB_OUTPUT" ++ echo "source_run_url=$SOURCE_RUN_URL" >> "$GITHUB_OUTPUT" ++ echo "source_sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT" ++ echo "correlation_id=$CORRELATION_ID" >> "$GITHUB_OUTPUT" ++ ++ - name: Checkout repository ++ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 ++ ++ - name: Run smoke tests ++ run: | ++ set -euo pipefail ++ just test ++ ++ - name: Create downstream release note ++ id: notes ++ env: ++ TAG: ${{ steps.payload.outputs.tag }} ++ RELEASE_KIND: ${{ steps.payload.outputs.release_kind }} ++ SOURCE_REPO: ${{ steps.payload.outputs.source_repo }} ++ SOURCE_WORKFLOW: ${{ steps.payload.outputs.source_workflow }} ++ SOURCE_RUN_ID: ${{ steps.payload.outputs.source_run_id }} ++ SOURCE_RUN_URL: ${{ steps.payload.outputs.source_run_url }} ++ SOURCE_SHA: ${{ steps.payload.outputs.source_sha }} ++ CORRELATION_ID: ${{ steps.payload.outputs.correlation_id }} ++ run: | ++ set -euo pipefail ++ cat > /tmp/smoke-test-release-notes.md </dev/null 2>&1; then ++ echo "ERROR: Release '$TAG' already exists in downstream repository" ++ exit 1 ++ fi ++ ++ if [ "$RELEASE_KIND" = "candidate" ]; then +``` + +This fails the workflow if a GitHub release for the tag already exists. That makes the dispatch handler non-idempotent and will break upstream re-dispatch/retry scenarios for the same tag (e.g., re-running the upstream job after a transient failure). A more robust approach is to treat an existing release as success after validating its `prerelease` flag matches the expected kind (or update the existing release notes instead of hard-failing). + + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:26 PM_ + +_File: [`.github/workflows/release.yml (line 200 RIGHT)`](https://github.com/vig-os/devcontainer/pull/332#discussion_r2943297780)_ + +```diff +@@ -173,6 +174,81 @@ jobs: + echo "release_url=$RELEASE_URL" >> $GITHUB_OUTPUT + echo "Publish version: $PUBLISH_VERSION" + ++ - name: Find latest RC tag ++ if: steps.vars.outputs.release_kind == 'final' ++ id: latest_rc ++ env: ++ VERSION: ${{ steps.vars.outputs.version }} ++ run: | ++ set -euo pipefail ++ TAG_PATTERN="${VERSION}-rc*" ++ EXISTING_TAGS=$(git ls-remote --tags --refs origin "$TAG_PATTERN" \ ++ | awk '{print $2}' | sed 's#refs/tags/##') ++ ++ if [ -z "$EXISTING_TAGS" ]; then ++ echo "ERROR: No RC tags found for version $VERSION" ++ echo "Publish at least one candidate before finalizing: just publish-candidate $VERSION" ++ exit 1 ++ fi ++ ++ while IFS= read -r tag; do ++ [ -z "$tag" ] && continue ++ ++ if ! echo "$tag" | grep -qE "^${VERSION}-rc[0-9]+$"; then ++ echo "ERROR: Malformed candidate tag detected: $tag" ++ echo "Expected format: ${VERSION}-rcN" ++ exit 1 +``` + +The tag-validation regex interpolates `$VERSION` directly into an ERE. Since versions contain dots, `.` will match any character, so tags like `1x2y3-rc1` could incorrectly pass. Escape `$VERSION` for regex use (e.g., replace `.` with `\.`) or avoid regex by parsing the rc suffix numerically after checking the fixed `"${VERSION}-rc"` prefix. + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:26 PM_ + +_File: [`.github/workflows/release.yml (line 1096 RIGHT)`](https://github.com/vig-os/devcontainer/pull/332#discussion_r2943297793)_ + +```diff +@@ -958,18 +1044,55 @@ jobs: + -f "client_payload[correlation_id]=$CORRELATION_ID" + echo "✓ Triggered smoke-test dispatch for release tag: $RELEASE_TAG" + +- - name: Warn if smoke-test dispatch token generation failed +- if: (needs.validate.outputs.release_kind == 'candidate' || needs.validate.outputs.release_kind == 'final') && steps.smoke-app-token.outcome != 'success' ++ - name: Wait for downstream smoke-test release completion ++ env: ++ GH_TOKEN: ${{ steps.smoke-app-token.outputs.token }} ++ RELEASE_TAG: ${{ needs.validate.outputs.publish_version }} ++ RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + run: | +- echo "WARNING: Could not generate cross-repo token for smoke-test dispatch." +- echo "Install RELEASE_APP on vig-os/devcontainer-smoke-test with contents:read permission." ++ set -euo pipefail ++ ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ EXPECTED_PRERELEASE=true ++ else ++ EXPECTED_PRERELEASE=false ++ fi ++ ++ TIMEOUT=480 ++ INTERVAL=15 ++ ELAPSED=0 ++ ++ echo "Waiting for downstream release '$RELEASE_TAG' (prerelease=$EXPECTED_PRERELEASE)..." ++ while [ "$ELAPSED" -lt "$TIMEOUT" ]; do ++ RELEASE_JSON=$(gh api "repos/vig-os/devcontainer-smoke-test/releases/tags/$RELEASE_TAG" 2>/dev/null || true) ++ ++ if [ -n "$RELEASE_JSON" ]; then ++ IS_PRERELEASE=$(echo "$RELEASE_JSON" | jq -r '.prerelease') ++ if [ "$IS_PRERELEASE" = "$EXPECTED_PRERELEASE" ]; then ++ echo "✓ Downstream smoke-test release completed successfully for $RELEASE_TAG" ++ exit 0 ++ fi ++ ++ echo "ERROR: Downstream release '$RELEASE_TAG' has prerelease=$IS_PRERELEASE (expected $EXPECTED_PRERELEASE)" ++ exit 1 ++ fi ++ ++ sleep "$INTERVAL" ++ ELAPSED=$((ELAPSED + INTERVAL)) ++ echo "Still waiting for downstream release creation... (${ELAPSED}s / ${TIMEOUT}s)" ++ done ++ ++ echo "ERROR: Timed out waiting for downstream release '$RELEASE_TAG'" ++ echo "Inspect downstream run status:" ++ echo " gh -R vig-os/devcontainer-smoke-test run list --workflow repository-dispatch.yml --limit 5" ++ exit 1 + + rollback: + name: Rollback on Failure + needs: [validate, finalize, build-and-test, publish] + runs-on: ubuntu-22.04 + timeout-minutes: 10 +- if: failure() ++ if: ${{ always() && (needs.validate.result == 'failure' || needs.finalize.result == 'failure' || needs.build-and-test.result == 'failure' || needs.publish.result == 'failure') }} + permissions: +``` + +The new downstream smoke-test gate is implemented as its own job, but `rollback` doesn't depend on (or check) `smoke-test`. If the downstream dispatch/release creation fails, the workflow will fail after publish, but rollback won't run to clean up tags/branches, which undermines the “release gate”/cleanup guarantees. Include `smoke-test` in `needs:` and in the failure condition (or explicitly document that downstream-gate failures are non-rollbackable). + +--- + +### Review by [@Copilot](https://github.com/apps/copilot-pull-request-reviewer) + +_Posted on March 16, 2026 at 10:26 PM_ + +_File: [`.github/workflows/release.yml (line 1073 RIGHT)`](https://github.com/vig-os/devcontainer/pull/332#discussion_r2943297811)_ + +```diff +@@ -958,18 +1044,55 @@ jobs: + -f "client_payload[correlation_id]=$CORRELATION_ID" + echo "✓ Triggered smoke-test dispatch for release tag: $RELEASE_TAG" + +- - name: Warn if smoke-test dispatch token generation failed +- if: (needs.validate.outputs.release_kind == 'candidate' || needs.validate.outputs.release_kind == 'final') && steps.smoke-app-token.outcome != 'success' ++ - name: Wait for downstream smoke-test release completion ++ env: ++ GH_TOKEN: ${{ steps.smoke-app-token.outputs.token }} ++ RELEASE_TAG: ${{ needs.validate.outputs.publish_version }} ++ RELEASE_KIND: ${{ needs.validate.outputs.release_kind }} + run: | +- echo "WARNING: Could not generate cross-repo token for smoke-test dispatch." +- echo "Install RELEASE_APP on vig-os/devcontainer-smoke-test with contents:read permission." ++ set -euo pipefail ++ ++ if [ "$RELEASE_KIND" = "candidate" ]; then ++ EXPECTED_PRERELEASE=true ++ else ++ EXPECTED_PRERELEASE=false ++ fi ++ ++ TIMEOUT=480 ++ INTERVAL=15 ++ ELAPSED=0 ++ ++ echo "Waiting for downstream release '$RELEASE_TAG' (prerelease=$EXPECTED_PRERELEASE)..." ++ while [ "$ELAPSED" -lt "$TIMEOUT" ]; do ++ RELEASE_JSON=$(gh api "repos/vig-os/devcontainer-smoke-test/releases/tags/$RELEASE_TAG" 2>/dev/null || true) ++ ++ if [ -n "$RELEASE_JSON" ]; then ++ IS_PRERELEASE=$(echo "$RELEASE_JSON" | jq -r '.prerelease') ++ if [ "$IS_PRERELEASE" = "$EXPECTED_PRERELEASE" ]; then ++ echo "✓ Downstream smoke-test release completed successfully for $RELEASE_TAG" ++ exit 0 +``` + +This gate considers the downstream run successful once a GitHub Release exists for the dispatched tag. That requires the downstream `vig-os/devcontainer-smoke-test` dispatch handler to create releases. In this repo, the existing smoke-test receiver template (`assets/smoke-test/.github/workflows/repository-dispatch.yml`) deploys and opens a PR but does not create a release, so the gate would time out if that template is what the downstream repo is based on. Either update the smoke-test template to create the release object, or adjust the upstream gate to wait on a downstream workflow conclusion instead of a release. + +--- + diff --git a/docs/pull-requests/pr-54.md b/docs/pull-requests/pr-54.md index aab216e1..15ad45ef 100644 --- a/docs/pull-requests/pr-54.md +++ b/docs/pull-requests/pr-54.md @@ -12,9 +12,8 @@ labels: feature assignees: c-vigo milestone: none projects: none -relationship: none merged: 2026-02-17T15:32:55Z -synced: 2026-02-20T13:18:27.531Z +synced: 2026-03-14T04:18:31.705Z --- # [PR 54](https://github.com/vig-os/devcontainer/pull/54) feat: automate and standardize repository setup @@ -142,9 +141,9 @@ This is a large feature branch (70 files changed, ~10,300 insertions, ~1,800 del --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/54#issuecomment-3895415302) by [@github-advanced-security[bot]](https://github.com/apps/github-advanced-security) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/54#issuecomment-3895415302) by [@github-advanced-security[bot]](https://github.com/apps/github-advanced-security) _Posted on February 13, 2026 at 07:42 AM_ diff --git a/docs/pull-requests/pr-6.md b/docs/pull-requests/pr-6.md index 91884ae9..47e07f53 100644 --- a/docs/pull-requests/pr-6.md +++ b/docs/pull-requests/pr-6.md @@ -12,9 +12,8 @@ labels: none assignees: c-vigo milestone: Initial release projects: none -relationship: none merged: 2025-12-09T05:58:54Z -synced: 2026-02-18T08:57:14.282Z +synced: 2026-03-14T04:18:48.716Z --- # [PR 6](https://github.com/vig-os/devcontainer/pull/6) Release Candidate 0.1 for the vigOS Development Environment @@ -175,9 +174,9 @@ code . --- --- -## Comments (9) +# Comments (9) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3605990335) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3605990335) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 09:53 AM_ @@ -192,7 +191,7 @@ For me this takes less than 5 seconds running with "dev" tag, maybe it had to bu --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606003142) by [@c-vigo](https://github.com/c-vigo) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606003142) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 09:56 AM_ @@ -226,7 +225,7 @@ services: --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606043989) by [@c-vigo](https://github.com/c-vigo) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606043989) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 10:06 AM_ @@ -238,7 +237,7 @@ git push --force-with-lease --- -### [Comment #4](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606082389) by [@gerchowl](https://github.com/gerchowl) +## [Comment #4](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606082389) by [@gerchowl](https://github.com/gerchowl) _Posted on December 3, 2025 at 10:15 AM_ @@ -269,7 +268,7 @@ Date: Tue Dec 2 18:15:23 2025 +0100 --- -### [Comment #5](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606101015) by [@c-vigo](https://github.com/c-vigo) +## [Comment #5](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606101015) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 10:18 AM_ @@ -280,7 +279,7 @@ I still see it unsigned [here](https://github.com/vig-os/devcontainer/compare/ma --- -### [Comment #6](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606106742) by [@gerchowl](https://github.com/gerchowl) +## [Comment #6](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606106742) by [@gerchowl](https://github.com/gerchowl) _Posted on December 3, 2025 at 10:20 AM_ @@ -357,20 +356,20 @@ services: --- -### [Comment #7](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606161603) by [@c-vigo](https://github.com/c-vigo) +## [Comment #7](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606161603) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 10:32 AM_ -## Build time +### Build time No clue, I tried with --force and it is still fast in my test. Maybe the problem is the "find" command in line 100 of init_workspace.sh? -## {{IMAGE_TAG}} +### {{IMAGE_TAG}} - did you use make build? - do the tests pass? in particular, TestDevContainerPlaceholders in test_integration.py --- -### [Comment #8](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606269248) by [@gerchowl](https://github.com/gerchowl) +## [Comment #8](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3606269248) by [@gerchowl](https://github.com/gerchowl) _Posted on December 3, 2025 at 10:54 AM_ @@ -458,7 +457,7 @@ make: *** [test-integration] Error 1 --- -### [Comment #9](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3607479024) by [@c-vigo](https://github.com/c-vigo) +## [Comment #9](https://github.com/vig-os/devcontainer/pull/6#issuecomment-3607479024) by [@c-vigo](https://github.com/c-vigo) _Posted on December 3, 2025 at 03:42 PM_ diff --git a/docs/pull-requests/pr-62.md b/docs/pull-requests/pr-62.md index f502f880..11e5f85a 100644 --- a/docs/pull-requests/pr-62.md +++ b/docs/pull-requests/pr-62.md @@ -12,8 +12,7 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-02-20T15:25:41.596Z +synced: 2026-03-14T04:18:27.747Z --- # [PR 62](https://github.com/vig-os/devcontainer/pull/62) feat: add agent-friendly issue templates, changelog rule, and PR template enhancements (#61) @@ -100,9 +99,9 @@ Closes #61 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/62#issuecomment-3917902907) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/62#issuecomment-3917902907) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 01:07 AM_ diff --git a/docs/pull-requests/pr-65.md b/docs/pull-requests/pr-65.md index e2d8c7a1..3302d26c 100644 --- a/docs/pull-requests/pr-65.md +++ b/docs/pull-requests/pr-65.md @@ -12,8 +12,7 @@ labels: none assignees: gerchowl milestone: none projects: none -relationship: none -synced: 2026-02-20T15:25:40.368Z +synced: 2026-03-14T04:18:26.715Z --- # [PR 65](https://github.com/vig-os/devcontainer/pull/65) feat: add Cursor commands and rules for agent-driven development workflows (#63) @@ -110,9 +109,9 @@ Closes #63 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/65#issuecomment-3917880820) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/65#issuecomment-3917880820) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 12:59 AM_ diff --git a/docs/pull-requests/pr-68.md b/docs/pull-requests/pr-68.md index 3778b206..fa4ddcbe 100644 --- a/docs/pull-requests/pr-68.md +++ b/docs/pull-requests/pr-68.md @@ -12,9 +12,8 @@ labels: none assignees: none milestone: none projects: none -relationship: none merged: 2026-02-20T09:28:26Z -synced: 2026-02-20T13:18:19.423Z +synced: 2026-03-14T04:18:25.838Z --- # [PR 68](https://github.com/vig-os/devcontainer/pull/68) feat: agent-driven development workflows, templates, and declarative sync manifest @@ -94,23 +93,23 @@ Closes #90 --- --- -## Comments (18) +# Comments (18) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919321616) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919321616) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 08:11 AM_ -## Idea: Use GitHub issue bodies for implementation plan tracking instead of `docs/plans/` files +### Idea: Use GitHub issue bodies for implementation plan tracking instead of `docs/plans/` files The current `/plan` command writes plans to `docs/plans/YYYY-MM-DD--plan.md` and `/execute-plan` reads from there. An alternative approach: write and track the implementation plan directly in the **GitHub issue body** (using task lists / checkboxes), and have the agent update task status there as work progresses. -### How it would work +#### How it would work - `/plan` reads the issue, breaks it into tasks, then **appends or replaces a "## Implementation Plan" section** in the issue body (via `gh issue edit --body`). - `/execute-plan` reads the issue body, picks up unchecked tasks, executes them, and **checks them off** in the issue body as they complete. - The issue becomes the single source of truth for both *what* needs doing and *what's done*. -### Pros of issue-body plans +#### Pros of issue-body plans | | | |---|---| @@ -121,7 +120,7 @@ The current `/plan` command writes plans to `docs/plans/YYYY-MM-DD--plan.m | **Cross-agent continuity** | A new agent session (or different worktree) can pick up where the last left off — just read the issue body. No need to find the right plan file. | | **Audit trail via issue edit history** | GitHub tracks every edit to the issue body, so you get a changelog of plan evolution for free. | -### Cons / risks of issue-body plans +#### Cons / risks of issue-body plans | | | |---|---| @@ -133,7 +132,7 @@ The current `/plan` command writes plans to `docs/plans/YYYY-MM-DD--plan.m | **Pollutes issue body** | The issue body serves double duty (problem description + implementation plan), which can make it long and harder for humans to scan. A `
` collapse could mitigate this. | | **`/plan` origin (superpowers repo)** | The current `/plan` + `/execute-plan` pattern comes from the [superpowers](https://github.com/rileytomasek/cursor-superpowers) community repo. Keeping the file-based approach means we stay compatible with that ecosystem and its updates. Diverging means maintaining our own variant. | -### Possible hybrid approach +#### Possible hybrid approach Use **both**: write a concise task checklist into the issue body for visibility and cross-agent continuity, but keep the detailed plan (file paths, verification steps, dependencies) in `docs/plans/` for structured parsing and offline use. The issue body becomes the "dashboard" and the plan file is the "spec". @@ -143,7 +142,7 @@ Worth discussing whether the visibility and cross-agent continuity benefits outw --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919327460) by [@gerchowl](https://github.com/gerchowl) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919327460) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 08:12 AM_ @@ -151,7 +150,7 @@ _Posted on February 18, 2026 at 08:12 AM_ --- -### [Comment #3](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919336528) by [@gerchowl](https://github.com/gerchowl) +## [Comment #3](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919336528) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 08:14 AM_ @@ -173,7 +172,7 @@ Rather than allowing `/plan` to run in isolation, we should enforce that a plan --- -### [Comment #4](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919983081) by [@c-vigo](https://github.com/c-vigo) +## [Comment #4](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919983081) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 10:27 AM_ @@ -186,7 +185,7 @@ Are we sure about this? I'd argue that a version bump by dependabot should be do --- -### [Comment #5](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919996744) by [@c-vigo](https://github.com/c-vigo) +## [Comment #5](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3919996744) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 10:29 AM_ @@ -205,7 +204,7 @@ Just an example, may be others. --- -### [Comment #6](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920159677) by [@c-vigo](https://github.com/c-vigo) +## [Comment #6](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920159677) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 11:02 AM_ @@ -233,7 +232,7 @@ Note on issue comment formatting: --- -### [Comment #7](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920174152) by [@gerchowl](https://github.com/gerchowl) +## [Comment #7](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920174152) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:05 AM_ @@ -250,7 +249,7 @@ dont think a wait is needed. whatever is added as a comment persists in that cha --- -### [Comment #8](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920177003) by [@c-vigo](https://github.com/c-vigo) +## [Comment #8](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920177003) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 11:06 AM_ @@ -258,7 +257,7 @@ Question: can (or already does) `sync_manifest.py` replace `utils.py`? --- -### [Comment #9](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920189916) by [@c-vigo](https://github.com/c-vigo) +## [Comment #9](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920189916) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 11:08 AM_ @@ -269,7 +268,7 @@ It makes the plan persistently available locally, e.g. if you launch another pla --- -### [Comment #10](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920203770) by [@c-vigo](https://github.com/c-vigo) +## [Comment #10](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920203770) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 11:12 AM_ @@ -277,7 +276,7 @@ I see the files referenced by the manifest are now permanently duplicated, is th --- -### [Comment #11](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920204605) by [@gerchowl](https://github.com/gerchowl) +## [Comment #11](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920204605) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:12 AM_ @@ -298,7 +297,7 @@ yeah one can generally introduce more granularity with a 'tree' model of rules a --- -### [Comment #12](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920236214) by [@gerchowl](https://github.com/gerchowl) +## [Comment #12](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920236214) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:18 AM_ @@ -314,7 +313,7 @@ one could adopt the logic to have it sync stuff at different times through the m --- -### [Comment #13](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920239948) by [@gerchowl](https://github.com/gerchowl) +## [Comment #13](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920239948) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:19 AM_ @@ -326,7 +325,7 @@ sorry, i was unclear, the wait is not required in the workflow. with having it a --- -### [Comment #14](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920267853) by [@c-vigo](https://github.com/c-vigo) +## [Comment #14](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920267853) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 11:25 AM_ @@ -334,7 +333,7 @@ There is a clash between the template labels in `.github/ISSUE_TEMPLATE` and the --- -### [Comment #15](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920276550) by [@gerchowl](https://github.com/gerchowl) +## [Comment #15](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920276550) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:27 AM_ @@ -345,7 +344,7 @@ i'd opt more for the case of refactoring the classes and functions of the manife --- -### [Comment #16](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920295361) by [@gerchowl](https://github.com/gerchowl) +## [Comment #16](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3920295361) by [@gerchowl](https://github.com/gerchowl) _Posted on February 18, 2026 at 11:31 AM_ @@ -357,7 +356,7 @@ we can set ours as we like, or we have on 'curl .. install.sh' a `create-gh-iss --- -### [Comment #17](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3921006265) by [@c-vigo](https://github.com/c-vigo) +## [Comment #17](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3921006265) by [@c-vigo](https://github.com/c-vigo) _Posted on February 18, 2026 at 02:02 PM_ @@ -367,15 +366,15 @@ This is just a checkbox test: --- -### [Comment #18](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3925664035) by [@gerchowl](https://github.com/gerchowl) +## [Comment #18](https://github.com/vig-os/devcontainer/pull/68#issuecomment-3925664035) by [@gerchowl](https://github.com/gerchowl) _Posted on February 19, 2026 at 08:43 AM_ -## Implementation Plan — Addressing Review Comments +### Implementation Plan — Addressing Review Comments @c-vigo Here's the plan for addressing all your review comments. Please confirm you agree or suggest changes before we start. -### Tasks — Code Fixes (from inline review comments) +#### Tasks — Code Fixes (from inline review comments) - [x] **Task 1:** Add branch-aware note to `ci:fix` step 2 — apply your suggestion about using the correct branch from workflow run details — `.cursor/skills/ci:fix/SKILL.md` - [x] **Task 2:** Make `code:review` base-branch agnostic — replace hardcoded `dev` in `git diff dev...HEAD` / `git log dev..HEAD` with a dynamic base-branch lookup — `.cursor/skills/code:review/SKILL.md` @@ -384,7 +383,7 @@ _Posted on February 19, 2026 at 08:43 AM_ - [x] **Task 5:** Update `issue:triage` path — change `.github_data/issues/` to `docs/issues/` — `.cursor/skills/issue:triage/SKILL.md` - [x] **Task 6:** Specify `## Design` heading — change "must start with `##`" to "must start with `## Design`" — `.cursor/skills/design:brainstorm/SKILL.md` -### Tasks — Decisions from general comments +#### Tasks — Decisions from general comments - [x] **Task 7:** Update `changelog.mdc` with three clarifications: - **Dependabot dep bumps:** change from "skip" to "include" — dependency version bumps should be documented in CHANGELOG @@ -397,12 +396,12 @@ _Posted on February 19, 2026 at 08:43 AM_ - Also review skills for extractable branch-check boilerplate into a shared file - [x] **Task 9:** Label clash resolution — the existing `label-taxonomy.toml` + `scripts/setup-labels.sh` already solve this: run `setup-labels.sh` after repo creation to reconcile org defaults with our taxonomy. Will add a note documenting this and optionally `--prune` to remove org defaults that don't match. -### Tasks — Sync and cleanup +#### Tasks — Sync and cleanup - [x] **Task 10:** Run `sync-workspace` to propagate all changes to `assets/workspace/` - [x] **Task 11:** Create follow-up issue for `sync_manifest.py` vs `utils.py` refactor (out of scope for this PR) → #96 -### Dependencies +#### Dependencies Tasks 1–9 are independent. Task 10 depends on 1–9. Task 11 is independent. diff --git a/docs/pull-requests/pr-87.md b/docs/pull-requests/pr-87.md index 7f754886..a7642c45 100644 --- a/docs/pull-requests/pr-87.md +++ b/docs/pull-requests/pr-87.md @@ -12,8 +12,7 @@ labels: none assignees: none milestone: none projects: none -relationship: none -synced: 2026-02-25T04:26:14.377Z +synced: 2026-03-14T04:18:19.264Z --- # [PR 87](https://github.com/vig-os/devcontainer/pull/87) Wire up version-check notification and add host-side devcontainer-upgrade recipe @@ -125,17 +124,17 @@ Closes #73 --- --- -## Comments (2) +# Comments (2) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/87#issuecomment-3925023981) by [@gerchowl](https://github.com/gerchowl) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/87#issuecomment-3925023981) by [@gerchowl](https://github.com/gerchowl) _Posted on February 19, 2026 at 06:49 AM_ -## Security Vulnerabilities Update +### Security Vulnerabilities Update The 6 security vulnerabilities mentioned during `git push` are **pre-existing and unrelated to this PR**. -### Analysis +#### Analysis ✅ **Confirmed:** No dependency changes in this PR - `pyproject.toml` - unchanged @@ -150,13 +149,13 @@ The 6 security vulnerabilities mentioned during `git push` are **pre-existing an - Development environment only, no production runtime exposure - Straightforward updates with patched versions available -### Recommendation +#### Recommendation This PR is **clear to merge** - it neither introduces nor worsens existing vulnerabilities. The dependency updates will be handled in #88. --- -### [Comment #2](https://github.com/vig-os/devcontainer/pull/87#issuecomment-3927611852) by [@gerchowl](https://github.com/gerchowl) +## [Comment #2](https://github.com/vig-os/devcontainer/pull/87#issuecomment-3927611852) by [@gerchowl](https://github.com/gerchowl) _Posted on February 19, 2026 at 02:22 PM_ diff --git a/docs/pull-requests/pr-97.md b/docs/pull-requests/pr-97.md index 949b5e3e..e55944fb 100644 --- a/docs/pull-requests/pr-97.md +++ b/docs/pull-requests/pr-97.md @@ -12,8 +12,7 @@ labels: none assignees: none milestone: none projects: none -relationship: none -synced: 2026-02-20T13:18:01.787Z +synced: 2026-03-14T04:18:15.281Z --- # [PR 97](https://github.com/vig-os/devcontainer/pull/97) chore: merge main into dev to apply changes from #91 @@ -39,9 +38,9 @@ Refs: #91 --- --- -## Comments (1) +# Comments (1) -### [Comment #1](https://github.com/vig-os/devcontainer/pull/97#issuecomment-3928966940) by [@c-vigo](https://github.com/c-vigo) +## [Comment #1](https://github.com/vig-os/devcontainer/pull/97#issuecomment-3928966940) by [@c-vigo](https://github.com/c-vigo) _Posted on February 19, 2026 at 06:07 PM_ diff --git a/package-lock.json b/package-lock.json index f28206b2..cd533327 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,23 +6,23 @@ "": { "name": "devcontainer-ci-deps", "dependencies": { - "@devcontainers/cli": "0.81.1", + "@devcontainers/cli": "0.84.1", "bats": "1.13.0", - "bats-assert": "github:bats-core/bats-assert#v2.2.0", + "bats-assert": "github:bats-core/bats-assert#v2.2.4", "bats-file": "github:bats-core/bats-file#v0.4.0", "bats-support": "github:bats-core/bats-support#v0.3.0" } }, "node_modules/@devcontainers/cli": { - "version": "0.81.1", - "resolved": "https://registry.npmjs.org/@devcontainers/cli/-/cli-0.81.1.tgz", - "integrity": "sha512-6xfQk1kDJz0qCfBVb9MW596zKnrb7wvtw414dbrf7ExSk5e+Moq+K/unalVDqhmNXn72EaURxHFaDFW9IeLDgg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@devcontainers/cli/-/cli-0.84.1.tgz", + "integrity": "sha512-r+JR/4R8lznPQNwLyHPIzHJ1mj3p2l5lGyHeq2FetEfpe6s6BVLE9mFl7MxQI4wKNqfWCIO7DSokoCWRlzQSIg==", "license": "MIT", "bin": { "devcontainer": "devcontainer.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=20.0.0" } }, "node_modules/bats": { @@ -35,8 +35,9 @@ } }, "node_modules/bats-assert": { - "version": "2.2.0", - "resolved": "git+ssh://git@github.com/bats-core/bats-assert.git#d396ee3e943f7c1c058f3a1f4baddc12fab875ef", + "version": "2.2.4", + "resolved": "git+ssh://git@github.com/bats-core/bats-assert.git#f1e9280eaae8f86cbe278a687e6ba755bc802c1a", + "integrity": "sha512-EcaY4Z+Tbz1c7pnC1SrVSq0epr7tLwFpz6qt7KUW9K8uSw8V12DTfH9d2HxZWvBEATaCuMsZ7KoZMFiSQPRoXw==", "license": "CC0-1.0", "peerDependencies": { "bats": "0.4 || ^1", diff --git a/package.json b/package.json index d2a2829e..13a17178 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "private": true, "description": "CI-only npm dependencies tracked by Dependabot", "dependencies": { - "@devcontainers/cli": "0.81.1", + "@devcontainers/cli": "0.84.1", "bats": "1.13.0", "bats-support": "github:bats-core/bats-support#v0.3.0", - "bats-assert": "github:bats-core/bats-assert#v2.2.0", + "bats-assert": "github:bats-core/bats-assert#v2.2.4", "bats-file": "github:bats-core/bats-file#v0.4.0" } } diff --git a/packages/vig-utils/README.md b/packages/vig-utils/README.md index 32ef6437..52928e1d 100644 --- a/packages/vig-utils/README.md +++ b/packages/vig-utils/README.md @@ -25,7 +25,7 @@ uv run check-action-pins --help |---|---|---| | `validate-commit-msg` | Python | Enforce commit message standard | | `check-action-pins` | Python | Ensure GitHub Actions are SHA pinned | -| `prepare-changelog` | Python | Validate/prepare/finalize/reset changelog | +| `prepare-changelog` | Python | Validate/prepare/finalize/reset/unprepare changelog | | `gh-issues` | Python | Rich issue/PR dashboard via `gh` | | `prepare-commit-msg-strip-trailers` | Python | Remove blocked trailers from commit messages | | `check-agent-identity` | Python | Block commits from agent fingerprints in author identity | @@ -87,6 +87,7 @@ prepare-changelog validate [FILE] prepare-changelog prepare [FILE] prepare-changelog finalize [FILE] prepare-changelog reset [FILE] +prepare-changelog unprepare [FILE] ``` Examples: @@ -96,6 +97,7 @@ prepare-changelog validate prepare-changelog prepare 0.3.0 prepare-changelog finalize 0.3.0 2026-03-04 prepare-changelog reset +prepare-changelog unprepare ``` ### `gh-issues` diff --git a/packages/vig-utils/pyproject.toml b/packages/vig-utils/pyproject.toml index b6efb4c9..4a80fd13 100644 --- a/packages/vig-utils/pyproject.toml +++ b/packages/vig-utils/pyproject.toml @@ -27,6 +27,7 @@ resolve-branch = "vig_utils.resolve_branch:main" derive-branch-summary = "vig_utils.derive_branch_summary:main" check-skill-names = "vig_utils.check_skill_names:main" setup-labels = "vig_utils.setup_labels:main" +retry = "vig_utils.retry:main" vig-utils = "vig_utils.utils:main" [tool.hatch.build.targets.wheel] diff --git a/packages/vig-utils/src/vig_utils/prepare_changelog.py b/packages/vig-utils/src/vig_utils/prepare_changelog.py index 403c6ee5..ae4dd8b3 100644 --- a/packages/vig-utils/src/vig_utils/prepare_changelog.py +++ b/packages/vig-utils/src/vig_utils/prepare_changelog.py @@ -175,6 +175,52 @@ def reset_unreleased(filepath="CHANGELOG.md"): raise ValueError("Could not find appropriate location for Unreleased section") +def unprepare_changelog(filepath="CHANGELOG.md"): + """ + Rename the first top-level version section to ## Unreleased (inverse of prepare). + + Used when the workspace CHANGELOG was replaced by a scaffold but the canonical + entries live under ``## [X.Y.Z] - …`` (e.g. copied from ``.devcontainer/CHANGELOG.md``). + + - If the first ``## `` heading is already ``## Unreleased``, no-op. + - If it matches ``## [MAJOR.MINOR.PATCH] - …`` (semver + suffix), replace with + ``## Unreleased``. + - Otherwise raises ValueError. + + Args: + filepath: Path to CHANGELOG.md + + Returns: + True if the file was modified, False if already ``## Unreleased``. + """ + path = Path(filepath) + if not path.exists(): + raise FileNotFoundError(f"CHANGELOG not found: {filepath}") + + content = path.read_text() + match = re.search(r"^## .+$", content, re.MULTILINE) + if not match: + raise ValueError("No top-level ## heading found in CHANGELOG") + + line = match.group(0).rstrip("\r\n") + if line == "## Unreleased": + return False + + # Match ## [X.Y.Z] - TBD or ## [X.Y.Z] - YYYY-MM-DD (same semver rule as prepare) + version_heading = re.compile( + r"^## \[(\d+\.\d+\.\d+)\] - .+$", + ) + if not version_heading.match(line): + raise ValueError( + f"Unexpected first CHANGELOG section heading: {line!r} " + "(expected ## Unreleased or ## [semver] - …)" + ) + + new_content = content[: match.start()] + "## Unreleased" + content[match.end() :] + path.write_text(new_content) + return True + + def prepare_changelog(version, filepath="CHANGELOG.md"): """ Prepare CHANGELOG for release. @@ -262,6 +308,14 @@ def cmd_reset(args): print("✓ Created fresh empty section for next release") +def cmd_unprepare(args): + """Handle unprepare command.""" + if unprepare_changelog(args.file): + print(f"✓ Renamed top version section to ## Unreleased in {args.file}") + else: + print(f"✓ Top section already ## Unreleased in {args.file} (no changes)") + + def finalize_release_date(version, release_date, filepath="CHANGELOG.md"): """ Replace TBD date with actual release date for a version. @@ -332,6 +386,9 @@ def main(): # Reset Unreleased section after release merge %(prog)s reset + + # Rename top ## [version] - … to ## Unreleased (smoke-test deploy sync) + %(prog)s unprepare """, ) @@ -385,6 +442,19 @@ def main(): ) reset_parser.set_defaults(func=cmd_reset) + # unprepare command + unprepare_parser = subparsers.add_parser( + "unprepare", + help="Rename first ## [semver] - … heading to ## Unreleased", + ) + unprepare_parser.add_argument( + "file", + nargs="?", + default="CHANGELOG.md", + help="Path to CHANGELOG file (default: CHANGELOG.md)", + ) + unprepare_parser.set_defaults(func=cmd_unprepare) + # finalize command finalize_parser = subparsers.add_parser( "finalize", diff --git a/packages/vig-utils/src/vig_utils/retry.py b/packages/vig-utils/src/vig_utils/retry.py new file mode 100644 index 00000000..94657084 --- /dev/null +++ b/packages/vig-utils/src/vig_utils/retry.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Retry CLI for transient command failures with bounded exponential backoff. + +Use `uv run retry -- ...` on bare runners that execute within the repository's +Python environment, or `retry -- ...` in devcontainer jobs where the command is +already available on PATH. +""" + +from __future__ import annotations + +import subprocess +import sys +import time +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence + + +def _execution_error_exit_code(error: OSError) -> int: + if isinstance(error, FileNotFoundError): + return 127 + if isinstance(error, PermissionError): + return 126 + return 1 + + +def _parse_positive_int(value: str, option_name: str) -> int: + if not value.isdigit() or int(value) <= 0: + raise ValueError(f"retry: {option_name} must be a positive integer") + return int(value) + + +def parse_cli(argv: Sequence[str]) -> tuple[int, int, int, list[str]]: + """Parse retry options and command from argv (without program name).""" + retries = 3 + backoff = 5 + max_backoff = 60 + command_start = None + i = 0 + + while i < len(argv): + arg = argv[i] + if arg == "--": + command_start = i + 1 + break + if arg == "--retries": + if i + 1 >= len(argv): + raise ValueError("retry: missing value for '--retries'") + retries = _parse_positive_int(argv[i + 1], "--retries") + i += 2 + continue + if arg == "--backoff": + if i + 1 >= len(argv): + raise ValueError("retry: missing value for '--backoff'") + backoff = _parse_positive_int(argv[i + 1], "--backoff") + i += 2 + continue + if arg == "--max-backoff": + if i + 1 >= len(argv): + raise ValueError("retry: missing value for '--max-backoff'") + max_backoff = _parse_positive_int(argv[i + 1], "--max-backoff") + i += 2 + continue + raise ValueError(f"retry: unknown option '{arg}'") + + if command_start is None or command_start >= len(argv): + raise ValueError("retry: missing command after '--'") + + return retries, backoff, max_backoff, list(argv[command_start:]) + + +def retry_command( + command: list[str], + *, + retries: int, + backoff: int, + max_backoff: int, +) -> int: + """Run command with bounded exponential retry.""" + command_display = " ".join(command) + exit_code = 0 + for attempt in range(1, retries + 1): + try: + result = subprocess.run( + command, + stdin=sys.stdin, + stdout=sys.stdout, + stderr=sys.stderr, + check=False, + ) + except OSError as error: + print( + f"retry: failed to execute command: {command_display} ({error})", + file=sys.stderr, + ) + return _execution_error_exit_code(error) + if result.returncode == 0: + return 0 + exit_code = result.returncode + + if attempt == retries: + print( + f"Command failed after {retries}/{retries} attempts (exit: {exit_code})", + file=sys.stderr, + ) + return exit_code + + wait_seconds = min(backoff * (1 << (attempt - 1)), max_backoff) + print( + f"Attempt {attempt}/{retries} failed (exit: {exit_code}); retrying in {wait_seconds}s...", + file=sys.stderr, + ) + time.sleep(wait_seconds) + return exit_code + + +def main() -> int: + try: + retries, backoff, max_backoff, command = parse_cli(sys.argv[1:]) + except ValueError as error: + print(str(error), file=sys.stderr) + return 2 + + return retry_command( + command, + retries=retries, + backoff=backoff, + max_backoff=max_backoff, + ) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/packages/vig-utils/tests/test_prepare_changelog.py b/packages/vig-utils/tests/test_prepare_changelog.py index 19b5ec85..3b2fda57 100644 --- a/packages/vig-utils/tests/test_prepare_changelog.py +++ b/packages/vig-utils/tests/test_prepare_changelog.py @@ -10,6 +10,7 @@ Tests are organized by function under test, from low-level helpers up to the CLI layer. """ +import re import shutil import subprocess from unittest.mock import patch @@ -20,6 +21,7 @@ cmd_finalize, cmd_prepare, cmd_reset, + cmd_unprepare, cmd_validate, create_new_changelog, extract_unreleased_content, @@ -27,6 +29,7 @@ main, prepare_changelog, reset_unreleased, + unprepare_changelog, validate_changelog, ) @@ -872,6 +875,115 @@ def test_raises_when_no_version_heading(self, tmp_path): reset_unreleased(str(f)) +# ═════════════════════════════════════════════════════════════════════════════ +# unprepare_changelog +# ═════════════════════════════════════════════════════════════════════════════ + +TOP_VERSION_TBD_THEN_OLDER = """\ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0.0] - TBD + +### Added + +- **Feature** ([#1](https://example.com/1)) + +## [0.9.0] - 2026-01-01 + +### Added + +- Prior +""" + + +class TestUnprepareChangelog: + """Unit tests for unprepare_changelog().""" + + def test_renames_tbd_header(self, tmp_path): + """Top ## [semver] - TBD becomes ## Unreleased; body preserved.""" + f = tmp_path / "CHANGELOG.md" + f.write_text(TOP_VERSION_TBD_THEN_OLDER) + assert unprepare_changelog(str(f)) is True + text = f.read_text() + assert text.startswith("# Changelog") + first_h2 = re.search(r"^## .+$", text, re.MULTILINE) + assert first_h2 is not None + assert first_h2.group(0) == "## Unreleased" + assert "## [1.0.0] - TBD" not in text + assert "**Feature**" in text + assert "## [0.9.0] - 2026-01-01" in text + + def test_renames_dated_header(self, tmp_path): + """Top ## [semver] - YYYY-MM-DD becomes ## Unreleased.""" + body = """\ +# Changelog + +## [2.0.0] - 2026-03-23 + +### Fixed + +- Bug + +""" + f = tmp_path / "CHANGELOG.md" + f.write_text(body) + assert unprepare_changelog(str(f)) is True + assert f.read_text().split("\n")[2] == "## Unreleased" + assert "- Bug" in f.read_text() + + def test_noop_when_already_unreleased(self, tmp_path): + """Returns False and leaves file unchanged.""" + f = tmp_path / "CHANGELOG.md" + f.write_text(BASIC_CHANGELOG) + before = f.read_text() + assert unprepare_changelog(str(f)) is False + assert f.read_text() == before + + def test_raises_no_heading(self, tmp_path): + """No ## line raises ValueError.""" + f = tmp_path / "CHANGELOG.md" + f.write_text("# Title only\n\nNo section.\n") + with pytest.raises(ValueError, match="No top-level"): + unprepare_changelog(str(f)) + + def test_raises_unexpected_heading(self, tmp_path): + """Non-version first ## heading raises.""" + f = tmp_path / "CHANGELOG.md" + f.write_text("# C\n\n## Random\n\n- x\n") + with pytest.raises(ValueError, match="Unexpected first"): + unprepare_changelog(str(f)) + + def test_raises_missing_file(self, tmp_path): + with pytest.raises(FileNotFoundError, match="CHANGELOG not found"): + unprepare_changelog(str(tmp_path / "missing.md")) + + +class TestCmdUnprepare: + """Tests for cmd_unprepare handler.""" + + def _make_args(self, filepath): + from argparse import Namespace + + return Namespace(file=filepath) + + def test_output_when_modified(self, tmp_path, capsys): + f = tmp_path / "CHANGELOG.md" + f.write_text(TOP_VERSION_TBD_THEN_OLDER) + cmd_unprepare(self._make_args(str(f))) + out = capsys.readouterr().out + assert "Renamed" in out + assert "## Unreleased" in f.read_text() + + def test_output_when_noop(self, tmp_path, capsys): + f = tmp_path / "CHANGELOG.md" + f.write_text(BASIC_CHANGELOG) + cmd_unprepare(self._make_args(str(f))) + out = capsys.readouterr().out + assert "no changes" in out.lower() or "already" in out.lower() + + # ═════════════════════════════════════════════════════════════════════════════ # finalize_release_date # ═════════════════════════════════════════════════════════════════════════════ @@ -1210,6 +1322,16 @@ def test_finalize_via_main(self, tmp_path): main() assert "## [1.0.0] - 2026-02-11" in f.read_text() + def test_unprepare_via_main(self, tmp_path): + """main() with 'unprepare' should rename top version heading.""" + f = tmp_path / "CHANGELOG.md" + f.write_text(TOP_VERSION_TBD_THEN_OLDER) + with patch("sys.argv", ["prog", "unprepare", str(f)]): + main() + first = re.search(r"^## .+$", f.read_text(), re.MULTILINE) + assert first is not None + assert first.group(0) == "## Unreleased" + def test_main_catches_exceptions(self, tmp_path): """main() should convert exceptions to stderr + exit(1).""" with ( @@ -1324,3 +1446,24 @@ def test_finalize_version_not_found_e2e(self, tmp_path): f.write_text(CHANGELOG_WITH_TBD) result = self._run("finalize", "9.9.9", "2026-02-11", str(f)) assert result.returncode != 0 + + # ── unprepare ───────────────────────────────────────────────────────── + + def test_unprepare_e2e(self, tmp_path): + """unprepare via subprocess renames top version heading.""" + f = tmp_path / "CHANGELOG.md" + f.write_text(TOP_VERSION_TBD_THEN_OLDER) + result = self._run("unprepare", str(f)) + assert result.returncode == 0 + first = re.search(r"^## .+$", f.read_text(), re.MULTILINE) + assert first is not None + assert first.group(0) == "## Unreleased" + + def test_unprepare_noop_e2e(self, tmp_path): + """unprepare leaves Unreleased changelog unchanged.""" + f = tmp_path / "CHANGELOG.md" + f.write_text(BASIC_CHANGELOG) + before = f.read_text() + result = self._run("unprepare", str(f)) + assert result.returncode == 0 + assert f.read_text() == before diff --git a/packages/vig-utils/tests/test_retry.py b/packages/vig-utils/tests/test_retry.py new file mode 100644 index 00000000..cdf87037 --- /dev/null +++ b/packages/vig-utils/tests/test_retry.py @@ -0,0 +1,155 @@ +"""Tests for the retry CLI.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import pytest +from vig_utils import retry + +REPO_ROOT = Path(__file__).resolve().parents[3] + + +def test_retry_happy_path_succeeds_first_attempt( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = {"count": 0} + + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + calls["count"] += 1 + return subprocess.CompletedProcess(args=["true"], returncode=0) + + monkeypatch.setattr(subprocess, "run", fake_run) + rc = retry.retry_command(["true"], retries=3, backoff=1, max_backoff=2) + assert rc == 0 + assert calls["count"] == 1 + + +def test_retry_retries_then_succeeds(monkeypatch: pytest.MonkeyPatch) -> None: + attempts = {"count": 0} + sleep_calls: list[int] = [] + + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + attempts["count"] += 1 + if attempts["count"] < 3: + return subprocess.CompletedProcess(args=["cmd"], returncode=42) + return subprocess.CompletedProcess(args=["cmd"], returncode=0) + + monkeypatch.setattr(subprocess, "run", fake_run) + monkeypatch.setattr(retry.time, "sleep", sleep_calls.append) + + rc = retry.retry_command(["cmd"], retries=4, backoff=2, max_backoff=10) + + assert rc == 0 + assert attempts["count"] == 3 + assert sleep_calls == [2, 4] + + +def test_retry_exhausts_attempts_and_returns_last_exit_code( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + sleep_calls: list[int] = [] + + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + return subprocess.CompletedProcess(args=["cmd"], returncode=17) + + monkeypatch.setattr(subprocess, "run", fake_run) + monkeypatch.setattr(retry.time, "sleep", sleep_calls.append) + + rc = retry.retry_command(["cmd"], retries=3, backoff=1, max_backoff=5) + captured = capsys.readouterr() + + assert rc == 17 + assert sleep_calls == [1, 2] + assert "Command failed after 3/3 attempts (exit: 17)" in captured.err + + +@pytest.mark.parametrize( + "argv", + [ + ["--retries", "3", "--backoff", "1", "--max-backoff", "1", "--"], + ["--retries", "0", "--backoff", "1", "--max-backoff", "1", "--", "true"], + ["--retries", "3", "--backoff", "nope", "--max-backoff", "1", "--", "true"], + ], +) +def test_retry_input_validation_returns_exit_2( + monkeypatch: pytest.MonkeyPatch, argv: list[str] +) -> None: + monkeypatch.setattr(sys, "argv", ["retry", *argv]) + rc = retry.main() + assert rc == 2 + + +def test_retry_caps_backoff_with_max_backoff(monkeypatch: pytest.MonkeyPatch) -> None: + sleep_calls: list[int] = [] + + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + return subprocess.CompletedProcess(args=["cmd"], returncode=1) + + monkeypatch.setattr(subprocess, "run", fake_run) + monkeypatch.setattr(retry.time, "sleep", sleep_calls.append) + + rc = retry.retry_command(["cmd"], retries=4, backoff=5, max_backoff=6) + + assert rc == 1 + assert sleep_calls == [5, 6, 6] + + +def test_retry_idempotent_for_successful_commands( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + return subprocess.CompletedProcess(args=["true"], returncode=0) + + monkeypatch.setattr(subprocess, "run", fake_run) + + rc1 = retry.retry_command(["true"], retries=2, backoff=1, max_backoff=1) + rc2 = retry.retry_command(["true"], retries=2, backoff=1, max_backoff=1) + + assert rc1 == rc2 == 0 + + +def test_retry_cli_module_invocation_succeeds() -> None: + result = subprocess.run( + [ + sys.executable, + "-m", + "vig_utils.retry", + "--retries", + "2", + "--backoff", + "1", + "--max-backoff", + "1", + "--", + "true", + ], + text=True, + capture_output=True, + check=False, + cwd=REPO_ROOT, + ) + assert result.returncode == 0, result.stderr + + +def test_retry_handles_command_execution_oserror( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + sleep_calls: list[int] = [] + + def fake_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]: + raise FileNotFoundError(2, "No such file or directory", "missing-cmd") + + monkeypatch.setattr(subprocess, "run", fake_run) + monkeypatch.setattr(retry.time, "sleep", sleep_calls.append) + + rc = retry.retry_command( + ["missing-cmd", "--flag"], retries=3, backoff=1, max_backoff=5 + ) + captured = capsys.readouterr() + + assert rc == 127 + assert sleep_calls == [] + assert "retry: failed to execute command: missing-cmd --flag" in captured.err diff --git a/scripts/manifest.toml b/scripts/manifest.toml index 765d7b04..3bd739ee 100644 --- a/scripts/manifest.toml +++ b/scripts/manifest.toml @@ -32,6 +32,10 @@ src = ".pymarkdown" [[entries]] src = ".pymarkdown.config.md" +[[entries]] +src = "CHANGELOG.md" +dest = ".devcontainer/CHANGELOG.md" + [[entries]] src = ".hadolint.yaml" @@ -63,12 +67,6 @@ transforms = [ [[entries]] src = ".github/workflows/scorecard.yml" -[[entries]] -src = ".github/workflows/sync-issues.yml" - -[[entries]] -src = ".github/workflows/sync-main-to-dev.yml" - [[entries]] src = ".github/workflows/codeql.yml" diff --git a/tests/bats/just.bats b/tests/bats/just.bats index 05f47a7b..c05b71fe 100644 --- a/tests/bats/just.bats +++ b/tests/bats/just.bats @@ -39,3 +39,128 @@ setup() { run bash -lc "grep -Fq -- 'git/refs/heads/$RELEASE_BRANCH' .github/workflows/prepare-release.yml" assert_success } + +@test "release workflow regenerates docs during finalization" { + run bash -lc "grep -Fq -- 'name: Regenerate docs for finalized release' .github/workflows/release.yml" + assert_success +} + +@test "release workflow commits dynamic finalization file paths" { + run bash -lc "grep -Fq -- 'id: finalize-files' .github/workflows/release.yml && grep -Fq -- 'steps.finalize-files.outputs.file_paths' .github/workflows/release.yml" + assert_success +} + +@test "prepare-release PR body omits persistent checklist and related sections" { + run bash -lc "! awk '/^ - name: Create draft PR to main/{flag=1} /^ - name: Roll back prepare-release side effects on failure/{flag=0} flag {print}' .github/workflows/prepare-release.yml | grep -Fq -- '### Testing Checklist' && ! awk '/^ - name: Create draft PR to main/{flag=1} /^ - name: Roll back prepare-release side effects on failure/{flag=0} flag {print}' .github/workflows/prepare-release.yml | grep -Fq -- '### When Ready to Release' && ! awk '/^ - name: Create draft PR to main/{flag=1} /^ - name: Roll back prepare-release side effects on failure/{flag=0} flag {print}' .github/workflows/prepare-release.yml | grep -Fq -- '### Related'" + assert_success +} + +@test "release workflow refreshes release PR body from changelog" { + run bash -lc 'grep -Fq -- "name: Refresh release PR body from finalized changelog" .github/workflows/release.yml && grep -Fq -- "CHANGELOG_CONTENT=\$(sed -n" .github/workflows/release.yml && grep -Fq -- "gh pr edit \"\$PR_NUMBER\" --body-file /tmp/release-pr-body.md" .github/workflows/release.yml' + assert_success +} + +@test "candidate dispatch includes smoke-test source metadata payload fields" { + run bash -lc "grep -Fq -- 'event_type=smoke-test-trigger' .github/workflows/release.yml && grep -Fq -- 'client_payload[source_repo]' .github/workflows/release.yml && grep -Fq -- 'client_payload[source_workflow]' .github/workflows/release.yml && grep -Fq -- 'client_payload[source_run_id]' .github/workflows/release.yml && grep -Fq -- 'client_payload[source_run_url]' .github/workflows/release.yml && grep -Fq -- 'client_payload[source_sha]' .github/workflows/release.yml && grep -Fq -- 'client_payload[correlation_id]' .github/workflows/release.yml" + assert_success +} + +@test "smoke-test dispatch template logs source metadata and writes summary" { + run bash -lc "grep -Fq -- 'EFFECTIVE_SOURCE_RUN_URL=' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'source_run_url=' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'correlation_id=' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'GITHUB_STEP_SUMMARY' assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "smoke-test dispatch computes base version output from tag" { + run bash -lc "grep -Fq -- 'base_version:' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- \"sed 's/-rc[0-9]*\\$//'\" assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "smoke-test dispatch validates workspace changelog exists after install" { + run bash -lc 'grep -Fq -- "expected CHANGELOG.md after install (workspace scaffold)" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "CHANGELOG.md is not readable after ownership repair" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch repairs ownership when installer leaves root-owned files" { + run bash -lc 'grep -Fq -- "NEEDS_CHOWN=false" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "sudo chown -R" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "OWNER_UID_GID=\"\$(id -u):\$(id -g)\"" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch waits for deploy PR merge before release orchestration" { + run bash -lc 'grep -Fq -- "wait-deploy-merge:" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "gh pr view \"\${PR_URL}\" --json state --jq" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch grants PR read permission for deploy-merge polling" { + run bash -lc 'grep -Fq -- "wait-deploy-merge:" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "pull-requests: read" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch removes publish-release job" { + run bash -lc "! grep -Fq -- 'publish-release:' assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "smoke-test dispatch triggers downstream prepare-release workflow" { + run bash -lc 'grep -Fq -- "cleanup-release:" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "gh workflow run prepare-release.yml" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch preflight validates required workflow contract" { + run bash -lc "grep -Fq -- 'Preflight check required release workflows on dispatch ref' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'REQUIRED_WORKFLOWS=(prepare-release.yml release.yml)' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'for workflow_file in \"\${REQUIRED_WORKFLOWS[@]}\"; do' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'WORKFLOW_CHECK_OUTPUT=\"\$(gh workflow view \"\${workflow_file}\" --ref \"\${WORKFLOW_REF}\" --yaml 2>&1 >/dev/null)\"' assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "smoke-test dispatch wait logic tracks prepare-release run after dispatch" { + run bash -lc 'grep -Fq -- "Capture latest prepare-release run id" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "gh run list --workflow prepare-release.yml --branch \"\${WORKFLOW_REF}\"" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "BEFORE_RUN_ID: \${{ steps.capture_prepare_before.outputs.before_run_id }}" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "[ \"\${RUN_ID}\" -gt \"\${BEFORE_RUN_ID}\" ]" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch wait logic tracks release run after dispatch" { + run bash -lc 'grep -Fq -- "Capture latest release run id" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "gh run list --workflow release.yml --branch \"\${WORKFLOW_REF}\"" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "BEFORE_RUN_ID: \${{ steps.capture_release_before.outputs.before_run_id }}" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "[ \"\${RUN_ID}\" -gt \"\${BEFORE_RUN_ID}\" ]" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch triggers release workflow with base version and release kind" { + run bash -lc 'grep -Fq -- "gh workflow run release.yml \\" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "-f version=\"\${BASE_VERSION}\"" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "-f release-kind=\"\${RELEASE_KIND}\"" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "needs: [validate, ready-release-pr]" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch merges release PR after successful release workflow" { + run bash -lc 'grep -Fq -- "merge-release-pr:" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "Poll release PR merge status" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "Waiting for release PR merge" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "needs: [ready-release-pr, trigger-release]" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch readies release PR with release kind label" { + run bash -lc 'grep -Fq -- "gh pr ready" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "release-kind:candidate" assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- "Label release PR with release kind" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch tolerates transient auto-merge enable failures" { + run bash -lc 'grep -Fq -- "Warning: could not enable auto-merge yet" assets/smoke-test/.github/workflows/repository-dispatch.yml' + assert_success +} + +@test "smoke-test dispatch notifies upstream on orchestration failure" { + run bash -lc "grep -Fq -- 'notify-failure:' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'gh issue create \\' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- '--repo vig-os/devcontainer' assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "smoke-test dispatch summary includes release-orchestration job results" { + run bash -lc "grep -Fq -- 'needs.wait-deploy-merge.result' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'needs.cleanup-release.result' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'needs.trigger-prepare-release.result' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'needs.ready-release-pr.result' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'needs.trigger-release.result' assets/smoke-test/.github/workflows/repository-dispatch.yml && grep -Fq -- 'needs.merge-release-pr.result' assets/smoke-test/.github/workflows/repository-dispatch.yml" + assert_success +} + +@test "release workflow rollback resolves container image independently of core outputs" { + run bash -lc "grep -Fq -- 'resolve-image:' assets/workspace/.github/workflows/release.yml && grep -Fq -- 'needs: [resolve-image, core, extension, publish]' assets/workspace/.github/workflows/release.yml && grep -Fq -- 'image: ghcr.io/vig-os/devcontainer:\${{ needs.resolve-image.outputs.image-tag }}' assets/workspace/.github/workflows/release.yml" + assert_success +} + +@test "release workflows configure safe.directory in container jobs that run git" { + run bash -lc "awk '/^ validate:/{flag=1} /^ finalize:/{flag=0} flag {print}' assets/workspace/.github/workflows/release-core.yml | grep -Fq -- 'name: Fix git safe.directory' && grep -Fq -- 'name: Fix git safe.directory' assets/workspace/.github/workflows/release-publish.yml && [ \"$(grep -Fc -- 'name: Fix git safe.directory' assets/workspace/.github/workflows/sync-main-to-dev.yml)\" -ge 2 ] && grep -Fq -- 'name: Fix git safe.directory' assets/workspace/.github/workflows/release.yml" + assert_success +} + +@test "release caller and reusable workflows define explicit minimal permissions for gh operations" { + run bash -lc "awk '/^ core:/{flag=1} /^ extension:/{flag=0} flag {print}' assets/workspace/.github/workflows/release.yml | grep -Fq -- 'actions: write' && awk '/^ core:/{flag=1} /^ extension:/{flag=0} flag {print}' assets/workspace/.github/workflows/release.yml | grep -Fq -- 'pull-requests: read' && awk '/^ publish:/{flag=1} /^ rollback:/{flag=0} flag {print}' assets/workspace/.github/workflows/release.yml | grep -Fq -- 'contents: write' && awk '/^ validate:/{flag=1} /^ finalize:/{flag=0} flag {print}' assets/workspace/.github/workflows/release-core.yml | grep -Fq -- 'pull-requests: read' && awk '/^ finalize:/{flag=1} /^ test:/{flag=0} flag {print}' assets/workspace/.github/workflows/release-core.yml | grep -Fq -- 'actions: write'" + assert_success +} diff --git a/tests/test_image.py b/tests/test_image.py index 0512cb7b..7ee113db 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -20,13 +20,13 @@ "git": "2.", # Major version check (from apt package) "curl": "8.", # Major version check (from apt package) "gh": "2.88.", # Minor version check (GitHub CLI (manually installed from latest release) - "uv": "0.10.", # Minor version check (manually installed from latest release) + "uv": "0.11.", # Minor version check (manually installed from latest release) "python": "3.12", # Python (from base image) "pre_commit": "4.5.", # Minor version check (installed via uv pip) "ruff": "0.15.", # Minor version check (installed via uv pip) "bandit": "1.9.", # Minor version check (installed via uv pip) "pip_licenses": "5.", # Major version check (installed via uv pip) - "just": "1.46.", # Minor version check (manually installed from latest release) + "just": "1.48.", # Minor version check (manually installed from latest release) "hadolint": "2.14.", # Minor version check (manually installed from pinned release) "taplo": "0.10.", # Minor version check (manually installed from latest release) "cargo-binstall": "1.17.", # Minor version check (installed from latest release), @@ -556,6 +556,7 @@ def test_assets_workspace_structure(self, host): "/root/assets/workspace/.vig-os", # .devcontainer files "/root/assets/workspace/.devcontainer/.gitignore", + "/root/assets/workspace/.devcontainer/CHANGELOG.md", "/root/assets/workspace/.devcontainer/README.md", "/root/assets/workspace/.devcontainer/devcontainer.json", "/root/assets/workspace/.devcontainer/docker-compose.yml", diff --git a/tests/test_integration.py b/tests/test_integration.py index 425dbd1d..b9fa31fd 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -892,6 +892,36 @@ def test_default_init_does_not_deploy_repository_dispatch( "repository-dispatch.yml should not be deployed without --smoke-test" ) + def test_smoke_workspace_changelog_available_in_devcontainer_and_root( + self, initialized_smoke_workspace + ): + """Smoke template should ship root and devcontainer changelogs with distinct roles.""" + root_changelog = initialized_smoke_workspace / "CHANGELOG.md" + devcontainer_changelog = ( + initialized_smoke_workspace / ".devcontainer" / "CHANGELOG.md" + ) + + assert root_changelog.exists(), "Root CHANGELOG.md not found in smoke workspace" + assert devcontainer_changelog.exists(), ( + ".devcontainer/CHANGELOG.md not found in smoke workspace" + ) + root_content = root_changelog.read_text(encoding="utf-8") + devcontainer_content = devcontainer_changelog.read_text(encoding="utf-8") + + # Root changelog is a copy of .devcontainer/CHANGELOG.md with the top semver + # heading renamed via prepare-changelog unprepare; older release sections stay. + first_h2 = re.search(r"^## .+$", root_content, re.MULTILINE) + assert first_h2 is not None, "Root changelog should have a top-level ## heading" + assert first_h2.group(0).rstrip("\r\n") == "## Unreleased", ( + "Root changelog top section should be ## Unreleased after smoke-test unprepare" + ) + assert re.search(r"^## \[\d+\.\d+\.\d+\]", root_content, re.MULTILINE), ( + "Root changelog should retain semver release sections below Unreleased" + ) + assert re.search( + r"^## \[\d+\.\d+\.\d+\]", devcontainer_content, re.MULTILINE + ), ".devcontainer changelog should include semver release history" + class TestDevContainerGit: """Test that git configuration files are set up."""