From 617db2d8dc5e8b87c048888e090eb28b8ff48323 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Wed, 18 Feb 2026 17:03:48 +0000 Subject: [PATCH 01/24] feat: add placeholders for CI and release workflows - Introduced CI workflow for pull request and push events on dev, main, and release branches. - Added CodeQL analysis workflow for security scanning. - Implemented post-release workflow to sync dev with main after merges. - Created prepare-release workflow for validating and preparing release branches. - Established release workflow for finalizing, building, testing, and publishing releases. - Added scorecard and security scan workflows for ongoing security assessments. - Included a composite action to set up the CI environment with Python, uv, and optional tools. Refs: #6 --- .github/actions/setup-env/action.yml | 173 +++++++++ .github/workflows/ci.yml | 25 ++ .github/workflows/codeql.yml | 25 ++ .github/workflows/post-release.yml | 150 ++++++++ .github/workflows/prepare-release.yml | 290 ++++++++++++++ .github/workflows/release.yml | 518 ++++++++++++++++++++++++++ .github/workflows/scorecard.yml | 24 ++ .github/workflows/security-scan.yml | 25 ++ 8 files changed, 1230 insertions(+) create mode 100644 .github/actions/setup-env/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/post-release.yml create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .github/workflows/security-scan.yml diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml new file mode 100644 index 0000000..e2d3b57 --- /dev/null +++ b/.github/actions/setup-env/action.yml @@ -0,0 +1,173 @@ +# Composite action to set up the CI environment +# +# Installs Python, uv, 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, 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" + + # ── 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 + 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/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b79bcb9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +# CI Workflow +# +# This workflow placeholder is registered with GitHub to enable CI checks. +# It serves as a pre-merge gate for pull requests targeting dev, main, and release branches. +# Full implementation details are managed separately. + +name: CI + +"on": + pull_request: + push: + branches: + - dev + - main + - release/** + +permissions: + contents: read + +jobs: + ci: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2a4480a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,25 @@ +# CodeQL Analysis Workflow +# +# This workflow placeholder is registered with GitHub to enable CodeQL security scanning. +# It performs static analysis on the codebase to detect potential vulnerabilities and code quality issues. +# Full implementation details are managed separately. + +name: CodeQL + +"on": + pull_request: # TODO: consider restricting to protected branches (dev, main, release/**) when implementing + push: + branches: + - dev + - main + # TODO: add schedule trigger (e.g. weekly cron) for drift detection when implementing + +permissions: + contents: read + +jobs: + codeql: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 0000000..7f7c9a3 --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,150 @@ +# Post-Release - Sync dev with main after merge +# +# Triggered automatically when a PR is merged into main. +# Keeps the dev branch in sync by merging main back into dev. +# +# After a release merge (release/X.Y.Z -> main): +# - Merges main into dev +# - Resets CHANGELOG Unreleased section for next cycle +# - Deletes the release branch +# +# After any other merge to main (e.g., hotfix): +# - Merges main into dev +# - CHANGELOG reset is skipped (Unreleased section already exists) + +name: Post-Release + +on: # yamllint disable-line rule:truthy + # NOTE: GitHub Actions does not support a "merged" filter on pull_request triggers. + # This fires on every closed PR targeting main; the job-level `if` condition + # below correctly skips runs where the PR was closed without merging. + pull_request: + types: [closed] + branches: [main] + workflow_dispatch: + inputs: + git-user-name: + description: 'Git user name for commits' + required: false + default: 'vigOS Release Bot' + type: string + git-user-email: + description: 'Git user email for commits' + required: false + default: 'release@vig-os.local' + type: string + +permissions: + contents: read # safe default; sync-dev job escalates as needed + +jobs: + sync-dev: + name: Sync dev with main + runs-on: ubuntu-22.04 + timeout-minutes: 10 + permissions: + contents: write # push merge commit to dev + actions: write # trigger sync-issues workflow + if: >- + ${{ github.event_name == 'workflow_dispatch' + || github.event.pull_request.merged == true }} + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + with: + app-id: ${{ secrets.APP_SYNC_ISSUES_ID }} + private-key: ${{ secrets.APP_SYNC_ISSUES_PRIVATE_KEY }} + + - name: Checkout dev branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: dev + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + + - name: Configure git + env: + GIT_USER_NAME: ${{ inputs.git-user-name || 'vigOS Release Bot' }} + GIT_USER_EMAIL: ${{ inputs.git-user-email || 'release@vig-os.local' }} + run: | + git config user.name "$GIT_USER_NAME" + git config user.email "$GIT_USER_EMAIL" + + - name: Merge main into dev + run: | + set -euo pipefail + git fetch origin main + if git merge origin/main --no-edit; then + echo "✓ Merged main into dev" + else + echo "ERROR: Merge conflict merging main into dev" + echo "" + echo "Manual resolution required:" + echo " git checkout dev" + echo " git pull origin dev" + echo " git merge origin/main" + echo " # resolve conflicts..." + echo " git commit" + echo " git push origin dev" + exit 1 + fi + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + sync-dependencies: 'true' + + - name: Reset CHANGELOG for next cycle + id: reset-changelog + # Only reset after a release merge (release/X.Y.Z -> main). + # Hotfix merges already have the Unreleased section; reset would fail. + if: startsWith(github.event.pull_request.head.ref, 'release/') + run: | + set -euo pipefail + uv run prepare-changelog reset CHANGELOG.md + echo "✓ CHANGELOG Unreleased section reset" + echo "reset=true" >> $GITHUB_OUTPUT + + - name: Commit and push changes + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + + git add -A + if git diff --cached --quiet; then + echo "No changes to commit (dev already in sync)" + else + COMMIT_MSG="chore: sync dev with main after merge" + if [ -n "$PR_NUMBER" ]; then + COMMIT_MSG="$COMMIT_MSG + + Refs: #$PR_NUMBER" + fi + git commit -m "$COMMIT_MSG" + git push origin dev + echo "✓ Changes committed and pushed to dev" + fi + + - name: Trigger sync-issues workflow + run: | + set -euo pipefail + echo "Triggering sync-issues workflow..." + gh workflow run sync-issues.yml \ + -f "target-branch=dev" + echo "✓ sync-issues workflow triggered" + env: + GH_TOKEN: ${{ github.token }} + + - name: Summary + env: + EVENT_NAME: ${{ github.event_name }} + CHANGELOG_RESET: ${{ steps.reset-changelog.outcome }} + run: | + echo "✓ Post-release sync complete" + echo "" + echo "Trigger: $EVENT_NAME" + echo "CHANGELOG reset: $CHANGELOG_RESET" diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..c25b0c8 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,290 @@ +# Prepare Release Branch for Testing and Validation +# +# Workflow that creates and prepares a release branch for testing: +# 1. Validate: Check inputs and prerequisites (version format, no existing branch/tag, CHANGELOG ready) +# 2. Prepare: Create release branch, prepare CHANGELOG, commit, push, create draft PR +# +# This replaces the local prepare-release.sh script, providing: +# - Consistent CI environment (not dependent on developer tools) +# - Audit trail of all actions via GitHub Actions logs +# - CI triggering on new release branch (via GitHub App token) + +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: 'vigOS Release Bot' + type: string + git-user-email: + description: 'Git user email for commits' + required: false + default: 'release@vig-os.local' + type: string + +permissions: + contents: read # safe default; prepare job escalates as needed + +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 }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: dev + fetch-depth: 0 + + - name: Validate and prepare variables + id: vars + env: + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + set -euo pipefail + VERSION="$INPUT_VERSION" + + # Validate semantic version format + 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 + echo "✓ Version format validated: $VERSION" + + - name: Verify release branch does not exist + env: + VERSION: ${{ steps.vars.outputs.version }} + run: | + set -euo pipefail + RELEASE_BRANCH="release/$VERSION" + + 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 + + echo "✓ Release branch does not exist: $RELEASE_BRANCH" + + - 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 + + echo "✓ Tag does not exist: $VERSION" + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + sync-dependencies: 'true' + + - name: Verify CHANGELOG has Unreleased section with content + run: | + set -euo pipefail + uv run prepare-changelog validate CHANGELOG.md + + - name: Verify dev branch is checked out + run: | + set -euo pipefail + CURRENT_BRANCH=$(git branch --show-current) + if [ "$CURRENT_BRANCH" != "dev" ]; then + echo "ERROR: Expected to be on dev branch, but on: $CURRENT_BRANCH" + exit 1 + fi + echo "✓ Checked out dev branch" + + - name: Summary + env: + VERSION: ${{ steps.vars.outputs.version }} + RELEASE_BRANCH: ${{ steps.vars.outputs.release_branch }} + DRY_RUN: ${{ github.event.inputs.dry-run }} + run: | + echo "✓ All validation checks passed" + echo "" + echo "Release Preparation Configuration:" + echo " Version: $VERSION" + echo " Release Branch: $RELEASE_BRANCH" + echo " Dry-run: $DRY_RUN" + + prepare: + name: Prepare Release Branch + needs: validate + runs-on: ubuntu-22.04 + timeout-minutes: 15 + if: ${{ github.event.inputs.dry-run != 'true' }} + permissions: + contents: read # checkout dev branch + pull-requests: write # create draft PR + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + with: + app-id: ${{ secrets.APP_SYNC_ISSUES_ID }} + private-key: ${{ secrets.APP_SYNC_ISSUES_PRIVATE_KEY }} + + - name: Checkout dev branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: dev + fetch-depth: 0 + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + sync-dependencies: 'true' + + - name: Prepare CHANGELOG + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + echo "Preparing CHANGELOG for version $VERSION..." + uv run prepare-changelog prepare "$VERSION" CHANGELOG.md + echo "✓ CHANGELOG prepared" + + - name: Extract CHANGELOG content for PR body + id: changelog + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + + # Extract the version section from CHANGELOG (between ## [VERSION] and next ## [) + CHANGELOG_CONTENT=$(sed -n "/## \[$VERSION\]/,/## \[/p" CHANGELOG.md | sed '$d') + + # Use heredoc delimiter for multiline output + { + echo "changelog<> "$GITHUB_OUTPUT" + + - name: Create release branch via API + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} + run: | + set -euo pipefail + DEV_SHA=$(git rev-parse HEAD) + echo "Creating branch $RELEASE_BRANCH from dev at $DEV_SHA..." + gh api "repos/${{ github.repository }}/git/refs" \ + -f ref="refs/heads/$RELEASE_BRANCH" \ + -f sha="$DEV_SHA" + echo "✓ Release branch created on remote" + + - name: Commit release preparation via API + uses: vig-os/commit-action@b70c2d87acd0f146c40e8d88a9bda40b76c084b5 # v0.1.3 + env: + GH_TOKEN: ${{ steps.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 }} + + Prepare CHANGELOG.md structure for version ${{ needs.validate.outputs.version }}. + Release date TBD (set during finalization). + FILE_PATHS: CHANGELOG.md + + - name: Create draft PR to main + id: pr + env: + GH_TOKEN: ${{ steps.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 v$VERSION + + This PR prepares release v$VERSION for merge to main. + + ### 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 + Run: \`just finalize-release $VERSION\` + + This will: + - Set release date in CHANGELOG + - Update documentation + - Create commit and push + - Build and test container images for all architectures + - Create release tag and publish to GHCR + + ### Related + - Release automation: #48 + " + + PR_URL=$(gh pr create \ + --base main \ + --head "$RELEASE_BRANCH" \ + --title "Release v$VERSION" \ + --body "$PR_BODY" \ + --draft) + + # Extract PR number from URL (format: https://github.com/owner/repo/pull/123) + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + echo "✓ Draft PR created: $PR_URL (#$PR_NUMBER)" + + - name: Summary + env: + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_BRANCH: ${{ needs.validate.outputs.release_branch }} + run: | + echo "✓ Release branch prepared successfully!" + echo "" + echo "Release Summary:" + echo " Version: $VERSION" + echo " Branch: $RELEASE_BRANCH" + echo "" + echo "Next steps:" + echo " 1. Test release: git checkout $RELEASE_BRANCH" + echo " 2. Review draft PR and monitor CI" + echo " 3. Fix any issues via bugfix PRs to $RELEASE_BRANCH" + echo " 4. Mark PR as ready for review (gh pr ready )" + echo " 5. Get PR approval from reviewer" + echo " 6. Run: just finalize-release $VERSION" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d1b863e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,518 @@ +# Release Workflow - Finalize, Build, Test, and Publish +# +# Unified release workflow that handles the complete release process: +# 1. Validate: Check all prerequisites (PR status, CI passed, CHANGELOG ready) +# 2. Finalize: Set release date in CHANGELOG, trigger PR doc sync +# 3. Build & Test: Build and test images for all architectures +# 4. Publish: Create tag and publish images only if all tests pass +# 5. Rollback (on failure): Revert changes and create issue +# +# Design: Everything happens in one workflow dispatch before creating the tag. +# This ensures no broken releases are tagged. + +name: Release + +on: # yamllint disable-line rule:truthy + workflow_dispatch: + inputs: + version: + description: 'Semantic version to release (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: 'vigOS Release Bot' + type: string + git-user-email: + description: 'Git user email for commits' + required: false + default: 'release@vig-os.local' + type: string + +concurrency: + # Prevent multiple releases from running simultaneously (race conditions on tags/manifests) + group: publish-image + cancel-in-progress: false + +permissions: + contents: read # safe default; jobs escalate as needed + +jobs: + validate: + name: Validate Release + 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 }} + build_timestamp: ${{ steps.vars.outputs.build_timestamp }} + release_url: ${{ steps.vars.outputs.release_url }} + pre_finalize_sha: ${{ steps.pre_sha.outputs.pre_finalize_sha }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Validate and prepare variables + id: vars + env: + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + set -euo pipefail + VERSION="$INPUT_VERSION" + + # Validate semantic version format + 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" + RELEASE_DATE=$(date -u +%Y-%m-%d) + BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + RELEASE_URL="https://github.com/${GITHUB_REPOSITORY}/releases/tag/$VERSION" + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT + echo "build_timestamp=$BUILD_TIMESTAMP" >> $GITHUB_OUTPUT + echo "release_url=$RELEASE_URL" >> $GITHUB_OUTPUT + echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + + - name: Checkout release branch + env: + VERSION: ${{ steps.vars.outputs.version }} + run: | + git fetch origin "release/$VERSION" || { + echo "ERROR: Release branch not found: release/$VERSION" + echo "Did you run: just prepare-release $VERSION" + exit 1 + } + git checkout "origin/release/$VERSION" + + - 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: Set up GitHub CLI + run: | + gh version + env: + GH_TOKEN: ${{ github.token }} + + - name: Find and verify PR + id: pr + env: + VERSION: ${{ steps.vars.outputs.version }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + RELEASE_BRANCH="release/$VERSION" + + # Find PR from release branch to main + PR_JSON=$(gh pr list \ + --head "$RELEASE_BRANCH" \ + --base main \ + --json number,isDraft,reviewDecision,statusCheckRollup \ + --limit 1) + + PR_COUNT=$(echo "$PR_JSON" | jq 'length') + + if [ "$PR_COUNT" != "1" ]; then + if [ "$PR_COUNT" = "0" ]; then + echo "ERROR: No PR found from $RELEASE_BRANCH to main" + exit 1 + else + echo "ERROR: Multiple PRs found from $RELEASE_BRANCH to main" + exit 1 + fi + 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 status" + echo "Mark it ready for review first: gh pr ready $PR_NUMBER" + exit 1 + fi + + if [ "$REVIEW_DECISION" != "APPROVED" ]; then + echo "ERROR: PR #$PR_NUMBER does not have approvals (status: $REVIEW_DECISION)" + exit 1 + fi + + # Check CI status + 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" + echo "Fix CI issues before releasing" + exit 1 + fi + + # Check that at least some checks have passed + CI_SUCCESS=$(echo "$STATUS_ROLLUP" | jq '[.[] | select(.conclusion == "SUCCESS")] | length') + if [ "$CI_SUCCESS" = "0" ]; then + echo "ERROR: PR #$PR_NUMBER has no successful CI checks" + echo "Ensure CI has run on the release branch" + exit 1 + fi + + echo "✓ PR #$PR_NUMBER verified: ready and approved" + + - 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 "✓ All validation checks passed" + echo "" + echo "Release Configuration:" + echo " Version: $VERSION" + echo " Release 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: 15 + if: ${{ github.event.inputs.dry-run != 'true' }} + permissions: + contents: write # read release branch (fetch/reset) + actions: write # trigger sync-issues workflow + outputs: + finalize_sha: ${{ steps.finalize.outputs.finalize_sha }} + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + with: + app-id: ${{ secrets.APP_SYNC_ISSUES_ID }} + private-key: ${{ secrets.APP_SYNC_ISSUES_PRIVATE_KEY }} + + - name: Checkout release branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: release/${{ needs.validate.outputs.version }} + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + + - name: Set up environment + uses: ./.github/actions/setup-env + with: + sync-dependencies: 'true' + + - name: Set release date in CHANGELOG + env: + VERSION: ${{ needs.validate.outputs.version }} + RELEASE_DATE: ${{ needs.validate.outputs.release_date }} + run: | + set -euo pipefail + uv run prepare-changelog finalize "$VERSION" "$RELEASE_DATE" + echo "✓ Release date set in CHANGELOG.md" + + - name: Commit and push finalization changes via API + uses: vig-os/commit-action@b70c2d87acd0f146c40e8d88a9bda40b76c084b5 # v0.1.3 + env: + GH_TOKEN: ${{ steps.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 + 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" + + build-and-test: + name: Build and Test + needs: [validate, finalize] + runs-on: ubuntu-22.04 + timeout-minutes: 10 + # TODO: implement build and test steps + + steps: + - name: Checkout release commit + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.finalize.outputs.finalize_sha }} + + publish: + name: Publish Release + needs: [validate, finalize, build-and-test] + runs-on: ubuntu-22.04 + timeout-minutes: 30 + permissions: + contents: write # create and push tags + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + with: + app-id: ${{ secrets.APP_SYNC_ISSUES_ID }} + private-key: ${{ secrets.APP_SYNC_ISSUES_PRIVATE_KEY }} + + - name: Checkout release commit + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.finalize.outputs.finalize_sha }} + token: ${{ steps.app-token.outputs.token }} + 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 annotated tag + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + git tag -a "$VERSION" -m "Release $VERSION" + echo "✓ Tag created: $VERSION" + + - name: Push tag + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + git push origin "$VERSION" + echo "✓ Tag pushed" + + - name: Summary + env: + VERSION: ${{ needs.validate.outputs.version }} + run: | + echo "✓ Release published successfully!" + + rollback: + name: Rollback on Failure + needs: [validate, finalize, build-and-test, publish] + runs-on: ubuntu-22.04 + timeout-minutes: 10 + if: failure() + permissions: + issues: write # create failure issue + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + with: + app-id: ${{ secrets.APP_SYNC_ISSUES_ID }} + private-key: ${{ secrets.APP_SYNC_ISSUES_PRIVATE_KEY }} + + - name: Rollback release branch + id: rollback-branch + continue-on-error: true + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ needs.validate.outputs.version }} + PRE_SHA: ${{ needs.validate.outputs.pre_finalize_sha }} + run: | + set -euo pipefail + echo "Rolling back release branch to pre-finalization state..." + gh api "repos/${{ github.repository }}/git/refs/heads/release/$VERSION" \ + -X PATCH \ + -f sha="$PRE_SHA" \ + -F force=true + echo "✓ Release branch rolled back to $PRE_SHA" + + - name: Delete tag if created + id: rollback-tag + continue-on-error: true + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + TAG="$VERSION" + if 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 + echo "✓ Tag deleted" + else + echo "Tag does not exist on remote (not created)" + fi + + - name: Create failure issue + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const version = '${{ needs.validate.outputs.version }}'; + const workflowUrl = `${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`; + const prNumber = ${{ needs.validate.outputs.pr_number || 'null' }}; + + let failedJobs = []; + if ('${{ needs.validate.result }}' !== 'success') failedJobs.push('validate'); + if ('${{ needs.finalize.result }}' !== 'success') failedJobs.push('finalize'); + if ('${{ needs.build-and-test.result }}' !== 'success') failedJobs.push('build-and-test'); + if ('${{ needs.publish.result }}' !== 'success') failedJobs.push('publish'); + + const rollbackBranch = '${{ steps.rollback-branch.outcome }}'; + const rollbackTag = '${{ steps.rollback-tag.outcome }}'; + + const title = `Release ${version} failed -- automatic rollback`; + const body = ` + Release ${version} encountered an error during the automated release workflow. + + **Failed Jobs:** ${failedJobs.join(', ')} + + **Workflow Run:** [View logs](${workflowUrl}) + + **Release PR:** #${prNumber} + + **Rollback Results:** + - Branch rollback: ${rollbackBranch} + - Tag deletion: ${rollbackTag} + + **Actions Taken:** + - Release branch rolled back to pre-finalization state + - Release tag deleted (if created) + - This issue created for investigation + + **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. + `; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: ['bug', 'release'] + }); + + console.log(`Created issue: ${title}`); + + - name: Summary + run: | + echo "✗ Release workflow failed" + echo "" + echo "Automatic rollback completed:" + echo " - Release branch reset to pre-finalization state" + echo " - Release tag deleted (if created)" + echo " - GitHub issue created for investigation" + echo "" + echo "Check the workflow logs and issue for details" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..44efe3b --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,24 @@ +# Scorecard Workflow +# +# This workflow placeholder is registered with GitHub to enable OpenSSF Scorecard analysis. +# It evaluates repository security posture and best practices compliance. +# Runs on main branch pushes to track security metrics over time. +# Full implementation details are managed separately. + +name: Scorecard + +"on": + push: + branches: + - main + # TODO: add schedule trigger (e.g. weekly cron) for continuous monitoring when implementing + +permissions: + contents: read + +jobs: + scorecard: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..574a71a --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,25 @@ +# Security Scan Workflow +# +# This workflow placeholder is registered with GitHub to enable security scanning checks. +# It scans for vulnerabilities in dependencies, container images, and code. +# Runs on pull requests and pushes to dev and main branches as a security gate. +# Full implementation details are managed separately. + +name: Security Scan + +"on": + pull_request: # TODO: consider restricting to protected branches (dev, main, release/**) when implementing + push: + branches: + - dev + - main + +permissions: + contents: read + +jobs: + security-scan: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 From 3398e56a9e6841aa54702e9a6a989c2040a74bf7 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Wed, 18 Feb 2026 17:04:41 +0000 Subject: [PATCH 02/24] docs: update CHANGELOG Refs: #6 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1520cb2..077bf02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- CI workflow placeholder registered with GitHub as a pre-merge gate for pull requests targeting `dev`, `main`, and `release/**` branches +- CodeQL analysis workflow for automated security vulnerability scanning +- Post-release workflow to automatically sync `dev` with `main` after release merges +- Prepare-release workflow for validating and preparing release branches +- Release workflow for finalizing, building, testing, and publishing releases +- Scorecard workflow for ongoing supply-chain security assessments +- Security scan workflow for continuous security monitoring +- Composite `setup-env` action to set up the CI environment with Python, `uv`, and optional tooling + ### Changed ### Removed From 9889abe64115f678ae7250749878ea0001cb1d1a Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:57:27 +0000 Subject: [PATCH 03/24] chore: sync issues and PRs --- docs/issues/issue-10.md | 91 +++++++++++++++++++++++++++ docs/issues/issue-13.md | 70 +++++++++++++++++++++ docs/pull-requests/pr-12.md | 118 ++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 docs/issues/issue-10.md create mode 100644 docs/issues/issue-13.md create mode 100644 docs/pull-requests/pr-12.md diff --git a/docs/issues/issue-10.md b/docs/issues/issue-10.md new file mode 100644 index 0000000..03990e5 --- /dev/null +++ b/docs/issues/issue-10.md @@ -0,0 +1,91 @@ +--- +type: issue +state: open +created: 2026-02-20T10:58:09Z +updated: 2026-02-20T12:43:37Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/10 +comments: 1 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-20T13:57:23.282Z +--- + +# [Issue 10]: [[BUG] --force-update does not re-sync issues (only PRs)](https://github.com/vig-os/sync-issues-action/issues/10) + +## Description + +When triggering a workflow with `force-update: true`, only PRs are re-synced. Issues are skipped even though `updated-since` is set to `1970-01-01T00:00:00Z` (epoch), which should cause all items to be fetched. + +The calling workflow correctly passes the epoch timestamp via: + +```yaml +updated-since: ${{ (github.event.inputs.force-update == 'true' && '1970-01-01T00:00:00Z') || '' }} +``` + +## Steps to Reproduce + +1. Go to Actions > "Sync Issues and PRs" in a repo using this action +2. Trigger a `workflow_dispatch` run with `force-update: true` +3. Observe the output: PR markdown files are updated, but issue markdown files are not + +## Expected Behavior + +All issues and PRs should be re-synced when `updated-since` is set to epoch, regardless of the last sync timestamp. + +## Actual Behavior + +Only PRs are re-synced. Issues that haven't been modified since the last sync are skipped. + +## Environment + +- GitHub Actions runner: `ubuntu-22.04` +- sync-issues-action: `v0.1.1` (`b4cdf37`) + +## Additional Context + +The `updated-since` input is shared for both issues and PRs in the action. The action may be applying additional filtering to issues that ignores this parameter. + +## Possible Solution + +Investigate why the `updated-since` parameter is not honored for issue fetching. The issue-fetching code path likely has a separate filter or early-exit condition that doesn't respect this override. + +## Changelog Category + +Fixed +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 20, 2026 at 12:43 PM_ + +## Implementation Plan + +Issue: #10 +Branch: bugfix/10-force-update-issues + +### Root Cause + +Both `syncIssuesToMarkdown` (line 250) and `syncPRsToMarkdown` (line 331) in `src/index.ts` call `hasContentChanged` before writing. This function strips frontmatter (including the `synced:` timestamp) via `normalizeContent` and compares the body only. When nothing has changed on GitHub, the body is identical and the write is skipped -- even during a force-update. + +The user observes PRs being re-written because closed PRs gain a new commits section (or other metadata shifts), while issues with no GitHub-side changes remain byte-identical and are skipped. + +The action currently has no way to know the caller intends a force-update; `updated-since` set to epoch controls *which items are fetched* from the API, but not whether `hasContentChanged` is bypassed. + +### Fix + +Add a `force-update` boolean input to the action. When active, skip the `hasContentChanged` gate and always write (which updates the `synced:` frontmatter timestamp, producing a real git diff). + +### Tasks + +- [x] Task 1: Write failing test -- when `force-update` is `'true'` and an issue file already exists with identical body content, the action should still re-write the file — `src/__tests__/unit/index.test.ts` — verify: `npx jest -t "should re-write issue files"` +- [x] Task 2: Write failing test -- same scenario for PRs — `src/__tests__/unit/index.test.ts` — verify: `npx jest -t "should re-write PR files"` +- [x] Task 3: Add `force-update` input (boolean string, default `'false'`) — `action.yml` — verify: input present in file +- [x] Task 4: Read `force-update` input, thread `forceUpdate` flag into `syncIssuesToMarkdown` and `syncPRsToMarkdown`, bypass `hasContentChanged` when true — `src/index.ts` — verify: `npx jest` +- [x] Task 5: Pass `force-update` workflow dispatch input to the action — `.github/workflows/sync-issues.yml` — verify: input present in `with:` block +- [x] Task 6: Run full test suite — verify: `npx jest` (89 passed, 0 failed) + diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md new file mode 100644 index 0000000..5c3191c --- /dev/null +++ b/docs/issues/issue-13.md @@ -0,0 +1,70 @@ +--- +type: issue +state: open +created: 2026-02-20T13:57:05Z +updated: 2026-02-20T13:57:05Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/13 +comments: 0 +labels: chore, area:ci +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-20T13:57:22.824Z +--- + +# [Issue 13]: [[CHORE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) + +### Chore Type + +CI / Build change + +### Description + +The repository has four GitHub Actions workflows and a composite action carried over from the vigOS devcontainer template. They need to be reviewed, customized for this project, and validated end-to-end before branch protection can gate on them. + +**Workflows:** +| File | Purpose | +|------|---------| +| `.github/workflows/ci.yml` | Lint, test, security scan, dependency review, summary gate | +| `.github/workflows/codeql.yml` | CodeQL static analysis for Python | +| `.github/workflows/scorecard.yml` | OpenSSF Scorecard with SARIF upload | +| `.github/workflows/release.yml` | Release automation (validate → finalize → test → release → rollback) | +| `.github/actions/setup-env/action.yml` | Composite action: Python, uv, optional tooling | + +### Acceptance Criteria + +- [ ] `ci.yml` runs successfully on a PR to `dev` (lint, test, security, dependency-review, summary) +- [ ] `codeql.yml` runs CodeQL analysis on Python files without errors +- [ ] `scorecard.yml` runs and uploads SARIF to the Security tab +- [ ] `release.yml` completes a dry-run validation successfully +- [ ] `setup-env` composite action installs Python, uv, and syncs project dependencies +- [ ] All action references are pinned to full SHA commits +- [ ] Branch protection on `dev` and `main` requires CI Summary to pass + +### Implementation Notes + +- `ci.yml` depends on the `setup-env` composite action — verify inputs/outputs match this project's needs +- `release.yml` triggers a `sync-issues` workflow mid-run — verify it exists or stub/remove the step +- `release.yml` references `vig-os/commit-action` — confirm the repo has access to this action +- `security` job hard-codes `safety==3.2.11` — verify version compatibility with current deps +- `scorecard.yml` uses `codeql-action/upload-sarif@v3` (SHA `b5ebac6`) while `codeql.yml` uses `codeql-action@v4` (SHA `45cbd0c`) — verify this is intentional or align versions +- Runner is `ubuntu-22.04` across all workflows — decide whether to stay or move to `ubuntu-24.04` + +### Related Issues + +Related to #6 + +### Priority + +High + +### Changelog Category + +Added + +### Additional Context + +The `setup-env` action also supports optional tooling (podman, Node.js, devcontainer CLI, BATS, just) that isn't currently used by any workflow but may be needed later. diff --git a/docs/pull-requests/pr-12.md b/docs/pull-requests/pr-12.md new file mode 100644 index 0000000..aeb8d9b --- /dev/null +++ b/docs/pull-requests/pr-12.md @@ -0,0 +1,118 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/10-force-update-issues → dev +created: 2026-02-20T13:35:39Z +updated: 2026-02-20T13:51:16Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/12 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +relationship: none +merged: 2026-02-20T13:51:00Z +synced: 2026-02-20T13:57:24.996Z +--- + +# [PR 12](https://github.com/vig-os/sync-issues-action/pull/12) fix: force-update does not re-sync issues (#10) + +## Description + +When triggering a workflow with `force-update: true`, only PRs were re-synced while issues were silently skipped. The root cause is that `hasContentChanged` strips frontmatter (including the `synced:` timestamp) and compares body content only -- when nothing has changed on GitHub, the body is identical and the write is skipped, even during a force-update. The action had no mechanism to bypass this content-comparison gate. + +This PR adds a `force-update` boolean input to the action. When active, both `syncIssuesToMarkdown` and `syncPRsToMarkdown` skip the `hasContentChanged` check and always write files (updating the `synced:` frontmatter timestamp, which produces a real git diff). + +## Related Issue(s) + +Closes #10 + +## Type of Change + +- [x] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [x] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] CI / Build change +- [x] Test updates + +## Changes Made + +- `action.yml` -- added `force-update` input (boolean string, default `'false'`) +- `src/index.ts` -- read the new input, thread a `forceUpdate` boolean into `syncIssuesToMarkdown` and `syncPRsToMarkdown`, short-circuit with `forceUpdate || hasContentChanged(...)` +- `.github/workflows/sync-issues.yml` -- pass `force-update: ${{ github.event.inputs.force-update }}` to the action +- `src/__tests__/unit/index.test.ts` -- two new tests (issues + PRs) verifying force-update bypasses `hasContentChanged` +- `CHANGELOG.md` -- added entry under Unreleased > Fixed +- `README.md` -- added `force-update` row to the Options table + +## Changelog Entry + +### Fixed + +- **`--force-update` does not re-sync issues (only PRs)** ([#10](https://github.com/vig-os/sync-issues-action/issues/10)) + - Added `force-update` action input that bypasses the `hasContentChanged` content-comparison gate + - When active, all fetched items are re-written (with updated `synced:` frontmatter) even if body content is unchanged + - Updated `sync-issues.yml` workflow to pass the `force-update` dispatch input to the action + +## Testing + +- [x] Tests pass locally (`npx jest` -- 89 passed, 0 failed) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `npx jest -t "force-update input"` to verify both new tests pass (issue + PR force-update) +- Ran `npx jest -t "should skip writing when only synced timestamp changes"` to confirm existing behaviour is preserved when `force-update` is not set + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [x] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly +- [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 +- [x] Any dependent changes have been merged and published + +## Additional Notes + +The fix follows TDD: failing tests were committed first, then the implementation, then the workflow and docs updates. The commit history proves compliance: + +1. `test:` failing test for issues +2. `test:` failing test for PRs +3. `fix:` implementation (action.yml + src/index.ts) +4. `fix:` workflow update +5. `docs:` changelog +6. `docs:` README + +Refs: #10 + + + +--- +--- + +## Commits + +### Commit 1: [b759324](https://github.com/vig-os/sync-issues-action/commit/b759324d59ca158b8fb16577c318a10806169cbb) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:39 PM +test: add failing test for force-update bypassing hasContentChanged on issues, 57 files modified (src/__tests__/unit/index.test.ts) + +### Commit 2: [cba6182](https://github.com/vig-os/sync-issues-action/commit/cba6182630b4239552543a252e19c81f1c1deb7d) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:40 PM +test: add failing test for force-update bypassing hasContentChanged on PRs, 60 files modified (src/__tests__/unit/index.test.ts) + +### Commit 3: [0117510](https://github.com/vig-os/sync-issues-action/commit/0117510379d064fb04ae4e6c786b43d3984ecd50) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:42 PM +fix: add force-update input to bypass hasContentChanged gate, 24 files modified (action.yml, src/index.ts) + +### Commit 4: [5478179](https://github.com/vig-os/sync-issues-action/commit/5478179d217c224b67b1aab84dfb39db76e0e690) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:42 PM +fix: pass force-update input to sync-issues action in workflow, 56 files modified (.github/workflows/sync-issues.yml) + +### Commit 5: [25826f7](https://github.com/vig-os/sync-issues-action/commit/25826f78a920ae6aba9bd7f85987e0631039b53e) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 01:18 PM +docs: update changelog for force-update fix, 4 files modified (CHANGELOG.md) + +### Commit 6: [f7065e8](https://github.com/vig-os/sync-issues-action/commit/f7065e8e5461cc24c10d377f6f56b684b73861d7) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 01:21 PM +docs: add force-update input to README options table, 1 file modified (README.md) From ffa21a624aae1ecdca9ef8a7b04092a9d8a454d2 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:13:53 +0000 Subject: [PATCH 04/24] chore: sync issues and PRs --- docs/issues/issue-10.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/issues/issue-10.md b/docs/issues/issue-10.md index 03990e5..e3950f9 100644 --- a/docs/issues/issue-10.md +++ b/docs/issues/issue-10.md @@ -1,8 +1,8 @@ --- type: issue -state: open +state: closed created: 2026-02-20T10:58:09Z -updated: 2026-02-20T12:43:37Z +updated: 2026-02-20T14:13:37Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/10 @@ -12,7 +12,7 @@ assignees: none milestone: none projects: none relationship: none -synced: 2026-02-20T13:57:23.282Z +synced: 2026-02-20T14:13:51.874Z --- # [Issue 10]: [[BUG] --force-update does not re-sync issues (only PRs)](https://github.com/vig-os/sync-issues-action/issues/10) From 81771e5966fc7b948a3bdeea1750e27e47eacb36 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:15:39 +0000 Subject: [PATCH 05/24] chore: sync issues and PRs --- docs/issues/issue-13.md | 57 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md index 5c3191c..ee0a081 100644 --- a/docs/issues/issue-13.md +++ b/docs/issues/issue-13.md @@ -2,28 +2,24 @@ type: issue state: open created: 2026-02-20T13:57:05Z -updated: 2026-02-20T13:57:05Z +updated: 2026-02-20T14:15:18Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/13 comments: 0 -labels: chore, area:ci +labels: area:ci, feature assignees: none milestone: none projects: none relationship: none -synced: 2026-02-20T13:57:22.824Z +synced: 2026-02-20T14:15:37.733Z --- -# [Issue 13]: [[CHORE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) - -### Chore Type - -CI / Build change +# [Issue 13]: [[FEATURE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) ### Description -The repository has four GitHub Actions workflows and a composite action carried over from the vigOS devcontainer template. They need to be reviewed, customized for this project, and validated end-to-end before branch protection can gate on them. +Add fully operational CI/CD workflows to the repository. Four GitHub Actions workflows and a composite setup action exist as templates carried over from the vigOS devcontainer but have not been validated or customized for this project. **Workflows:** | File | Purpose | @@ -34,18 +30,23 @@ The repository has four GitHub Actions workflows and a composite action carried | `.github/workflows/release.yml` | Release automation (validate → finalize → test → release → rollback) | | `.github/actions/setup-env/action.yml` | Composite action: Python, uv, optional tooling | -### Acceptance Criteria +### Problem Statement + +The repository has no working CI pipeline. PRs can be merged without lint checks, tests, or security scans. The release workflow has never been exercised. Without validated CI, there is no automated quality gate and branch protection cannot be meaningfully configured. -- [ ] `ci.yml` runs successfully on a PR to `dev` (lint, test, security, dependency-review, summary) -- [ ] `codeql.yml` runs CodeQL analysis on Python files without errors -- [ ] `scorecard.yml` runs and uploads SARIF to the Security tab -- [ ] `release.yml` completes a dry-run validation successfully -- [ ] `setup-env` composite action installs Python, uv, and syncs project dependencies -- [ ] All action references are pinned to full SHA commits -- [ ] Branch protection on `dev` and `main` requires CI Summary to pass +### Proposed Solution -### Implementation Notes +Review, customize, and validate each workflow end-to-end: +1. **`ci.yml`** — Confirm lint, test, security, dependency-review, and summary jobs run successfully on a PR to `dev` +2. **`codeql.yml`** — Verify CodeQL analysis runs on Python files (PRs, pushes to main, weekly schedule) +3. **`scorecard.yml`** — Verify Scorecard runs on push to main and weekly; SARIF uploads to Security tab +4. **`release.yml`** — Complete a dry-run validation successfully +5. **`setup-env`** — Verify composite action installs Python, uv, and syncs project dependencies +6. **All workflows** — Ensure action references are pinned to full SHA commits +7. **Branch protection** — Configure `dev` and `main` to require CI Summary to pass + +**Implementation notes:** - `ci.yml` depends on the `setup-env` composite action — verify inputs/outputs match this project's needs - `release.yml` triggers a `sync-issues` workflow mid-run — verify it exists or stub/remove the step - `release.yml` references `vig-os/commit-action` — confirm the repo has access to this action @@ -53,18 +54,22 @@ The repository has four GitHub Actions workflows and a composite action carried - `scorecard.yml` uses `codeql-action/upload-sarif@v3` (SHA `b5ebac6`) while `codeql.yml` uses `codeql-action@v4` (SHA `45cbd0c`) — verify this is intentional or align versions - Runner is `ubuntu-22.04` across all workflows — decide whether to stay or move to `ubuntu-24.04` -### Related Issues +### Alternatives Considered + +- **Minimal CI (lint + test only):** Faster to set up but leaves security scanning and release automation for later. Rejected because the workflows already exist and just need validation. +- **Third-party CI (CircleCI, etc.):** Would require rewriting all workflows. Not justified since GitHub Actions is already in use. -Related to #6 +### Additional Context + +- Related to #6 +- The `setup-env` action also supports optional tooling (podman, Node.js, devcontainer CLI, BATS, just) that isn't currently used by any workflow but may be needed later. -### Priority +### Impact -High +- All contributors benefit from automated quality gates on PRs +- Backward compatible — adds CI infrastructure without changing existing code +- Enables branch protection rules that require CI to pass ### Changelog Category Added - -### Additional Context - -The `setup-env` action also supports optional tooling (podman, Node.js, devcontainer CLI, BATS, just) that isn't currently used by any workflow but may be needed later. From 7ae203a87eff3d727a725eb0e8a0a3514fdabe52 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:17:15 +0000 Subject: [PATCH 06/24] chore: sync issues and PRs --- docs/issues/issue-13.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md index ee0a081..a88d190 100644 --- a/docs/issues/issue-13.md +++ b/docs/issues/issue-13.md @@ -2,7 +2,7 @@ type: issue state: open created: 2026-02-20T13:57:05Z -updated: 2026-02-20T14:15:18Z +updated: 2026-02-20T14:16:58Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/13 @@ -12,12 +12,12 @@ assignees: none milestone: none projects: none relationship: none -synced: 2026-02-20T14:15:37.733Z +synced: 2026-02-20T14:17:13.467Z --- # [Issue 13]: [[FEATURE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) -### Description +## Description Add fully operational CI/CD workflows to the repository. Four GitHub Actions workflows and a composite setup action exist as templates carried over from the vigOS devcontainer but have not been validated or customized for this project. @@ -30,11 +30,11 @@ Add fully operational CI/CD workflows to the repository. Four GitHub Actions wor | `.github/workflows/release.yml` | Release automation (validate → finalize → test → release → rollback) | | `.github/actions/setup-env/action.yml` | Composite action: Python, uv, optional tooling | -### Problem Statement +## Problem Statement The repository has no working CI pipeline. PRs can be merged without lint checks, tests, or security scans. The release workflow has never been exercised. Without validated CI, there is no automated quality gate and branch protection cannot be meaningfully configured. -### Proposed Solution +## Proposed Solution Review, customize, and validate each workflow end-to-end: @@ -54,22 +54,22 @@ Review, customize, and validate each workflow end-to-end: - `scorecard.yml` uses `codeql-action/upload-sarif@v3` (SHA `b5ebac6`) while `codeql.yml` uses `codeql-action@v4` (SHA `45cbd0c`) — verify this is intentional or align versions - Runner is `ubuntu-22.04` across all workflows — decide whether to stay or move to `ubuntu-24.04` -### Alternatives Considered +## Alternatives Considered - **Minimal CI (lint + test only):** Faster to set up but leaves security scanning and release automation for later. Rejected because the workflows already exist and just need validation. - **Third-party CI (CircleCI, etc.):** Would require rewriting all workflows. Not justified since GitHub Actions is already in use. -### Additional Context +## Additional Context - Related to #6 - The `setup-env` action also supports optional tooling (podman, Node.js, devcontainer CLI, BATS, just) that isn't currently used by any workflow but may be needed later. -### Impact +## Impact - All contributors benefit from automated quality gates on PRs - Backward compatible — adds CI infrastructure without changing existing code - Enables branch protection rules that require CI to pass -### Changelog Category +## Changelog Category Added From 9a60cc19caff443222ff7e190a3cd5acccd289ca Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:29:13 +0000 Subject: [PATCH 07/24] chore: sync issues and PRs --- docs/issues/issue-13.md | 187 +++++++++++++++++++++++++++++++++++- docs/pull-requests/pr-14.md | 92 ++++++++++++++++++ 2 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 docs/pull-requests/pr-14.md diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md index a88d190..6cf187a 100644 --- a/docs/issues/issue-13.md +++ b/docs/issues/issue-13.md @@ -2,17 +2,17 @@ type: issue state: open created: 2026-02-20T13:57:05Z -updated: 2026-02-20T14:16:58Z +updated: 2026-02-20T14:31:51Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/13 -comments: 0 +comments: 1 labels: area:ci, feature assignees: none milestone: none projects: none relationship: none -synced: 2026-02-20T14:17:13.467Z +synced: 2026-02-21T01:29:10.591Z --- # [Issue 13]: [[FEATURE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) @@ -73,3 +73,184 @@ Review, customize, and validate each workflow end-to-end: ## Changelog Category Added +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 20, 2026 at 02:31 PM_ + +## Implementation Plan + +**TDD**: Skipped — non-testable changes (config/infrastructure YAML). + +### Current State + +Five workflow/action files exist as templates from the vigOS devcontainer. All action references are already SHA-pinned. The repo is public at `vig-os/sync-issues-action`. External dependencies (`vig-os/commit-action`, `vig-os/sync-issues-action`) are verified accessible. `sync-issues.yml` workflow exists and is referenced by `release.yml`. + +### Branch + +`feature/13-ci-cd-workflows` from `dev` + +--- + +### Changes Required + +#### 1. `scorecard.yml` — Align codeql-action version (v3 → v4) + +`.github/workflows/scorecard.yml` uses `codeql-action/upload-sarif@b5ebac6...` (v3), while `.github/workflows/codeql.yml` uses `codeql-action@45cbd0c...` (v4). Align the scorecard upload-sarif step to v4: + +```yaml +# Before +uses: github/codeql-action/upload-sarif@b5ebac6f4c00c8ccddb7cdcd45fdb248329f808a # v3 +# After +uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4 +``` + +#### 2. `ci.yml` — Update safety version and add API key + +The security job pins `safety==3.2.11`. Update to latest (`3.7.0`) and wire up the `SAFETY_API_KEY` secret for full vulnerability database access: + +- Change `uv pip install safety==3.2.11` to `uv pip install safety==3.7.0` +- Add `SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}` as env to the safety check step + +#### 3. `codeql.yml` — Customize and verify + +**Current state (verified correct):** +- Language matrix: `['python']` +- Triggers: PRs to `dev`/`release/**`/`main` (path `**.py`), push to `main` (path `**.py`), weekly cron `15 2 * * 1` +- Actions SHA-pinned: `codeql-action/init` and `codeql-action/analyze` both at `@45cbd0c...` (v4) +- Permissions scoped: `security-events: write`, `contents: read`, `actions: read` + +**Change needed — update header comment:** + +The header says "Runs GitHub CodeQL analysis on Python scripts used in the build toolchain" — vigOS template comment. This project has actual Python product code in `src/sync_issues_action/`. Update to reflect this project: + +```yaml +# Before +# Runs GitHub CodeQL analysis on Python scripts used in the build toolchain. +# While these are build tools (not product code), static analysis provides +# defense-in-depth for scripts that handle git operations, file I/O, and +# subprocess execution. + +# After +# Runs GitHub CodeQL analysis on the Python codebase. +``` + +**Validation:** CodeQL triggers on PRs only when `**.py` files change. The YAML-only change will not trigger it — accept structural review as sufficient; the workflow will fire on the next PR that touches Python code. + +**Follow-up (out of scope):** Adding `javascript-typescript` to the CodeQL language matrix for the TypeScript source code. + +#### 4. `release.yml` — Customize and review integration points + +**Current state — 5 jobs:** `validate` → `finalize` → `test` → `release` → `rollback` (on failure) + +**External dependencies (all verified accessible):** +- `vig-os/commit-action@b70c2d87...` (v0.1.3) — used in `finalize` job to commit CHANGELOG date via GitHub API. Passes config through `env:` vars (`GH_TOKEN`, `GITHUB_REPOSITORY`, `TARGET_BRANCH`, `COMMIT_MESSAGE`, `FILE_PATHS`), which is the correct interface for this action. +- `sync-issues.yml` workflow — triggered mid-release via `gh workflow run sync-issues.yml -f "target-branch=release/$VERSION"`. The workflow exists and its `workflow_dispatch` accepts a `target-branch` input. The wait loop (120s timeout, 10s interval) is reasonable. + +**Project-specific prerequisites (verified):** +- `CHANGELOG.md` exists and uses the `## [X.Y.Z] - TBD` format +- `setup-env` composite action is used in the `test` job with `sync-dependencies: 'true'` +- Branch pattern `release/X.Y.Z` and base `main` match the project's git workflow + +**Change needed — update header comment:** + +```yaml +# Before +# Default template for Python projects using the vigOS devcontainer. +# After +# Customized for the sync-issues-action project. +``` + +**Key finding — GitHub App token TODO (line 22-24):** + +The workflow has an existing TODO about replacing `GITHUB_TOKEN` with a GitHub App token for branch-protected repos. This becomes relevant when branch protection is configured (task 7). The `vig-os/commit-action` uses the GitHub API (not git push), so it MAY work with `GITHUB_TOKEN` even under branch protection — known risk to test during the dry-run. + +**Dry-run validation:** Deferred — requires a `release/X.Y.Z` branch + approved PR to `main` + CI green. Since this is v0.1.0 with no release branch yet, defer to the first release cycle. + +**No structural changes needed** to the workflow logic. + +#### 5. `setup-env/action.yml` — Add npm dependency support (based on `post-create.sh`) + +Comparing `.github/actions/setup-env/action.yml` against `.devcontainer/scripts/post-create.sh` reveals a gap: the devcontainer installs Node.js **and** npm dependencies, but setup-env only installs Node.js — it has no step to run `npm ci` to install dependencies from `package.json`. + +This matters because the project is a TypeScript GitHub Action. `package.json` defines `npm test` (jest), `npm run build` (tsc), and `npm run package` (ncc). CI cannot run TypeScript tests or build the action without npm dependencies installed. + +**Changes:** + +1. Add `sync-npm-dependencies` input (default `'false'`): +```yaml + sync-npm-dependencies: + description: 'Run npm ci to install Node.js dependencies from package.json (requires Node.js)' + required: false + default: 'false' +``` + +2. Add `npm ci` step after the Node.js install step: +```yaml + - name: Install npm dependencies + if: inputs.sync-npm-dependencies == 'true' + shell: bash + run: npm ci +``` + +3. Auto-trigger Node.js install when `sync-npm-dependencies` is true (consistent with `install-devcontainer-cli` pattern): +```yaml + if: inputs.install-node == 'true' || inputs.install-devcontainer-cli == 'true' || inputs.sync-npm-dependencies == 'true' +``` + +**Not carried over from `post-create.sh` (dev-only tools):** +- `act` (nektos) — local workflow runner, not needed inside GitHub Actions +- `@github/local-action` + `tsx` — action testing tool for local dev + +**Follow-up suggestion:** Open a separate issue to change `--all-extras` to `--extra dev` in setup-env to avoid installing unnecessary science deps in CI. + +#### 6. SHA pin audit + +All actions are already SHA-pinned. Confirm no unpinned references exist by grep-searching all workflow files for `uses:` lines without `@` followed by a 40-char hex SHA. + +#### 7. Branch protection + +After CI is validated on the PR, configure branch protection rules via `gh api`: + +- **`dev` branch**: Require `CI Summary` status check to pass before merge +- **`main` branch**: Require `CI Summary` status check to pass before merge + +Requires repository admin permissions. + +#### 8. Runner version + +Staying on `ubuntu-22.04`. No changes to runner configuration. + +--- + +### Validation Strategy + +1. Push branch and open PR to `dev` +2. Verify CI jobs run: lint, test, security, dependency-review, summary +3. CodeQL: structural review sufficient (path filter `**.py` means YAML-only changes won't trigger it) +4. Scorecard: structural review sufficient (only runs on push to `main` / schedule) +5. Release dry-run: deferred to first release cycle (no release branch exists yet) +6. Branch protection: configure after CI Summary is confirmed green + +### Files Modified + +- `.github/workflows/scorecard.yml` — align codeql-action to v4 +- `.github/workflows/ci.yml` — update safety version + API key +- `.github/workflows/codeql.yml` — update header comment to match project +- `.github/workflows/release.yml` — update header comment to match project +- `.github/actions/setup-env/action.yml` — add `sync-npm-dependencies` input + `npm ci` step + +### Task List + +- [ ] Create `feature/13-ci-cd-workflows` branch from `dev` +- [ ] Align `scorecard.yml` codeql-action/upload-sarif from v3 to v4 SHA +- [ ] Update `ci.yml` safety version to 3.7.0 and add SAFETY_API_KEY env +- [ ] Customize `codeql.yml` header comment for this project +- [ ] Customize `release.yml` header comment and review integration points +- [ ] Add `sync-npm-dependencies` input + `npm ci` step to setup-env action +- [ ] Audit all workflow files for unpinned action references +- [ ] Commit changes (Refs: #13) and push branch +- [ ] Open PR to `dev`, verify CI jobs run and pass +- [ ] Configure branch protection on `dev` and `main` to require CI Summary + diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md new file mode 100644 index 0000000..8fe1c0c --- /dev/null +++ b/docs/pull-requests/pr-14.md @@ -0,0 +1,92 @@ +--- +type: pull_request +state: open +branch: feature/13-ci-cd-workflows → dev +created: 2026-02-20T16:41:37Z +updated: 2026-02-20T21:57:10Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/14 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +relationship: none +synced: 2026-02-21T01:29:11.589Z +--- + +# [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: develop and validate CI/CD workflows + +## Description + +Add the full CI/CD pipeline for the project: a CI workflow for pull request checks, a release workflow for versioned publishing, security scanning, and supporting composite actions. + +## 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 + +- Add CI workflow with lint, build, test, dependency-review, and summary gate jobs +- Add release workflow with validate → finalize → test → release → rollback stages and dry-run support +- Add CodeQL static analysis and OpenSSF Scorecard security workflows +- Add `setup-env` composite action for consistent Node.js + npm setup across workflows +- Add `build-dist` composite action for ncc bundling and dist/ diff verification +- Add Dependabot configuration for weekly Actions and npm dependency updates +- Add `.nvmrc` pinning Node.js 20 as the single source of truth across CI and devcontainer +- Rebuild dist bundle for the force-update input that was committed without a dist rebuild (#10) + +## Changelog Entry + +### Added + +- **CI/CD pipeline** ([#13](https://github.com/vig-os/sync-issues-action/issues/13)) + - CI workflow running lint, test, dependency-review, and summary gate jobs on PRs to `dev`, `release/**`, and `main` + - Release workflow with dry-run support, CHANGELOG date finalization, and automatic rollback with failure issue on error + - CodeQL static analysis on PRs and pushes, and OpenSSF Scorecard supply-chain security analysis uploading SARIF to the Security tab + - `setup-env` composite action for consistent Node.js installation and npm dependency caching across workflows + - `build-dist` composite action for ncc bundling and dist diff verification to prevent stale compiled output + - Dependabot configuration for automated weekly updates of GitHub Actions (sha-pinned) and npm packages targeting `dev` + - `.nvmrc` pinning Node.js 20 as the single source of truth for the runtime version + - `install-uv` input on `setup-env` to optionally install `uv` on GitHub-hosted runners for `pre-commit` + +## Testing + +- [ ] Tests pass locally (`npm test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +Workflows validated via pre-commit YAML linting and action-pin checks. Full end-to-end testing requires a push to the remote and observing GitHub Actions runs. + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [x] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly +- [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 +- [x] Any dependent changes have been merged and published + +## Additional Notes + +The `build-dist` action enforces that `dist/` is always committed in sync with source. Going forward, any PR with stale dist will fail the CI `build` job. The `.nvmrc` file ensures local devcontainer and CI always use the same Node.js major version — bumping Node requires only a single-line change. + +Refs: #13 From a4d54ae843d4aacb8ada74aae5ba69a3f64de0c1 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:01:29 +0000 Subject: [PATCH 08/24] chore: sync issues and PRs --- docs/issues/issue-15.md | 54 ++++++++++++++++++++++++ docs/pull-requests/pr-14.md | 84 +++++++++++++++++++++++++------------ 2 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 docs/issues/issue-15.md diff --git a/docs/issues/issue-15.md b/docs/issues/issue-15.md new file mode 100644 index 0000000..f9cc614 --- /dev/null +++ b/docs/issues/issue-15.md @@ -0,0 +1,54 @@ +--- +type: issue +state: open +created: 2026-02-22T11:01:08Z +updated: 2026-02-22T11:01:08Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/15 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-22T11:01:26.230Z +--- + +# [Issue 15]: [Suppress noisy parentIssue GraphQL warnings when sub-issues API is unavailable](https://github.com/vig-os/sync-issues-action/issues/15) + +## Problem + +The integration test CI logs show repeated warnings: + +``` +##[warning]Failed to fetch sub-issue relationships: Request failed due to following response errors: + - Field 'parentIssue' doesn't exist on type 'Issue' +``` + +This fires for every batch of issues fetched. The `parentIssue` and `subIssues` fields are part of GitHub's **Sub-issues** feature, which is only available on certain plans/repos. When the API doesn't support these fields, the GraphQL query fails and emits a noisy warning. + +**Current behavior:** The error is caught and a warning is logged per batch — no functional impact, sync completes successfully. + +**Desired behavior:** Detect that the sub-issues API is unavailable and skip relationship fetches for the rest of the run, rather than warning on every batch. + +## Relevant code + +`src/index.ts` around line 416 builds the GraphQL query: + +```typescript +`issue_${num}: issue(number: ${num}) { + parentIssue { number } + subIssues(first: 100) { nodes { number } } +}` +``` + +## Suggested approaches + +1. **Attempt once, then skip** — try the relationship query on the first batch; if it fails with a schema error, set a flag and skip all subsequent batches. +2. **Make it opt-in** — add an action input (e.g. `sync-sub-issues: true`) so the query is only made when explicitly requested. +3. **Combine both** — opt-in input + graceful fallback on schema error. + +## Context + +Observed in the `integration-test.yml` workflow on PR #14. diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md index 8fe1c0c..bcec914 100644 --- a/docs/pull-requests/pr-14.md +++ b/docs/pull-requests/pr-14.md @@ -3,7 +3,7 @@ type: pull_request state: open branch: feature/13-ci-cd-workflows → dev created: 2026-02-20T16:41:37Z -updated: 2026-02-20T21:57:10Z +updated: 2026-02-22T10:58:02Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/pull/14 @@ -13,23 +13,23 @@ assignees: c-vigo milestone: none projects: none relationship: none -synced: 2026-02-21T01:29:11.589Z +synced: 2026-02-22T11:01:27.627Z --- # [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: develop and validate CI/CD workflows ## Description -Add the full CI/CD pipeline for the project: a CI workflow for pull request checks, a release workflow for versioned publishing, security scanning, and supporting composite actions. +Implement the full CI/CD pipeline for the project: continuous integration checks on pull requests, a three-phase release process (prepare → release → post-release), security scanning, integration testing, and supporting composite actions and tooling. ## Type of Change - [ ] `feat` -- New feature - [ ] `fix` -- Bug fix - [ ] `docs` -- Documentation only -- [ ] `chore` -- Maintenance task (deps, config, etc.) +- [x] `chore` -- Maintenance task (deps, config, etc.) - [ ] `refactor` -- Code restructuring (no behavior change) -- [ ] `test` -- Adding or updating tests +- [x] `test` -- Adding or updating tests - [x] `ci` -- CI/CD pipeline changes - [x] `build` -- Build system or dependency changes - [ ] `revert` -- Reverts a previous commit @@ -41,37 +41,63 @@ Add the full CI/CD pipeline for the project: a CI workflow for pull request chec ## Changes Made -- Add CI workflow with lint, build, test, dependency-review, and summary gate jobs -- Add release workflow with validate → finalize → test → release → rollback stages and dry-run support -- Add CodeQL static analysis and OpenSSF Scorecard security workflows -- Add `setup-env` composite action for consistent Node.js + npm setup across workflows -- Add `build-dist` composite action for ncc bundling and dist/ diff verification -- Add Dependabot configuration for weekly Actions and npm dependency updates -- Add `.nvmrc` pinning Node.js 20 as the single source of truth across CI and devcontainer -- Rebuild dist bundle for the force-update input that was committed without a dist rebuild (#10) +### CI Workflow (`ci.yml`) -## Changelog Entry +- Lint, build, test, integration-test, dependency-review, and summary gate jobs on PRs to `dev`, `release/**`, and `main` +- Manual `workflow_dispatch` with selectable test suite + +### Three-Phase Release Process + +- **Phase 1 — Prepare Release** (`prepare-release.yml`): create `release/X.Y.Z` branch, prepare CHANGELOG heading, open draft PR to `main` +- **Phase 2 — Release** (`release.yml`): validate prerequisites, finalize CHANGELOG date + `package.json` version, run full test suite on finalized code, create `vX.Y.Z` tag with provenance, publish GitHub Release with auto-generated notes, update floating tags (`vX`, `vX.Y`), automatic rollback with failure-issue on error +- **Phase 3 — Post-Release** (`post-release.yml`): merge `main` back into `dev`, reset CHANGELOG Unreleased section for the next cycle +- Dry-run mode across all phases with deep preview (CHANGELOG diff, version bump preview, tag listing) + +### Security Workflows + +- CodeQL static analysis on PRs and pushes (`codeql.yml`) +- OpenSSF Scorecard supply-chain security analysis uploading SARIF to the Security tab (`scorecard.yml`) + +### Integration Testing + +- Reusable `integration-test.yml` workflow called by CI and release, running 4 scenarios: issues-only, PRs-only, force-update, and include-closed + +### Composite Actions -### Added +- `setup-env` — consistent Node.js installation (version from `.nvmrc`), npm dependency caching, optional `uv` install for pre-commit +- `build-dist` — ncc bundling with dist/ diff verification to prevent stale compiled output -- **CI/CD pipeline** ([#13](https://github.com/vig-os/sync-issues-action/issues/13)) - - CI workflow running lint, test, dependency-review, and summary gate jobs on PRs to `dev`, `release/**`, and `main` - - Release workflow with dry-run support, CHANGELOG date finalization, and automatic rollback with failure issue on error - - CodeQL static analysis on PRs and pushes, and OpenSSF Scorecard supply-chain security analysis uploading SARIF to the Security tab - - `setup-env` composite action for consistent Node.js installation and npm dependency caching across workflows - - `build-dist` composite action for ncc bundling and dist diff verification to prevent stale compiled output - - Dependabot configuration for automated weekly updates of GitHub Actions (sha-pinned) and npm packages targeting `dev` - - `.nvmrc` pinning Node.js 20 as the single source of truth for the runtime version - - `install-uv` input on `setup-env` to optionally install `uv` on GitHub-hosted runners for `pre-commit` +### Release Tooling + +- `prepare_changelog.py` — standalone Python 3 CLI (zero external deps) for CHANGELOG heading management, date stamping, and Unreleased section reset, with unit tests (`test_prepare_changelog.py`) +- `release_helpers.sh` — shell helper functions for release validation, with tests (`test_release_helpers.sh`) + +### Configuration & Maintenance + +- Dependabot configuration for weekly Actions (sha-pinned) and npm dependency updates targeting `dev` +- `.nvmrc` pinning Node.js 20 as single source of truth for CI, devcontainer, and local development +- `CODEOWNERS` file for automated review assignment +- Overhauled issue templates: `bug`, `chore`, `discussion`, `docs`, `feature`, `refactor` (removed generic `task`) +- Updated PR template with conventional-commit type checkboxes, changelog entry section, and refs footer +- Pre-commit config: removed Python checks, switched `uv run` to `uvx` for pre-commit invocation +- Removed source maps from `dist/` to fix CI dist staleness checks +- `sync-issues.yml` updated to use local action (`uses: ./`) and GitHub App token for cache deletion + +## Changelog Entry + +No changelog needed — all changes are CI/CD pipeline internals, repo configuration, and contributor tooling with no user-visible behavior change to the action itself. ## Testing -- [ ] Tests pass locally (`npm test`) +- [x] Tests pass locally (`npm test`) - [x] Manual testing performed (describe below) ### Manual Testing Details -Workflows validated via pre-commit YAML linting and action-pin checks. Full end-to-end testing requires a push to the remote and observing GitHub Actions runs. +- Workflows validated via pre-commit YAML linting (`yamllint`), action-pin checks, and shellcheck +- `prepare_changelog.py` unit tests: `python -m pytest .github/tests/test_prepare_changelog.py` +- `release_helpers.sh` unit tests: `bash .github/tests/test_release_helpers.sh` +- Full end-to-end workflow testing requires a push to the remote and observing GitHub Actions runs ## Checklist @@ -87,6 +113,10 @@ Workflows validated via pre-commit YAML linting and action-pin checks. Full end- ## Additional Notes -The `build-dist` action enforces that `dist/` is always committed in sync with source. Going forward, any PR with stale dist will fail the CI `build` job. The `.nvmrc` file ensures local devcontainer and CI always use the same Node.js major version — bumping Node requires only a single-line change. +The `build-dist` action enforces that `dist/` is always committed in sync with source — any PR with stale dist will fail the CI `build` job. The `.nvmrc` file ensures local devcontainer and CI always use the same Node.js major version; bumping Node requires only a single-line change. + +The release process uses a GitHub App token (`APP_ID` / `APP_PRIVATE_KEY` secrets) for operations that need to bypass branch protection rules (pushing to release branches, creating tags). The `GITHUB_TOKEN` is used everywhere else with least-privilege permissions. + +`prepare_changelog.py` is a temporary in-repo tool that will eventually be extracted into a standalone GitHub Action or pip package. Refs: #13 From a42f7750c7dcbba93deab15adb255336f7c4be64 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:00:19 +0000 Subject: [PATCH 09/24] chore: sync issues and PRs --- docs/issues/issue-15.md | 33 +++++++++++++++++--- docs/pull-requests/pr-14.md | 4 +-- docs/pull-requests/pr-16.md | 61 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 docs/pull-requests/pr-16.md diff --git a/docs/issues/issue-15.md b/docs/issues/issue-15.md index f9cc614..bb6c19d 100644 --- a/docs/issues/issue-15.md +++ b/docs/issues/issue-15.md @@ -1,18 +1,18 @@ --- type: issue -state: open +state: closed created: 2026-02-22T11:01:08Z -updated: 2026-02-22T11:01:08Z +updated: 2026-02-23T09:00:01Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/15 -comments: 0 +comments: 1 labels: none assignees: none milestone: none projects: none relationship: none -synced: 2026-02-22T11:01:26.230Z +synced: 2026-02-23T09:00:15.530Z --- # [Issue 15]: [Suppress noisy parentIssue GraphQL warnings when sub-issues API is unavailable](https://github.com/vig-os/sync-issues-action/issues/15) @@ -52,3 +52,28 @@ This fires for every batch of issues fetched. The `parentIssue` and `subIssues` ## Context Observed in the `integration-test.yml` workflow on PR #14. +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 23, 2026 at 08:50 AM_ + +## Root cause found + +The GraphQL warnings were **not** caused by API unavailability or plan restrictions. The query used the wrong field name: + +- **Wrong:** `parentIssue { number }` (doesn't exist in the schema) +- **Correct:** `parent { number }` (standard field, no preview header needed) + +Schema introspection confirms `parent`, `subIssues`, and `subIssuesSummary` are available in the standard GraphQL schema on `github.com`. The `GraphQL-Features: sub_issues` preview header was also unnecessary. + +## Changes on `bugfix/15-suppress-sub-issues-warnings` + +1. **Fixed the field name** — `parentIssue` → `parent` in the GraphQL query +2. **Removed `GraphQL-Features: sub_issues` header** — not needed for standard schema fields +3. **Added `sync-sub-issues` opt-in input** (default `true`) — allows users to disable sub-issue fetching if their environment (e.g. older GHES) doesn't support these fields +4. **Graceful schema error handling** — if the fields genuinely don't exist, emits `core.info()` instead of `core.warning()` and continues without relationships +5. **Integration test** — verifies parent/children frontmatter on issues 13 and 15 + +The REST sub-issues API (`/issues/{number}/parent`, `/issues/{number}/sub_issues`) also works and was considered as a fallback, but the GraphQL fix makes it unnecessary since it preserves the efficient batched queries (50 issues per request). + diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md index bcec914..43a7e73 100644 --- a/docs/pull-requests/pr-14.md +++ b/docs/pull-requests/pr-14.md @@ -3,7 +3,7 @@ type: pull_request state: open branch: feature/13-ci-cd-workflows → dev created: 2026-02-20T16:41:37Z -updated: 2026-02-22T10:58:02Z +updated: 2026-02-23T08:59:40Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/pull/14 @@ -13,7 +13,7 @@ assignees: c-vigo milestone: none projects: none relationship: none -synced: 2026-02-22T11:01:27.627Z +synced: 2026-02-23T09:00:17.839Z --- # [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: develop and validate CI/CD workflows diff --git a/docs/pull-requests/pr-16.md b/docs/pull-requests/pr-16.md new file mode 100644 index 0000000..c0fc831 --- /dev/null +++ b/docs/pull-requests/pr-16.md @@ -0,0 +1,61 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/15-suppress-sub-issues-warnings → feature/13-ci-cd-workflows +created: 2026-02-23T08:58:53Z +updated: 2026-02-23T08:59:45Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/16 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T08:59:38Z +synced: 2026-02-23T09:00:16.924Z +--- + +# [PR 16](https://github.com/vig-os/sync-issues-action/pull/16) fix: suppress sub-issues GraphQL warnings and fix field name + +## Summary + +- Fix root cause: GraphQL query used `parentIssue` (doesn't exist) instead of `parent` (correct field name). Remove unnecessary `GraphQL-Features: sub_issues` preview header. +- Add `sync-sub-issues` action input (default `true`) so users can disable sub-issue fetching on environments where the fields are unavailable (e.g. older GHES). +- Graceful schema error handling: emit `core.info()` instead of `core.warning()` when the sub-issues fields don't exist, and continue without relationships. +- Integration test: verify parent/children frontmatter on issues 13 and 15 using this repo's own data. + +## Test plan + +- [x] Unit tests pass (93/93) +- [ ] Integration test in CI validates `issue-15.md` has `parent: 13` and `issue-13.md` lists `15` as a child + +Refs: #15 + + +--- +--- + +## Commits + +### Commit 1: [336cc03](https://github.com/vig-os/sync-issues-action/commit/336cc035cea231679c46b7a4376eaf4b33149e36) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:33 AM +test: add failing tests for sync-sub-issues opt-in and schema error handling, 121 files modified (src/__tests__/unit/index.test.ts) + +### Commit 2: [6af39fa](https://github.com/vig-os/sync-issues-action/commit/6af39fa9086d7ff413315e4fbcb8498f9f5ad2ad) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:34 AM +fix: add sync-sub-issues opt-in input and graceful schema error handling, 27 files modified (action.yml, src/index.ts) + +### Commit 3: [2592992](https://github.com/vig-os/sync-issues-action/commit/2592992b96fdfb0360ff9709b09f2750741018a6) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:35 AM +build(dist): rebuild bundle for sync-sub-issues changes, 137 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 4: [bc05b4b](https://github.com/vig-os/sync-issues-action/commit/bc05b4b55fa4a269fb24c4fdb830e862bec4db99) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:47 AM +test: update mocks for correct GraphQL field name and default behavior, 21 files modified (src/__tests__/unit/index.test.ts) + +### Commit 5: [1654f0b](https://github.com/vig-os/sync-issues-action/commit/1654f0b1971559e23a0a984a097b0c46f4361e14) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:48 AM +fix: use correct GraphQL field name `parent` instead of `parentIssue`, 11 files modified (action.yml, src/index.ts) + +### Commit 6: [8190449](https://github.com/vig-os/sync-issues-action/commit/81904498d9f588bca21951e68558553e27ebd54b) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:49 AM +ci: add sub-issue relationships integration test, 56 files modified (.github/workflows/integration-test.yml) + +### Commit 7: [d9d9b14](https://github.com/vig-os/sync-issues-action/commit/d9d9b142dc57c2d622fee4aa7648e7d75c687701) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:50 AM +build(dist): rebuild bundle with corrected GraphQL field name, 35 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) From 26b22fbe332a59459f40c011dc2da114a4a17b23 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:42:21 +0000 Subject: [PATCH 10/24] chore: sync issues and PRs --- docs/issues/issue-17.md | 53 +++++++++++++++++++++++++++++++++++++ docs/pull-requests/pr-14.md | 4 +-- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 docs/issues/issue-17.md diff --git a/docs/issues/issue-17.md b/docs/issues/issue-17.md new file mode 100644 index 0000000..472f3f8 --- /dev/null +++ b/docs/issues/issue-17.md @@ -0,0 +1,53 @@ +--- +type: issue +state: open +created: 2026-02-23T09:42:03Z +updated: 2026-02-23T09:42:03Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/17 +comments: 0 +labels: feature +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T09:42:18.470Z +--- + +# [Issue 17]: [[FEATURE] Support user-configurable formatting hook for generated markdown](https://github.com/vig-os/sync-issues-action/issues/17) + +### Description + +The action writes markdown files (issues and PRs) directly to disk and reports them via the `modified-files` output. These files are then committed by a downstream step. There is currently no mechanism for users to run formatting or linting tools (e.g. pymarkdown, prettier, end-of-file-fixer) on the generated files before they are committed. + +This causes problems in repos that enforce formatting via pre-commit or CI lint checks — the auto-committed files regularly introduce trailing whitespace, missing trailing newlines, heading-level violations, and typos that then break unrelated PRs. + +### Problem Statement + +Consumers of the action have no clean integration point for formatting. The workaround is adding manual workflow steps between the sync and commit steps, but this is boilerplate-heavy and easy to get wrong. Repos with strict pre-commit configs (pymarkdown, trailing-whitespace, end-of-file-fixer, typos) see repeated CI failures on synced docs. + +### Proposed Solution + +Provide a way for users to run their own formatting/linting tools on the generated files, integrated into the action's lifecycle. Several approaches are worth considering: + +1. **`format-command` input** — A new action input that accepts a shell command. The action executes it after writing files but before setting outputs. A placeholder like `{files}` is replaced with the modified file paths. Simple, composable, and tool-agnostic. + +2. **Hook script convention** — The action checks for a script at a well-known path (e.g. `.github/sync-issues/format.sh`) in the consumer's repo and executes it with the modified file paths as arguments. Config lives in the repo, not the workflow. + +3. **Pre-commit integration** — A boolean input (e.g. `run-pre-commit: true`) that runs `pre-commit run --files ` after writing. Leverages existing pre-commit infrastructure but requires pre-commit to be installed and may run unwanted hooks. + +4. **Workflow-level documentation** — Document the pattern of adding a formatting step between sync and commit in the example workflow. Zero code changes, but more boilerplate for consumers. + +The key architectural constraint: formatting must run **after** files are written to disk but **before** the commit step picks them up. + +### Alternatives Considered + +- **Built-in formatter (bundle pymarkdown/prettier)**: Opinionated, bloats the action, hard to customize. pymarkdown is Python and can't be bundled into a Node action. Not recommended. +- **Exclude synced dirs from pre-commit**: Hides real formatting issues and doesn't fix them. Already used as a workaround in some repos. + +### Additional Context + +- Upstream issue: [vig-os/devcontainer#69](https://github.com/vig-os/devcontainer/issues/69) — documents the repeated CI failures caused by unformatted synced files. +- The action already outputs `modified-files` (comma-separated paths), which is the natural input for any formatting step. +- The action is Node.js-based (`node20`), so `child_process.execSync` is available for running external commands. diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md index 43a7e73..e525fe8 100644 --- a/docs/pull-requests/pr-14.md +++ b/docs/pull-requests/pr-14.md @@ -3,7 +3,7 @@ type: pull_request state: open branch: feature/13-ci-cd-workflows → dev created: 2026-02-20T16:41:37Z -updated: 2026-02-23T08:59:40Z +updated: 2026-02-23T09:36:30Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/pull/14 @@ -13,7 +13,7 @@ assignees: c-vigo milestone: none projects: none relationship: none -synced: 2026-02-23T09:00:17.839Z +synced: 2026-02-23T09:42:19.686Z --- # [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: develop and validate CI/CD workflows From 5e28af124571c7450f93cb9fc7757f69ab820b3a Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:06:08 +0000 Subject: [PATCH 11/24] chore: sync issues and PRs --- docs/issues/issue-13.md | 6 +- docs/pull-requests/pr-14.md | 160 +++++++++++++++++++++++++++++++++--- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md index 6cf187a..2252030 100644 --- a/docs/issues/issue-13.md +++ b/docs/issues/issue-13.md @@ -1,8 +1,8 @@ --- type: issue -state: open +state: closed created: 2026-02-20T13:57:05Z -updated: 2026-02-20T14:31:51Z +updated: 2026-02-23T10:05:50Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/13 @@ -12,7 +12,7 @@ assignees: none milestone: none projects: none relationship: none -synced: 2026-02-21T01:29:10.591Z +synced: 2026-02-23T10:06:05.022Z --- # [Issue 13]: [[FEATURE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md index e525fe8..ecc665d 100644 --- a/docs/pull-requests/pr-14.md +++ b/docs/pull-requests/pr-14.md @@ -1,9 +1,9 @@ --- type: pull_request -state: open +state: closed (merged) branch: feature/13-ci-cd-workflows → dev created: 2026-02-20T16:41:37Z -updated: 2026-02-23T09:36:30Z +updated: 2026-02-23T10:05:25Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/pull/14 @@ -13,23 +13,24 @@ assignees: c-vigo milestone: none projects: none relationship: none -synced: 2026-02-23T09:42:19.686Z +merged: 2026-02-23T10:05:15Z +synced: 2026-02-23T10:06:06.666Z --- -# [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: develop and validate CI/CD workflows +# [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: implement CI/CD pipeline, release process, and repo tooling ## Description -Implement the full CI/CD pipeline for the project: continuous integration checks on pull requests, a three-phase release process (prepare → release → post-release), security scanning, integration testing, and supporting composite actions and tooling. +Implement the full CI/CD pipeline for the project: continuous integration checks, a three-phase release process (prepare → release → post-release), security scanning, integration testing, composite actions, and supporting tooling. Also includes a merged bugfix for sub-issue sync (#15/#16). ## Type of Change - [ ] `feat` -- New feature - [ ] `fix` -- Bug fix - [ ] `docs` -- Documentation only -- [x] `chore` -- Maintenance task (deps, config, etc.) +- [ ] `chore` -- Maintenance task (deps, config, etc.) - [ ] `refactor` -- Code restructuring (no behavior change) -- [x] `test` -- Adding or updating tests +- [ ] `test` -- Adding or updating tests - [x] `ci` -- CI/CD pipeline changes - [x] `build` -- Build system or dependency changes - [ ] `revert` -- Reverts a previous commit @@ -58,9 +59,10 @@ Implement the full CI/CD pipeline for the project: continuous integration checks - CodeQL static analysis on PRs and pushes (`codeql.yml`) - OpenSSF Scorecard supply-chain security analysis uploading SARIF to the Security tab (`scorecard.yml`) -### Integration Testing +### Integration Testing (`integration-test.yml`) -- Reusable `integration-test.yml` workflow called by CI and release, running 4 scenarios: issues-only, PRs-only, force-update, and include-closed +- Reusable workflow called by CI and release +- 8 parallel scenarios: no-op baseline, issues-only, PRs-only, force-update, include-closed, sub-issues, updated-since/state-file, and default-mode ### Composite Actions @@ -79,13 +81,25 @@ Implement the full CI/CD pipeline for the project: continuous integration checks - `CODEOWNERS` file for automated review assignment - Overhauled issue templates: `bug`, `chore`, `discussion`, `docs`, `feature`, `refactor` (removed generic `task`) - Updated PR template with conventional-commit type checkboxes, changelog entry section, and refs footer -- Pre-commit config: removed Python checks, switched `uv run` to `uvx` for pre-commit invocation +- Pre-commit config: removed Python checks, switched to `uvx` for pre-commit invocation - Removed source maps from `dist/` to fix CI dist staleness checks - `sync-issues.yml` updated to use local action (`uses: ./`) and GitHub App token for cache deletion +### Sub-Issues Bugfix (merged from #16, Refs: #15) + +- Added `sync-sub-issues` opt-in action input (default: `true`) +- Fixed GraphQL field name: `parent` instead of `parentIssue` +- Graceful schema error handling: emits info message and falls back to `relationship: none` if the sub-issues API is unavailable +- Updated mocks and tests for correct field name and default behavior + +### Documentation + +- CHANGELOG updated with all Added/Changed/Fixed/Security entries for the Unreleased version +- README updated with `force-update` and `sync-sub-issues` inputs in the options table + ## Changelog Entry -No changelog needed — all changes are CI/CD pipeline internals, repo configuration, and contributor tooling with no user-visible behavior change to the action itself. +See `## Unreleased` in `CHANGELOG.md` — all user-visible changes (sub-issues sync, force-update, CI/CD pipeline, security workflows) are documented there. ## Testing @@ -97,6 +111,7 @@ No changelog needed — all changes are CI/CD pipeline internals, repo configura - Workflows validated via pre-commit YAML linting (`yamllint`), action-pin checks, and shellcheck - `prepare_changelog.py` unit tests: `python -m pytest .github/tests/test_prepare_changelog.py` - `release_helpers.sh` unit tests: `bash .github/tests/test_release_helpers.sh` +- Integration test suite validated via CI (8 parallel scenario jobs) - Full end-to-end workflow testing requires a push to the remote and observing GitHub Actions runs ## Checklist @@ -120,3 +135,126 @@ The release process uses a GitHub App token (`APP_ID` / `APP_PRIVATE_KEY` secret `prepare_changelog.py` is a temporary in-repo tool that will eventually be extracted into a standalone GitHub Action or pip package. Refs: #13 + + +--- +--- + +## Commits + +### Commit 1: [8ed0182](https://github.com/vig-os/sync-issues-action/commit/8ed01823cce1aa963a472dbaefb5f99a493c9008) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:21 PM +chore: update pre-commit hooks and pymarkdown config, 129 files modified (.pre-commit-config.yaml, .pymarkdown, .pymarkdown.config.md) + +### Commit 2: [3e09cb5](https://github.com/vig-os/sync-issues-action/commit/3e09cb518b4cffa05ea3079a4c71cc58ea6511a4) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +chore: overhaul issue templates, 427 files modified + +### Commit 3: [9029a5d](https://github.com/vig-os/sync-issues-action/commit/9029a5d1927982f7cda8a4a012215b14c516d9d2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +chore: add CODEOWNERS and update PR template, 66 files modified (.github/CODEOWNERS, .github/pull_request_template.md) + +### Commit 4: [838370e](https://github.com/vig-os/sync-issues-action/commit/838370ee23d0e913de32ee0c5260ecbe91de550e) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add dependabot configuration, 38 files modified (.github/dependabot.yml) + +### Commit 5: [7c02f41](https://github.com/vig-os/sync-issues-action/commit/7c02f410b3e3a181a72c21e686e5192f2c9d1aff) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add setup-env composite action, 49 files modified (.github/actions/setup-env/action.yml) + +### Commit 6: [511127f](https://github.com/vig-os/sync-issues-action/commit/511127fc600914bf376a02e6621f7c2c1c5315ea) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add CI workflow, 152 files modified (.github/workflows/ci.yml) + +### Commit 7: [ae6a55f](https://github.com/vig-os/sync-issues-action/commit/ae6a55f4e1809e447504cd1d8237970c441efbdd) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +ci: add CodeQL and Scorecard security analysis workflows, 115 files modified (.github/workflows/codeql.yml, .github/workflows/scorecard.yml) + +### Commit 8: [c605dfc](https://github.com/vig-os/sync-issues-action/commit/c605dfcf425fde70b379c1ff4f6d3258500dffc2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +ci: add release workflow, 478 files modified (.github/workflows/release.yml) + +### Commit 9: [70f375c](https://github.com/vig-os/sync-issues-action/commit/70f375cb09083051387c4cd3c00c39def266dcd7) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +fix(ci): use GitHub App token for cache deletion in sync-issues, 2 files modified (.github/workflows/sync-issues.yml) + +### Commit 10: [8cc3446](https://github.com/vig-os/sync-issues-action/commit/8cc3446d064b07ad6ae728b93b512db875d4ec78) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:27 PM +chore: remove Python checks from pre-commit config, 9 files modified (.pre-commit-config.yaml) + +### Commit 11: [7354165](https://github.com/vig-os/sync-issues-action/commit/7354165281bf6d557e8db3240bc1a9b98baf9510) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:49 PM +ci: add optional uv installation to setup-env action, 22 files modified (.github/actions/setup-env/action.yml, .github/workflows/ci.yml) + +### Commit 12: [5be6e8c](https://github.com/vig-os/sync-issues-action/commit/5be6e8c380338a82e3b29dde0549b3e1ef05eb18) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:26 PM +ci: add build-dist action and build job to CI and release, 101 files modified (.github/actions/build-dist/action.yml, .github/workflows/ci.yml, .github/workflows/release.yml) + +### Commit 13: [222f104](https://github.com/vig-os/sync-issues-action/commit/222f104fb01119d06e22ef0272ec099469dbe646) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:26 PM +ci: pin Node.js version via .nvmrc as single source of truth, 13 files modified (.devcontainer/scripts/post-create.sh, .github/actions/setup-env/action.yml, .nvmrc) + +### Commit 14: [293e7b4](https://github.com/vig-os/sync-issues-action/commit/293e7b4a866f339982ebe3502207d4108a2d5c3f) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:37 PM +build: rebuild bundle dist, 20 files modified (dist/index.js, dist/index.js.map, dist/src/index.d.ts.map) + +### Commit 15: [c8eab8b](https://github.com/vig-os/sync-issues-action/commit/c8eab8bcf99a53f531be1c98bbe511ce198eb8a2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 07:18 PM +build(dist): remove source maps to fix CI dist staleness, 695 files modified + +### Commit 16: [c7f0e5d](https://github.com/vig-os/sync-issues-action/commit/c7f0e5daed492140b99ea96ad01d0784d4122788) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 08:59 PM +fix(ci): replace uv run with uvx for pre-commit invocation, 12 files modified (.github/workflows/ci.yml, .pre-commit-config.yaml) + +### Commit 17: [382264a](https://github.com/vig-os/sync-issues-action/commit/382264a045ae78c1bee0807e96efbcb94cdcee1f) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 09:01 PM +feat( ci): use local action (uses: ./) in sync-issues workflow, 3 files modified (.github/workflows/sync-issues.yml) + +### Commit 18: [5b69195](https://github.com/vig-os/sync-issues-action/commit/5b691956e23978cf94248b5164a36e73fb457def) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +test(ci): add prepare_changelog unit tests, 122 files modified (.github/tests/test_prepare_changelog.py) + +### Commit 19: [ebb5de7](https://github.com/vig-os/sync-issues-action/commit/ebb5de7912b8d912b4d24b1019684b1d6c42bc8e) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +ci: add CHANGELOG management tool for release workflow, 416 files modified (.github/prepare_changelog.py) + +### Commit 20: [ff0fafc](https://github.com/vig-os/sync-issues-action/commit/ff0fafcfe4399915e7e302e56ecf1ac33c35511e) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +ci: add integration-test reusable workflow and wire into CI, 215 files modified (.github/workflows/ci.yml, .github/workflows/integration-test.yml) + +### Commit 21: [d18ec8a](https://github.com/vig-os/sync-issues-action/commit/d18ec8a9f1eb8cb84a72baebf4e74afc02f9c412) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: add prepare-release workflow (Phase 1), 292 files modified (.github/workflows/prepare-release.yml) + +### Commit 22: [76fa183](https://github.com/vig-os/sync-issues-action/commit/76fa183b1b51c8316b00b13d8ef3e26fc43a1f45) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: overhaul release workflow with v-prefix tags, app tokens, and provenance, 276 files modified (.github/workflows/release.yml) + +### Commit 23: [d4a6cc4](https://github.com/vig-os/sync-issues-action/commit/d4a6cc4f670216fdf308971a42dbe60458c1459b) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: add post-release workflow (Phase 3), 133 files modified (.github/workflows/post-release.yml) + +### Commit 24: [fae558a](https://github.com/vig-os/sync-issues-action/commit/fae558a99b70dc5e1178616fbc280453bfe384f1) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:44 AM +test(ci): add release helper functions with tests, 252 files modified (.github/prepare_changelog.py, .github/release_helpers.sh, .github/tests/test_prepare_changelog.py, .github/tests/test_release_helpers.sh) + +### Commit 25: [84b1812](https://github.com/vig-os/sync-issues-action/commit/84b1812e34dffa4852da6b1174631a42d7d38d13) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:44 AM +ci: enhance release workflow dry-run with deep preview, 122 files modified (.github/workflows/prepare-release.yml, .github/workflows/release.yml) + +### Commit 26: [fcbd2f3](https://github.com/vig-os/sync-issues-action/commit/fcbd2f31d571055585692f5b10a65c2a4fbb6f34) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:56 AM +fix(ci): add missing permissions for reusable workflow in CI, 2 files modified (.github/workflows/ci.yml) + +### Commit 27: [336cc03](https://github.com/vig-os/sync-issues-action/commit/336cc035cea231679c46b7a4376eaf4b33149e36) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:33 AM +test: add failing tests for sync-sub-issues opt-in and schema error handling, 121 files modified (src/__tests__/unit/index.test.ts) + +### Commit 28: [6af39fa](https://github.com/vig-os/sync-issues-action/commit/6af39fa9086d7ff413315e4fbcb8498f9f5ad2ad) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:34 AM +fix: add sync-sub-issues opt-in input and graceful schema error handling, 27 files modified (action.yml, src/index.ts) + +### Commit 29: [2592992](https://github.com/vig-os/sync-issues-action/commit/2592992b96fdfb0360ff9709b09f2750741018a6) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:35 AM +build(dist): rebuild bundle for sync-sub-issues changes, 137 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 30: [bc05b4b](https://github.com/vig-os/sync-issues-action/commit/bc05b4b55fa4a269fb24c4fdb830e862bec4db99) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:47 AM +test: update mocks for correct GraphQL field name and default behavior, 21 files modified (src/__tests__/unit/index.test.ts) + +### Commit 31: [1654f0b](https://github.com/vig-os/sync-issues-action/commit/1654f0b1971559e23a0a984a097b0c46f4361e14) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:48 AM +fix: use correct GraphQL field name `parent` instead of `parentIssue`, 11 files modified (action.yml, src/index.ts) + +### Commit 32: [8190449](https://github.com/vig-os/sync-issues-action/commit/81904498d9f588bca21951e68558553e27ebd54b) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:49 AM +ci: add sub-issue relationships integration test, 56 files modified (.github/workflows/integration-test.yml) + +### Commit 33: [d9d9b14](https://github.com/vig-os/sync-issues-action/commit/d9d9b142dc57c2d622fee4aa7648e7d75c687701) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:50 AM +build(dist): rebuild bundle with corrected GraphQL field name, 35 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 34: [77f28d7](https://github.com/vig-os/sync-issues-action/commit/77f28d7d2d94b1a34cb07170ad47332431946da2) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:59 AM +Merge pull request #16 from vig-os/bugfix/15-suppress-sub-issues-warnings, 382 files modified (.github/workflows/integration-test.yml, action.yml, dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js, src/__tests__/unit/index.test.ts, src/index.ts) + +### Commit 35: [caaf1b2](https://github.com/vig-os/sync-issues-action/commit/caaf1b2e531260803c726eb99d33188f88234219) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:15 AM +ci: add no-op baseline integration test, 30 files modified (.github/workflows/integration-test.yml) + +### Commit 36: [8bb9c51](https://github.com/vig-os/sync-issues-action/commit/8bb9c51582f40be56b36bc6e90c5eeed53492bc1) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:18 AM +ci: split integration tests into parallel jobs, 124 files modified (.github/workflows/integration-test.yml) + +### Commit 37: [9c789ad](https://github.com/vig-os/sync-issues-action/commit/9c789adf900ca98b11e6d091283bdc85f603027d) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:31 AM +test: harden integration tests and add missing feature coverage, 258 files modified (.github/workflows/integration-test.yml) + +### Commit 38: [ef0338e](https://github.com/vig-os/sync-issues-action/commit/ef0338e0614f2d0564fd93134aff713b17424df3) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:36 AM +fix(ci): include closed issues in sub-issues tests, 4 files modified (.github/workflows/integration-test.yml) + +### Commit 39: [a727430](https://github.com/vig-os/sync-issues-action/commit/a7274307aab7875d9bbb4e11093e8277fd02a593) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:55 AM +docs: update CHANGELOG and README, 30 files modified (CHANGELOG.md, README.md) From 970929d3ad88f78fff200d4846314cf2da52e940 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:22:33 +0000 Subject: [PATCH 12/24] chore: sync issues and PRs --- docs/issues/issue-18.md | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/issues/issue-18.md diff --git a/docs/issues/issue-18.md b/docs/issues/issue-18.md new file mode 100644 index 0000000..5b82ebe --- /dev/null +++ b/docs/issues/issue-18.md @@ -0,0 +1,101 @@ +--- +type: issue +state: open +created: 2026-02-23T10:22:12Z +updated: 2026-02-23T10:22:12Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/18 +comments: 0 +labels: bug, area:ci +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T10:22:29.769Z +--- + +# [Issue 18]: [[BUG] Prepare-release workflow fails: App token missing PR permissions + CHANGELOG regex truncation](https://github.com/vig-os/sync-issues-action/issues/18) + +## Description + +The prepare-release workflow (`prepare-release.yml`) fails at the "Create draft PR to main" step with: + +``` +pull request create failed: GraphQL: Resource not accessible by integration (createPullRequest) +``` + +**Failed run:** https://github.com/vig-os/sync-issues-action/actions/runs/22301550388/job/64510387061 + +Investigation also revealed a secondary data-loss bug in `prepare_changelog.py` that silently truncates changelog entries containing inline `##` or `###` characters. + +## Steps to Reproduce + +1. Trigger the **Prepare Release** workflow (`workflow_dispatch`) with version `0.2.0` +2. Validate job passes, Prepare job begins +3. Branch creation and commit steps succeed (these only need `contents:write`) +4. "Create draft PR to main" step fails because the App token lacks `pull_requests:write` + +For the CHANGELOG bug: + +1. Have a CHANGELOG entry containing inline heading markers, e.g.: + ``` + - Corrected heading hierarchy: promoted from `##` to `#` + ``` +2. Run `prepare_changelog.py prepare ` +3. The Fixed section is truncated at the first inline `##` + +## Expected Behavior + +1. The workflow should successfully create a draft PR from the release branch to main +2. `prepare_changelog.py` should preserve all CHANGELOG content, treating inline `##`/`###` (within backticks or mid-line) as literal text + +## Actual Behavior + +**Bug 1 — PR creation fails:** +The GitHub App (`APP_SYNC_ISSUES`) token does not have `pull_requests:write` permission. The `gh pr create` GraphQL call returns `Resource not accessible by integration`. + +**Bug 2 — CHANGELOG truncation:** +`extract_unreleased_content()` in `prepare_changelog.py` (line 38) uses regex: +```python +pattern = rf"### {section}\s*\n((?:(?!###|##).)*)" +``` +The `(?!###|##)` lookahead matches `##` at **any character position** (including inline within text), not just at line starts. This truncates the Fixed section from 6 entries down to 1 incomplete entry: +``` +- Corrected heading hierarchy in `formatPRAsMarkdown`: promoted the Comments section header from ` +``` +The remaining 5 Fixed entries and the trailing text are silently dropped. + +## Environment + +- **Runner**: `ubuntu-22.04` (GitHub-hosted) +- **Workflow**: `.github/workflows/prepare-release.yml` +- **Script**: `.github/prepare_changelog.py` +- **gh CLI**: default version on `ubuntu-22.04` + +## Possible Solution + +**Bug 1** — Use `github.token` (which inherits the job-level `pull-requests: write` permission) for the PR creation step instead of the App token: +```yaml +# In "Create draft PR to main" step env: +GH_TOKEN: ${{ github.token }} +``` +Alternative: add `Pull requests: Read & write` to the GitHub App's installation permissions. + +**Bug 2** — Anchor the regex to line starts using `re.MULTILINE | re.DOTALL`: +```python +pattern = rf"^### {section}\s*\n(.*?)(?=^### |^## |\Z)" +match = re.search(pattern, unreleased_text, re.MULTILINE | re.DOTALL) +``` +This only matches `###` or `##` at the start of a line (actual headings), ignoring inline occurrences. + +## Cleanup Required + +The failed run left a `release/0.2.0` branch (with a truncated CHANGELOG commit). It must be deleted before re-running: +```bash +gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DELETE +``` + +## Changelog Category + +Fixed From 8d9b92c1e7b61766127ba5ef7ef4c8e7f743244f Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:35:24 +0000 Subject: [PATCH 13/24] chore: sync issues and PRs --- docs/issues/issue-18.md | 79 +++++++++++++++++++++++++++++++++++-- docs/pull-requests/pr-19.md | 42 ++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 docs/pull-requests/pr-19.md diff --git a/docs/issues/issue-18.md b/docs/issues/issue-18.md index 5b82ebe..0f98af9 100644 --- a/docs/issues/issue-18.md +++ b/docs/issues/issue-18.md @@ -1,18 +1,18 @@ --- type: issue -state: open +state: closed created: 2026-02-23T10:22:12Z -updated: 2026-02-23T10:22:12Z +updated: 2026-02-23T10:35:09Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/18 -comments: 0 +comments: 1 labels: bug, area:ci assignees: none milestone: none projects: none relationship: none -synced: 2026-02-23T10:22:29.769Z +synced: 2026-02-23T10:35:22.331Z --- # [Issue 18]: [[BUG] Prepare-release workflow fails: App token missing PR permissions + CHANGELOG regex truncation](https://github.com/vig-os/sync-issues-action/issues/18) @@ -99,3 +99,74 @@ gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DEL ## Changelog Category Fixed +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 23, 2026 at 10:23 AM_ + +## Implementation Plan + +### Root Cause + +The workflow logs show: + +``` +pull request create failed: GraphQL: Resource not accessible by integration (createPullRequest) +``` + +The GitHub App token (from `APP_SYNC_ISSUES`) is used for the `gh pr create` call, but the App lacks `pull_requests:write` permission. Earlier steps (branch creation, commit) only need `contents:write` and succeed. + +### Fix 1: PR Creation Permissions (primary failure) + +In `.github/workflows/prepare-release.yml`, the "Create draft PR to main" step (line 220) uses `GH_TOKEN: ${{ steps.app-token.outputs.token }}`. Change it to use `github.token`, which inherits the job-level `pull-requests: write` permission. + +```yaml +# Line 223 — change from: +GH_TOKEN: ${{ steps.app-token.outputs.token }} +# to: +GH_TOKEN: ${{ github.token }} +``` + +The App token is still used (correctly) for branch creation and commit steps that need to bypass branch protection. Only the PR creation step changes. + +**Alternative**: Add `Pull requests: Read & write` to the GitHub App's permissions in Settings > GitHub Apps > APP_SYNC_ISSUES > Permissions. This would let the existing code work as-is, but the `github.token` approach is simpler and avoids coupling PR creation to App config. + +### Fix 2: CHANGELOG Regex Truncation Bug (data loss) + +In `.github/prepare_changelog.py` line 38, the `extract_unreleased_content` function uses: + +```python +pattern = rf"### {section}\s*\n((?:(?!###|##).)*)" +``` + +The `(?!###|##)` lookahead checks at **every character position**, so inline `` `##` `` or `` `###` `` inside markdown text is treated as a section boundary. This truncates the `### Fixed` section because the first entry contains: + +``` +promoted the Comments section header from `##` to `#` and individual comment entry headers from `###` to `##` +``` + +The prepared CHANGELOG (committed to `release/0.2.0`) lost 5 of 6 Fixed entries. + +**Fix**: Use a line-anchored pattern with `re.MULTILINE | re.DOTALL`: + +```python +pattern = rf"^### {section}\s*\n(.*?)(?=^### |^## |\Z)" +match = re.search(pattern, unreleased_text, re.MULTILINE | re.DOTALL) +``` + +This only matches `###` or `##` at the **start of a line** (actual heading markers), ignoring inline occurrences within text. + +### Cleanup Before Re-running + +The failed run left a `release/0.2.0` branch on the remote (with a truncated CHANGELOG commit). Must be deleted before re-running since the validate job checks the branch doesn't exist. No PR was created, so no PR cleanup needed. + +```bash +gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DELETE +``` + +### Summary of Changes + +- `.github/workflows/prepare-release.yml` — Use `github.token` for PR creation step +- `.github/prepare_changelog.py` — Fix regex to anchor `##`/`###` matching to line starts + diff --git a/docs/pull-requests/pr-19.md b/docs/pull-requests/pr-19.md new file mode 100644 index 0000000..528caad --- /dev/null +++ b/docs/pull-requests/pr-19.md @@ -0,0 +1,42 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/18-fix-prepare-release → dev +created: 2026-02-23T10:30:21Z +updated: 2026-02-23T10:34:55Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/19 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T10:34:55Z +synced: 2026-02-23T10:35:23.487Z +--- + +# [PR 19](https://github.com/vig-os/sync-issues-action/pull/19) fix(ci): use github.token for PR creation and fix changelog regex truncation + +## Summary + +- Use `github.token` instead of the App token for the "Create draft PR to main" step in `prepare-release.yml`, fixing `Resource not accessible by integration (createPullRequest)` error +- Fix `extract_unreleased_content()` regex in `prepare_changelog.py` that matched `##`/`###` at any character position, silently truncating changelog entries containing inline heading markers (e.g. `` `##` ``) + +## Test plan + +- [x] All 19 existing `test_prepare_changelog.py` tests pass +- [x] Manual verification: `prepare_changelog.py prepare` + `extract-notes` now preserves all 6 Fixed entries (was truncated to 1) +- [ ] Re-run Prepare Release workflow with version `0.2.0` after merge (stale `release/0.2.0` branch already deleted) + +Refs: #18 + + +--- +--- + +## Commits + +### Commit 1: [ffe9e9b](https://github.com/vig-os/sync-issues-action/commit/ffe9e9b9937d691fe989bb56a4b12db13307cd08) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 10:25 AM +fix(ci): use github.token for PR creation and fix changelog regex truncation, 9 files modified (.github/prepare_changelog.py, .github/workflows/prepare-release.yml) From 2e7d717eff12048e499498a59009ad9c29ebf409 Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:43:18 +0000 Subject: [PATCH 14/24] chore: sync issues and PRs --- docs/issues/issue-18.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/issues/issue-18.md b/docs/issues/issue-18.md index 0f98af9..46ab5e8 100644 --- a/docs/issues/issue-18.md +++ b/docs/issues/issue-18.md @@ -1,18 +1,18 @@ --- type: issue -state: closed +state: open created: 2026-02-23T10:22:12Z -updated: 2026-02-23T10:35:09Z +updated: 2026-02-23T10:43:11Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/18 -comments: 1 +comments: 2 labels: bug, area:ci assignees: none milestone: none projects: none relationship: none -synced: 2026-02-23T10:35:22.331Z +synced: 2026-02-23T10:43:16.492Z --- # [Issue 18]: [[BUG] Prepare-release workflow fails: App token missing PR permissions + CHANGELOG regex truncation](https://github.com/vig-os/sync-issues-action/issues/18) @@ -170,3 +170,20 @@ gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DEL - `.github/workflows/prepare-release.yml` — Use `github.token` for PR creation step - `.github/prepare_changelog.py` — Fix regex to anchor `##`/`###` matching to line starts +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on February 23, 2026 at 10:43 AM_ + +## Fix Plan + +**Root Cause:** In `.github/workflows/prepare-release.yml`, the "Create draft PR to main" step (line 223) sets `GH_TOKEN: ${{ github.token }}` — the default `GITHUB_TOKEN` which lacks permission to create pull requests when the repo setting "Allow GitHub Actions to create and approve pull requests" is disabled. + +Every other step in the `prepare` job already uses the GitHub App token correctly: +- "Create release branch via API" → `steps.app-token.outputs.token` +- "Commit release preparation via API" → `steps.app-token.outputs.token` +- **"Create draft PR to main" → `github.token`** (BUG) + +**Fix:** Change line 223 from `GH_TOKEN: ${{ github.token }}` to `GH_TOKEN: ${{ steps.app-token.outputs.token }}`. One-line change, no other files affected. + From 479ccecd7fe1d0ff82f8029f9e7a63fb0173ed6d Mon Sep 17 00:00:00 2001 From: "commit-action-bot[bot]" <248498966+commit-action-bot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:46:48 +0000 Subject: [PATCH 15/24] chore: sync issues and PRs --- docs/issues/issue-18.md | 6 +++--- docs/pull-requests/pr-20.md | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 docs/pull-requests/pr-20.md diff --git a/docs/issues/issue-18.md b/docs/issues/issue-18.md index 46ab5e8..ff296de 100644 --- a/docs/issues/issue-18.md +++ b/docs/issues/issue-18.md @@ -1,8 +1,8 @@ --- type: issue -state: open +state: closed created: 2026-02-23T10:22:12Z -updated: 2026-02-23T10:43:11Z +updated: 2026-02-23T10:46:30Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/18 @@ -12,7 +12,7 @@ assignees: none milestone: none projects: none relationship: none -synced: 2026-02-23T10:43:16.492Z +synced: 2026-02-23T10:46:45.205Z --- # [Issue 18]: [[BUG] Prepare-release workflow fails: App token missing PR permissions + CHANGELOG regex truncation](https://github.com/vig-os/sync-issues-action/issues/18) diff --git a/docs/pull-requests/pr-20.md b/docs/pull-requests/pr-20.md new file mode 100644 index 0000000..dbacc8f --- /dev/null +++ b/docs/pull-requests/pr-20.md @@ -0,0 +1,39 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/18-fix-prepare-release → dev +created: 2026-02-23T10:44:24Z +updated: 2026-02-23T10:46:19Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/20 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T10:46:14Z +synced: 2026-02-23T10:46:46.605Z +--- + +# [PR 20](https://github.com/vig-os/sync-issues-action/pull/20) fix(ci): use app token for PR creation in prepare-release + +## Summary + +- The "Create draft PR to main" step in `prepare-release.yml` was using `github.token` (the default `GITHUB_TOKEN`), which lacks permission to create pull requests when the repo setting is disabled. Switched to the GitHub App token (`steps.app-token.outputs.token`) already generated earlier in the same job. + +## Test plan + +- [ ] Re-run the Prepare Release workflow and confirm the draft PR is created successfully + +Refs: #18 + + +--- +--- + +## Commits + +### Commit 1: [5f88772](https://github.com/vig-os/sync-issues-action/commit/5f88772775fb0e1661582e436459708e7b811bbf) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 10:43 AM +fix(ci): use app token for PR creation in prepare-release, 2 files modified (.github/workflows/prepare-release.yml) From 726e953be86e289d3d46ba556a6dad88aa8f64c3 Mon Sep 17 00:00:00 2001 From: "vig-os-release-app[bot]" <263368078+vig-os-release-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:55:27 +0000 Subject: [PATCH 16/24] chore: prepare release 0.2.0 Prepare CHANGELOG.md structure for version 0.2.0. Release date TBD (set during finalization). --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a7933b..1940924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## Unreleased +## [0.2.0] - TBD ### Added @@ -48,7 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CodeQL scans JavaScript/TypeScript on push and PR - Scorecard publishes results to the Security tab via SARIF - ## [0.1.1](https://github.com/vig-os/sync-issues-action/releases/tag/v0.1.1) - 2025-12-19 ### Fixed From 3eb6acd9f86cf921bafaa5e38f5ad45f23616591 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 14:13:33 +0000 Subject: [PATCH 17/24] chore: merge main into release/0.2.0 Refs: #22 --- .github/workflows/release.yml | 1 + .github/workflows/security-scan.yml | 25 +++ CHANGELOG.md | 3 + docs/issues/issue-10.md | 39 ++++- docs/issues/issue-13.md | 256 +++++++++++++++++++++++++++ docs/issues/issue-15.md | 79 +++++++++ docs/issues/issue-17.md | 53 ++++++ docs/issues/issue-18.md | 189 ++++++++++++++++++++ docs/pull-requests/pr-12.md | 118 +++++++++++++ docs/pull-requests/pr-14.md | 260 ++++++++++++++++++++++++++++ docs/pull-requests/pr-16.md | 61 +++++++ docs/pull-requests/pr-19.md | 42 +++++ docs/pull-requests/pr-20.md | 39 +++++ 13 files changed, 1161 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/security-scan.yml create mode 100644 docs/issues/issue-13.md create mode 100644 docs/issues/issue-15.md create mode 100644 docs/issues/issue-17.md create mode 100644 docs/issues/issue-18.md create mode 100644 docs/pull-requests/pr-12.md create mode 100644 docs/pull-requests/pr-14.md create mode 100644 docs/pull-requests/pr-16.md create mode 100644 docs/pull-requests/pr-19.md create mode 100644 docs/pull-requests/pr-20.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e025f40..f3710e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,7 @@ # Trigger: # - Manual workflow_dispatch with version input # + name: Release on: # yamllint disable-line rule:truthy diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..574a71a --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,25 @@ +# Security Scan Workflow +# +# This workflow placeholder is registered with GitHub to enable security scanning checks. +# It scans for vulnerabilities in dependencies, container images, and code. +# Runs on pull requests and pushes to dev and main branches as a security gate. +# Full implementation details are managed separately. + +name: Security Scan + +"on": + pull_request: # TODO: consider restricting to protected branches (dev, main, release/**) when implementing + push: + branches: + - dev + - main + +permissions: + contents: read + +jobs: + security-scan: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1940924..4110c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CHANGELOG management CLI (`prepare_changelog.py`) for automated release note preparation - Dependabot configuration for automated dependency updates - CODEOWNERS file for automated review assignment + - CodeQL analysis workflow for automated security vulnerability scanning + - Scorecard workflow for ongoing supply-chain security assessments + - Security scan workflow for continuous security monitoring ### Changed diff --git a/docs/issues/issue-10.md b/docs/issues/issue-10.md index 3981681..a01bae1 100644 --- a/docs/issues/issue-10.md +++ b/docs/issues/issue-10.md @@ -1,18 +1,18 @@ --- type: issue -state: open +state: closed created: 2026-02-20T10:58:09Z -updated: 2026-02-20T10:58:09Z +updated: 2026-02-20T14:13:37Z author: c-vigo author_url: https://github.com/c-vigo url: https://github.com/vig-os/sync-issues-action/issues/10 -comments: 0 +comments: 1 labels: none assignees: none milestone: none projects: none relationship: none -synced: 2026-02-20T12:25:11.501Z +synced: 2026-02-20T14:13:51.874Z --- # [Issue 10]: [[BUG] --force-update does not re-sync issues (only PRs)](https://github.com/vig-os/sync-issues-action/issues/10) @@ -57,3 +57,34 @@ Investigate why the `updated-since` parameter is not honored for issue fetching. ## Changelog Category Fixed +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 20, 2026 at 12:43 PM_ + +## Implementation Plan + +Issue: #10 +Branch: bugfix/10-force-update-issues + +### Root Cause + +Both `syncIssuesToMarkdown` (line 250) and `syncPRsToMarkdown` (line 331) in `src/index.ts` call `hasContentChanged` before writing. This function strips frontmatter (including the `synced:` timestamp) via `normalizeContent` and compares the body only. When nothing has changed on GitHub, the body is identical and the write is skipped -- even during a force-update. + +The user observes PRs being re-written because closed PRs gain a new commits section (or other metadata shifts), while issues with no GitHub-side changes remain byte-identical and are skipped. + +The action currently has no way to know the caller intends a force-update; `updated-since` set to epoch controls *which items are fetched* from the API, but not whether `hasContentChanged` is bypassed. + +### Fix + +Add a `force-update` boolean input to the action. When active, skip the `hasContentChanged` gate and always write (which updates the `synced:` frontmatter timestamp, producing a real git diff). + +### Tasks + +- [x] Task 1: Write failing test -- when `force-update` is `'true'` and an issue file already exists with identical body content, the action should still re-write the file — `src/__tests__/unit/index.test.ts` — verify: `npx jest -t "should re-write issue files"` +- [x] Task 2: Write failing test -- same scenario for PRs — `src/__tests__/unit/index.test.ts` — verify: `npx jest -t "should re-write PR files"` +- [x] Task 3: Add `force-update` input (boolean string, default `'false'`) — `action.yml` — verify: input present in file +- [x] Task 4: Read `force-update` input, thread `forceUpdate` flag into `syncIssuesToMarkdown` and `syncPRsToMarkdown`, bypass `hasContentChanged` when true — `src/index.ts` — verify: `npx jest` +- [x] Task 5: Pass `force-update` workflow dispatch input to the action — `.github/workflows/sync-issues.yml` — verify: input present in `with:` block +- [x] Task 6: Run full test suite — verify: `npx jest` (89 passed, 0 failed) diff --git a/docs/issues/issue-13.md b/docs/issues/issue-13.md new file mode 100644 index 0000000..2252030 --- /dev/null +++ b/docs/issues/issue-13.md @@ -0,0 +1,256 @@ +--- +type: issue +state: closed +created: 2026-02-20T13:57:05Z +updated: 2026-02-23T10:05:50Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/13 +comments: 1 +labels: area:ci, feature +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T10:06:05.022Z +--- + +# [Issue 13]: [[FEATURE] Develop and validate CI/CD workflows](https://github.com/vig-os/sync-issues-action/issues/13) + +## Description + +Add fully operational CI/CD workflows to the repository. Four GitHub Actions workflows and a composite setup action exist as templates carried over from the vigOS devcontainer but have not been validated or customized for this project. + +**Workflows:** +| File | Purpose | +|------|---------| +| `.github/workflows/ci.yml` | Lint, test, security scan, dependency review, summary gate | +| `.github/workflows/codeql.yml` | CodeQL static analysis for Python | +| `.github/workflows/scorecard.yml` | OpenSSF Scorecard with SARIF upload | +| `.github/workflows/release.yml` | Release automation (validate → finalize → test → release → rollback) | +| `.github/actions/setup-env/action.yml` | Composite action: Python, uv, optional tooling | + +## Problem Statement + +The repository has no working CI pipeline. PRs can be merged without lint checks, tests, or security scans. The release workflow has never been exercised. Without validated CI, there is no automated quality gate and branch protection cannot be meaningfully configured. + +## Proposed Solution + +Review, customize, and validate each workflow end-to-end: + +1. **`ci.yml`** — Confirm lint, test, security, dependency-review, and summary jobs run successfully on a PR to `dev` +2. **`codeql.yml`** — Verify CodeQL analysis runs on Python files (PRs, pushes to main, weekly schedule) +3. **`scorecard.yml`** — Verify Scorecard runs on push to main and weekly; SARIF uploads to Security tab +4. **`release.yml`** — Complete a dry-run validation successfully +5. **`setup-env`** — Verify composite action installs Python, uv, and syncs project dependencies +6. **All workflows** — Ensure action references are pinned to full SHA commits +7. **Branch protection** — Configure `dev` and `main` to require CI Summary to pass + +**Implementation notes:** +- `ci.yml` depends on the `setup-env` composite action — verify inputs/outputs match this project's needs +- `release.yml` triggers a `sync-issues` workflow mid-run — verify it exists or stub/remove the step +- `release.yml` references `vig-os/commit-action` — confirm the repo has access to this action +- `security` job hard-codes `safety==3.2.11` — verify version compatibility with current deps +- `scorecard.yml` uses `codeql-action/upload-sarif@v3` (SHA `b5ebac6`) while `codeql.yml` uses `codeql-action@v4` (SHA `45cbd0c`) — verify this is intentional or align versions +- Runner is `ubuntu-22.04` across all workflows — decide whether to stay or move to `ubuntu-24.04` + +## Alternatives Considered + +- **Minimal CI (lint + test only):** Faster to set up but leaves security scanning and release automation for later. Rejected because the workflows already exist and just need validation. +- **Third-party CI (CircleCI, etc.):** Would require rewriting all workflows. Not justified since GitHub Actions is already in use. + +## Additional Context + +- Related to #6 +- The `setup-env` action also supports optional tooling (podman, Node.js, devcontainer CLI, BATS, just) that isn't currently used by any workflow but may be needed later. + +## Impact + +- All contributors benefit from automated quality gates on PRs +- Backward compatible — adds CI infrastructure without changing existing code +- Enables branch protection rules that require CI to pass + +## Changelog Category + +Added +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 20, 2026 at 02:31 PM_ + +## Implementation Plan + +**TDD**: Skipped — non-testable changes (config/infrastructure YAML). + +### Current State + +Five workflow/action files exist as templates from the vigOS devcontainer. All action references are already SHA-pinned. The repo is public at `vig-os/sync-issues-action`. External dependencies (`vig-os/commit-action`, `vig-os/sync-issues-action`) are verified accessible. `sync-issues.yml` workflow exists and is referenced by `release.yml`. + +### Branch + +`feature/13-ci-cd-workflows` from `dev` + +--- + +### Changes Required + +#### 1. `scorecard.yml` — Align codeql-action version (v3 → v4) + +`.github/workflows/scorecard.yml` uses `codeql-action/upload-sarif@b5ebac6...` (v3), while `.github/workflows/codeql.yml` uses `codeql-action@45cbd0c...` (v4). Align the scorecard upload-sarif step to v4: + +```yaml +# Before +uses: github/codeql-action/upload-sarif@b5ebac6f4c00c8ccddb7cdcd45fdb248329f808a # v3 +# After +uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4 +``` + +#### 2. `ci.yml` — Update safety version and add API key + +The security job pins `safety==3.2.11`. Update to latest (`3.7.0`) and wire up the `SAFETY_API_KEY` secret for full vulnerability database access: + +- Change `uv pip install safety==3.2.11` to `uv pip install safety==3.7.0` +- Add `SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}` as env to the safety check step + +#### 3. `codeql.yml` — Customize and verify + +**Current state (verified correct):** +- Language matrix: `['python']` +- Triggers: PRs to `dev`/`release/**`/`main` (path `**.py`), push to `main` (path `**.py`), weekly cron `15 2 * * 1` +- Actions SHA-pinned: `codeql-action/init` and `codeql-action/analyze` both at `@45cbd0c...` (v4) +- Permissions scoped: `security-events: write`, `contents: read`, `actions: read` + +**Change needed — update header comment:** + +The header says "Runs GitHub CodeQL analysis on Python scripts used in the build toolchain" — vigOS template comment. This project has actual Python product code in `src/sync_issues_action/`. Update to reflect this project: + +```yaml +# Before +# Runs GitHub CodeQL analysis on Python scripts used in the build toolchain. +# While these are build tools (not product code), static analysis provides +# defense-in-depth for scripts that handle git operations, file I/O, and +# subprocess execution. + +# After +# Runs GitHub CodeQL analysis on the Python codebase. +``` + +**Validation:** CodeQL triggers on PRs only when `**.py` files change. The YAML-only change will not trigger it — accept structural review as sufficient; the workflow will fire on the next PR that touches Python code. + +**Follow-up (out of scope):** Adding `javascript-typescript` to the CodeQL language matrix for the TypeScript source code. + +#### 4. `release.yml` — Customize and review integration points + +**Current state — 5 jobs:** `validate` → `finalize` → `test` → `release` → `rollback` (on failure) + +**External dependencies (all verified accessible):** +- `vig-os/commit-action@b70c2d87...` (v0.1.3) — used in `finalize` job to commit CHANGELOG date via GitHub API. Passes config through `env:` vars (`GH_TOKEN`, `GITHUB_REPOSITORY`, `TARGET_BRANCH`, `COMMIT_MESSAGE`, `FILE_PATHS`), which is the correct interface for this action. +- `sync-issues.yml` workflow — triggered mid-release via `gh workflow run sync-issues.yml -f "target-branch=release/$VERSION"`. The workflow exists and its `workflow_dispatch` accepts a `target-branch` input. The wait loop (120s timeout, 10s interval) is reasonable. + +**Project-specific prerequisites (verified):** +- `CHANGELOG.md` exists and uses the `## [X.Y.Z] - TBD` format +- `setup-env` composite action is used in the `test` job with `sync-dependencies: 'true'` +- Branch pattern `release/X.Y.Z` and base `main` match the project's git workflow + +**Change needed — update header comment:** + +```yaml +# Before +# Default template for Python projects using the vigOS devcontainer. +# After +# Customized for the sync-issues-action project. +``` + +**Key finding — GitHub App token TODO (line 22-24):** + +The workflow has an existing TODO about replacing `GITHUB_TOKEN` with a GitHub App token for branch-protected repos. This becomes relevant when branch protection is configured (task 7). The `vig-os/commit-action` uses the GitHub API (not git push), so it MAY work with `GITHUB_TOKEN` even under branch protection — known risk to test during the dry-run. + +**Dry-run validation:** Deferred — requires a `release/X.Y.Z` branch + approved PR to `main` + CI green. Since this is v0.1.0 with no release branch yet, defer to the first release cycle. + +**No structural changes needed** to the workflow logic. + +#### 5. `setup-env/action.yml` — Add npm dependency support (based on `post-create.sh`) + +Comparing `.github/actions/setup-env/action.yml` against `.devcontainer/scripts/post-create.sh` reveals a gap: the devcontainer installs Node.js **and** npm dependencies, but setup-env only installs Node.js — it has no step to run `npm ci` to install dependencies from `package.json`. + +This matters because the project is a TypeScript GitHub Action. `package.json` defines `npm test` (jest), `npm run build` (tsc), and `npm run package` (ncc). CI cannot run TypeScript tests or build the action without npm dependencies installed. + +**Changes:** + +1. Add `sync-npm-dependencies` input (default `'false'`): +```yaml + sync-npm-dependencies: + description: 'Run npm ci to install Node.js dependencies from package.json (requires Node.js)' + required: false + default: 'false' +``` + +2. Add `npm ci` step after the Node.js install step: +```yaml + - name: Install npm dependencies + if: inputs.sync-npm-dependencies == 'true' + shell: bash + run: npm ci +``` + +3. Auto-trigger Node.js install when `sync-npm-dependencies` is true (consistent with `install-devcontainer-cli` pattern): +```yaml + if: inputs.install-node == 'true' || inputs.install-devcontainer-cli == 'true' || inputs.sync-npm-dependencies == 'true' +``` + +**Not carried over from `post-create.sh` (dev-only tools):** +- `act` (nektos) — local workflow runner, not needed inside GitHub Actions +- `@github/local-action` + `tsx` — action testing tool for local dev + +**Follow-up suggestion:** Open a separate issue to change `--all-extras` to `--extra dev` in setup-env to avoid installing unnecessary science deps in CI. + +#### 6. SHA pin audit + +All actions are already SHA-pinned. Confirm no unpinned references exist by grep-searching all workflow files for `uses:` lines without `@` followed by a 40-char hex SHA. + +#### 7. Branch protection + +After CI is validated on the PR, configure branch protection rules via `gh api`: + +- **`dev` branch**: Require `CI Summary` status check to pass before merge +- **`main` branch**: Require `CI Summary` status check to pass before merge + +Requires repository admin permissions. + +#### 8. Runner version + +Staying on `ubuntu-22.04`. No changes to runner configuration. + +--- + +### Validation Strategy + +1. Push branch and open PR to `dev` +2. Verify CI jobs run: lint, test, security, dependency-review, summary +3. CodeQL: structural review sufficient (path filter `**.py` means YAML-only changes won't trigger it) +4. Scorecard: structural review sufficient (only runs on push to `main` / schedule) +5. Release dry-run: deferred to first release cycle (no release branch exists yet) +6. Branch protection: configure after CI Summary is confirmed green + +### Files Modified + +- `.github/workflows/scorecard.yml` — align codeql-action to v4 +- `.github/workflows/ci.yml` — update safety version + API key +- `.github/workflows/codeql.yml` — update header comment to match project +- `.github/workflows/release.yml` — update header comment to match project +- `.github/actions/setup-env/action.yml` — add `sync-npm-dependencies` input + `npm ci` step + +### Task List + +- [ ] Create `feature/13-ci-cd-workflows` branch from `dev` +- [ ] Align `scorecard.yml` codeql-action/upload-sarif from v3 to v4 SHA +- [ ] Update `ci.yml` safety version to 3.7.0 and add SAFETY_API_KEY env +- [ ] Customize `codeql.yml` header comment for this project +- [ ] Customize `release.yml` header comment and review integration points +- [ ] Add `sync-npm-dependencies` input + `npm ci` step to setup-env action +- [ ] Audit all workflow files for unpinned action references +- [ ] Commit changes (Refs: #13) and push branch +- [ ] Open PR to `dev`, verify CI jobs run and pass +- [ ] Configure branch protection on `dev` and `main` to require CI Summary + diff --git a/docs/issues/issue-15.md b/docs/issues/issue-15.md new file mode 100644 index 0000000..bb6c19d --- /dev/null +++ b/docs/issues/issue-15.md @@ -0,0 +1,79 @@ +--- +type: issue +state: closed +created: 2026-02-22T11:01:08Z +updated: 2026-02-23T09:00:01Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/15 +comments: 1 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T09:00:15.530Z +--- + +# [Issue 15]: [Suppress noisy parentIssue GraphQL warnings when sub-issues API is unavailable](https://github.com/vig-os/sync-issues-action/issues/15) + +## Problem + +The integration test CI logs show repeated warnings: + +``` +##[warning]Failed to fetch sub-issue relationships: Request failed due to following response errors: + - Field 'parentIssue' doesn't exist on type 'Issue' +``` + +This fires for every batch of issues fetched. The `parentIssue` and `subIssues` fields are part of GitHub's **Sub-issues** feature, which is only available on certain plans/repos. When the API doesn't support these fields, the GraphQL query fails and emits a noisy warning. + +**Current behavior:** The error is caught and a warning is logged per batch — no functional impact, sync completes successfully. + +**Desired behavior:** Detect that the sub-issues API is unavailable and skip relationship fetches for the rest of the run, rather than warning on every batch. + +## Relevant code + +`src/index.ts` around line 416 builds the GraphQL query: + +```typescript +`issue_${num}: issue(number: ${num}) { + parentIssue { number } + subIssues(first: 100) { nodes { number } } +}` +``` + +## Suggested approaches + +1. **Attempt once, then skip** — try the relationship query on the first batch; if it fails with a schema error, set a flag and skip all subsequent batches. +2. **Make it opt-in** — add an action input (e.g. `sync-sub-issues: true`) so the query is only made when explicitly requested. +3. **Combine both** — opt-in input + graceful fallback on schema error. + +## Context + +Observed in the `integration-test.yml` workflow on PR #14. +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 23, 2026 at 08:50 AM_ + +## Root cause found + +The GraphQL warnings were **not** caused by API unavailability or plan restrictions. The query used the wrong field name: + +- **Wrong:** `parentIssue { number }` (doesn't exist in the schema) +- **Correct:** `parent { number }` (standard field, no preview header needed) + +Schema introspection confirms `parent`, `subIssues`, and `subIssuesSummary` are available in the standard GraphQL schema on `github.com`. The `GraphQL-Features: sub_issues` preview header was also unnecessary. + +## Changes on `bugfix/15-suppress-sub-issues-warnings` + +1. **Fixed the field name** — `parentIssue` → `parent` in the GraphQL query +2. **Removed `GraphQL-Features: sub_issues` header** — not needed for standard schema fields +3. **Added `sync-sub-issues` opt-in input** (default `true`) — allows users to disable sub-issue fetching if their environment (e.g. older GHES) doesn't support these fields +4. **Graceful schema error handling** — if the fields genuinely don't exist, emits `core.info()` instead of `core.warning()` and continues without relationships +5. **Integration test** — verifies parent/children frontmatter on issues 13 and 15 + +The REST sub-issues API (`/issues/{number}/parent`, `/issues/{number}/sub_issues`) also works and was considered as a fallback, but the GraphQL fix makes it unnecessary since it preserves the efficient batched queries (50 issues per request). + diff --git a/docs/issues/issue-17.md b/docs/issues/issue-17.md new file mode 100644 index 0000000..472f3f8 --- /dev/null +++ b/docs/issues/issue-17.md @@ -0,0 +1,53 @@ +--- +type: issue +state: open +created: 2026-02-23T09:42:03Z +updated: 2026-02-23T09:42:03Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/17 +comments: 0 +labels: feature +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T09:42:18.470Z +--- + +# [Issue 17]: [[FEATURE] Support user-configurable formatting hook for generated markdown](https://github.com/vig-os/sync-issues-action/issues/17) + +### Description + +The action writes markdown files (issues and PRs) directly to disk and reports them via the `modified-files` output. These files are then committed by a downstream step. There is currently no mechanism for users to run formatting or linting tools (e.g. pymarkdown, prettier, end-of-file-fixer) on the generated files before they are committed. + +This causes problems in repos that enforce formatting via pre-commit or CI lint checks — the auto-committed files regularly introduce trailing whitespace, missing trailing newlines, heading-level violations, and typos that then break unrelated PRs. + +### Problem Statement + +Consumers of the action have no clean integration point for formatting. The workaround is adding manual workflow steps between the sync and commit steps, but this is boilerplate-heavy and easy to get wrong. Repos with strict pre-commit configs (pymarkdown, trailing-whitespace, end-of-file-fixer, typos) see repeated CI failures on synced docs. + +### Proposed Solution + +Provide a way for users to run their own formatting/linting tools on the generated files, integrated into the action's lifecycle. Several approaches are worth considering: + +1. **`format-command` input** — A new action input that accepts a shell command. The action executes it after writing files but before setting outputs. A placeholder like `{files}` is replaced with the modified file paths. Simple, composable, and tool-agnostic. + +2. **Hook script convention** — The action checks for a script at a well-known path (e.g. `.github/sync-issues/format.sh`) in the consumer's repo and executes it with the modified file paths as arguments. Config lives in the repo, not the workflow. + +3. **Pre-commit integration** — A boolean input (e.g. `run-pre-commit: true`) that runs `pre-commit run --files ` after writing. Leverages existing pre-commit infrastructure but requires pre-commit to be installed and may run unwanted hooks. + +4. **Workflow-level documentation** — Document the pattern of adding a formatting step between sync and commit in the example workflow. Zero code changes, but more boilerplate for consumers. + +The key architectural constraint: formatting must run **after** files are written to disk but **before** the commit step picks them up. + +### Alternatives Considered + +- **Built-in formatter (bundle pymarkdown/prettier)**: Opinionated, bloats the action, hard to customize. pymarkdown is Python and can't be bundled into a Node action. Not recommended. +- **Exclude synced dirs from pre-commit**: Hides real formatting issues and doesn't fix them. Already used as a workaround in some repos. + +### Additional Context + +- Upstream issue: [vig-os/devcontainer#69](https://github.com/vig-os/devcontainer/issues/69) — documents the repeated CI failures caused by unformatted synced files. +- The action already outputs `modified-files` (comma-separated paths), which is the natural input for any formatting step. +- The action is Node.js-based (`node20`), so `child_process.execSync` is available for running external commands. diff --git a/docs/issues/issue-18.md b/docs/issues/issue-18.md new file mode 100644 index 0000000..ff296de --- /dev/null +++ b/docs/issues/issue-18.md @@ -0,0 +1,189 @@ +--- +type: issue +state: closed +created: 2026-02-23T10:22:12Z +updated: 2026-02-23T10:46:30Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/issues/18 +comments: 2 +labels: bug, area:ci +assignees: none +milestone: none +projects: none +relationship: none +synced: 2026-02-23T10:46:45.205Z +--- + +# [Issue 18]: [[BUG] Prepare-release workflow fails: App token missing PR permissions + CHANGELOG regex truncation](https://github.com/vig-os/sync-issues-action/issues/18) + +## Description + +The prepare-release workflow (`prepare-release.yml`) fails at the "Create draft PR to main" step with: + +``` +pull request create failed: GraphQL: Resource not accessible by integration (createPullRequest) +``` + +**Failed run:** https://github.com/vig-os/sync-issues-action/actions/runs/22301550388/job/64510387061 + +Investigation also revealed a secondary data-loss bug in `prepare_changelog.py` that silently truncates changelog entries containing inline `##` or `###` characters. + +## Steps to Reproduce + +1. Trigger the **Prepare Release** workflow (`workflow_dispatch`) with version `0.2.0` +2. Validate job passes, Prepare job begins +3. Branch creation and commit steps succeed (these only need `contents:write`) +4. "Create draft PR to main" step fails because the App token lacks `pull_requests:write` + +For the CHANGELOG bug: + +1. Have a CHANGELOG entry containing inline heading markers, e.g.: + ``` + - Corrected heading hierarchy: promoted from `##` to `#` + ``` +2. Run `prepare_changelog.py prepare ` +3. The Fixed section is truncated at the first inline `##` + +## Expected Behavior + +1. The workflow should successfully create a draft PR from the release branch to main +2. `prepare_changelog.py` should preserve all CHANGELOG content, treating inline `##`/`###` (within backticks or mid-line) as literal text + +## Actual Behavior + +**Bug 1 — PR creation fails:** +The GitHub App (`APP_SYNC_ISSUES`) token does not have `pull_requests:write` permission. The `gh pr create` GraphQL call returns `Resource not accessible by integration`. + +**Bug 2 — CHANGELOG truncation:** +`extract_unreleased_content()` in `prepare_changelog.py` (line 38) uses regex: +```python +pattern = rf"### {section}\s*\n((?:(?!###|##).)*)" +``` +The `(?!###|##)` lookahead matches `##` at **any character position** (including inline within text), not just at line starts. This truncates the Fixed section from 6 entries down to 1 incomplete entry: +``` +- Corrected heading hierarchy in `formatPRAsMarkdown`: promoted the Comments section header from ` +``` +The remaining 5 Fixed entries and the trailing text are silently dropped. + +## Environment + +- **Runner**: `ubuntu-22.04` (GitHub-hosted) +- **Workflow**: `.github/workflows/prepare-release.yml` +- **Script**: `.github/prepare_changelog.py` +- **gh CLI**: default version on `ubuntu-22.04` + +## Possible Solution + +**Bug 1** — Use `github.token` (which inherits the job-level `pull-requests: write` permission) for the PR creation step instead of the App token: +```yaml +# In "Create draft PR to main" step env: +GH_TOKEN: ${{ github.token }} +``` +Alternative: add `Pull requests: Read & write` to the GitHub App's installation permissions. + +**Bug 2** — Anchor the regex to line starts using `re.MULTILINE | re.DOTALL`: +```python +pattern = rf"^### {section}\s*\n(.*?)(?=^### |^## |\Z)" +match = re.search(pattern, unreleased_text, re.MULTILINE | re.DOTALL) +``` +This only matches `###` or `##` at the start of a line (actual headings), ignoring inline occurrences. + +## Cleanup Required + +The failed run left a `release/0.2.0` branch (with a truncated CHANGELOG commit). It must be deleted before re-running: +```bash +gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DELETE +``` + +## Changelog Category + +Fixed +--- + +# [Comment #1]() by [c-vigo]() + +_Posted on February 23, 2026 at 10:23 AM_ + +## Implementation Plan + +### Root Cause + +The workflow logs show: + +``` +pull request create failed: GraphQL: Resource not accessible by integration (createPullRequest) +``` + +The GitHub App token (from `APP_SYNC_ISSUES`) is used for the `gh pr create` call, but the App lacks `pull_requests:write` permission. Earlier steps (branch creation, commit) only need `contents:write` and succeed. + +### Fix 1: PR Creation Permissions (primary failure) + +In `.github/workflows/prepare-release.yml`, the "Create draft PR to main" step (line 220) uses `GH_TOKEN: ${{ steps.app-token.outputs.token }}`. Change it to use `github.token`, which inherits the job-level `pull-requests: write` permission. + +```yaml +# Line 223 — change from: +GH_TOKEN: ${{ steps.app-token.outputs.token }} +# to: +GH_TOKEN: ${{ github.token }} +``` + +The App token is still used (correctly) for branch creation and commit steps that need to bypass branch protection. Only the PR creation step changes. + +**Alternative**: Add `Pull requests: Read & write` to the GitHub App's permissions in Settings > GitHub Apps > APP_SYNC_ISSUES > Permissions. This would let the existing code work as-is, but the `github.token` approach is simpler and avoids coupling PR creation to App config. + +### Fix 2: CHANGELOG Regex Truncation Bug (data loss) + +In `.github/prepare_changelog.py` line 38, the `extract_unreleased_content` function uses: + +```python +pattern = rf"### {section}\s*\n((?:(?!###|##).)*)" +``` + +The `(?!###|##)` lookahead checks at **every character position**, so inline `` `##` `` or `` `###` `` inside markdown text is treated as a section boundary. This truncates the `### Fixed` section because the first entry contains: + +``` +promoted the Comments section header from `##` to `#` and individual comment entry headers from `###` to `##` +``` + +The prepared CHANGELOG (committed to `release/0.2.0`) lost 5 of 6 Fixed entries. + +**Fix**: Use a line-anchored pattern with `re.MULTILINE | re.DOTALL`: + +```python +pattern = rf"^### {section}\s*\n(.*?)(?=^### |^## |\Z)" +match = re.search(pattern, unreleased_text, re.MULTILINE | re.DOTALL) +``` + +This only matches `###` or `##` at the **start of a line** (actual heading markers), ignoring inline occurrences within text. + +### Cleanup Before Re-running + +The failed run left a `release/0.2.0` branch on the remote (with a truncated CHANGELOG commit). Must be deleted before re-running since the validate job checks the branch doesn't exist. No PR was created, so no PR cleanup needed. + +```bash +gh api repos/vig-os/sync-issues-action/git/refs/heads/release/0.2.0 --method DELETE +``` + +### Summary of Changes + +- `.github/workflows/prepare-release.yml` — Use `github.token` for PR creation step +- `.github/prepare_changelog.py` — Fix regex to anchor `##`/`###` matching to line starts + +--- + +# [Comment #2]() by [c-vigo]() + +_Posted on February 23, 2026 at 10:43 AM_ + +## Fix Plan + +**Root Cause:** In `.github/workflows/prepare-release.yml`, the "Create draft PR to main" step (line 223) sets `GH_TOKEN: ${{ github.token }}` — the default `GITHUB_TOKEN` which lacks permission to create pull requests when the repo setting "Allow GitHub Actions to create and approve pull requests" is disabled. + +Every other step in the `prepare` job already uses the GitHub App token correctly: +- "Create release branch via API" → `steps.app-token.outputs.token` +- "Commit release preparation via API" → `steps.app-token.outputs.token` +- **"Create draft PR to main" → `github.token`** (BUG) + +**Fix:** Change line 223 from `GH_TOKEN: ${{ github.token }}` to `GH_TOKEN: ${{ steps.app-token.outputs.token }}`. One-line change, no other files affected. + diff --git a/docs/pull-requests/pr-12.md b/docs/pull-requests/pr-12.md new file mode 100644 index 0000000..aeb8d9b --- /dev/null +++ b/docs/pull-requests/pr-12.md @@ -0,0 +1,118 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/10-force-update-issues → dev +created: 2026-02-20T13:35:39Z +updated: 2026-02-20T13:51:16Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/12 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +relationship: none +merged: 2026-02-20T13:51:00Z +synced: 2026-02-20T13:57:24.996Z +--- + +# [PR 12](https://github.com/vig-os/sync-issues-action/pull/12) fix: force-update does not re-sync issues (#10) + +## Description + +When triggering a workflow with `force-update: true`, only PRs were re-synced while issues were silently skipped. The root cause is that `hasContentChanged` strips frontmatter (including the `synced:` timestamp) and compares body content only -- when nothing has changed on GitHub, the body is identical and the write is skipped, even during a force-update. The action had no mechanism to bypass this content-comparison gate. + +This PR adds a `force-update` boolean input to the action. When active, both `syncIssuesToMarkdown` and `syncPRsToMarkdown` skip the `hasContentChanged` check and always write files (updating the `synced:` frontmatter timestamp, which produces a real git diff). + +## Related Issue(s) + +Closes #10 + +## Type of Change + +- [x] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [x] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] CI / Build change +- [x] Test updates + +## Changes Made + +- `action.yml` -- added `force-update` input (boolean string, default `'false'`) +- `src/index.ts` -- read the new input, thread a `forceUpdate` boolean into `syncIssuesToMarkdown` and `syncPRsToMarkdown`, short-circuit with `forceUpdate || hasContentChanged(...)` +- `.github/workflows/sync-issues.yml` -- pass `force-update: ${{ github.event.inputs.force-update }}` to the action +- `src/__tests__/unit/index.test.ts` -- two new tests (issues + PRs) verifying force-update bypasses `hasContentChanged` +- `CHANGELOG.md` -- added entry under Unreleased > Fixed +- `README.md` -- added `force-update` row to the Options table + +## Changelog Entry + +### Fixed + +- **`--force-update` does not re-sync issues (only PRs)** ([#10](https://github.com/vig-os/sync-issues-action/issues/10)) + - Added `force-update` action input that bypasses the `hasContentChanged` content-comparison gate + - When active, all fetched items are re-written (with updated `synced:` frontmatter) even if body content is unchanged + - Updated `sync-issues.yml` workflow to pass the `force-update` dispatch input to the action + +## Testing + +- [x] Tests pass locally (`npx jest` -- 89 passed, 0 failed) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Ran `npx jest -t "force-update input"` to verify both new tests pass (issue + PR force-update) +- Ran `npx jest -t "should skip writing when only synced timestamp changes"` to confirm existing behaviour is preserved when `force-update` is not set + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [x] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly +- [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 +- [x] Any dependent changes have been merged and published + +## Additional Notes + +The fix follows TDD: failing tests were committed first, then the implementation, then the workflow and docs updates. The commit history proves compliance: + +1. `test:` failing test for issues +2. `test:` failing test for PRs +3. `fix:` implementation (action.yml + src/index.ts) +4. `fix:` workflow update +5. `docs:` changelog +6. `docs:` README + +Refs: #10 + + + +--- +--- + +## Commits + +### Commit 1: [b759324](https://github.com/vig-os/sync-issues-action/commit/b759324d59ca158b8fb16577c318a10806169cbb) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:39 PM +test: add failing test for force-update bypassing hasContentChanged on issues, 57 files modified (src/__tests__/unit/index.test.ts) + +### Commit 2: [cba6182](https://github.com/vig-os/sync-issues-action/commit/cba6182630b4239552543a252e19c81f1c1deb7d) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:40 PM +test: add failing test for force-update bypassing hasContentChanged on PRs, 60 files modified (src/__tests__/unit/index.test.ts) + +### Commit 3: [0117510](https://github.com/vig-os/sync-issues-action/commit/0117510379d064fb04ae4e6c786b43d3984ecd50) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:42 PM +fix: add force-update input to bypass hasContentChanged gate, 24 files modified (action.yml, src/index.ts) + +### Commit 4: [5478179](https://github.com/vig-os/sync-issues-action/commit/5478179d217c224b67b1aab84dfb39db76e0e690) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 12:42 PM +fix: pass force-update input to sync-issues action in workflow, 56 files modified (.github/workflows/sync-issues.yml) + +### Commit 5: [25826f7](https://github.com/vig-os/sync-issues-action/commit/25826f78a920ae6aba9bd7f85987e0631039b53e) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 01:18 PM +docs: update changelog for force-update fix, 4 files modified (CHANGELOG.md) + +### Commit 6: [f7065e8](https://github.com/vig-os/sync-issues-action/commit/f7065e8e5461cc24c10d377f6f56b684b73861d7) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 01:21 PM +docs: add force-update input to README options table, 1 file modified (README.md) diff --git a/docs/pull-requests/pr-14.md b/docs/pull-requests/pr-14.md new file mode 100644 index 0000000..ecc665d --- /dev/null +++ b/docs/pull-requests/pr-14.md @@ -0,0 +1,260 @@ +--- +type: pull_request +state: closed (merged) +branch: feature/13-ci-cd-workflows → dev +created: 2026-02-20T16:41:37Z +updated: 2026-02-23T10:05:25Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/14 +comments: 0 +labels: none +assignees: c-vigo +milestone: none +projects: none +relationship: none +merged: 2026-02-23T10:05:15Z +synced: 2026-02-23T10:06:06.666Z +--- + +# [PR 14](https://github.com/vig-os/sync-issues-action/pull/14) ci: implement CI/CD pipeline, release process, and repo tooling + +## Description + +Implement the full CI/CD pipeline for the project: continuous integration checks, a three-phase release process (prepare → release → post-release), security scanning, integration testing, composite actions, and supporting tooling. Also includes a merged bugfix for sub-issue sync (#15/#16). + +## 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 + +### CI Workflow (`ci.yml`) + +- Lint, build, test, integration-test, dependency-review, and summary gate jobs on PRs to `dev`, `release/**`, and `main` +- Manual `workflow_dispatch` with selectable test suite + +### Three-Phase Release Process + +- **Phase 1 — Prepare Release** (`prepare-release.yml`): create `release/X.Y.Z` branch, prepare CHANGELOG heading, open draft PR to `main` +- **Phase 2 — Release** (`release.yml`): validate prerequisites, finalize CHANGELOG date + `package.json` version, run full test suite on finalized code, create `vX.Y.Z` tag with provenance, publish GitHub Release with auto-generated notes, update floating tags (`vX`, `vX.Y`), automatic rollback with failure-issue on error +- **Phase 3 — Post-Release** (`post-release.yml`): merge `main` back into `dev`, reset CHANGELOG Unreleased section for the next cycle +- Dry-run mode across all phases with deep preview (CHANGELOG diff, version bump preview, tag listing) + +### Security Workflows + +- CodeQL static analysis on PRs and pushes (`codeql.yml`) +- OpenSSF Scorecard supply-chain security analysis uploading SARIF to the Security tab (`scorecard.yml`) + +### Integration Testing (`integration-test.yml`) + +- Reusable workflow called by CI and release +- 8 parallel scenarios: no-op baseline, issues-only, PRs-only, force-update, include-closed, sub-issues, updated-since/state-file, and default-mode + +### Composite Actions + +- `setup-env` — consistent Node.js installation (version from `.nvmrc`), npm dependency caching, optional `uv` install for pre-commit +- `build-dist` — ncc bundling with dist/ diff verification to prevent stale compiled output + +### Release Tooling + +- `prepare_changelog.py` — standalone Python 3 CLI (zero external deps) for CHANGELOG heading management, date stamping, and Unreleased section reset, with unit tests (`test_prepare_changelog.py`) +- `release_helpers.sh` — shell helper functions for release validation, with tests (`test_release_helpers.sh`) + +### Configuration & Maintenance + +- Dependabot configuration for weekly Actions (sha-pinned) and npm dependency updates targeting `dev` +- `.nvmrc` pinning Node.js 20 as single source of truth for CI, devcontainer, and local development +- `CODEOWNERS` file for automated review assignment +- Overhauled issue templates: `bug`, `chore`, `discussion`, `docs`, `feature`, `refactor` (removed generic `task`) +- Updated PR template with conventional-commit type checkboxes, changelog entry section, and refs footer +- Pre-commit config: removed Python checks, switched to `uvx` for pre-commit invocation +- Removed source maps from `dist/` to fix CI dist staleness checks +- `sync-issues.yml` updated to use local action (`uses: ./`) and GitHub App token for cache deletion + +### Sub-Issues Bugfix (merged from #16, Refs: #15) + +- Added `sync-sub-issues` opt-in action input (default: `true`) +- Fixed GraphQL field name: `parent` instead of `parentIssue` +- Graceful schema error handling: emits info message and falls back to `relationship: none` if the sub-issues API is unavailable +- Updated mocks and tests for correct field name and default behavior + +### Documentation + +- CHANGELOG updated with all Added/Changed/Fixed/Security entries for the Unreleased version +- README updated with `force-update` and `sync-sub-issues` inputs in the options table + +## Changelog Entry + +See `## Unreleased` in `CHANGELOG.md` — all user-visible changes (sub-issues sync, force-update, CI/CD pipeline, security workflows) are documented there. + +## Testing + +- [x] Tests pass locally (`npm test`) +- [x] Manual testing performed (describe below) + +### Manual Testing Details + +- Workflows validated via pre-commit YAML linting (`yamllint`), action-pin checks, and shellcheck +- `prepare_changelog.py` unit tests: `python -m pytest .github/tests/test_prepare_changelog.py` +- `release_helpers.sh` unit tests: `bash .github/tests/test_release_helpers.sh` +- Integration test suite validated via CI (8 parallel scenario jobs) +- Full end-to-end workflow testing requires a push to the remote and observing GitHub Actions runs + +## Checklist + +- [x] My code follows the project's style guidelines +- [x] I have performed a self-review of my code +- [x] I have commented my code, particularly in hard-to-understand areas +- [x] I have updated the documentation accordingly +- [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 +- [x] Any dependent changes have been merged and published + +## Additional Notes + +The `build-dist` action enforces that `dist/` is always committed in sync with source — any PR with stale dist will fail the CI `build` job. The `.nvmrc` file ensures local devcontainer and CI always use the same Node.js major version; bumping Node requires only a single-line change. + +The release process uses a GitHub App token (`APP_ID` / `APP_PRIVATE_KEY` secrets) for operations that need to bypass branch protection rules (pushing to release branches, creating tags). The `GITHUB_TOKEN` is used everywhere else with least-privilege permissions. + +`prepare_changelog.py` is a temporary in-repo tool that will eventually be extracted into a standalone GitHub Action or pip package. + +Refs: #13 + + +--- +--- + +## Commits + +### Commit 1: [8ed0182](https://github.com/vig-os/sync-issues-action/commit/8ed01823cce1aa963a472dbaefb5f99a493c9008) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:21 PM +chore: update pre-commit hooks and pymarkdown config, 129 files modified (.pre-commit-config.yaml, .pymarkdown, .pymarkdown.config.md) + +### Commit 2: [3e09cb5](https://github.com/vig-os/sync-issues-action/commit/3e09cb518b4cffa05ea3079a4c71cc58ea6511a4) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +chore: overhaul issue templates, 427 files modified + +### Commit 3: [9029a5d](https://github.com/vig-os/sync-issues-action/commit/9029a5d1927982f7cda8a4a012215b14c516d9d2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +chore: add CODEOWNERS and update PR template, 66 files modified (.github/CODEOWNERS, .github/pull_request_template.md) + +### Commit 4: [838370e](https://github.com/vig-os/sync-issues-action/commit/838370ee23d0e913de32ee0c5260ecbe91de550e) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add dependabot configuration, 38 files modified (.github/dependabot.yml) + +### Commit 5: [7c02f41](https://github.com/vig-os/sync-issues-action/commit/7c02f410b3e3a181a72c21e686e5192f2c9d1aff) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add setup-env composite action, 49 files modified (.github/actions/setup-env/action.yml) + +### Commit 6: [511127f](https://github.com/vig-os/sync-issues-action/commit/511127fc600914bf376a02e6621f7c2c1c5315ea) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:23 PM +ci: add CI workflow, 152 files modified (.github/workflows/ci.yml) + +### Commit 7: [ae6a55f](https://github.com/vig-os/sync-issues-action/commit/ae6a55f4e1809e447504cd1d8237970c441efbdd) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +ci: add CodeQL and Scorecard security analysis workflows, 115 files modified (.github/workflows/codeql.yml, .github/workflows/scorecard.yml) + +### Commit 8: [c605dfc](https://github.com/vig-os/sync-issues-action/commit/c605dfcf425fde70b379c1ff4f6d3258500dffc2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +ci: add release workflow, 478 files modified (.github/workflows/release.yml) + +### Commit 9: [70f375c](https://github.com/vig-os/sync-issues-action/commit/70f375cb09083051387c4cd3c00c39def266dcd7) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:24 PM +fix(ci): use GitHub App token for cache deletion in sync-issues, 2 files modified (.github/workflows/sync-issues.yml) + +### Commit 10: [8cc3446](https://github.com/vig-os/sync-issues-action/commit/8cc3446d064b07ad6ae728b93b512db875d4ec78) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:27 PM +chore: remove Python checks from pre-commit config, 9 files modified (.pre-commit-config.yaml) + +### Commit 11: [7354165](https://github.com/vig-os/sync-issues-action/commit/7354165281bf6d557e8db3240bc1a9b98baf9510) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 03:49 PM +ci: add optional uv installation to setup-env action, 22 files modified (.github/actions/setup-env/action.yml, .github/workflows/ci.yml) + +### Commit 12: [5be6e8c](https://github.com/vig-os/sync-issues-action/commit/5be6e8c380338a82e3b29dde0549b3e1ef05eb18) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:26 PM +ci: add build-dist action and build job to CI and release, 101 files modified (.github/actions/build-dist/action.yml, .github/workflows/ci.yml, .github/workflows/release.yml) + +### Commit 13: [222f104](https://github.com/vig-os/sync-issues-action/commit/222f104fb01119d06e22ef0272ec099469dbe646) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:26 PM +ci: pin Node.js version via .nvmrc as single source of truth, 13 files modified (.devcontainer/scripts/post-create.sh, .github/actions/setup-env/action.yml, .nvmrc) + +### Commit 14: [293e7b4](https://github.com/vig-os/sync-issues-action/commit/293e7b4a866f339982ebe3502207d4108a2d5c3f) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 04:37 PM +build: rebuild bundle dist, 20 files modified (dist/index.js, dist/index.js.map, dist/src/index.d.ts.map) + +### Commit 15: [c8eab8b](https://github.com/vig-os/sync-issues-action/commit/c8eab8bcf99a53f531be1c98bbe511ce198eb8a2) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 07:18 PM +build(dist): remove source maps to fix CI dist staleness, 695 files modified + +### Commit 16: [c7f0e5d](https://github.com/vig-os/sync-issues-action/commit/c7f0e5daed492140b99ea96ad01d0784d4122788) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 08:59 PM +fix(ci): replace uv run with uvx for pre-commit invocation, 12 files modified (.github/workflows/ci.yml, .pre-commit-config.yaml) + +### Commit 17: [382264a](https://github.com/vig-os/sync-issues-action/commit/382264a045ae78c1bee0807e96efbcb94cdcee1f) by [c-vigo](https://github.com/c-vigo) on February 20, 2026 at 09:01 PM +feat( ci): use local action (uses: ./) in sync-issues workflow, 3 files modified (.github/workflows/sync-issues.yml) + +### Commit 18: [5b69195](https://github.com/vig-os/sync-issues-action/commit/5b691956e23978cf94248b5164a36e73fb457def) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +test(ci): add prepare_changelog unit tests, 122 files modified (.github/tests/test_prepare_changelog.py) + +### Commit 19: [ebb5de7](https://github.com/vig-os/sync-issues-action/commit/ebb5de7912b8d912b4d24b1019684b1d6c42bc8e) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +ci: add CHANGELOG management tool for release workflow, 416 files modified (.github/prepare_changelog.py) + +### Commit 20: [ff0fafc](https://github.com/vig-os/sync-issues-action/commit/ff0fafcfe4399915e7e302e56ecf1ac33c35511e) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:30 AM +ci: add integration-test reusable workflow and wire into CI, 215 files modified (.github/workflows/ci.yml, .github/workflows/integration-test.yml) + +### Commit 21: [d18ec8a](https://github.com/vig-os/sync-issues-action/commit/d18ec8a9f1eb8cb84a72baebf4e74afc02f9c412) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: add prepare-release workflow (Phase 1), 292 files modified (.github/workflows/prepare-release.yml) + +### Commit 22: [76fa183](https://github.com/vig-os/sync-issues-action/commit/76fa183b1b51c8316b00b13d8ef3e26fc43a1f45) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: overhaul release workflow with v-prefix tags, app tokens, and provenance, 276 files modified (.github/workflows/release.yml) + +### Commit 23: [d4a6cc4](https://github.com/vig-os/sync-issues-action/commit/d4a6cc4f670216fdf308971a42dbe60458c1459b) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:31 AM +ci: add post-release workflow (Phase 3), 133 files modified (.github/workflows/post-release.yml) + +### Commit 24: [fae558a](https://github.com/vig-os/sync-issues-action/commit/fae558a99b70dc5e1178616fbc280453bfe384f1) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:44 AM +test(ci): add release helper functions with tests, 252 files modified (.github/prepare_changelog.py, .github/release_helpers.sh, .github/tests/test_prepare_changelog.py, .github/tests/test_release_helpers.sh) + +### Commit 25: [84b1812](https://github.com/vig-os/sync-issues-action/commit/84b1812e34dffa4852da6b1174631a42d7d38d13) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:44 AM +ci: enhance release workflow dry-run with deep preview, 122 files modified (.github/workflows/prepare-release.yml, .github/workflows/release.yml) + +### Commit 26: [fcbd2f3](https://github.com/vig-os/sync-issues-action/commit/fcbd2f31d571055585692f5b10a65c2a4fbb6f34) by [c-vigo](https://github.com/c-vigo) on February 22, 2026 at 10:56 AM +fix(ci): add missing permissions for reusable workflow in CI, 2 files modified (.github/workflows/ci.yml) + +### Commit 27: [336cc03](https://github.com/vig-os/sync-issues-action/commit/336cc035cea231679c46b7a4376eaf4b33149e36) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:33 AM +test: add failing tests for sync-sub-issues opt-in and schema error handling, 121 files modified (src/__tests__/unit/index.test.ts) + +### Commit 28: [6af39fa](https://github.com/vig-os/sync-issues-action/commit/6af39fa9086d7ff413315e4fbcb8498f9f5ad2ad) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:34 AM +fix: add sync-sub-issues opt-in input and graceful schema error handling, 27 files modified (action.yml, src/index.ts) + +### Commit 29: [2592992](https://github.com/vig-os/sync-issues-action/commit/2592992b96fdfb0360ff9709b09f2750741018a6) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:35 AM +build(dist): rebuild bundle for sync-sub-issues changes, 137 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 30: [bc05b4b](https://github.com/vig-os/sync-issues-action/commit/bc05b4b55fa4a269fb24c4fdb830e862bec4db99) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:47 AM +test: update mocks for correct GraphQL field name and default behavior, 21 files modified (src/__tests__/unit/index.test.ts) + +### Commit 31: [1654f0b](https://github.com/vig-os/sync-issues-action/commit/1654f0b1971559e23a0a984a097b0c46f4361e14) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:48 AM +fix: use correct GraphQL field name `parent` instead of `parentIssue`, 11 files modified (action.yml, src/index.ts) + +### Commit 32: [8190449](https://github.com/vig-os/sync-issues-action/commit/81904498d9f588bca21951e68558553e27ebd54b) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:49 AM +ci: add sub-issue relationships integration test, 56 files modified (.github/workflows/integration-test.yml) + +### Commit 33: [d9d9b14](https://github.com/vig-os/sync-issues-action/commit/d9d9b142dc57c2d622fee4aa7648e7d75c687701) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:50 AM +build(dist): rebuild bundle with corrected GraphQL field name, 35 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 34: [77f28d7](https://github.com/vig-os/sync-issues-action/commit/77f28d7d2d94b1a34cb07170ad47332431946da2) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:59 AM +Merge pull request #16 from vig-os/bugfix/15-suppress-sub-issues-warnings, 382 files modified (.github/workflows/integration-test.yml, action.yml, dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js, src/__tests__/unit/index.test.ts, src/index.ts) + +### Commit 35: [caaf1b2](https://github.com/vig-os/sync-issues-action/commit/caaf1b2e531260803c726eb99d33188f88234219) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:15 AM +ci: add no-op baseline integration test, 30 files modified (.github/workflows/integration-test.yml) + +### Commit 36: [8bb9c51](https://github.com/vig-os/sync-issues-action/commit/8bb9c51582f40be56b36bc6e90c5eeed53492bc1) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:18 AM +ci: split integration tests into parallel jobs, 124 files modified (.github/workflows/integration-test.yml) + +### Commit 37: [9c789ad](https://github.com/vig-os/sync-issues-action/commit/9c789adf900ca98b11e6d091283bdc85f603027d) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:31 AM +test: harden integration tests and add missing feature coverage, 258 files modified (.github/workflows/integration-test.yml) + +### Commit 38: [ef0338e](https://github.com/vig-os/sync-issues-action/commit/ef0338e0614f2d0564fd93134aff713b17424df3) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:36 AM +fix(ci): include closed issues in sub-issues tests, 4 files modified (.github/workflows/integration-test.yml) + +### Commit 39: [a727430](https://github.com/vig-os/sync-issues-action/commit/a7274307aab7875d9bbb4e11093e8277fd02a593) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 09:55 AM +docs: update CHANGELOG and README, 30 files modified (CHANGELOG.md, README.md) diff --git a/docs/pull-requests/pr-16.md b/docs/pull-requests/pr-16.md new file mode 100644 index 0000000..180021f --- /dev/null +++ b/docs/pull-requests/pr-16.md @@ -0,0 +1,61 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/15-suppress-sub-issues-warnings → feature/13-ci-cd-workflows +created: 2026-02-23T08:58:53Z +updated: 2026-02-23T08:59:45Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/16 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T08:59:38Z +synced: 2026-02-23T09:00:16.924Z +--- + +# [PR 16](https://github.com/vig-os/sync-issues-action/pull/16) fix: suppress sub-issues GraphQL warnings and fix field name + +## Summary + +- Fix root cause: GraphQL query used `parentIssue` (doesn't exist) instead of `parent` (correct field name). Remove unnecessary `GraphQL-Features: sub_issues` preview header. +- Add `sync-sub-issues` action input (default `true`) so users can disable sub-issue fetching on environments where the fields are unavailable (e.g. older GHES). +- Graceful schema error handling: emit `core.info()` instead of `core.warning()` when the sub-issues fields don't exist, and continue without relationships. +- Integration test: verify parent/children frontmatter on issues 13 and 15 using this repo's own data. + +## Test plan + +- [x] Unit tests pass (93/93) +- [ ] Integration test in CI validates `issue-15.md` has `parent: 13` and `issue-13.md` lists `15` as a child + +Refs: #15 + + +--- +--- + +## Commits + +### Commit 1: [336cc03](https://github.com/vig-os/sync-issues-action/commit/336cc035cea231679c46b7a4376eaf4b33149e36) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:33 AM +test: add failing tests for sync-sub-issues opt-in and schema error handling, 121 files modified (src/__tests__/unit/index.test.ts) + +### Commit 2: [6af39fa](https://github.com/vig-os/sync-issues-action/commit/6af39fa9086d7ff413315e4fbcb8498f9f5ad2ad) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:34 AM +fix: add sync-sub-issues opt-in input and graceful schema error handling, 27 files modified (action.yml, src/index.ts) + +### Commit 3: [2592992](https://github.com/vig-os/sync-issues-action/commit/2592992b96fdfb0360ff9709b09f2750741018a6) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:35 AM +build(dist): rebuild bundle for sync-sub-issues changes, 137 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) + +### Commit 4: [bc05b4b](https://github.com/vig-os/sync-issues-action/commit/bc05b4b55fa4a269fb24c4fdb830e862bec4db99) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:47 AM +test: update mocks for correct GraphQL field name and default behavior, 21 files modified (src/__tests__/unit/index.test.ts) + +### Commit 5: [1654f0b](https://github.com/vig-os/sync-issues-action/commit/1654f0b1971559e23a0a984a097b0c46f4361e14) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:48 AM +fix: use correct GraphQL field name `parent` instead of `parentIssue`, 11 files modified (action.yml, src/index.ts) + +### Commit 6: [8190449](https://github.com/vig-os/sync-issues-action/commit/81904498d9f588bca21951e68558553e27ebd54b) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:49 AM +ci: add sub-issue relationships integration test, 56 files modified (.github/workflows/integration-test.yml) + +### Commit 7: [d9d9b14](https://github.com/vig-os/sync-issues-action/commit/d9d9b142dc57c2d622fee4aa7648e7d75c687701) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 08:50 AM +build(dist): rebuild bundle with corrected GraphQL field name, 35 files modified (dist/index.js, dist/src/__tests__/unit/index.test.js, dist/src/index.js) diff --git a/docs/pull-requests/pr-19.md b/docs/pull-requests/pr-19.md new file mode 100644 index 0000000..528caad --- /dev/null +++ b/docs/pull-requests/pr-19.md @@ -0,0 +1,42 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/18-fix-prepare-release → dev +created: 2026-02-23T10:30:21Z +updated: 2026-02-23T10:34:55Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/19 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T10:34:55Z +synced: 2026-02-23T10:35:23.487Z +--- + +# [PR 19](https://github.com/vig-os/sync-issues-action/pull/19) fix(ci): use github.token for PR creation and fix changelog regex truncation + +## Summary + +- Use `github.token` instead of the App token for the "Create draft PR to main" step in `prepare-release.yml`, fixing `Resource not accessible by integration (createPullRequest)` error +- Fix `extract_unreleased_content()` regex in `prepare_changelog.py` that matched `##`/`###` at any character position, silently truncating changelog entries containing inline heading markers (e.g. `` `##` ``) + +## Test plan + +- [x] All 19 existing `test_prepare_changelog.py` tests pass +- [x] Manual verification: `prepare_changelog.py prepare` + `extract-notes` now preserves all 6 Fixed entries (was truncated to 1) +- [ ] Re-run Prepare Release workflow with version `0.2.0` after merge (stale `release/0.2.0` branch already deleted) + +Refs: #18 + + +--- +--- + +## Commits + +### Commit 1: [ffe9e9b](https://github.com/vig-os/sync-issues-action/commit/ffe9e9b9937d691fe989bb56a4b12db13307cd08) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 10:25 AM +fix(ci): use github.token for PR creation and fix changelog regex truncation, 9 files modified (.github/prepare_changelog.py, .github/workflows/prepare-release.yml) diff --git a/docs/pull-requests/pr-20.md b/docs/pull-requests/pr-20.md new file mode 100644 index 0000000..c9e4358 --- /dev/null +++ b/docs/pull-requests/pr-20.md @@ -0,0 +1,39 @@ +--- +type: pull_request +state: closed (merged) +branch: bugfix/18-fix-prepare-release → dev +created: 2026-02-23T10:44:24Z +updated: 2026-02-23T10:46:19Z +author: c-vigo +author_url: https://github.com/c-vigo +url: https://github.com/vig-os/sync-issues-action/pull/20 +comments: 0 +labels: none +assignees: none +milestone: none +projects: none +relationship: none +merged: 2026-02-23T10:46:14Z +synced: 2026-02-23T10:46:46.605Z +--- + +# [PR 20](https://github.com/vig-os/sync-issues-action/pull/20) fix(ci): use app token for PR creation in prepare-release + +## Summary + +- The "Create draft PR to main" step in `prepare-release.yml` was using `github.token` (the default `GITHUB_TOKEN`), which lacks permission to create pull requests when the repo setting is disabled. Switched to the GitHub App token (`steps.app-token.outputs.token`) already generated earlier in the same job. + +## Test plan + +- [ ] Re-run the Prepare Release workflow and confirm the draft PR is created successfully + +Refs: #18 + + +--- +--- + +## Commits + +### Commit 1: [5f88772](https://github.com/vig-os/sync-issues-action/commit/5f88772775fb0e1661582e436459708e7b811bbf) by [c-vigo](https://github.com/c-vigo) on February 23, 2026 at 10:43 AM +fix(ci): use app token for PR creation in prepare-release, 2 files modified (.github/workflows/prepare-release.yml) From 750711cef03c2f24b714fc0aed20280d4ad79db6 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 15:53:30 +0000 Subject: [PATCH 18/24] fix: align syncSubIssues default with action.yml The function signature defaulted syncSubIssues to false while action.yml defaults sync-sub-issues to true. Although the caller always passes the value explicitly (no runtime impact), the mismatch could cause incorrect behavior if the function were ever called without the argument. Refs: #26 --- dist/index.js | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index a18110f..29401f3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -30069,7 +30069,7 @@ async function run() { } } } -async function syncIssuesToMarkdown(octokit, owner, repo, outputDir, includeClosed, updatedSince, forceUpdate = false, syncSubIssues = false) { +async function syncIssuesToMarkdown(octokit, owner, repo, outputDir, includeClosed, updatedSince, forceUpdate = false, syncSubIssues = true) { const state = includeClosed ? 'all' : 'open'; let page = 1; const perPage = 100; diff --git a/src/index.ts b/src/index.ts index c83b65b..396bbc3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -212,7 +212,7 @@ async function syncIssuesToMarkdown( includeClosed: boolean, updatedSince?: string, forceUpdate = false, - syncSubIssues = false + syncSubIssues = true ): Promise<{ count: number; files: string[] }> { const state = includeClosed ? 'all' : 'open'; let page = 1; From 12bb18e7cc1ea8601ccf21a3c022ffe4a77da423 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 15:56:40 +0000 Subject: [PATCH 19/24] fix: return partial results from fetchIssueRelationships on batch failure (#25) Move try/catch inside the batch loop so that: - Schema errors ("doesn't exist on type") break immediately - Transient errors warn per-batch and continue to the next - Successfully fetched relationships are preserved instead of being discarded on a later batch failure Add tests for multi-batch partial-result and schema-error-break scenarios. --- dist/index.js | 47 ++++++++++++------------ dist/src/index.d.ts | 1 + src/__tests__/unit/index.test.ts | 63 +++++++++++++++++++++++++++++++- src/index.ts | 60 +++++++++++++++--------------- 4 files changed, 117 insertions(+), 54 deletions(-) diff --git a/dist/index.js b/dist/index.js index a18110f..7a11fb4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29961,6 +29961,7 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.GRAPHQL_BATCH_SIZE = void 0; exports.fetchIssueRelationships = fetchIssueRelationships; exports.formatIssueAsMarkdown = formatIssueAsMarkdown; exports.formatPRAsMarkdown = formatPRAsMarkdown; @@ -30206,26 +30207,26 @@ async function fetchComments(octokit, owner, repo, issueNumber) { } return comments; } -const GRAPHQL_BATCH_SIZE = 50; +exports.GRAPHQL_BATCH_SIZE = 50; async function fetchIssueRelationships(octokit, owner, repo, issueNumbers) { const relationships = new Map(); if (issueNumbers.length === 0) { return relationships; } - try { - for (let i = 0; i < issueNumbers.length; i += GRAPHQL_BATCH_SIZE) { - const batch = issueNumbers.slice(i, i + GRAPHQL_BATCH_SIZE); - const issueFields = batch - .map((num) => `issue_${num}: issue(number: ${num}) { - parent { number } - subIssues(first: 100) { nodes { number } } - }`) - .join('\n'); - const query = `query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - ${issueFields} - } - }`; + for (let i = 0; i < issueNumbers.length; i += exports.GRAPHQL_BATCH_SIZE) { + const batch = issueNumbers.slice(i, i + exports.GRAPHQL_BATCH_SIZE); + const issueFields = batch + .map((num) => `issue_${num}: issue(number: ${num}) { + parent { number } + subIssues(first: 100) { nodes { number } } + }`) + .join('\n'); + const query = `query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + ${issueFields} + } + }`; + try { const response = await octokit.graphql(query, { owner, repo, @@ -30240,16 +30241,14 @@ async function fetchIssueRelationships(octokit, owner, repo, issueNumbers) { } } } - } - catch (error) { - const message = error instanceof Error ? error.message : 'Unknown error'; - if (message.includes("doesn't exist on type")) { - core.info('Sub-issues API is not available for this repository. Skipping relationship sync.'); - } - else { - core.warning(`Failed to fetch sub-issue relationships: ${message}`); + catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + if (message.includes("doesn't exist on type")) { + core.info('Sub-issues API is not available for this repository. Skipping relationship sync.'); + break; + } + core.warning(`Failed to fetch sub-issue relationships (batch ${Math.floor(i / exports.GRAPHQL_BATCH_SIZE) + 1}): ${message}`); } - return new Map(); } return relationships; } diff --git a/dist/src/index.d.ts b/dist/src/index.d.ts index 1dbfdc8..346e883 100644 --- a/dist/src/index.d.ts +++ b/dist/src/index.d.ts @@ -85,6 +85,7 @@ interface IssueRelationship { children: number[]; } declare function run(): Promise; +export declare const GRAPHQL_BATCH_SIZE = 50; export declare function fetchIssueRelationships(octokit: ReturnType, owner: string, repo: string, issueNumbers: number[]): Promise>; export declare function formatIssueAsMarkdown(issue: Issue, comments?: Comment[], relationship?: IssueRelationship): string; export declare function formatPRAsMarkdown(pr: PullRequest, comments?: Comment[], reviewComments?: ReviewComment[], commits?: Array<{ diff --git a/src/__tests__/unit/index.test.ts b/src/__tests__/unit/index.test.ts index 6659a2a..44c72a7 100644 --- a/src/__tests__/unit/index.test.ts +++ b/src/__tests__/unit/index.test.ts @@ -10,6 +10,7 @@ import { formatPRAsMarkdown, shiftHeadersToMinLevel, fetchIssueRelationships, + GRAPHQL_BATCH_SIZE, run, } from '../../index'; @@ -1051,7 +1052,7 @@ describe('Sync Issues Action', () => { expect(mockOctokit.graphql).not.toHaveBeenCalled(); }); - it('should return empty map and warn on GraphQL error', async () => { + it('should warn on GraphQL error and return empty results for that batch', async () => { mockOctokit.graphql.mockRejectedValueOnce(new Error('GraphQL rate limit')); const result = await fetchIssueRelationships(mockOctokit, 'owner', 'repo', [1, 2]); @@ -1089,6 +1090,66 @@ describe('Sync Issues Action', () => { ); expect(core.info).not.toHaveBeenCalled(); }); + + it('should return partial results when a later batch fails', async () => { + const batch1Issues = Array.from({ length: GRAPHQL_BATCH_SIZE }, (_, i) => i + 1); + const batch2Issues = [GRAPHQL_BATCH_SIZE + 1, GRAPHQL_BATCH_SIZE + 2]; + const allIssues = [...batch1Issues, ...batch2Issues]; + + const batch1Response: Record = {}; + for (const num of batch1Issues) { + batch1Response[`issue_${num}`] = { + parent: null, + subIssues: { nodes: [] }, + }; + } + + mockOctokit.graphql + .mockResolvedValueOnce({ repository: batch1Response }) + .mockRejectedValueOnce(new Error('Transient network error')); + + const result = await fetchIssueRelationships(mockOctokit, 'owner', 'repo', allIssues); + + expect(result.size).toBe(GRAPHQL_BATCH_SIZE); + for (const num of batch1Issues) { + expect(result.get(num)).toEqual({ parent: null, children: [] }); + } + expect(result.has(GRAPHQL_BATCH_SIZE + 1)).toBe(false); + expect(result.has(GRAPHQL_BATCH_SIZE + 2)).toBe(false); + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining('Transient network error') + ); + }); + + it('should break and return partial results on schema error in later batch', async () => { + const batch1Issues = Array.from({ length: GRAPHQL_BATCH_SIZE }, (_, i) => i + 1); + const batch2Issues = [GRAPHQL_BATCH_SIZE + 1]; + const allIssues = [...batch1Issues, ...batch2Issues]; + + const batch1Response: Record = {}; + for (const num of batch1Issues) { + batch1Response[`issue_${num}`] = { + parent: { number: 999 }, + subIssues: { nodes: [] }, + }; + } + + mockOctokit.graphql + .mockResolvedValueOnce({ repository: batch1Response }) + .mockRejectedValueOnce( + new Error("Field 'parent' doesn't exist on type 'Issue'") + ); + + const result = await fetchIssueRelationships(mockOctokit, 'owner', 'repo', allIssues); + + expect(result.size).toBe(GRAPHQL_BATCH_SIZE); + expect(result.get(1)).toEqual({ parent: 999, children: [] }); + expect(result.has(GRAPHQL_BATCH_SIZE + 1)).toBe(false); + expect(core.info).toHaveBeenCalledWith( + 'Sub-issues API is not available for this repository. Skipping relationship sync.' + ); + expect(core.warning).not.toHaveBeenCalled(); + }); }); describe('shiftHeadersToMinLevel', () => { diff --git a/src/index.ts b/src/index.ts index c83b65b..62cff93 100644 --- a/src/index.ts +++ b/src/index.ts @@ -398,7 +398,7 @@ async function fetchComments( return comments; } -const GRAPHQL_BATCH_SIZE = 50; +export const GRAPHQL_BATCH_SIZE = 50; export async function fetchIssueRelationships( octokit: ReturnType, @@ -412,26 +412,26 @@ export async function fetchIssueRelationships( return relationships; } - try { - for (let i = 0; i < issueNumbers.length; i += GRAPHQL_BATCH_SIZE) { - const batch = issueNumbers.slice(i, i + GRAPHQL_BATCH_SIZE); - - const issueFields = batch - .map( - (num) => - `issue_${num}: issue(number: ${num}) { - parent { number } - subIssues(first: 100) { nodes { number } } - }` - ) - .join('\n'); - - const query = `query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - ${issueFields} - } - }`; + for (let i = 0; i < issueNumbers.length; i += GRAPHQL_BATCH_SIZE) { + const batch = issueNumbers.slice(i, i + GRAPHQL_BATCH_SIZE); + + const issueFields = batch + .map( + (num) => + `issue_${num}: issue(number: ${num}) { + parent { number } + subIssues(first: 100) { nodes { number } } + }` + ) + .join('\n'); + + const query = `query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + ${issueFields} + } + }`; + try { const response: any = await octokit.graphql(query, { owner, repo, @@ -446,17 +446,19 @@ export async function fetchIssueRelationships( }); } } - } - } catch (error) { - const message = error instanceof Error ? error.message : 'Unknown error'; - if (message.includes("doesn't exist on type")) { - core.info( - 'Sub-issues API is not available for this repository. Skipping relationship sync.' + } catch (error) { + const message = + error instanceof Error ? error.message : 'Unknown error'; + if (message.includes("doesn't exist on type")) { + core.info( + 'Sub-issues API is not available for this repository. Skipping relationship sync.' + ); + break; + } + core.warning( + `Failed to fetch sub-issue relationships (batch ${Math.floor(i / GRAPHQL_BATCH_SIZE) + 1}): ${message}` ); - } else { - core.warning(`Failed to fetch sub-issue relationships: ${message}`); } - return new Map(); } return relationships; From 0d7b5e5fb520b424ae297c717fa7bd145471009e Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 16:01:28 +0000 Subject: [PATCH 20/24] chore: run full `npm run prepare` pipeline Refs: #26 --- dist/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/src/index.js b/dist/src/index.js index 60fbf4e..73ce100 100644 --- a/dist/src/index.js +++ b/dist/src/index.js @@ -141,7 +141,7 @@ async function run() { } } } -async function syncIssuesToMarkdown(octokit, owner, repo, outputDir, includeClosed, updatedSince, forceUpdate = false, syncSubIssues = false) { +async function syncIssuesToMarkdown(octokit, owner, repo, outputDir, includeClosed, updatedSince, forceUpdate = false, syncSubIssues = true) { const state = includeClosed ? 'all' : 'open'; let page = 1; const perPage = 100; From 040621bfb09dea6457de9381f38b963a0d3e8321 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 16:02:02 +0000 Subject: [PATCH 21/24] docs: update build command in README to use `npm run prepare` Refs: #26 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7299a2b..89f84aa 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ You can also run the following specific tests: ## Development 1. Make changes to `src/index.ts` -2. Build: `npm run build && npm run package` +2. Build: `npm run prepare` (runs `tsc` then `ncc build` to update all of `dist/`) 3. Run tests: `npm test` 4. Test locally with `local-action` From f8bdb5a023237240981df202f0d93ce5a50ff246 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Mon, 23 Feb 2026 16:02:56 +0000 Subject: [PATCH 22/24] chore: run full `npm run prepare` pipeline Refs: #25 --- dist/src/__tests__/unit/index.test.js | 46 +++++++++++++++++++++++++- dist/src/index.js | 47 +++++++++++++-------------- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/dist/src/__tests__/unit/index.test.js b/dist/src/__tests__/unit/index.test.js index 2283971..2c6984c 100644 --- a/dist/src/__tests__/unit/index.test.js +++ b/dist/src/__tests__/unit/index.test.js @@ -955,7 +955,7 @@ describe('Sync Issues Action', () => { expect(result.size).toBe(0); expect(mockOctokit.graphql).not.toHaveBeenCalled(); }); - it('should return empty map and warn on GraphQL error', async () => { + it('should warn on GraphQL error and return empty results for that batch', async () => { mockOctokit.graphql.mockRejectedValueOnce(new Error('GraphQL rate limit')); const result = await (0, index_1.fetchIssueRelationships)(mockOctokit, 'owner', 'repo', [1, 2]); expect(result.size).toBe(0); @@ -975,6 +975,50 @@ describe('Sync Issues Action', () => { expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Server error')); expect(core.info).not.toHaveBeenCalled(); }); + it('should return partial results when a later batch fails', async () => { + const batch1Issues = Array.from({ length: index_1.GRAPHQL_BATCH_SIZE }, (_, i) => i + 1); + const batch2Issues = [index_1.GRAPHQL_BATCH_SIZE + 1, index_1.GRAPHQL_BATCH_SIZE + 2]; + const allIssues = [...batch1Issues, ...batch2Issues]; + const batch1Response = {}; + for (const num of batch1Issues) { + batch1Response[`issue_${num}`] = { + parent: null, + subIssues: { nodes: [] }, + }; + } + mockOctokit.graphql + .mockResolvedValueOnce({ repository: batch1Response }) + .mockRejectedValueOnce(new Error('Transient network error')); + const result = await (0, index_1.fetchIssueRelationships)(mockOctokit, 'owner', 'repo', allIssues); + expect(result.size).toBe(index_1.GRAPHQL_BATCH_SIZE); + for (const num of batch1Issues) { + expect(result.get(num)).toEqual({ parent: null, children: [] }); + } + expect(result.has(index_1.GRAPHQL_BATCH_SIZE + 1)).toBe(false); + expect(result.has(index_1.GRAPHQL_BATCH_SIZE + 2)).toBe(false); + expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Transient network error')); + }); + it('should break and return partial results on schema error in later batch', async () => { + const batch1Issues = Array.from({ length: index_1.GRAPHQL_BATCH_SIZE }, (_, i) => i + 1); + const batch2Issues = [index_1.GRAPHQL_BATCH_SIZE + 1]; + const allIssues = [...batch1Issues, ...batch2Issues]; + const batch1Response = {}; + for (const num of batch1Issues) { + batch1Response[`issue_${num}`] = { + parent: { number: 999 }, + subIssues: { nodes: [] }, + }; + } + mockOctokit.graphql + .mockResolvedValueOnce({ repository: batch1Response }) + .mockRejectedValueOnce(new Error("Field 'parent' doesn't exist on type 'Issue'")); + const result = await (0, index_1.fetchIssueRelationships)(mockOctokit, 'owner', 'repo', allIssues); + expect(result.size).toBe(index_1.GRAPHQL_BATCH_SIZE); + expect(result.get(1)).toEqual({ parent: 999, children: [] }); + expect(result.has(index_1.GRAPHQL_BATCH_SIZE + 1)).toBe(false); + expect(core.info).toHaveBeenCalledWith('Sub-issues API is not available for this repository. Skipping relationship sync.'); + expect(core.warning).not.toHaveBeenCalled(); + }); }); describe('shiftHeadersToMinLevel', () => { it('should return empty/falsy content unchanged', () => { diff --git a/dist/src/index.js b/dist/src/index.js index 60fbf4e..35e8712 100644 --- a/dist/src/index.js +++ b/dist/src/index.js @@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.GRAPHQL_BATCH_SIZE = void 0; exports.fetchIssueRelationships = fetchIssueRelationships; exports.formatIssueAsMarkdown = formatIssueAsMarkdown; exports.formatPRAsMarkdown = formatPRAsMarkdown; @@ -278,26 +279,26 @@ async function fetchComments(octokit, owner, repo, issueNumber) { } return comments; } -const GRAPHQL_BATCH_SIZE = 50; +exports.GRAPHQL_BATCH_SIZE = 50; async function fetchIssueRelationships(octokit, owner, repo, issueNumbers) { const relationships = new Map(); if (issueNumbers.length === 0) { return relationships; } - try { - for (let i = 0; i < issueNumbers.length; i += GRAPHQL_BATCH_SIZE) { - const batch = issueNumbers.slice(i, i + GRAPHQL_BATCH_SIZE); - const issueFields = batch - .map((num) => `issue_${num}: issue(number: ${num}) { - parent { number } - subIssues(first: 100) { nodes { number } } - }`) - .join('\n'); - const query = `query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - ${issueFields} - } - }`; + for (let i = 0; i < issueNumbers.length; i += exports.GRAPHQL_BATCH_SIZE) { + const batch = issueNumbers.slice(i, i + exports.GRAPHQL_BATCH_SIZE); + const issueFields = batch + .map((num) => `issue_${num}: issue(number: ${num}) { + parent { number } + subIssues(first: 100) { nodes { number } } + }`) + .join('\n'); + const query = `query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + ${issueFields} + } + }`; + try { const response = await octokit.graphql(query, { owner, repo, @@ -312,16 +313,14 @@ async function fetchIssueRelationships(octokit, owner, repo, issueNumbers) { } } } - } - catch (error) { - const message = error instanceof Error ? error.message : 'Unknown error'; - if (message.includes("doesn't exist on type")) { - core.info('Sub-issues API is not available for this repository. Skipping relationship sync.'); - } - else { - core.warning(`Failed to fetch sub-issue relationships: ${message}`); + catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + if (message.includes("doesn't exist on type")) { + core.info('Sub-issues API is not available for this repository. Skipping relationship sync.'); + break; + } + core.warning(`Failed to fetch sub-issue relationships (batch ${Math.floor(i / exports.GRAPHQL_BATCH_SIZE) + 1}): ${message}`); } - return new Map(); } return relationships; } From 5a0fc426ea6933ab455f0e57c29aa6e96f8ad646 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Wed, 25 Feb 2026 11:31:06 +0000 Subject: [PATCH 23/24] chore(ci): clarify weekly CodeQL scheduled scan purpose Refs: #29 --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 900cb3c..54166b4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,7 +5,7 @@ # Triggers: # - Pull requests to dev, release/**, and main # - Pushes to main (post-merge analysis) -# - Weekly schedule (catch newly disclosed patterns) +# - Weekly schedule (re-run with updated CodeQL rules/engines even when repo code is unchanged) name: CodeQL From 2f0beacb2e46f8e3dc7e1af9373252917555df71 Mon Sep 17 00:00:00 2001 From: Carlos Vigo Date: Wed, 25 Feb 2026 11:49:19 +0000 Subject: [PATCH 24/24] fix: allow required read permissions for release integration workflow Refs: #31 --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3710e5..460c9c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -301,6 +301,10 @@ jobs: name: Integration Test (Finalized) needs: [validate, finalize] uses: ./.github/workflows/integration-test.yml + permissions: + contents: read + issues: read + pull-requests: read with: ref: ${{ needs.finalize.outputs.finalize_sha }}