Skip to content

A.2 - Reusable Quality Gate Workflows

Each reusable workflow implements exactly one quality gate. All are invoked via workflow_call, declare their own permissions (reusable workflows do not inherit from callers), and produce structured output via GITHUB_STEP_SUMMARY and (where applicable) PR comments.

A.2.1 - Prettier Formatting Verification

Runs prettier --check against Salesforce DX source files. Fails if any file is not formatted.

# .github/workflows/sf-prettier-verify.yml
# Reusable: runs prettier --check and fails if any file is not formatted.

name: Prettier Formatting Verification

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version to use'
        required: false
        type: string
        default: 'lts/*'
      source-path:
        description: 'Path to the source directory to check formatting'
        required: false
        type: string
        default: 'force-app'

jobs:
  prettier-verify:
    name: Formatting Verification
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: ${{ inputs.node-version }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Verify formatting
        run: npx prettier --check "${{ inputs.source-path }}/**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}"

      - name: Write job summary on failure
        if: failure()
        run: |
          echo "## Formatting Verification Failed" >> $GITHUB_STEP_SUMMARY
          echo "Run \`npx prettier --write\` locally and commit the result." >> $GITHUB_STEP_SUMMARY

A.2.2 - Secret Scanning

Placeholder workflow. Replace the run: step with the organization's chosen secret scanning tool. The configuration file should be centralized in the configuration repository to ensure consistent pattern detection across all project repositories.

# .github/workflows/sf-secret-scan.yml
# Reusable: runs secret scanning against changed files.
# Replace the placeholder run step with your organization's chosen tool
# (e.g. Gitleaks, TruffleHog, GitHub Advanced Security).

name: Secret Scanning

on:
  workflow_call:

jobs:
  secret-scan:
    name: Secret Scanning
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Run secret scanning
        run: |
          echo "Configure secret scanning tool here"
          # Replace with the secret scanning tool of your choice.
          # Example tools: Gitleaks (https://github.com/gitleaks/gitleaks),
          # TruffleHog (https://github.com/trufflesecurity/trufflehog),
          # or GitHub Advanced Security native secret scanning.
          # Ensure the tool references the centralised configuration
          # file from the template repository for consistent pattern detection.
          exit 0

A.2.3 - Semantic PR Title Check

Enforces Conventional Commits format on PR titles via amannn/action-semantic-pull-request. Because squash merge is the only permitted merge strategy on protected branches, the PR title becomes the commit message on the target branch: enforcing title format at PR open time is the enforcement point for the entire commit history discipline.

# .github/workflows/sf-semver-check.yml
# Reusable: enforces Conventional Commits on PR title.

name: SemVer / Conventional Commits Check

on:
  workflow_call:

jobs:
  semver-check:
    name: PR Title Convention
    runs-on: ubuntu-latest
    steps:
      - name: Validate PR title
        uses: amannn/action-semantic-pull-request@v6
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          types: |
            feat
            fix
            test
            perf
            chore
            refactor
            style
            docs
            revert
            deps
            build
            ci
          requireScope: false

A.2.4 - Destructive Change Detection

Detects destructiveChanges.xml in the PR diff, labels the PR with destructive-change, and (in combination with branch protection rules) blocks auto-merge when present. The presence of destructive metadata requires explicit human approval even when all automated gates pass.

# .github/workflows/sf-destructive-check.yml
# Reusable: detects destructiveChanges.xml in PR diff, labels the PR,
# and blocks auto-merge when destructive changes are present.

name: Destructive Change Detection

on:
  workflow_call:
    outputs:
      has-destructive:
        description: 'Whether destructive changes were detected'
        value: ${{ jobs.destructive-check.outputs.has-destructive }}

jobs:
  destructive-check:
    name: Destructive Change Detection
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    outputs:
      has-destructive: ${{ steps.check.outputs.has-destructive }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Check for destructive changes
        id: check
        run: |
          if git diff --name-only origin/${{ github.base_ref }}...HEAD \
            | grep -qE 'destructiveChanges(Pre)?\.xml'; then
            echo "has-destructive=true" >> $GITHUB_OUTPUT
            echo "::warning::Destructive changes detected in this PR."
          else
            echo "has-destructive=false" >> $GITHUB_OUTPUT
          fi

      - name: Label PR as destructive
        if: steps.check.outputs.has-destructive == 'true'
        uses: actions/github-script@v9
        with:
          script: |
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              labels: ['destructive-change']
            });

A.2.5 - Generate Delta Package

Generates an sfdx-git-delta deployment package from the PR diff and uploads it as a workflow artifact. All downstream PR jobs (code analysis, Apex tests, Jest tests, deployment validation) download this artifact by name rather than regenerating the delta independently. The artifact-uploaded output is the flag that downstream jobs use to skip cleanly when the PR contains no deployable metadata.

# .github/workflows/sf-generate-delta.yml
# Reusable: generates a sfdx-git-delta deployment package from the PR diff
# and uploads it as a workflow artifact for downstream jobs to consume.
#
# All org-connected PR jobs (code-analyzer in pr mode, apex-tests-pr,
# jest-tests, validate-deployment) download this artifact by name rather
# than regenerating it independently.

name: Generate Delta Package

on:
  workflow_call:
    inputs:
      source-dir:
        description: 'Source directory passed to --source-dir. Omit to let sfdx-git-delta infer from sfdx-project.json.'
        required: false
        type: string
        default: 'force-app'
      artifact-name:
        description: 'Name for the uploaded artifact. Must match the artifact-name input on all downstream jobs.'
        required: false
        type: string
        default: 'delta-package'
      artifact-retention-days:
        description: 'Days to retain the artifact. 1 is sufficient for intra-workflow use.'
        required: false
        type: string
        default: '1'
    outputs:
      artifact-uploaded:
        description: 'Whether the delta artifact was successfully created and uploaded (true/false)'
        value: ${{ jobs.generate-delta.outputs.artifact-uploaded }}

jobs:
  generate-delta:
    name: Generate Delta Package
    runs-on: ubuntu-latest
    outputs:
      artifact-uploaded: ${{ steps.set-output.outputs.uploaded }}
    permissions:
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Check for force-app changes
        id: check-changes
        run: |
          CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- ${{ inputs.source-dir }} | wc -l | tr -d ' ')
          echo "has-changes=$( [ "$CHANGED" -gt 0 ] && echo true || echo false )" >> $GITHUB_OUTPUT

      - name: Set up Salesforce environment
        if: steps.check-changes.outputs.has-changes == 'true'
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'

      - name: Generate delta package
        if: steps.check-changes.outputs.has-changes == 'true'
        run: |
          mkdir -p delta
          sf sgd source delta \
            --from origin/${{ github.base_ref }} \
            --to HEAD \
            --output delta \
            --generate-delta \
            --source-dir ${{ inputs.source-dir }}

      - name: Upload delta as artifact
        id: upload
        if: steps.check-changes.outputs.has-changes == 'true'
        uses: actions/upload-artifact@v7
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/
          retention-days: ${{ inputs.artifact-retention-days }}

      - name: Set upload output
        id: set-output
        run: |
          if [ "${{ steps.upload.outcome }}" == "success" ]; then
            echo "uploaded=true" >> $GITHUB_OUTPUT
          else
            echo "uploaded=false" >> $GITHUB_OUTPUT
          fi

A.2.6 - Static Code Analysis

Runs Salesforce Code Analyzer. Supports two modes: pr (scans the delta artifact, uploads SARIF to Code Scanning, posts a two-table PR comment, fails on blocking violations) and ci (scans the configured workspace, writes job summary only, never fails). Both modes install the CLI, Code Analyzer plugin, Java, and Python via setup-sf.

# .github/workflows/sf-code-analyzer.yml
# Reusable: runs sf code-analyzer run against either the PR delta package
# or a configured workspace path.
#
# mode: pr
#   - Downloads the named delta artifact
#   - Scans --workspace delta
#   - Uploads SARIF to GitHub Code Scanning
#   - Posts a structured PR comment (blocking vs. advisory two-table pattern)
#   - Fails on violations at or above severity-threshold
#
# mode: ci
#   - Scans --workspace <workspace input> (default: force-app)
#   - Writes job summary with violation counts
#   - Never fails (advisory only - does not block pushes)

name: Code Analyzer

on:
  workflow_call:
    inputs:
      mode:
        description: >
          "pr" to scan the delta artifact and post a PR comment.
          "ci" to scan the configured workspace and write a job summary only.
        required: false
        type: string
        default: 'ci'
      workspace:
        description: 'Workspace path to analyze (ci mode only; pr mode always uses the delta artifact)'
        required: false
        type: string
        default: 'force-app'
      artifact-name:
        description: 'Name of the delta package artifact to download (pr mode only)'
        required: false
        type: string
        default: 'delta-package'
      rule-selector:
        description: 'Code Analyzer rule selector expression'
        required: false
        type: string
        default: 'all:(Recommended,Security)'
      severity-threshold:
        description: 'Severity level (1, 2, 3, 4, or 5) at or above which the workflow fails (1=Critical, 2=High, 3=Moderate)'
        required: false
        type: string
        default: '2'

jobs:
  code-analyzer:
    name: Static Code Analysis
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write # for SARIF upload (pr mode)
      pull-requests: write # for PR comment (pr mode)
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-sfdx-git-delta: 'false'

      # pr mode: scan the uploaded delta artifact instead of the full workspace
      - name: Download delta package
        id: download-delta
        if: inputs.mode == 'pr'
        continue-on-error: true
        uses: actions/download-artifact@v8
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/

      - name: Resolve workspace path
        id: workspace
        run: |
          if [ "${{ inputs.mode }}" == "pr" ] && [ "${{ steps.download-delta.outcome }}" != "success" ]; then
            echo "No delta artifact found — skipping code analysis."
            echo "path=" >> $GITHUB_OUTPUT
            echo "skip=true" >> $GITHUB_OUTPUT
          elif [ "${{ inputs.mode }}" == "pr" ]; then
            echo "path=delta" >> $GITHUB_OUTPUT
            echo "skip=false" >> $GITHUB_OUTPUT
          else
            echo "path=${{ inputs.workspace }}" >> $GITHUB_OUTPUT
            echo "skip=false" >> $GITHUB_OUTPUT
          fi

      - name: Run Code Analyzer
        if: steps.workspace.outputs.skip != 'true'
        id: scan
        continue-on-error: true
        run: |
          sf code-analyzer run \
            --workspace ${{ steps.workspace.outputs.path }} \
            --rule-selector "${{ inputs.rule-selector }}" \
            --severity-threshold ${{ inputs.severity-threshold }} \
            --output-file results.json \
            --output-file results.sarif

      # pr mode only: upload findings to GitHub Code Scanning (Security tab)
      - name: Upload SARIF to GitHub Code Scanning
        if: inputs.mode == 'pr' && steps.workspace.outputs.skip != 'true'
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: results.sarif

      # Parse results and write detailed job summary (both modes)
      - name: Generate violation summary
        if: always() && steps.workspace.outputs.skip != 'true' && hashFiles('results.json') != ''
        id: report
        uses: ./.github/actions/sf-code-analyzer-report
        with:
          results-path: results.json
          severity-cutoff: ${{ inputs.severity-threshold }}

      # pr mode: build structured two-table comment body (blocking vs. advisory)
      - name: Build PR comment body
        if: always() && inputs.mode == 'pr' && steps.report.outcome == 'success'
        id: comment-body
        env:
          THRESHOLD: ${{ inputs.severity-threshold }}
          HIGH_MD: ${{ steps.report.outputs.high-priority-markdown }}
          LOW_MD: ${{ steps.report.outputs.low-priority-markdown }}
        run: |
          ADVISORY_MIN=$((THRESHOLD + 1))
          {
            echo "body<<EOF_BODY"
            echo "## Code Analyzer Results"
            echo ""
            echo "**Blocking Violations (Severity 1-${THRESHOLD})**"
            echo ""
            echo "$HIGH_MD"
            echo ""
            echo "**Advisory Violations (Severity ${ADVISORY_MIN}-5)**"
            echo ""
            echo "$LOW_MD"
            echo ""
            echo "_Scan run against delta package._"
            echo "EOF_BODY"
          } >> "$GITHUB_OUTPUT"

      # pr mode: post the PR comment
      - name: Post PR comment
        if: always() && inputs.mode == 'pr' && steps.comment-body.outcome == 'success'
        uses: ./.github/actions/post-pr-comment
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          marker: '## Code Analyzer Results'
          body: ${{ steps.comment-body.outputs.body }}

      # pr mode only: fail if blocking violations found
      - name: Fail if blocking violations found (PR)
        if: inputs.mode == 'pr' && steps.workspace.outputs.skip != 'true'
        run: |
          THRESHOLD=${{ inputs.severity-threshold }}
          BLOCKING=$(jq --argjson t "$THRESHOLD" '[.violations[] | select(.severity <= $t)] | length' results.json)
          if [ "$BLOCKING" -gt 0 ]; then
            echo "::error::$BLOCKING blocking violation(s) detected."
            exit 1
          fi

A.2.7 - LWC Jest Tests

Runs LWC Jest tests with coverage and uploads to Codecov. Downloads the delta artifact and skips execution when no LWC changes are present. The skip-delta-check input bypasses the delta filter: used by the push baseline (ci.yml) where there is no PR diff to reason about.

# .github/workflows/sf-jest-tests.yml
# Reusable: runs LWC Jest tests with coverage, uploads results to Codecov.
# Skips execution when no LWC changes are present in the delta package.

name: LWC Jest Tests

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version'
        required: false
        type: string
        default: 'lts/*'
      skip-delta-check:
        description: 'Set to true to run Jest regardless of delta content'
        required: false
        type: boolean
        default: false
      artifact-name:
        description: 'Name of the delta package artifact to download (pr mode only)'
        required: false
        type: string
        default: 'delta-package'
    secrets:
      CODECOV_TOKEN:
        description: 'Codecov upload token (required for private repositories)'
        required: false

jobs:
  jest-tests:
    name: LWC Jest Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Download delta package
        id: download-delta
        if: ${{ !inputs.skip-delta-check }}
        continue-on-error: true
        uses: actions/download-artifact@v8
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/

      - name: Check for LWC changes in delta
        id: check-lwc
        run: |
          if [ "${{ inputs.skip-delta-check }}" == "true" ]; then
            echo "run-jest=true" >> $GITHUB_OUTPUT
          elif [ "${{ steps.download-delta.outcome }}" != "success" ]; then
            echo "No delta artifact found — skipping Jest tests."
            echo "run-jest=false" >> $GITHUB_OUTPUT
          elif grep -q '<types>' delta/package/package.xml 2>/dev/null; then
            echo "--- Analyzing PR content for LWC ---"
            pipx install yq >/dev/null
            LWCCOUNT=$(cat delta/package/package.xml | xq -j | jq -r '.Package.types | [.] | flatten | map(select(.name | IN("LightningWebComponent"))) | .[] | .members | [.] | flatten | length')
            if [ "$LWCCOUNT" -eq 0 ]; then
              echo "run-jest=false" >> $GITHUB_OUTPUT
            else
              echo "run-jest=true" >> $GITHUB_OUTPUT
            fi
          else
            echo "run-jest=false" >> $GITHUB_OUTPUT
          fi

      - name: Setup Node.js
        if: steps.check-lwc.outputs.run-jest == 'true'
        uses: actions/setup-node@v6
        with:
          node-version: ${{ inputs.node-version }}
          cache: npm

      - name: Install dependencies
        if: steps.check-lwc.outputs.run-jest == 'true'
        run: npm ci

      - name: Run LWC Jest tests with coverage
        if: steps.check-lwc.outputs.run-jest == 'true'
        run: npm run test:unit:coverage

      - name: Upload code coverage for LWC to Codecov
        if: steps.check-lwc.outputs.run-jest == 'true'
        uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          flags: LWC

A.2.8 - Apex Tests (PR-Scoped)

Runs Apex tests scoped to the PR delta. Uses RunSpecifiedTests when changed Apex classes are detected, falls back to RunLocalTests otherwise. Parses results into a structured PR comment via the apex-test-report composite action and uploads coverage to Codecov.

The apex-list composite action enumerates all ApexClass and ApexTrigger names from the CI org so that the report can surface classes and triggers absent from the coverage report (i.e. never exercised by any test). This list is the distinguishing signal between "low coverage" and "no coverage at all".

# .github/workflows/sf-apex-tests-pr.yml
# Reusable: runs Apex tests scoped to the PR delta.
# Uses RunSpecifiedTests when changed Apex classes are detected,
# falls back to RunLocalTests otherwise. Posts structured results as a PR comment.

name: Apex Tests (PR)

on:
  workflow_call:
    inputs:
      coverage-threshold:
        description: 'Minimum Apex coverage percentage to flag in PR comment'
        required: false
        type: string
        default: '80'
      artifact-name:
        description: 'Name of the delta package artifact to download (pr mode only)'
        required: false
        type: string
        default: 'delta-package'
    secrets:
      SFDX_AUTH_URL:
        required: true
      CODECOV_TOKEN:
        required: false

jobs:
  apex-tests:
    name: Apex Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    env:
      TEST_RESULTS_DIR: test-results
      ORG_ALIAS: ci-org
      HAS_CODECOV: ${{ secrets.CODECOV_TOKEN != '' }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to CI sandbox (SFDX URL)
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: ${{ env.ORG_ALIAS }}

      - name: Download delta package
        id: download-delta
        continue-on-error: true
        uses: actions/download-artifact@v8
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/

      - name: Determine test level from delta
        id: check-apex
        run: |
          if [ "${{ steps.download-delta.outcome }}" != "success" ]; then
            echo "No delta artifact found — skipping Apex tests."
            echo "test-level=skip" >> $GITHUB_OUTPUT
            echo "classes=" >> $GITHUB_OUTPUT
          elif grep -q '<types>' delta/package/package.xml 2>/dev/null; then
            echo "--- Analyzing PR content for Apex Classes ---"
            pipx install yq >/dev/null
            CLASSES=$(cat delta/package/package.xml | xq -j | jq -r '.Package.types | [.] | flatten \
              | map(select(.name | IN("ApexClass"))) | .[] | .members | [.] \
              | flatten | map(select(. | index("*") | not)) | unique | join(" --class-names ")')
            if [ -z "$CLASSES" ]; then
              echo "test-level=RunLocalTests" >> $GITHUB_OUTPUT
              echo "classes=" >> $GITHUB_OUTPUT
            else
              echo "test-level=RunSpecifiedTests" >> $GITHUB_OUTPUT
              echo "classes=$CLASSES" >> $GITHUB_OUTPUT
            fi
          else
            echo "---- No changes to test ----"
            echo "test-level=skip" >> $GITHUB_OUTPUT
            echo "classes=" >> $GITHUB_OUTPUT
          fi

      - name: Run Apex tests (RunSpecifiedTests)
        if: steps.check-apex.outputs.test-level == 'RunSpecifiedTests'
        continue-on-error: true
        id: run-specified
        run: |
          sf apex run test \
            --target-org "$ORG_ALIAS" \
            --test-level RunSpecifiedTests \
            --class-names ${{ steps.check-apex.outputs.classes }} \
            --result-format json \
            --code-coverage \
            --output-dir "$TEST_RESULTS_DIR" \
            --wait 60

      - name: Run Apex tests (RunLocalTests fallback)
        if: steps.check-apex.outputs.test-level == 'RunLocalTests'
        continue-on-error: true
        id: run-local
        run: |
          sf apex run test \
            --target-org "$ORG_ALIAS" \
            --test-level RunLocalTests \
            --result-format json \
            --code-coverage \
            --output-dir "$TEST_RESULTS_DIR" \
            --wait 60

      - name: List Apex classes and triggers from org
        if: always() && (steps.run-specified.outcome == 'success' || steps.run-specified.outcome == 'failure' || steps.run-local.outcome == 'success' || steps.run-local.outcome == 'failure') && steps.check-apex.outputs.test-level != 'skip'
        id: org-apex
        continue-on-error: true
        uses: ./.github/actions/apex-list
        with:
          target-org: ${{ env.ORG_ALIAS }}
          output-file: org-apex.txt

      - name: Parse test results and write summary
        if: always() && (steps.run-specified.outcome == 'success' || steps.run-specified.outcome == 'failure' || steps.run-local.outcome == 'success' || steps.run-local.outcome == 'failure') && steps.check-apex.outputs.test-level != 'skip'
        id: report
        uses: ./.github/actions/apex-test-report
        with:
          results-dir: ${{ env.TEST_RESULTS_DIR }}
          coverage-threshold: ${{ inputs.coverage-threshold }}
          org-apex-file: ${{ steps.org-apex.outputs.file }}

      - name: Build PR comment body
        if: always() && steps.report.outcome == 'success'
        id: comment-body
        env:
          OUTCOME: ${{ steps.report.outputs.outcome }}
          PASSING: ${{ steps.report.outputs.passing }}
          FAILING: ${{ steps.report.outputs.failing }}
          COVERAGE: ${{ steps.report.outputs.coverage }}
          FAILING_MD: ${{ steps.report.outputs.failing-markdown }}
          LOW_COV_MD: ${{ steps.report.outputs.low-coverage-markdown }}
          UNTESTED_MD: ${{ steps.report.outputs.untested-markdown }}
          THRESHOLD: ${{ inputs.coverage-threshold }}
        run: |
          STATUS="PASSED"
          if [ "$OUTCOME" != "Passed" ]; then STATUS="FAILED"; fi
          {
            echo "body<<EOF_BODY"
            echo "## Apex Test Results - $STATUS"
            echo ""
            echo "**Outcome:** $OUTCOME | **Passed:** $PASSING | **Failed:** $FAILING | **Coverage:** $COVERAGE"
            echo ""
            if [ -n "$FAILING_MD" ]; then
              echo "### Failing Tests"
              echo ""
              echo "$FAILING_MD"
              echo ""
            fi
            if [ -n "$LOW_COV_MD" ]; then
              echo "### Classes Below ${THRESHOLD}% Coverage"
              echo ""
              echo "$LOW_COV_MD"
              echo ""
            fi
            if [ -n "$UNTESTED_MD" ]; then
              echo "### Untested Classes and Triggers"
              echo ""
              echo "$UNTESTED_MD"
            fi
            echo "EOF_BODY"
          } >> "$GITHUB_OUTPUT"

      - name: Post PR comment
        if: always() && steps.comment-body.outcome == 'success'
        uses: ./.github/actions/post-pr-comment
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          marker: '## Apex Test Results'
          body: ${{ steps.comment-body.outputs.body }}

      - name: Upload coverage to CodeCov
        if: (steps.run-specified.outcome == 'success' || steps.run-specified.outcome == 'failure' || steps.run-local.outcome == 'success' || steps.run-local.outcome == 'failure') && env.HAS_CODECOV == 'true' && steps.check-apex.outputs.test-level != 'skip'
        uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ${{ env.TEST_RESULTS_DIR }}/test-result-codecoverage.json
          flags: apex
          name: apex-coverage-${{ github.event.pull_request.number }}
          fail_ci_if_error: false
          verbose: false

      - name: Fail if tests did not pass
        if: |
          steps.run-specified.outcome == 'failure' ||
          steps.run-local.outcome == 'failure'
        run: |
          echo "::error::Apex test execution failed."
          exit 1

A.2.9 - Apex Tests (Full Org)

Runs full-org RunLocalTests. Creates a GitHub issue on failure. Intended for scheduled surveillance, not PR-time gating.

# .github/workflows/sf-apex-tests.yml
# Reusable: runs full-org Apex tests (RunLocalTests).
# Intended for scheduled/nightly surveillance, not PR-time gating.
# Creates a GitHub issue on failure.

name: Apex Tests (Full Org)

on:
  workflow_call:
    inputs:
      wait-minutes:
        description: 'Minutes to wait for test run to complete'
        required: false
        type: string
        default: '120'
    secrets:
      SFDX_AUTH_URL:
        required: true
      CODECOV_TOKEN:
        required: false

jobs:
  full-org-tests:
    name: Full-Org Apex Test Run
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    env:
      TEST_RESULTS_DIR: test-results
      ORG_ALIAS: monitor-org
      HAS_CODECOV: ${{ secrets.CODECOV_TOKEN != '' }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to monitoring org (SFDX URL)
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: ${{ env.ORG_ALIAS }}

      - name: Run full org Apex tests
        id: run-tests
        continue-on-error: true
        run: |
          sf apex run test \
            --target-org "$ORG_ALIAS" \
            --test-level RunLocalTests \
            --result-format json \
            --code-coverage \
            --output-dir "$TEST_RESULTS_DIR" \
            --wait ${{ inputs.wait-minutes }}

      - name: List Apex classes and triggers from org
        if: always() && (steps.run-tests.outcome == 'success' || steps.run-tests.outcome == 'failure')
        id: org-apex
        continue-on-error: true
        uses: ./.github/actions/apex-list
        with:
          target-org: ${{ env.ORG_ALIAS }}
          output-file: org-apex.txt

      - name: Parse test results and write summary
        if: always() && (steps.run-tests.outcome == 'success' || steps.run-tests.outcome == 'failure')
        uses: ./.github/actions/apex-test-report
        with:
          results-dir: ${{ env.TEST_RESULTS_DIR }}
          org-apex-file: ${{ steps.org-apex.outputs.file }}

      - name: Upload coverage to CodeCov
        if: (steps.run-tests.outcome == 'success' || steps.run-tests.outcome == 'failure') && env.HAS_CODECOV == 'true'
        uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ${{ env.TEST_RESULTS_DIR }}/test-result-codecoverage.json
          flags: apex
          name: apex-coverage-full-org
          fail_ci_if_error: false
          verbose: false

      - name: Create GitHub issue on failure
        if: steps.run-tests.outcome == 'failure'
        uses: actions/github-script@v9
        with:
          script: |
            const date = new Date().toISOString().split('T')[0];
            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `[Test Failure] Full-org Apex test run failed - ${date}`,
              body: `## Full-Org Apex Test Failure\n\nFailures detected that were not caught by PR-time scoped test execution.\n\nReview the [workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.\n\n_Detected by nightly surveillance on ${date}._`,
              labels: ['test-failure', 'needs-triage']
            });

A.2.10 - Apex Compilation Check

Retrieves all ApexClass and ApexTrigger metadata from the monitoring org and runs a dry-run deployment with NoTestRun to verify org-wide compilation. Unlike the delta-scoped validation in A.2.11, this check covers the entire org and catches compilation failures not surfaced by PR-scoped gates. Creates a GitHub issue on failure.

# .github/workflows/sf-apex-compilation.yml
# Reusable: verifies that all Apex classes and triggers in the org compile
# cleanly by running a dry-run deployment with NoTestRun.
#
# Unlike sf-validate-deployment.yml (which validates a delta package against
# a manifest), this workflow deploys all ApexClass and ApexTrigger metadata
# types to catch org-wide compilation failures not caught by PR-scoped gates.
# Intended for scheduled/nightly surveillance — not PR-time gating.
# Creates a GitHub issue on failure.

name: Apex Compilation Check

on:
  workflow_call:
    inputs:
      wait-minutes:
        description: 'Minutes to wait for the compilation dry-run to complete'
        required: false
        type: string
        default: '60'
    secrets:
      SFDX_AUTH_URL:
        required: true

jobs:
  apex-compilation:
    name: Apex Compilation Check
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to monitoring org (SFDX URL)
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: monitor-org

      - name: Retrieve all Apex from org
        run: |
          # Pull every ApexClass and ApexTrigger from the org into the default
          # package directory so the dry-run deploy below covers the full org,
          # not just what happens to be checked in.
          sf project retrieve start \
            --target-org monitor-org \
            --metadata ApexClass ApexTrigger \
            --wait ${{ inputs.wait-minutes }}

      - name: Run Apex compilation check
        id: compile
        continue-on-error: true
        run: |
          # Dry-run deployment of all Apex compiles every class and trigger
          # without executing tests, confirming compilation integrity.
          sf project deploy start \
            --dry-run \
            --target-org monitor-org \
            --metadata ApexClass ApexTrigger \
            --test-level NoTestRun \
            --ignore-conflicts \
            --wait ${{ inputs.wait-minutes }} \
            --json > compile-results.json

      - name: Write job summary
        if: always()
        run: |
          echo "## Apex Compilation Check" >> $GITHUB_STEP_SUMMARY
          STATUS=$(jq -r '.status' compile-results.json 2>/dev/null || echo "unknown")
          if [ "$STATUS" == "0" ]; then
            echo "All Apex compiled successfully." >> $GITHUB_STEP_SUMMARY
          else
            echo "Compilation failures detected." >> $GITHUB_STEP_SUMMARY
            jq -r '.result.details.componentFailures[]? | "- \(.fullName): \(.problem)"' \
              compile-results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true
          fi

      - name: Create GitHub issue on compilation failure
        if: steps.compile.outcome == 'failure'
        uses: actions/github-script@v9
        with:
          script: |
            const date = new Date().toISOString().split('T')[0];
            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `[Compilation Failure] Apex compilation check failed - ${date}`,
              body: `## Apex Compilation Failure\n\nCompilation failures detected that were not caught at PR time.\n\nReview the [workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.\n\n_Detected by nightly surveillance on ${date}._`,
              labels: ['compilation-failure', 'needs-triage']
            });

A.2.11 - Metadata Validation

Runs sf project deploy start --dry-run against the target org using the delta package manifest. Guards against empty deltas (skips cleanly rather than erroring against an empty manifest). Posts component failure details as a PR comment.

# .github/workflows/sf-validate-deployment.yml
# Reusable: runs sf project deploy start --dry-run against the target org.
# Validates that the delta package is deployable in context.
# Posts component failure details as a PR comment.

name: Metadata Validation

on:
  workflow_call:
    inputs:
      test-level:
        description: 'Test level for validation run'
        required: false
        type: string
        default: 'RunLocalTests'
      wait-minutes:
        description: 'Minutes to wait for validation to complete'
        required: false
        type: string
        default: '60'
      artifact-name:
        description: 'Name of the delta package artifact to download (pr mode only)'
        required: false
        type: string
        default: 'delta-package'
    secrets:
      SFDX_AUTH_URL_VALIDATION:
        required: true

jobs:
  validate-deploy:
    name: Metadata Validation
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to validation org (SFDX URL)
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL_VALIDATION }}
          alias: validation-org

      - name: Download delta package
        id: download-delta
        uses: actions/download-artifact@v8
        continue-on-error: true
        with:
          name: ${{ inputs.artifact-name }}
          path: delta/

      - name: Check for deployable metadata
        id: check-delta
        run: |
          if [ "${{ steps.download-delta.outcome }}" != "success" ] || [ ! -f delta/package/package.xml ]; then
            echo "has-metadata=false" >> $GITHUB_OUTPUT
            echo "---- No delta package artifact found - exiting gracefully ----"
          elif grep -q '<types>' delta/package/package.xml 2>/dev/null; then
            echo "has-metadata=true" >> $GITHUB_OUTPUT
            echo "--- Deployable metadata detected - proceeding with validation ---"
          else
            echo "has-metadata=false" >> $GITHUB_OUTPUT
            echo "---- No deployable metadata in delta - skipping validation ----"
          fi

      - name: Validate delta deployment
        if: steps.check-delta.outputs.has-metadata == 'true'
        run: |
          sf project deploy start \
            --dry-run \
            --target-org validation-org \
            --manifest delta/package/package.xml \
            --test-level ${{ inputs.test-level }} \
            --wait ${{ inputs.wait-minutes }} \
            --json > validation-results.json || true

      - name: Build PR comment body
        if: steps.check-delta.outputs.has-metadata == 'true' && hashFiles('validation-results.json') != ''
        id: comment-body
        run: |
          STATUS_CODE=$(jq -r '.status' validation-results.json)
          if [ "$STATUS_CODE" = "0" ]; then
            STATUS="PASSED"
            STATUS_LABEL="Passed"
          else
            STATUS="FAILED"
            STATUS_LABEL="Failed"
          fi
          FAILURE_TABLE=$(jq -r '
            .result.details.componentFailures
            | if type == "array" and length > 0 then
                "### Component Failures\n\n| Component | Type | Problem |\n|---|---|---|\n"
                + (map("| `\(.fullName)` | \(.componentType) | \(.problem) |") | join("\n"))
              else "" end
          ' validation-results.json)
          {
            echo "body<<EOF_BODY"
            echo "## Metadata Validation - $STATUS"
            echo ""
            echo "**Status:** $STATUS_LABEL"
            echo ""
            if [ -n "$FAILURE_TABLE" ]; then
              echo "$FAILURE_TABLE"
            fi
            echo "EOF_BODY"
          } >> "$GITHUB_OUTPUT"

      - name: Post PR comment
        if: steps.comment-body.outcome == 'success'
        uses: ./.github/actions/post-pr-comment
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          marker: '## Metadata Validation'
          body: ${{ steps.comment-body.outputs.body }}

      - name: Fail if validation did not pass
        if: steps.check-delta.outputs.has-metadata == 'true'
        run: |
          if [ ! -f validation-results.json ]; then
            echo "::error::validation-results.json not found - deployment validation did not produce a results file."
            exit 1
          fi
          STATUS=$(jq -r '.status' validation-results.json)
          if [ "$STATUS" != "0" ]; then
            echo "::error::Metadata validation failed."
            exit 1
          fi

A.2.12 - Metadata Drift Detection

Retrieves current org metadata via sf project retrieve start, compares against the repository state using git diff, and creates a GitHub issue when drift is detected. The git add -N step is critical: sf project retrieve start writes files as untracked, which git diff ignores by default; git add -N registers them with intent-to-add so new files surface in the diff alongside content changes.

# .github/workflows/sf-drift-detection.yml
# Reusable: compares org state to repository state via sf project retrieve.
# Creates a GitHub issue when drift is detected.
# Intended for scheduled/nightly surveillance workflows.

name: Metadata Drift Detection

on:
  workflow_call:
    inputs:
      manifest-path:
        description: 'Path to the package.xml manifest for retrieval'
        required: false
        type: string
        default: 'manifest/package.xml'
    secrets:
      SFDX_AUTH_URL:
        required: true
    outputs:
      drift-detected:
        description: 'Whether drift was detected'
        value: ${{ jobs.drift-detection.outputs.drift-detected }}

jobs:
  drift-detection:
    name: Metadata Drift Detection
    runs-on: ubuntu-latest
    permissions:
      issues: write
      contents: read
    outputs:
      drift-detected: ${{ steps.drift.outputs.drift-detected }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set up Salesforce environment
        uses: ./.github/actions/setup-sf
        with:
          install-code-analyzer: 'false'
          install-sfdx-git-delta: 'false'

      - name: Authenticate to monitoring org (SFDX URL)
        uses: ./.github/actions/sf-auth-sfdx-url
        with:
          sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
          alias: monitor-org

      - name: Retrieve current org metadata
        run: |
          mkdir -p org-state
          sf project retrieve start \
            --target-org monitor-org \
            --manifest ${{ inputs.manifest-path }} \
            --output-dir org-state \
            --wait 30

      - name: Compare org state to repository
        id: drift
        run: |
          # `sf project retrieve start` writes files as untracked — `git diff`
          # ignores those. `git add -N` registers them with intent-to-add so
          # the subsequent diff surfaces new files alongside content changes.
          git add -N org-state/ 2>/dev/null || true
          DIFF=$(git diff --stat HEAD -- org-state/ 2>/dev/null || echo "")
          if [ -n "$DIFF" ]; then
            echo "drift-detected=true" >> $GITHUB_OUTPUT
            echo "$DIFF" > drift-report.txt
          else
            echo "drift-detected=false" >> $GITHUB_OUTPUT
          fi

      - name: Create GitHub issue for drift
        if: steps.drift.outputs.drift-detected == 'true'
        uses: actions/github-script@v9
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('drift-report.txt', 'utf8');
            const date = new Date().toISOString().split('T')[0];
            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `[Drift Detected] Org metadata divergence - ${date}`,
              body: `## Metadata Drift Report\n\nDivergence detected between repository state and org.\n\n### Changed Files\n\`\`\`\n${report}\n\`\`\`\n\nResolve via a PR so the change enters the quality gate chain.\n\n_Detected by nightly surveillance on ${date}._`,
              labels: ['drift-detected', 'needs-triage']
            });

      - name: Write job summary
        if: always()
        run: |
          echo "## Metadata Drift Detection" >> $GITHUB_STEP_SUMMARY
          if [ "${{ steps.drift.outputs.drift-detected }}" == "true" ]; then
            echo "**Drift detected.** A GitHub issue has been created." >> $GITHUB_STEP_SUMMARY
          else
            echo "**No drift detected.**" >> $GITHUB_STEP_SUMMARY
          fi

A.2.13 - Slack Notification

Posts a structured notification to Slack via an incoming webhook. The inputs interface is stable: calling workflows require no changes if the posting mechanism is later swapped for the Slack GitHub App or another tool.

# .github/workflows/sf-notify-slack.yml
# Reusable workflow: posts a notification to a Slack channel.
#
# This implementation uses a raw curl call to a Slack incoming webhook URL,
# stored as the SLACK_WEBHOOK_URL secret. This is the lowest-friction option
# and requires no Slack app installation.
#
# Alternative: replace the curl step with slackapi/slack-github-action
# if your organisation uses the Slack GitHub App rather than incoming webhooks.
# The inputs interface of this reusable workflow is stable across either
# implementation - calling workflows require no changes when the posting
# mechanism is swapped.

name: Notify Slack

on:
  workflow_call:
    inputs:
      status:
        description: "Status label - e.g. 'Failure' or 'Warning'"
        required: true
        type: string
      title:
        description: 'Short title for the notification'
        required: true
        type: string
      message:
        description: 'Body text describing the event'
        required: true
        type: string
      run-url:
        description: 'URL to the GitHub Actions workflow run'
        required: true
        type: string
    secrets:
      SLACK_WEBHOOK_URL:
        required: true

jobs:
  notify:
    name: Post Slack Notification
    runs-on: ubuntu-latest
    steps:
      - name: Post message to Slack
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \
            -H "Content-Type: application/json" \
            -d '{
              "blocks": [
                {
                  "type": "header",
                  "text": {
                    "type": "plain_text",
                    "text": "${{ inputs.status }}: ${{ inputs.title }}"
                  }
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "${{ inputs.message }}"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": { "type": "plain_text", "text": "View Workflow Run" },
                      "url": "${{ inputs.run-url }}"
                    }
                  ]
                }
              ]
            }'

A.2.14 - Release Please

Maintains a rolling Release PR via googleapis/release-please-action. Every merge to main that carries a Conventional Commits message updates the Release PR's changelog and version. When the Release PR is itself merged, Release Please creates a tagged GitHub Release with the generated changelog as release notes.

Release Please ships a native sfdx release type that understands sfdx-project.json versioning, making this the correct release-type for Salesforce projects.

Token requirement: Release Please must open and update pull requests. If you want subsequent CI workflows to run on the Release PR, the workflow must use a PAT or GitHub App token rather than the default GITHUB_TOKEN (GITHUB_TOKEN-created PRs do not trigger further workflow runs by design).

Version bump rules (for PRs landing as Conventional Commits-formatted squash merges on main):

Type/Commit Prefix SemVer bump Usage Example
feat!: or BREAKING CHANGE: footer Major (X.0.0) A feature that is a breaking change.
This is extremely rare, but can happen in packages if significant metadata is deleted.
feat(permissions)!: replace legacy profile with permission set model
feat: Minor (x.Y.0) A new feature feat(case): add approval routing to case escalation flow
fix: Patch (x.y.Z) A bug fix fix(contact): handle null account on contact trigger
docs: No bump (changelog entry only) Changes to documentation only docs(AccountSelector): add example code to all methods
test: Adding/updating tests only (Agentforce, Apex, Flow, etc.) test(AccountTriggerHandler): add tests for ServiceNow integration
chore: Updating tasks, scripts, changelog, release chore: update PMD ruleset threshold
ci: Changes to CI configuration and scripts (GitHub Actions workflow) ci(deps): Bump actions/checkout from 3 to 4
build: Changes to dependencies specified in package.json build(deps-dev): Bump lint-staged from 13.2.0 to 13.2.1
revert: Revert a previous commit revert: Revert commits 676304e, a215968
style: Formatting, etc. for non-Salesforce metadata files such as documentation (hidden - rarely used)
refactor: Refactoring production code without adding new features, e.g. renaming a variable (hidden - rarely used)
perf: Improving performance such as optimizing a query or removing a loop (hidden - rarely used)
# .github/workflows/release-please.yml
# Reusable workflow: automated release management via release-please.
# Called from push-quality-baseline.yml on pushes to main only.
#
# Maintains a rolling Release PR that accumulates unreleased changes,
# bumps the version in sfdx-project.json, and creates a tagged GitHub
# Release when the Release PR is merged.

name: Release Please (Reusable)

on:
  workflow_call:
    secrets:
      RELEASE_PLEASE_TOKEN:
        description: >
          PAT or GitHub App token with contents:write and pull-requests:write.
          Required so that the Release PR triggers subsequent CI workflow runs.
          The default GITHUB_TOKEN will not trigger further workflows on PRs it creates.
        required: true
      SLACK_WEBHOOK_URL:
        required: false
    outputs:
      release_created:
        description: 'Whether a GitHub Release was created on this run'
        value: ${{ jobs.release-please.outputs.release_created }}
      tag_name:
        description: 'The tag name of the created release, if any'
        value: ${{ jobs.release-please.outputs.tag_name }}
      version:
        description: 'The version string of the created release, if any'
        value: ${{ jobs.release-please.outputs.version }}
      sha:
        description: 'The commit SHA of the created release, if any'
        value: ${{ jobs.release-please.outputs.sha }}

# Permissions are declared at the reusable workflow level.
# Reusable workflows do not inherit permissions from calling workflows;
# they must declare their own.
permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  release-please:
    name: Release Please
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
      tag_name: ${{ steps.release.outputs.tag_name }}
      version: ${{ steps.release.outputs.version }}
      sha: ${{ steps.release.outputs.sha }}
    steps:
      - name: Run release-please
        id: release
        uses: googleapis/release-please-action@v5
        with:
          # PAT required - GITHUB_TOKEN-created PRs do not trigger CI workflows.
          token: ${{ secrets.RELEASE_PLEASE_TOKEN }}

          # sfdx release type: updates version in sfdx-project.json.
          release-type: sfdx

          # release-please-config.json defines release strategy and changelog sections.
          config-file: release-please-config.json

          # .release-please-manifest.json tracks the current released version.
          # Must be initialised correctly at project kickstart - an incorrect
          # initial version produces unexpected first-release bumps that are
          # difficult to reverse once tags are pushed.
          manifest-file: .release-please-manifest.json

  # Notify Slack only when a GitHub Release is actually created -
  # not on every push to main. release_created is false on pushes
  # that only update the rolling Release PR without tagging.
  notify-release:
    name: Notify Slack - New Release
    needs: release-please
    if: needs.release-please.outputs.release_created == 'true'
    uses: ./.github/workflows/sf-notify-slack.yml
    with:
      status: 'Released'
      title: 'New Release: ${{ needs.release-please.outputs.tag_name }}'
      message: >
        Version ${{ needs.release-please.outputs.version }} has been tagged
        and released from commit ${{ needs.release-please.outputs.sha }}.
      run-url: >
        https://github.com/${{ github.repository }}/releases/tag/${{ needs.release-please.outputs.tag_name }}
    secrets:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

release-please-config.json (placed at the repository root):

{
    "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
    "release-type": "sfdx",
    "changelog-sections": [
        { "type": "feat", "section": "Features", "hidden": false },
        { "type": "fix", "section": "Bug Fixes", "hidden": false },
        { "type": "test", "section": "Tests", "hidden": false },
        { "type": "chore", "section": "Miscellaneous Chores", "hidden": false },
        { "type": "docs", "section": "Documentation", "hidden": false },
        { "type": "revert", "section": "Reverts", "hidden": false },
        { "type": "deps", "section": "Dependencies", "hidden": false },
        { "type": "build", "section": "Build System", "hidden": false },
        { "type": "ci", "section": "Continuous Integration", "hidden": false },
        { "type": "style", "section": "Styles", "hidden": true },
        { "type": "refactor", "section": "Code Refactoring", "hidden": true },
        { "type": "perf", "section": "Performance Improvements", "hidden": true }
    ],
    "packages": {
        ".": {}
    }
}

.release-please-manifest.json (placed at the repository root). Initialise this file manually before the first release. The value must match the current state of the repository: if pre-release, use 0.1.0; if the codebase has been delivered before without Release Please, set it to the last known version so the first automated release produces the correct next version:

{
    ".": "0.1.0"
}