[
  {
    "path": ".claude-plugin/marketplace.json",
    "content": "{\n  \"name\": \"hashicorp\",\n  \"owner\": {\n    \"name\": \"HashiCorp\"\n  },\n  \"metadata\": {\n    \"description\": \"Official HashiCorp plugins and skills for Claude Code\",\n    \"version\": \"1.0.0\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"terraform-code-generation\",\n      \"source\": \"./terraform/code-generation\",\n      \"description\": \"Terraform code generation skills including HCL generation, style guides, and testing.\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"HashiCorp\"\n      },\n      \"keywords\": [\"terraform\", \"hcl\", \"infrastructure\", \"iac\", \"testing\", \"style-guide\"],\n      \"category\": \"integration\",\n      \"license\": \"MPL-2.0\",\n      \"strict\": false\n    },\n    {\n      \"name\": \"terraform-module-generation\",\n      \"source\": \"./terraform/module-generation\",\n      \"description\": \"Terraform module generation and refactoring skills including module design and Terraform Stacks.\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"HashiCorp\"\n      },\n      \"keywords\": [\"terraform\", \"modules\", \"infrastructure\", \"iac\", \"stacks\", \"refactoring\"],\n      \"category\": \"integration\",\n      \"license\": \"MPL-2.0\",\n      \"strict\": false\n    },\n    {\n      \"name\": \"terraform-provider-development\",\n      \"source\": \"./terraform/provider-development\",\n      \"description\": \"Terraform provider development skills including resources, data sources, actions, and acceptance testing.\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"HashiCorp\"\n      },\n      \"keywords\": [\"terraform\", \"provider\", \"plugin-framework\", \"resources\", \"testing\"],\n      \"category\": \"integration\",\n      \"license\": \"MPL-2.0\",\n      \"strict\": false\n    },\n    {\n      \"name\": \"packer-builders\",\n      \"source\": \"./packer/builders\",\n      \"description\": \"Packer builder skills for AWS, Azure, and Windows image creation.\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"HashiCorp\"\n      },\n      \"keywords\": [\"packer\", \"aws\", \"azure\", \"windows\", \"ami\", \"image\", \"builder\"],\n      \"category\": \"integration\",\n      \"license\": \"MPL-2.0\",\n      \"strict\": false\n    },\n    {\n      \"name\": \"packer-hcp\",\n      \"source\": \"./packer/hcp\",\n      \"description\": \"HCP Packer registry integration for tracking and managing image metadata.\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"HashiCorp\"\n      },\n      \"keywords\": [\"packer\", \"hcp-packer\", \"registry\", \"metadata\", \"image-tracking\"],\n      \"category\": \"integration\",\n      \"license\": \"MPL-2.0\",\n      \"strict\": false\n    }\n  ]\n}\n"
  },
  {
    "path": ".copywrite.hcl",
    "content": "schema_version = 1\n\nproject {\n  license        = \"MPL-2.0\"\n  copyright_year = 2025\n\n  # (OPTIONAL) A list of globs that should not have copyright/license headers.\n  # Supports doublestar glob patterns for more flexibility in defining which\n  # files or folders should be ignored\n  header_ignore = [\n    # \"vendor/**\",\n    # \"**autogen**\",\n  ]\n}\n"
  },
  {
    "path": ".github/.tessl/skill-review-cache.json",
    "content": "{\n  \"version\": \"1\",\n  \"last_updated\": \"2026-02-24T00:00:00Z\",\n  \"skills\": {}\n}\n"
  },
  {
    "path": ".github/workflows/tessl-skill-review-comment.yml",
    "content": "# Companion workflow to tessl-skill-eval.yml.\n#\n# The main workflow (Tessl Skill Review) runs on pull_request events, which\n# don't have write access to the PR (especially for fork PRs). To work around\n# this, the main workflow saves the review results and the PR number as\n# artifacts. This workflow triggers on workflow_run (after the main workflow\n# completes), downloads those artifacts, and posts/updates the PR comment\n# with pull-requests: write permission.\n#\n# The PR number is read from the artifact file (pr-comment/pr_number) that\n# was written by the main workflow using github.event.pull_request.number.\nname: Post Tessl Review Comment\n\non:\n  workflow_run:\n    workflows: [\"Tessl Skill Review\"]\n    types: [completed]\n\npermissions:\n  pull-requests: write\n\njobs:\n  post-comment:\n    name: Post PR Comment\n    runs-on: ubuntu-latest\n    if: github.event.workflow_run.event == 'pull_request'\n\n    steps:\n      # Download the artifact produced by the main workflow's review-skills job.\n      # run-id ties this to the specific workflow run that triggered us.\n      - name: Download skill-review-comment artifact\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: skill-review-comment\n          path: skill-review-comment\n          run-id: ${{ github.event.workflow_run.id }}\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      # Read the PR number and comment body from the artifact, then create or\n      # update the PR comment. Uses an HTML comment marker (<!-- tessl-skill-review -->)\n      # to find and update an existing comment instead of posting duplicates.\n      - name: Post skill review comment\n        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1\n        with:\n          script: |\n            const fs = require('fs');\n            const prNumber = parseInt(fs.readFileSync('skill-review-comment/pr_number', 'utf8').trim());\n            const body = fs.readFileSync('skill-review-comment/comment.md', 'utf8');\n            const marker = '<!-- tessl-skill-review -->';\n\n            const { data: comments } = await github.rest.issues.listComments({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: prNumber,\n            });\n\n            const existing = comments.find(c => c.body.includes(marker));\n\n            if (existing) {\n              await github.rest.issues.updateComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                comment_id: existing.id,\n                body: body,\n              });\n            } else {\n              await github.rest.issues.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: prNumber,\n                body: body,\n              });\n            }\n"
  },
  {
    "path": ".github/workflows/tessl-skill-review.yml",
    "content": "name: Tessl Skill Review\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - '**/SKILL.md'\n      - '**/skills/**'\n      - '.github/workflows/tessl-skill-review.yml'\n  push:\n    branches: [main]\n    paths:\n      - '**/SKILL.md'\n      - '**/skills/**'\n  workflow_dispatch:\n\npermissions:\n  contents: write  # Required for cache commits\n\njobs:\n  review-skills:\n    name: Review Skills\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n        with:\n          node-version: '20'\n\n      - name: Install Tessl CLI\n        run: npm install -g @tessl/cli\n\n      - name: Detect changed skills\n        id: detect\n        env:\n          EVENT_NAME: ${{ github.event_name }}\n          BASE_REF: ${{ github.base_ref }}\n        run: |\n          if [[ \"$EVENT_NAME\" == \"pull_request\" ]]; then\n            CHANGED_SKILLS=$(git diff --name-only --diff-filter=ACMR \\\n              \"origin/${BASE_REF}\"...HEAD \\\n              -- '**/SKILL.md' '**/skills/**' | \\\n              grep 'SKILL.md$' | \\\n              xargs -I {} dirname {} | \\\n              sort -u)\n          else\n            # workflow_dispatch: find all skills\n            CHANGED_SKILLS=$(find . -name \"SKILL.md\" -not -path \"./node_modules/*\" -not -path \"./.git/*\" | \\\n              xargs -I {} dirname {} | \\\n              sed 's|^\\\\./||' | \\\n              sort -u)\n          fi\n\n          if [[ -z \"$CHANGED_SKILLS\" ]]; then\n            echo \"No skill changes detected.\"\n            echo \"skills=\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"Skills to review:\"\n            echo \"$CHANGED_SKILLS\"\n            EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)\n            echo \"skills<<${EOF_MARKER}\" >> \"$GITHUB_OUTPUT\"\n            echo \"$CHANGED_SKILLS\" >> \"$GITHUB_OUTPUT\"\n            echo \"${EOF_MARKER}\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Read review cache\n        if: steps.detect.outputs.skills != ''\n        id: cache\n        run: |\n          CACHE_FILE=\".github/.tessl/skill-review-cache.json\"\n\n          if [[ -f \"$CACHE_FILE\" ]]; then\n            echo \"Cache file found, loading...\"\n            if CACHE_CONTENT=$(cat \"$CACHE_FILE\" 2>&1); then\n              # Validate JSON\n              if echo \"$CACHE_CONTENT\" | jq empty 2>/dev/null; then\n                echo \"cache_exists=true\" >> \"$GITHUB_OUTPUT\"\n                # Export cache to environment for review step\n                EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)\n                echo \"REVIEW_CACHE<<${EOF_MARKER}\" >> \"$GITHUB_ENV\"\n                echo \"$CACHE_CONTENT\" >> \"$GITHUB_ENV\"\n                echo \"${EOF_MARKER}\" >> \"$GITHUB_ENV\"\n              else\n                echo \"::warning::Cache file is invalid JSON, ignoring\"\n                echo \"cache_exists=false\" >> \"$GITHUB_OUTPUT\"\n              fi\n            else\n              echo \"::warning::Cache file exists but cannot be read: $CACHE_CONTENT\"\n              echo \"cache_exists=false\" >> \"$GITHUB_OUTPUT\"\n            fi\n          else\n            echo \"No cache file found, will create new one\"\n            echo \"cache_exists=false\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Run skill reviews\n        if: steps.detect.outputs.skills != ''\n        id: review\n        env:\n          SKILLS: ${{ steps.detect.outputs.skills }}\n          TESSL_API_KEY: ${{ secrets.TESSL_API_KEY }}\n        run: |\n          FAILED=0\n          TABLE=\"| Skill | Status | Review Score | Change |\"\n          TABLE=\"${TABLE}\\\\n|-------|--------|--------------|--------|\"\n          DETAILS=\"\"\n\n          # Create temporary file for cache entries\n          CACHE_FILE_TEMP=\"cache-entries.tsv\"\n          echo \"Cache entries file: $CACHE_FILE_TEMP\"\n\n          while IFS= read -r dir; do\n            [[ -z \"$dir\" ]] && continue\n            echo \"::group::Reviewing $dir\"\n\n            # Run review with --json flag\n            JSON_OUTPUT=$(tessl skill review --json \"$dir\" 2>&1)\n            echo \"$JSON_OUTPUT\"\n            echo \"::endgroup::\"\n\n            # Extract JSON (skip everything before first '{')\n            JSON=$(echo \"$JSON_OUTPUT\" | sed -n '/{/,$p')\n\n            # Look up previous score from cache\n            PREV_SCORE=\"\"\n            PREV_DESC=\"\"\n            PREV_CONTENT=\"\"\n            if [[ -n \"$REVIEW_CACHE\" ]]; then\n              CACHE_ENTRY=$(echo \"$REVIEW_CACHE\" | jq -r --arg path \"$dir\" '.skills[$path] // empty')\n              if [[ -n \"$CACHE_ENTRY\" ]]; then\n                PREV_SCORE=$(echo \"$CACHE_ENTRY\" | jq -r '.score // empty')\n                PREV_DESC=$(echo \"$CACHE_ENTRY\" | jq -r '.dimensions.description // empty')\n                PREV_CONTENT=$(echo \"$CACHE_ENTRY\" | jq -r '.dimensions.content // empty')\n              fi\n            fi\n\n            # Validate PREV_SCORE is numeric\n            if [[ -n \"$PREV_SCORE\" && ! \"$PREV_SCORE\" =~ ^[0-9]+$ ]]; then\n              echo \"::warning::Invalid previous score for $dir: $PREV_SCORE, ignoring\"\n              PREV_SCORE=\"\"\n            fi\n\n            # Validate PREV_DESC and PREV_CONTENT are numeric\n            if [[ -n \"$PREV_DESC\" && ! \"$PREV_DESC\" =~ ^[0-9]+$ ]]; then\n              echo \"::warning::Invalid previous description score for $dir: $PREV_DESC, ignoring\"\n              PREV_DESC=\"\"\n            fi\n            if [[ -n \"$PREV_CONTENT\" && ! \"$PREV_CONTENT\" =~ ^[0-9]+$ ]]; then\n              echo \"::warning::Invalid previous content score for $dir: $PREV_CONTENT, ignoring\"\n              PREV_CONTENT=\"\"\n            fi\n\n            # Extract fields via jq\n            PASSED=$(echo \"$JSON\" | jq -r '.validation.overallPassed // false')\n\n            # Calculate average score from all 8 dimensions\n            AVG_SCORE=$(echo \"$JSON\" | jq -r '\n              def avg(obj): (obj.scores | to_entries | map(.value.score) | add) / (obj.scores | length) * 100 / 3;\n              (\n                [(.descriptionJudge.evaluation | avg(.)), (.contentJudge.evaluation | avg(.))] | add / 2\n              ) | round\n            ')\n\n            # Validate AVG_SCORE is numeric before arithmetic\n            if [[ ! \"$AVG_SCORE\" =~ ^[0-9]+$ ]]; then\n              echo \"::error::Invalid average score calculated for $dir: $AVG_SCORE\"\n              AVG_SCORE=0\n            fi\n\n            # Calculate diff\n            CHANGE=\"\"\n            if [[ -n \"$PREV_SCORE\" ]]; then\n              DIFF=$((AVG_SCORE - PREV_SCORE))\n              if [[ $DIFF -gt 0 ]]; then\n                CHANGE=\"🔺 +${DIFF}% (was ${PREV_SCORE}%)\"\n              elif [[ $DIFF -lt 0 ]]; then\n                CHANGE=\"🔻 ${DIFF}% (was ${PREV_SCORE}%)\"\n              else\n                CHANGE=\"➡️ no change\"\n              fi\n            fi\n\n            # Build status column\n            if [[ \"$PASSED\" == \"true\" ]]; then\n              STATUS=\"✅ PASSED\"\n            else\n              # Extract first validation error\n              ERROR=$(echo \"$JSON\" | jq -r '\n                .validation.checks\n                | map(select(.status != \"passed\"))\n                | first\n                | .message // \"Validation failed\"\n              ' | cut -c1-60)\n              STATUS=\"❌ FAILED — ${ERROR}\"\n              FAILED=1\n            fi\n\n            DIR_DISPLAY=$(echo \"$dir\" | tr '|' '/')\n            TABLE=\"${TABLE}\\\\n| \\`${DIR_DISPLAY}\\` | ${STATUS} | ${AVG_SCORE}% | ${CHANGE} |\"\n\n            # Calculate dimension scores for cache and details\n            DESC_SCORE=$(echo \"$JSON\" | jq -r '\n              (.descriptionJudge.evaluation.scores | to_entries | map(.value.score) | add) * 100 / ((.descriptionJudge.evaluation.scores | length) * 3) | round\n            ')\n            CONTENT_SCORE=$(echo \"$JSON\" | jq -r '\n              (.contentJudge.evaluation.scores | to_entries | map(.value.score) | add) * 100 / ((.contentJudge.evaluation.scores | length) * 3) | round\n            ')\n\n            # Validate dimension scores\n            if [[ ! \"$DESC_SCORE\" =~ ^[0-9]+$ ]]; then\n              echo \"::warning::Invalid description score for $dir: $DESC_SCORE, using 0\"\n              DESC_SCORE=0\n            fi\n            if [[ ! \"$CONTENT_SCORE\" =~ ^[0-9]+$ ]]; then\n              echo \"::warning::Invalid content score for $dir: $CONTENT_SCORE, using 0\"\n              CONTENT_SCORE=0\n            fi\n\n            # --- Extract detailed review for collapsible section ---\n            DESC_EVAL=$(echo \"$JSON\" | jq -r '.descriptionJudge.evaluation |\n              \"  Description: \" + ((.scores | to_entries | map(.value.score) | add) * 100 / ((.scores | length) * 3) | round | tostring) + \"%\\\\n\" +\n              (.scores | to_entries | map(\"    \\(.key): \\(.value.score)/3 - \\(.value.reasoning)\") | join(\"\\\\n\")) + \"\\\\n\\\\n\" +\n              \"    Assessment: \" + .overall_assessment\n            ')\n\n            CONTENT_EVAL=$(echo \"$JSON\" | jq -r '.contentJudge.evaluation |\n              \"  Content: \" + ((.scores | to_entries | map(.value.score) | add) * 100 / ((.scores | length) * 3) | round | tostring) + \"%\\\\n\" +\n              (.scores | to_entries | map(\"    \\(.key): \\(.value.score)/3 - \\(.value.reasoning)\") | join(\"\\\\n\")) + \"\\\\n\\\\n\" +\n              \"    Assessment: \" + .overall_assessment\n            ')\n\n            # Extract suggestions\n            SUGGESTIONS=$(echo \"$JSON\" | jq -r '\n              [.descriptionJudge.evaluation.suggestions // [], .contentJudge.evaluation.suggestions // []]\n              | flatten\n              | map(\"- \" + .)\n              | join(\"\\\\n\")\n            ')\n\n            # Build collapsible details block\n            DETAILS=\"${DETAILS}\\\\n\\\\n<details>\\\\n<summary><strong>${DIR_DISPLAY}</strong> — ${AVG_SCORE}% (${STATUS#* })</summary>\\\\n\\\\n\"\n\n            # Show score comparison if previous exists (all three must be valid)\n            if [[ -n \"$PREV_SCORE\" && -n \"$PREV_DESC\" && -n \"$PREV_CONTENT\" ]]; then\n              DETAILS=\"${DETAILS}**Previous:** ${PREV_SCORE}% (Description: ${PREV_DESC}%, Content: ${PREV_CONTENT}%)\\\\n\"\n              DETAILS=\"${DETAILS}**Current:**  ${AVG_SCORE}% (Description: ${DESC_SCORE}%, Content: ${CONTENT_SCORE}%)\\\\n\\\\n\"\n              DETAILS=\"${DETAILS}---\\\\n\\\\n\"\n            fi\n\n            DETAILS=\"${DETAILS}\\`\\`\\`\\\\n${DESC_EVAL}\\\\n\\\\n${CONTENT_EVAL}\\\\n\\`\\`\\`\\\\n\"\n\n            if [[ -n \"$SUGGESTIONS\" ]]; then\n              DETAILS=\"${DETAILS}\\\\n**Suggestions:**\\\\n\\\\n${SUGGESTIONS}\\\\n\"\n            fi\n\n            DETAILS=\"${DETAILS}\\\\n</details>\"\n\n            # Calculate content hash\n            if [[ ! -f \"$dir/SKILL.md\" ]]; then\n              echo \"::error::SKILL.md not found for $dir\"\n              continue\n            fi\n            CONTENT_HASH=$(shasum -a 256 \"$dir/SKILL.md\" 2>&1)\n            if [[ $? -ne 0 ]]; then\n              echo \"::error::Failed to calculate hash for $dir: $CONTENT_HASH\"\n              continue\n            fi\n            CONTENT_HASH=\"sha256:$(echo \"$CONTENT_HASH\" | awk '{print $1}')\"\n\n            # Build cache entry (compact to single line)\n            if ! CACHE_ENTRY=$(jq -nc \\\n              --arg score \"$AVG_SCORE\" \\\n              --arg passed \"$PASSED\" \\\n              --arg hash \"$CONTENT_HASH\" \\\n              --arg ts \"$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\" \\\n              --arg desc \"$DESC_SCORE\" \\\n              --arg content \"$CONTENT_SCORE\" \\\n              '{\n                score: ($score | tonumber),\n                validation_passed: ($passed == \"true\"),\n                content_hash: $hash,\n                timestamp: $ts,\n                dimensions: {\n                  description: ($desc | tonumber),\n                  content: ($content | tonumber)\n                }\n              }'); then\n              echo \"::error::Failed to build cache entry for $dir\"\n              continue\n            fi\n\n            # Write cache entry to file (tab-separated: path<tab>json)\n            printf '%s\\t%s\\n' \"$dir\" \"$CACHE_ENTRY\" >> \"$CACHE_FILE_TEMP\"\n\n          done <<< \"$SKILLS\"\n\n          # Save cache entries file path for update step\n          echo \"CACHE_ENTRIES_FILE=$CACHE_FILE_TEMP\" >> \"$GITHUB_ENV\"\n          echo \"Wrote $(wc -l < \"$CACHE_FILE_TEMP\") cache entries to $CACHE_FILE_TEMP\"\n\n          # Build PR comment body\n          COMMENT_BODY=$(printf '%b' \"<!-- tessl-skill-review -->\\\\n## Tessl Skill Review Results\\\\n\\\\n${TABLE}\\\\n\\\\n---\\\\n\\\\n### Detailed Review\\\\n${DETAILS}\\\\n\\\\n---\\\\n_Checks: frontmatter validity, required fields, body structure, examples, line count._\\\\n_Review score is informational — not used for pass/fail gating._\")\n\n          EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)\n          echo \"comment<<${EOF_MARKER}\" >> \"$GITHUB_OUTPUT\"\n          echo \"$COMMENT_BODY\" >> \"$GITHUB_OUTPUT\"\n          echo \"${EOF_MARKER}\" >> \"$GITHUB_OUTPUT\"\n\n          echo \"$COMMENT_BODY\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          if [[ \"$FAILED\" -eq 1 ]]; then\n            echo \"::error::One or more skills failed validation checks.\"\n            exit 1\n          fi\n\n      - name: Update review cache\n        if: always() && steps.review.outcome != 'skipped'\n        id: update-cache\n        run: |\n          CACHE_FILE=\".github/.tessl/skill-review-cache.json\"\n          mkdir -p .github/.tessl\n\n          # Load existing cache or create new structure\n          if [[ -f \"$CACHE_FILE\" ]]; then\n            if CACHE=$(cat \"$CACHE_FILE\" 2>&1); then\n              if ! echo \"$CACHE\" | jq empty 2>/dev/null; then\n                echo \"::warning::Cache file is invalid JSON, recreating\"\n                CACHE='{\"version\":\"1\",\"last_updated\":\"\",\"skills\":{}}'\n              fi\n            else\n              echo \"::warning::Cache file exists but cannot be read: $CACHE\"\n              CACHE='{\"version\":\"1\",\"last_updated\":\"\",\"skills\":{}}'\n            fi\n          else\n            echo \"Creating new cache file...\"\n            CACHE='{\"version\":\"1\",\"last_updated\":\"\",\"skills\":{}}'\n          fi\n\n          # Update timestamp\n          TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n          if ! CACHE=$(echo \"$CACHE\" | jq --arg ts \"$TIMESTAMP\" '.last_updated = $ts'); then\n            echo \"::error::Failed to update cache timestamp\"\n            exit 1\n          fi\n\n          # Merge cache updates (using TAB delimiter)\n          MERGED_COUNT=0\n          FAILED_COUNT=0\n\n          if [[ -f \"$CACHE_ENTRIES_FILE\" ]]; then\n            while IFS=$'\\t' read -r skill_path entry_json; do\n              [[ -z \"$skill_path\" ]] && continue\n              if NEW_CACHE=$(echo \"$CACHE\" | jq --arg path \"$skill_path\" --argjson entry \"$entry_json\" \\\n                '.skills[$path] = $entry' 2>&1); then\n                CACHE=\"$NEW_CACHE\"\n                MERGED_COUNT=$((MERGED_COUNT + 1))\n              else\n                echo \"::warning::Failed to merge cache entry for $skill_path: $NEW_CACHE\"\n                FAILED_COUNT=$((FAILED_COUNT + 1))\n                continue\n              fi\n            done < \"$CACHE_ENTRIES_FILE\"\n          fi\n\n          # Write cache file\n          if ! echo \"$CACHE\" | jq '.' > \"$CACHE_FILE\"; then\n            echo \"::error::Failed to write cache file\"\n            exit 1\n          fi\n\n          # Report accurate merge counts\n          if [[ $FAILED_COUNT -gt 0 ]]; then\n            echo \"Cache updated with $MERGED_COUNT entries ($FAILED_COUNT failed)\"\n          else\n            echo \"Cache updated with $MERGED_COUNT entries\"\n          fi\n\n      - name: Upload cache file\n        if: always() && steps.update-cache.outcome == 'success'\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: skill-review-cache\n          path: .github/.tessl/skill-review-cache.json\n\n      - name: Upload cache entries artifact\n        if: always() && steps.review.outcome != 'skipped'\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: cache-entries\n          path: cache-entries.tsv\n          retention-days: 1\n\n      - name: Save PR comment artifact\n        if: >-\n          github.event_name == 'pull_request'\n          && steps.detect.outputs.skills != ''\n          && (steps.review.outcome == 'success' || steps.review.outputs.comment != '')\n        env:\n          COMMENT_BODY: ${{ steps.review.outputs.comment }}\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          mkdir -p pr-comment\n          echo \"$PR_NUMBER\" > pr-comment/pr_number\n          echo \"$COMMENT_BODY\" > pr-comment/comment.md\n\n      - name: Upload PR comment artifact\n        if: >-\n          github.event_name == 'pull_request'\n          && steps.detect.outputs.skills != ''\n          && (steps.review.outcome == 'success' || steps.review.outputs.comment != '')\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: skill-review-comment\n          path: pr-comment/\n\n  commit-cache:\n    name: Commit Cache\n    runs-on: ubuntu-latest\n    needs: review-skills\n    if: github.event_name == 'push' && github.ref == 'refs/heads/main'\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Download cache file\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: skill-review-cache\n\n      - name: Move cache to correct location\n        run: |\n          mkdir -p .github/.tessl\n          mv skill-review-cache.json .github/.tessl/skill-review-cache.json\n\n      - name: Check for cache changes\n        id: check\n        run: |\n          if git diff --quiet HEAD .github/.tessl/skill-review-cache.json; then\n            echo \"changed=false\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"changed=true\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Commit cache\n        if: steps.check.outputs.changed == 'true'\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add .github/.tessl/skill-review-cache.json\n          git commit -m \"chore: update skill review cache [skip ci]\"\n          if ! git push; then\n            echo \"::error::Failed to push cache update to main\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/validate.yml",
    "content": "name: Validate Structure\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  validate-structure:\n    name: Validate Repository Structure\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n\n      - name: Validate structure\n        run: |\n          chmod +x ./scripts/validate-structure.sh\n          ./scripts/validate-structure.sh\n\n  validate-json:\n    name: Validate JSON Files\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n\n      - name: Validate all JSON files\n        run: |\n          echo \"Validating all JSON files...\"\n          ERRORS=0\n\n          for file in $(find . -name \"*.json\" -not -path \"./node_modules/*\" -not -path \"./.git/*\"); do\n            if ! jq empty \"$file\" 2>/dev/null; then\n              echo \"ERROR: Invalid JSON in $file\"\n              ERRORS=$((ERRORS + 1))\n            else\n              echo \"OK: $file\"\n            fi\n          done\n\n          if [[ \"$ERRORS\" -gt 0 ]]; then\n            echo \"Found $ERRORS invalid JSON file(s)\"\n            exit 1\n          fi\n\n          echo \"All JSON files are valid!\"\n\n  validate-skills:\n    name: Validate SKILL.md Files\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n\n      - name: Check SKILL.md frontmatter\n        run: |\n          echo \"Validating SKILL.md files...\"\n          ERRORS=0\n\n          for skill_file in $(find . -name \"SKILL.md\" -not -path \"./node_modules/*\"); do\n            echo \"Checking: $skill_file\"\n\n            # Check file starts with frontmatter\n            if ! head -1 \"$skill_file\" | grep -q \"^---\"; then\n              echo \"ERROR: $skill_file does not start with frontmatter (---)\"\n              ERRORS=$((ERRORS + 1))\n              continue\n            fi\n\n            # Extract frontmatter\n            FRONTMATTER=$(sed -n '/^---$/,/^---$/p' \"$skill_file\" | sed '1d;$d')\n\n            # Check for required fields\n            if ! echo \"$FRONTMATTER\" | grep -q \"^name:\"; then\n              echo \"ERROR: $skill_file missing 'name' in frontmatter\"\n              ERRORS=$((ERRORS + 1))\n            fi\n\n            if ! echo \"$FRONTMATTER\" | grep -q \"^description:\"; then\n              echo \"ERROR: $skill_file missing 'description' in frontmatter\"\n              ERRORS=$((ERRORS + 1))\n            fi\n          done\n\n          if [[ \"$ERRORS\" -gt 0 ]]; then\n            echo \"Found $ERRORS error(s) in SKILL.md files\"\n            exit 1\n          fi\n\n          echo \"All SKILL.md files are valid!\"\n\n  validate-marketplace-references:\n    name: Validate Marketplace References\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n\n      - name: Check marketplace plugin references\n        run: |\n          echo \"Validating marketplace.json plugin references...\"\n          ERRORS=0\n\n          MARKETPLACE=\".claude-plugin/marketplace.json\"\n\n          if [[ ! -f \"$MARKETPLACE\" ]]; then\n            echo \"ERROR: marketplace.json not found\"\n            exit 1\n          fi\n\n          # Check each plugin source path exists\n          for source in $(jq -r '.plugins[].source' \"$MARKETPLACE\"); do\n            # Remove leading ./\n            path=\"${source#./}\"\n\n            if [[ ! -d \"$path\" ]]; then\n              echo \"ERROR: Plugin path does not exist: $path\"\n              ERRORS=$((ERRORS + 1))\n            else\n              echo \"OK: $path exists\"\n            fi\n\n            # Check plugin.json exists in that path\n            if [[ ! -f \"$path/.claude-plugin/plugin.json\" ]]; then\n              echo \"ERROR: plugin.json not found in $path/.claude-plugin/\"\n              ERRORS=$((ERRORS + 1))\n            else\n              echo \"OK: $path/.claude-plugin/plugin.json exists\"\n            fi\n\n            # Check skills directory exists\n            if [[ ! -d \"$path/skills\" ]]; then\n              echo \"ERROR: skills/ directory not found in $path\"\n              ERRORS=$((ERRORS + 1))\n            else\n              echo \"OK: $path/skills/ exists\"\n            fi\n          done\n\n          if [[ \"$ERRORS\" -gt 0 ]]; then\n            echo \"Found $ERRORS error(s) in marketplace references\"\n            exit 1\n          fi\n\n          echo \"All marketplace references are valid!\"\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Agent Instructions\n\nThis repository contains agent skills and Claude Code plugins for HashiCorp products, including Terraform and Packer for infrastructure-as-code development.\n\n## Repository Structure\n\n```\nagent-skills/\n├── terraform/\n│   ├── code-generation/\n│   │   ├── .claude-plugin/plugin.json\n│   │   └── skills/\n│   │       ├── azure-verified-modules/\n│   │       ├── terraform-style-guide/\n│   │       ├── terraform-test/\n│   │       └── terraform-search-import/\n│   ├── module-generation/\n│   │   ├── .claude-plugin/plugin.json\n│   │   └── skills/\n│   │       ├── refactor-module/\n│   │       └── terraform-stacks/\n│   └── provider-development/\n│       ├── .claude-plugin/plugin.json\n│       └── skills/\n│           ├── new-terraform-provider/\n│           ├── run-acceptance-tests/\n│           ├── provider-actions/\n│           └── provider-resources/\n├── packer/\n│   ├── builders/\n│   │   ├── .claude-plugin/plugin.json\n│   │   └── skills/\n│   │       ├── aws-ami-builder/\n│   │       ├── azure-image-builder/\n│   │       └── windows-builder/\n│   └── hcp/\n│       ├── .claude-plugin/plugin.json\n│       └── skills/\n│           └── push-to-registry/\n├── .claude-plugin/marketplace.json\n├── README.md\n└── AGENTS.md\n```\n\n## Installation Methods\n\n### Method 1: Claude Code Plugin Installation\n\nInstall plugins using Claude Code CLI. First add the marketplace, then install plugins:\n\n```bash\n# Add the agent-skills marketplace\nclaude plugin marketplace add hashicorp/agent-skills\n\n# Install plugins\nclaude plugin install terraform-code-generation@hashicorp\nclaude plugin install terraform-module-generation@hashicorp\nclaude plugin install terraform-provider-development@hashicorp\nclaude plugin install packer-builders@hashicorp\nclaude plugin install packer-hcp@hashicorp\n```\n\nOr use the interactive interface within Claude Code:\n```\n/plugin\n```\n\nThis opens a tabbed interface where you can:\n- **Discover**: Browse available plugins from all marketplaces\n- **Installed**: View and manage installed plugins\n- **Marketplaces**: Add, remove, or update marketplaces\n\nAdditional plugin management commands:\n```bash\n# Disable a plugin\nclaude plugin disable terraform-code-generation@hashicorp\n\n# Re-enable a plugin\nclaude plugin enable terraform-code-generation@hashicorp\n\n# Uninstall a plugin\nclaude plugin uninstall terraform-code-generation@hashicorp\n\n# Update a plugin\nclaude plugin update terraform-code-generation@hashicorp\n```\n\nInstallation scopes (use `--scope` flag):\n- `user` (default): Available across all projects\n- `project`: Shared with team via `.claude/settings.json`\n- `local`: Project-specific, gitignored\n\n### Method 2: Individual Skill Installation\n\nInstall individual skills using `npx skills add`:\n\n```bash\n# List all available skills\nnpx skills add hashicorp/agent-skills\n\n# Code generation skills\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-test\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/azure-verified-modules\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-search-import\n\n# Module generation skills\nnpx skills add hashicorp/agent-skills/terraform/module-generation/skills/refactor-module\nnpx skills add hashicorp/agent-skills/terraform/module-generation/skills/terraform-stacks\n\n# Provider development skills\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/new-terraform-provider\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/run-acceptance-tests\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-actions\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-resources\n\n# Packer builder skills\nnpx skills add hashicorp/agent-skills/packer/builders/skills/aws-ami-builder\nnpx skills add hashicorp/agent-skills/packer/builders/skills/azure-image-builder\nnpx skills add hashicorp/agent-skills/packer/builders/skills/windows-builder\n\n# Packer HCP skills\nnpx skills add hashicorp/agent-skills/packer/hcp/skills/push-to-registry\n```\n\nSkills are installed to `~/.claude/skills/` or project `.claude/skills/` directory.\n\n### Method 3: Manual Installation\n\nCopy skills directly to your Claude Code configuration:\n\n```bash\n# Clone the repository\ngit clone https://github.com/hashicorp/agent-skills.git\n\n# Copy a plugin to Claude Code plugins directory\ncp -r agent-skills/terraform/code-generation ~/.claude/plugins/\n\n# Or copy individual skills\ncp -r agent-skills/terraform/code-generation/skills/terraform-style-guide ~/.claude/skills/\n```\n\n## Plugin Contents\n\n### terraform-code-generation\n\nSkills for generating and validating Terraform HCL code:\n\n| Skill | Description |\n|-------|-------------|\n| `terraform-style-guide` | Generate Terraform HCL code following HashiCorp style conventions and best practices |\n| `terraform-test` | Writing and running `.tftest.hcl` test files |\n| `azure-verified-modules` | Azure Verified Modules (AVM) requirements and certification |\n| `terraform-search-import` | Discover existing resources with Terraform Search and bulk import into state |\n\n### terraform-module-generation\n\nSkills for creating and refactoring Terraform modules:\n\n| Skill | Description |\n|-------|-------------|\n| `refactor-module` | Transform monolithic configs into reusable modules |\n| `terraform-stacks` | Multi-region/environment orchestration with Terraform Stacks |\n\n### terraform-provider-development\n\nSkills for developing Terraform providers:\n\n| Skill | Description |\n|-------|-------------|\n| `new-terraform-provider` | Scaffold a new Terraform provider |\n| `run-acceptance-tests` | Run and debug provider acceptance tests |\n| `provider-actions` | Implement provider actions (lifecycle operations) |\n| `provider-resources` | Implement resources and data sources |\n\n### packer-builders\n\nSkills for building images on AWS, Azure, and Windows:\n\n| Skill | Description |\n|-------|-------------|\n| `aws-ami-builder`     | Build Amazon Machine Images (AMIs) with amazon-ebs builder |\n| `azure-image-builder` | Build Azure managed images and Azure Compute Gallery images |\n| `windows-builder`     | Platform-agnostic Windows image patterns with WinRM and PowerShell |\n\n### packer-hcp\n\nSkills for HCP Packer registry integration:\n\n| Skill | Description |\n|-------|-------------|\n| `push-to-registry` | Configure hcp_packer_registry to push build metadata to HCP Packer |\n\n## Skill Format\n\nEach skill directory contains:\n- `SKILL.md` - Main skill definition with YAML frontmatter (`name`, `description`)\n- Optional `assets/`, `references/`, or `resources/` directories\n\n### SKILL.md Frontmatter\n\n```yaml\n---\nname: skill-name\ndescription: Brief description of when to use this skill.\n---\n```\n\n## When to Use Each Plugin\n\n### terraform-code-generation\nUse when:\n- Writing new Terraform configurations\n- Reviewing Terraform code for style compliance\n- Creating test files for Terraform modules\n- Generating HCL for specific providers\n\n### terraform-module-generation\nUse when:\n- Refactoring existing Terraform code into modules\n- Working with Terraform Stacks\n- Designing module interfaces and outputs\n- Managing multi-environment deployments\n\n### terraform-provider-development\nUse when:\n- Creating a new Terraform provider\n- Adding resources or data sources to an existing provider\n- Implementing provider actions\n- Running or debugging acceptance tests\n\n### packer-builders\nUse when:\n- Building AWS AMIs with amazon-ebs builder\n- Creating Azure managed images or Azure Compute Gallery images\n- Building Windows images (AWS, Azure, VMware, etc.)\n- Setting up WinRM and PowerShell provisioners\n- Troubleshooting Windows-specific image build issues\n\n### packer-hcp\nUse when:\n- Integrating Packer builds with HCP Packer registry\n- Tracking image metadata and versions\n- Setting up hcp_packer_registry block\n- Configuring CI/CD to push to HCP Packer\n- Querying HCP Packer images in Terraform\n\n## MCP Server Configuration\n\nAll Terraform plugins include MCP server configuration for the Terraform MCP Server:\n\n```json\n{\n  \"mcpServers\": {\n    \"terraform\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-e\", \"TFE_TOKEN\", \"-e\", \"TFE_ADDRESS\", \"hashicorp/terraform-mcp-server\"],\n      \"env\": {\n        \"TFE_TOKEN\": \"${TFE_TOKEN}\",\n        \"TFE_ADDRESS\": \"${TFE_ADDRESS}\"\n      }\n    }\n  }\n}\n```\n\nSet environment variables for HCP Terraform integration:\n- `TFE_TOKEN` - HCP Terraform API token\n- `TFE_ADDRESS` - HCP Terraform address (optional, defaults to app.terraform.io)\n\n## References\n\n- [Terraform Documentation](https://developer.hashicorp.com/terraform)\n- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Terraform MCP Server](https://github.com/hashicorp/terraform-mcp-server)\n- [Packer Documentation](https://developer.hashicorp.com/packer)\n- [HCP Packer](https://developer.hashicorp.com/hcp/docs/packer)\n- [Packer HCL2 Configuration](https://developer.hashicorp.com/packer/guides/hcl)\n- [Claude Code Plugins](https://docs.anthropic.com/claude-code/plugins)\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to the HashiCorp Agent Skills.\n\n## Unreleased\n\n### Added\n- `terraform-search-import` skill for discovering existing resources with Terraform Search and bulk import\n\n## 0.1.0\n\n### Added\n- 3 Claude Code plugins with 9 total skills\n- `terraform-code-generation`: terraform-style-guide, terraform-test, azure-verified-modules\n- `terraform-module-generation`: refactor-module, terraform-stacks\n- `terraform-provider-development`: new-terraform-provider, run-acceptance-tests, provider-actions, provider-resources\n- Marketplace manifest for Claude Code plugin installation\n- Support for `npx add-skill` installation\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# Default owner\n* @hashicorp/team-proj-mcp-servers\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "# HashiCorp Agent Skills\n\nA collection of Agent skills and Claude Code plugins for HashiCorp products.\n\n| Product | Use cases |\n|:--------|:----------|\n| [Terraform](./terraform/) | Write HCL code, build modules, develop providers, and run tests |\n| [Packer](./packer/) | Build machine images on AWS, Azure, and Windows; integrate with HCP Packer registry |\n\n> **Legal Note:** Your use of a third party MCP Client/LLM is subject solely to the terms of use for such MCP/LLM, and IBM is not responsible for the performance of such third party tools. IBM expressly disclaims any and all warranties and liability for third party MCP Clients/LLMs, and may not be able to provide support to resolve issues which are caused by the third party tools.\n\n## Installation\n\n### Individual Skills\n\nInstall Agent Skills in GitHub Copilot, Claude Code, Opencode, Cursor, and more:\n\n```bash\n# List all skills\nnpx skills add hashicorp/agent-skills\n\n# Install a specific skill\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide\n```\n\n### Claude Code Plugin\n\nFirst, add the marketplace, then install plugins:\n\n```bash\n# Add the HashiCorp marketplace\nclaude plugin marketplace add hashicorp/agent-skills\n\n# Install plugins\nclaude plugin install terraform-code-generation@hashicorp\nclaude plugin install terraform-module-generation@hashicorp\nclaude plugin install terraform-provider-development@hashicorp\nclaude plugin install packer-builders@hashicorp\nclaude plugin install packer-hcp@hashicorp\n```\n\nOr use the interactive interface:\n```bash\n/plugin\n```\n\n## Structure\n\n```\nagent-skills/\n├── .claude-plugin/\n│   └── marketplace.json\n├── terraform/              # Terraform skills\n├── packer/                 # Packer skills\n├── <product>/              # Future products (Vault, Consul, etc.)\n└── README.md\n```\n\nEach product folder contains plugins, and each plugin contains skills:\n\n```\n<product>/\n└── <plugin>/\n    ├── .claude-plugin/plugin.json\n    └── skills/\n        └── <skill>/\n            └── SKILL.md\n```\n\n## License\n\nMPL-2.0\n"
  },
  {
    "path": "packer/README.md",
    "content": "# Packer Skills\n\nAgent skills for building machine images with Packer and HCP Packer.\n\n## Plugins\n\n### packer-builders\n\nSkills for building images on AWS, Azure, and Windows.\n\n| Skill | Description |\n|-------|-------------|\n| aws-ami-builder     | Build Amazon Machine Images (AMIs) with amazon-ebs builder |\n| azure-image-builder | Build Azure managed images and Azure Compute Gallery images |\n| windows-builder     | Platform-agnostic Windows image patterns with WinRM and PowerShell |\n\n### packer-hcp\n\nSkills for HCP Packer registry integration.\n\n| Skill | Description |\n|-------|-------------|\n| push-to-registry | Configure hcp_packer_registry to push build metadata to HCP Packer |\n\n## Installation\n\n### Claude Code Plugin\n\n```bash\nclaude plugin marketplace add hashicorp/agent-skills\n\nclaude plugin install packer-builders@hashicorp\nclaude plugin install packer-hcp@hashicorp\n```\n\n### Individual Skills\n\n```bash\n# Builders\nnpx skills add hashicorp/agent-skills/packer/builders/skills/aws-ami-builder\nnpx skills add hashicorp/agent-skills/packer/builders/skills/azure-image-builder\nnpx skills add hashicorp/agent-skills/packer/builders/skills/windows-builder\n\n# HCP Packer\nnpx skills add hashicorp/agent-skills/packer/hcp/skills/push-to-registry\n```\n\n## Structure\n\n```\npacker/\n├── builders/\n│   ├── .claude-plugin/plugin.json\n│   └── skills/\n│       ├── aws-ami-builder/\n│       ├── azure-image-builder/\n│       └── windows-builder/\n└── hcp/\n    ├── .claude-plugin/plugin.json\n    └── skills/\n        └── push-to-registry/\n```\n\n## References\n\n- [Packer Documentation](https://developer.hashicorp.com/packer)\n- [HCP Packer](https://developer.hashicorp.com/hcp/docs/packer)\n- [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)\n- [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)\n"
  },
  {
    "path": "packer/builders/.claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"packer-builders\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Packer builder skills for AWS, Azure, and Windows image creation.\",\n  \"author\": {\n    \"name\": \"HashiCorp\",\n    \"url\": \"https://github.com/hashicorp\"\n  },\n  \"homepage\": \"https://developer.hashicorp.com/packer\",\n  \"repository\": \"https://github.com/hashicorp/agent-skills\",\n  \"license\": \"MPL-2.0\",\n  \"keywords\": [\"packer\", \"aws\", \"azure\", \"windows\", \"ami\", \"image\", \"builder\"]\n}\n"
  },
  {
    "path": "packer/builders/skills/aws-ami-builder/SKILL.md",
    "content": "---\nname: aws-ami-builder\ndescription: Build Amazon Machine Images (AMIs) with Packer using the amazon-ebs builder. Use when creating custom AMIs for EC2 instances.\n---\n\n# AWS AMI Builder\n\nBuild Amazon Machine Images (AMIs) using Packer's `amazon-ebs` builder.\n\n**Reference:** [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)\n\n> **Note:** Building AMIs incurs AWS costs (EC2 instances, EBS storage, data transfer). Builds typically take 10-30 minutes depending on provisioning complexity.\n\n## Basic AMI Template\n\n```hcl\npacker {\n  required_plugins {\n    amazon = {\n      source  = \"github.com/hashicorp/amazon\"\n      version = \"~> 1.3\"\n    }\n  }\n}\n\nvariable \"region\" {\n  type    = string\n  default = \"us-west-2\"\n}\n\nlocals {\n  timestamp = regex_replace(timestamp(), \"[- TZ:]\", \"\")\n}\n\nsource \"amazon-ebs\" \"ubuntu\" {\n  region        = var.region\n  instance_type = \"t3.micro\"\n\n  source_ami_filter {\n    filters = {\n      name                = \"ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*\"\n      root-device-type    = \"ebs\"\n      virtualization-type = \"hvm\"\n    }\n    most_recent = true\n    owners      = [\"099720109477\"] # Canonical\n  }\n\n  ssh_username = \"ubuntu\"\n  ami_name     = \"my-app-${local.timestamp}\"\n\n  tags = {\n    Name      = \"my-app\"\n    BuildDate = local.timestamp\n  }\n}\n\nbuild {\n  sources = [\"source.amazon-ebs.ubuntu\"]\n\n  provisioner \"shell\" {\n    inline = [\n      \"sudo apt-get update\",\n      \"sudo apt-get upgrade -y\",\n    ]\n  }\n}\n```\n\n## Common Source AMI Filters\n\n### Ubuntu 22.04 LTS\n```hcl\nsource_ami_filter {\n  filters = {\n    name                = \"ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*\"\n    root-device-type    = \"ebs\"\n    virtualization-type = \"hvm\"\n  }\n  most_recent = true\n  owners      = [\"099720109477\"] # Canonical\n}\n```\n\n### Amazon Linux 2023\n```hcl\nsource_ami_filter {\n  filters = {\n    name                = \"al2023-ami-*-x86_64\"\n    root-device-type    = \"ebs\"\n    virtualization-type = \"hvm\"\n  }\n  most_recent = true\n  owners      = [\"amazon\"]\n}\n```\n\n## Multi-Region AMI\n\n```hcl\nsource \"amazon-ebs\" \"ubuntu\" {\n  region        = \"us-west-2\"\n  instance_type = \"t3.micro\"\n\n  source_ami_filter {\n    filters = {\n      name = \"ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*\"\n    }\n    most_recent = true\n    owners      = [\"099720109477\"]\n  }\n\n  ssh_username = \"ubuntu\"\n  ami_name     = \"my-app-${local.timestamp}\"\n\n  # Copy to additional regions\n  ami_regions = [\"us-east-1\", \"us-east-2\", \"eu-west-1\"]\n}\n```\n\n## Authentication\n\nPacker uses AWS credential resolution:\n\n1. Environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`\n2. AWS credentials file: `~/.aws/credentials`\n3. IAM instance profile (when running on EC2)\n\n```bash\nexport AWS_ACCESS_KEY_ID=\"your-access-key\"\nexport AWS_SECRET_ACCESS_KEY=\"your-secret-key\"\nexport AWS_REGION=\"us-west-2\"\n\npacker build .\n```\n\n## Build Commands\n\n```bash\n# Initialize plugins\npacker init .\n\n# Validate template\npacker validate .\n\n# Build AMI\npacker build .\n\n# Build with variables\npacker build -var \"region=us-east-1\" .\n```\n\n## Common Issues\n\n**SSH Timeout**\n- Ensure security group allows SSH (port 22)\n- Verify subnet has internet access\n\n**AMI Already Exists**\n- AMI names must be unique\n- Use timestamp in name: `my-app-${local.timestamp}`\n\n**Volume Size Too Small**\n- Check source AMI's volume size\n- Set `launch_block_device_mappings.volume_size` accordingly\n\n## References\n\n- [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)\n- [AWS AMI Documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html)\n"
  },
  {
    "path": "packer/builders/skills/azure-image-builder/SKILL.md",
    "content": "---\nname: azure-image-builder\ndescription: Build Azure managed images and Azure Compute Gallery images with Packer. Use when creating custom images for Azure VMs.\n---\n\n# Azure Image Builder\n\nBuild Azure managed images and Azure Compute Gallery images using Packer's `azure-arm` builder.\n\n**Reference:** [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)\n\n> **Note:** Building Azure images incurs costs (compute, storage, data transfer). Builds typically take 15-45 minutes depending on provisioning and OS.\n\n## Basic Managed Image\n\n```hcl\npacker {\n  required_plugins {\n    azure = {\n      source  = \"github.com/hashicorp/azure\"\n      version = \"~> 2.0\"\n    }\n  }\n}\n\nvariable \"client_id\" {\n  type      = string\n  sensitive = true\n}\n\nvariable \"client_secret\" {\n  type      = string\n  sensitive = true\n}\n\nvariable \"subscription_id\" {\n  type = string\n}\n\nvariable \"tenant_id\" {\n  type = string\n}\n\nvariable \"resource_group\" {\n  type    = string\n  default = \"packer-images-rg\"\n}\n\nlocals {\n  timestamp = regex_replace(timestamp(), \"[- TZ:]\", \"\")\n}\n\nsource \"azure-arm\" \"ubuntu\" {\n  client_id       = var.client_id\n  client_secret   = var.client_secret\n  subscription_id = var.subscription_id\n  tenant_id       = var.tenant_id\n\n  managed_image_resource_group_name = var.resource_group\n  managed_image_name                = \"my-app-${local.timestamp}\"\n\n  os_type         = \"Linux\"\n  image_publisher = \"Canonical\"\n  image_offer     = \"0001-com-ubuntu-server-jammy\"\n  image_sku       = \"22_04-lts-gen2\"\n\n  location = \"East US\"\n  vm_size  = \"Standard_B2s\"\n\n  azure_tags = {\n    Name      = \"my-app\"\n    BuildDate = local.timestamp\n  }\n}\n\nbuild {\n  sources = [\"source.azure-arm.ubuntu\"]\n\n  provisioner \"shell\" {\n    inline = [\n      \"sudo apt-get update\",\n      \"sudo apt-get upgrade -y\",\n    ]\n  }\n}\n```\n\n## Azure Compute Gallery\n\n```hcl\nsource \"azure-arm\" \"ubuntu\" {\n  client_id       = var.client_id\n  client_secret   = var.client_secret\n  subscription_id = var.subscription_id\n  tenant_id       = var.tenant_id\n\n  os_type         = \"Linux\"\n  image_publisher = \"Canonical\"\n  image_offer     = \"0001-com-ubuntu-server-jammy\"\n  image_sku       = \"22_04-lts-gen2\"\n\n  location = \"East US\"\n  vm_size  = \"Standard_B2s\"\n\n  shared_image_gallery_destination {\n    resource_group       = \"gallery-rg\"\n    gallery_name         = \"myImageGallery\"\n    image_name           = \"ubuntu-webapp\"\n    image_version        = \"1.0.${formatdate(\"YYYYMMDD\", timestamp())}\"\n    replication_regions  = [\"East US\", \"West US 2\"]\n    storage_account_type = \"Standard_LRS\"\n  }\n}\n```\n\n## Authentication\n\n### Service Principal\n```bash\n# Create service principal\naz ad sp create-for-rbac \\\n  --name \"packer-sp\" \\\n  --role Contributor \\\n  --scopes /subscriptions/<subscription-id>\n\n# Set environment variables\nexport ARM_CLIENT_ID=\"<client-id>\"\nexport ARM_CLIENT_SECRET=\"<client-secret>\"\nexport ARM_SUBSCRIPTION_ID=\"<subscription-id>\"\nexport ARM_TENANT_ID=\"<tenant-id>\"\n```\n\n### Managed Identity\n```hcl\nsource \"azure-arm\" \"ubuntu\" {\n  use_azure_cli_auth = true\n  subscription_id    = var.subscription_id\n  # ... rest of configuration\n}\n```\n\n## Build Commands\n\n```bash\n# Set authentication\nexport ARM_CLIENT_ID=\"your-client-id\"\nexport ARM_CLIENT_SECRET=\"your-client-secret\"\nexport ARM_SUBSCRIPTION_ID=\"your-subscription-id\"\nexport ARM_TENANT_ID=\"your-tenant-id\"\n\n# Initialize plugins\npacker init .\n\n# Validate template\npacker validate .\n\n# Build image\npacker build .\n```\n\n## Common Issues\n\n**Authentication Failed**\n- Verify service principal credentials\n- Ensure Contributor role on resource group\n- Check subscription and tenant IDs\n\n**Compute Gallery Version Exists**\n- Image versions are immutable\n- Use unique version numbers with date/build number\n- Cannot overwrite existing versions\n\n**Timeout During Provisioning**\n- Check network connectivity from build VM\n- Verify NSG rules allow required traffic\n- Increase timeout if needed\n\n## References\n\n- [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)\n- [Azure Compute Gallery](https://learn.microsoft.com/en-us/azure/virtual-machines/azure-compute-gallery)\n"
  },
  {
    "path": "packer/builders/skills/windows-builder/SKILL.md",
    "content": "---\nname: windows-builder\ndescription: Build Windows images with Packer using WinRM communicator and PowerShell provisioners. Use when creating Windows AMIs, Azure images, or VMware templates.\n---\n\n# Windows Builder\n\nPlatform-agnostic patterns for building Windows images with Packer.\n\n**Reference:** [Windows Builders](https://developer.hashicorp.com/packer/guides/windows)\n\n> **Note:** Windows builds incur significant costs and time. Expect 45-120 minutes per build due to Windows Updates. Failed builds may leave resources running - always verify cleanup.\n\n## WinRM Communicator Setup\n\nWindows requires WinRM for Packer communication.\n\n### AWS Example\n\n```hcl\nsource \"amazon-ebs\" \"windows\" {\n  region        = \"us-west-2\"\n  instance_type = \"t3.medium\"\n\n  source_ami_filter {\n    filters = {\n      name = \"Windows_Server-2022-English-Full-Base-*\"\n    }\n    most_recent = true\n    owners      = [\"amazon\"]\n  }\n\n  ami_name = \"windows-server-2022-${local.timestamp}\"\n\n  communicator   = \"winrm\"\n  winrm_username = \"Administrator\"\n  winrm_use_ssl  = true\n  winrm_insecure = true\n  winrm_timeout  = \"15m\"\n\n  user_data_file = \"scripts/setup-winrm.ps1\"\n}\n```\n\n### WinRM Setup Script (scripts/setup-winrm.ps1)\n\n```powershell\n<powershell>\n# Configure WinRM\nwinrm quickconfig -q\nwinrm set winrm/config '@{MaxTimeoutms=\"1800000\"}'\nwinrm set winrm/config/service '@{AllowUnencrypted=\"true\"}'\nwinrm set winrm/config/service/auth '@{Basic=\"true\"}'\n\n# Configure firewall\nnetsh advfirewall firewall add rule name=\"WinRM 5985\" protocol=TCP dir=in localport=5985 action=allow\nnetsh advfirewall firewall add rule name=\"WinRM 5986\" protocol=TCP dir=in localport=5986 action=allow\n\n# Restart WinRM\nnet stop winrm\nnet start winrm\n</powershell>\n```\n\n### Azure Example\n\n```hcl\nsource \"azure-arm\" \"windows\" {\n  client_id       = var.client_id\n  client_secret   = var.client_secret\n  subscription_id = var.subscription_id\n  tenant_id       = var.tenant_id\n\n  managed_image_resource_group_name = \"images-rg\"\n  managed_image_name                = \"windows-${local.timestamp}\"\n\n  os_type         = \"Windows\"\n  image_publisher = \"MicrosoftWindowsServer\"\n  image_offer     = \"WindowsServer\"\n  image_sku       = \"2022-datacenter-g2\"\n\n  location = \"East US\"\n  vm_size  = \"Standard_D2s_v3\"\n\n  # Azure auto-configures WinRM\n  communicator   = \"winrm\"\n  winrm_use_ssl  = true\n  winrm_insecure = true\n  winrm_timeout  = \"15m\"\n  winrm_username = \"packer\"\n}\n```\n\n## PowerShell Provisioners\n\n### Install Software\n\n```hcl\nbuild {\n  sources = [\"source.amazon-ebs.windows\"]\n\n  # Install Chocolatey\n  provisioner \"powershell\" {\n    inline = [\n      \"Set-ExecutionPolicy Bypass -Scope Process -Force\",\n      \"iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))\"\n    ]\n  }\n\n  # Install applications\n  provisioner \"powershell\" {\n    inline = [\n      \"choco install -y googlechrome\",\n      \"choco install -y 7zip\",\n    ]\n  }\n\n  # Install IIS\n  provisioner \"powershell\" {\n    inline = [\n      \"Install-WindowsFeature -Name Web-Server -IncludeManagementTools\"\n    ]\n  }\n}\n```\n\n### Windows Updates\n\n```hcl\nprovisioner \"powershell\" {\n  inline = [\n    \"Install-PackageProvider -Name NuGet -Force\",\n    \"Install-Module -Name PSWindowsUpdate -Force\",\n    \"Import-Module PSWindowsUpdate\",\n    \"Get-WindowsUpdate -Install -AcceptAll -AutoReboot\",\n  ]\n  timeout = \"2h\"\n}\n\n# Wait for reboots\nprovisioner \"windows-restart\" {\n  restart_timeout = \"30m\"\n}\n```\n\n## Cleanup\n\n```hcl\nprovisioner \"powershell\" {\n  inline = [\n    \"# Clear temp files\",\n    \"Remove-Item -Path 'C:\\\\Windows\\\\Temp\\\\*' -Recurse -Force -ErrorAction SilentlyContinue\",\n    \"# Clear Windows Update cache\",\n    \"Stop-Service -Name wuauserv -Force\",\n    \"Remove-Item -Path 'C:\\\\Windows\\\\SoftwareDistribution\\\\*' -Recurse -Force -ErrorAction SilentlyContinue\",\n    \"Start-Service -Name wuauserv\",\n  ]\n}\n```\n\n## Common Issues\n\n**WinRM Timeout**\n- Increase `winrm_timeout` to 15m or more\n- Verify security group allows ports 5985/5986\n- Check user data script completed successfully\n\n**PowerShell Execution Policy**\n```hcl\nprovisioner \"powershell\" {\n  inline = [\n    \"Set-ExecutionPolicy Bypass -Scope Process -Force\",\n    \"# Your commands here\",\n  ]\n}\n```\n\n**Long Build Times**\n- Windows Updates can take 1-2 hours\n- Use pre-patched base images when available\n- Set provisioner `timeout = \"2h\"`\n\n## References\n\n- [Packer Windows Builders](https://developer.hashicorp.com/packer/guides/windows)\n- [WinRM Communicator](https://developer.hashicorp.com/packer/docs/communicators/winrm)\n- [PowerShell Provisioner](https://developer.hashicorp.com/packer/docs/provisioners/powershell)\n"
  },
  {
    "path": "packer/hcp/.claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"packer-hcp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"HCP Packer registry integration for tracking and managing image metadata.\",\n  \"author\": {\n    \"name\": \"HashiCorp\",\n    \"url\": \"https://github.com/hashicorp\"\n  },\n  \"homepage\": \"https://developer.hashicorp.com/hcp/docs/packer\",\n  \"repository\": \"https://github.com/hashicorp/agent-skills\",\n  \"license\": \"MPL-2.0\",\n  \"keywords\": [\"packer\", \"hcp-packer\", \"registry\", \"metadata\", \"image-tracking\"]\n}\n"
  },
  {
    "path": "packer/hcp/skills/push-to-registry/SKILL.md",
    "content": "---\nname: push-to-registry\ndescription: Push Packer build metadata to HCP Packer registry for tracking and managing image lifecycle. Use when integrating Packer builds with HCP Packer for version control and governance.\n---\n\n# Push to HCP Packer Registry\n\nConfigure Packer templates to push build metadata to HCP Packer registry.\n\n**Reference:** [HCP Packer Registry](https://developer.hashicorp.com/hcp/docs/packer)\n\n> **Note:** HCP Packer is free for basic use. Builds push metadata only (not actual images), adding minimal overhead (<1 minute).\n\n## Basic Registry Configuration\n\n```hcl\npacker {\n  required_version = \">= 1.7.7\"\n}\n\nvariable \"image_name\" {\n  type    = string\n  default = \"web-server\"\n}\n\nlocals {\n  timestamp = regex_replace(timestamp(), \"[- TZ:]\", \"\")\n}\n\nsource \"amazon-ebs\" \"ubuntu\" {\n  region        = \"us-west-2\"\n  instance_type = \"t3.micro\"\n\n  source_ami_filter {\n    filters = {\n      name = \"ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*\"\n    }\n    most_recent = true\n    owners      = [\"099720109477\"]\n  }\n\n  ssh_username = \"ubuntu\"\n  ami_name     = \"${var.image_name}-${local.timestamp}\"\n}\n\nbuild {\n  sources = [\"source.amazon-ebs.ubuntu\"]\n\n  hcp_packer_registry {\n    bucket_name = var.image_name\n    description = \"Ubuntu 22.04 base image for web servers\"\n\n    bucket_labels = {\n      \"os\"   = \"ubuntu\"\n      \"team\" = \"platform\"\n    }\n\n    build_labels = {\n      \"build-time\" = local.timestamp\n    }\n  }\n\n  provisioner \"shell\" {\n    inline = [\n      \"sudo apt-get update\",\n      \"sudo apt-get upgrade -y\",\n    ]\n  }\n}\n```\n\n## Authentication\n\nSet environment variables before building:\n\n```bash\nexport HCP_CLIENT_ID=\"your-service-principal-client-id\"\nexport HCP_CLIENT_SECRET=\"your-service-principal-secret\"\nexport HCP_ORGANIZATION_ID=\"your-org-id\"\nexport HCP_PROJECT_ID=\"your-project-id\"\n\npacker build .\n```\n\n### Create HCP Service Principal\n\n1. Navigate to HCP → Access Control (IAM)\n2. Create Service Principal\n3. Grant \"Contributor\" role on project\n4. Generate client secret\n5. Save client ID and secret\n\n## Registry Configuration Options\n\n### bucket_name (required)\nThe image identifier. Must stay consistent across builds!\n\n```hcl\nbucket_name = \"web-server\"  # Keep this constant\n```\n\n### bucket_labels (optional)\nMetadata at bucket level. Updates with each build.\n\n```hcl\nbucket_labels = {\n  \"os\"        = \"ubuntu\"\n  \"team\"      = \"platform\"\n  \"component\" = \"web\"\n}\n```\n\n### build_labels (optional)\nMetadata for each iteration. Immutable after build completes.\n\n```hcl\nbuild_labels = {\n  \"build-time\" = local.timestamp\n  \"git-commit\" = var.git_commit\n}\n```\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Build and Push to HCP Packer\n\non:\n  push:\n    branches: [main]\n\nenv:\n  HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }}\n  HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }}\n  HCP_ORGANIZATION_ID: ${{ secrets.HCP_ORGANIZATION_ID }}\n  HCP_PROJECT_ID: ${{ secrets.HCP_PROJECT_ID }}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: hashicorp/setup-packer@main\n\n      - name: Build and push\n        run: |\n          packer init .\n          packer build \\\n            -var \"git_commit=${{ github.sha }}\" \\\n            .\n```\n\n## Querying in Terraform\n\n```hcl\ndata \"hcp_packer_artifact\" \"ubuntu\" {\n  bucket_name  = \"web-server\"\n  channel_name = \"production\"\n  platform     = \"aws\"\n  region       = \"us-west-2\"\n}\n\nresource \"aws_instance\" \"web\" {\n  ami           = data.hcp_packer_artifact.ubuntu.external_identifier\n  instance_type = \"t3.micro\"\n\n  tags = {\n    PackerBucket = data.hcp_packer_artifact.ubuntu.bucket_name\n  }\n}\n```\n\n## Common Issues\n\n**Authentication Failed**\n- Verify HCP_CLIENT_ID and HCP_CLIENT_SECRET\n- Ensure service principal has Contributor role\n- Check organization and project IDs\n\n**Bucket Name Mismatch**\n- Keep `bucket_name` consistent across builds\n- Don't include timestamps in bucket_name\n- Creates new bucket if name changes\n\n**Build Fails**\n- Packer fails immediately if can't push metadata\n- Prevents drift between artifacts and registry\n- Check network connectivity to HCP API\n\n## Best Practices\n\n- **Consistent bucket names** - Never change for same image type\n- **Meaningful labels** - Use for versions, teams, compliance\n- **CI/CD automation** - Automate builds and registry pushes\n- **Immutable build labels** - Put changing data (git SHA, date) in build_labels\n\n## References\n\n- [HCP Packer Documentation](https://developer.hashicorp.com/hcp/docs/packer)\n- [hcp_packer_registry Block](https://developer.hashicorp.com/packer/docs/templates/hcl_templates/blocks/build/hcp_packer_registry)\n- [HCP Terraform Provider](https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_artifact)\n"
  },
  {
    "path": "scripts/validate-structure.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\n#\n# Validates the agent-skills repository structure\n# Ensures all plugins and skills follow the expected format\n#\n\nset -e\n\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nERRORS=0\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\nlog_error() {\n    echo -e \"${RED}ERROR:${NC} $1\"\n    ERRORS=$((ERRORS + 1))\n}\n\nlog_success() {\n    echo -e \"${GREEN}OK:${NC} $1\"\n}\n\nlog_info() {\n    echo -e \"${YELLOW}INFO:${NC} $1\"\n}\n\n# Check if jq is available\nif ! command -v jq &> /dev/null; then\n    echo \"jq is required but not installed. Please install jq.\"\n    exit 1\nfi\n\necho \"==========================================\"\necho \"Validating agent-skills repository structure\"\necho \"==========================================\"\necho \"\"\n\n# ---------------------------------------------\n# 1. Validate marketplace.json\n# ---------------------------------------------\necho \"1. Checking marketplace.json...\"\n\nMARKETPLACE_FILE=\"$REPO_ROOT/.claude-plugin/marketplace.json\"\n\nif [[ ! -f \"$MARKETPLACE_FILE\" ]]; then\n    log_error \"marketplace.json not found at $MARKETPLACE_FILE\"\nelse\n    # Check if valid JSON\n    if ! jq empty \"$MARKETPLACE_FILE\" 2>/dev/null; then\n        log_error \"marketplace.json is not valid JSON\"\n    else\n        log_success \"marketplace.json is valid JSON\"\n\n        # Check required fields\n        NAME=$(jq -r '.name // empty' \"$MARKETPLACE_FILE\")\n        if [[ -z \"$NAME\" ]]; then\n            log_error \"marketplace.json missing 'name' field\"\n        else\n            log_success \"marketplace.json has name: $NAME\"\n        fi\n\n        OWNER=$(jq -r '.owner.name // empty' \"$MARKETPLACE_FILE\")\n        if [[ -z \"$OWNER\" ]]; then\n            log_error \"marketplace.json missing 'owner.name' field\"\n        else\n            log_success \"marketplace.json has owner: $OWNER\"\n        fi\n\n        # Check plugins array exists\n        PLUGINS_COUNT=$(jq '.plugins | length' \"$MARKETPLACE_FILE\")\n        if [[ \"$PLUGINS_COUNT\" -eq 0 ]]; then\n            log_error \"marketplace.json has no plugins defined\"\n        else\n            log_success \"marketplace.json has $PLUGINS_COUNT plugin(s) defined\"\n        fi\n    fi\nfi\n\necho \"\"\n\n# ---------------------------------------------\n# 2. Validate each plugin referenced in marketplace\n# ---------------------------------------------\necho \"2. Checking plugins referenced in marketplace.json...\"\n\nif [[ -f \"$MARKETPLACE_FILE\" ]]; then\n    PLUGIN_SOURCES=$(jq -r '.plugins[].source' \"$MARKETPLACE_FILE\" 2>/dev/null)\n\n    for SOURCE in $PLUGIN_SOURCES; do\n        # Remove leading ./ if present\n        SOURCE_PATH=\"${SOURCE#./}\"\n        PLUGIN_DIR=\"$REPO_ROOT/$SOURCE_PATH\"\n        PLUGIN_JSON=\"$PLUGIN_DIR/.claude-plugin/plugin.json\"\n\n        echo \"\"\n        log_info \"Checking plugin: $SOURCE_PATH\"\n\n        # Check plugin directory exists\n        if [[ ! -d \"$PLUGIN_DIR\" ]]; then\n            log_error \"Plugin directory not found: $PLUGIN_DIR\"\n            continue\n        else\n            log_success \"Plugin directory exists\"\n        fi\n\n        # Check plugin.json exists\n        if [[ ! -f \"$PLUGIN_JSON\" ]]; then\n            log_error \"plugin.json not found: $PLUGIN_JSON\"\n            continue\n        else\n            log_success \"plugin.json exists\"\n        fi\n\n        # Validate plugin.json\n        if ! jq empty \"$PLUGIN_JSON\" 2>/dev/null; then\n            log_error \"plugin.json is not valid JSON: $PLUGIN_JSON\"\n            continue\n        else\n            log_success \"plugin.json is valid JSON\"\n        fi\n\n        # Check required fields in plugin.json\n        PLUGIN_NAME=$(jq -r '.name // empty' \"$PLUGIN_JSON\")\n        if [[ -z \"$PLUGIN_NAME\" ]]; then\n            log_error \"plugin.json missing 'name' field\"\n        else\n            log_success \"plugin.json has name: $PLUGIN_NAME\"\n        fi\n\n        PLUGIN_VERSION=$(jq -r '.version // empty' \"$PLUGIN_JSON\")\n        if [[ -z \"$PLUGIN_VERSION\" ]]; then\n            log_error \"plugin.json missing 'version' field\"\n        else\n            log_success \"plugin.json has version: $PLUGIN_VERSION\"\n        fi\n\n        PLUGIN_DESC=$(jq -r '.description // empty' \"$PLUGIN_JSON\")\n        if [[ -z \"$PLUGIN_DESC\" ]]; then\n            log_error \"plugin.json missing 'description' field\"\n        else\n            log_success \"plugin.json has description\"\n        fi\n\n        # Check skills directory exists\n        SKILLS_DIR=\"$PLUGIN_DIR/skills\"\n        if [[ ! -d \"$SKILLS_DIR\" ]]; then\n            log_error \"skills/ directory not found in plugin: $PLUGIN_DIR\"\n            continue\n        else\n            log_success \"skills/ directory exists\"\n        fi\n\n        # Validate each skill\n        SKILL_COUNT=0\n        for SKILL_DIR in \"$SKILLS_DIR\"/*/; do\n            if [[ -d \"$SKILL_DIR\" ]]; then\n                SKILL_NAME=$(basename \"$SKILL_DIR\")\n                SKILL_MD=\"$SKILL_DIR/SKILL.md\"\n\n                if [[ ! -f \"$SKILL_MD\" ]]; then\n                    log_error \"SKILL.md not found for skill: $SKILL_NAME\"\n                else\n                    # Check SKILL.md has frontmatter\n                    if ! head -1 \"$SKILL_MD\" | grep -q \"^---\"; then\n                        log_error \"SKILL.md missing frontmatter (---) for skill: $SKILL_NAME\"\n                    else\n                        # Extract and validate frontmatter\n                        FRONTMATTER=$(sed -n '/^---$/,/^---$/p' \"$SKILL_MD\" | sed '1d;$d')\n\n                        # Check for name field\n                        if ! echo \"$FRONTMATTER\" | grep -q \"^name:\"; then\n                            log_error \"SKILL.md missing 'name' in frontmatter for skill: $SKILL_NAME\"\n                        fi\n\n                        # Check for description field\n                        if ! echo \"$FRONTMATTER\" | grep -q \"^description:\"; then\n                            log_error \"SKILL.md missing 'description' in frontmatter for skill: $SKILL_NAME\"\n                        fi\n\n                        if echo \"$FRONTMATTER\" | grep -q \"^name:\" && echo \"$FRONTMATTER\" | grep -q \"^description:\"; then\n                            log_success \"SKILL.md valid for skill: $SKILL_NAME\"\n                        fi\n                    fi\n                fi\n                SKILL_COUNT=$((SKILL_COUNT + 1))\n            fi\n        done\n\n        if [[ \"$SKILL_COUNT\" -eq 0 ]]; then\n            log_error \"No skills found in $SKILLS_DIR\"\n        else\n            log_success \"Found $SKILL_COUNT skill(s) in plugin\"\n        fi\n    done\nfi\n\necho \"\"\n\n# ---------------------------------------------\n# 3. Check for orphaned plugins (not in marketplace)\n# ---------------------------------------------\necho \"3. Checking for orphaned plugins...\"\n\n# Find all plugin.json files\nFOUND_PLUGINS=$(find \"$REPO_ROOT\" -path \"*/.claude-plugin/plugin.json\" -not -path \"$REPO_ROOT/.claude-plugin/*\" 2>/dev/null)\n\nfor PLUGIN_JSON in $FOUND_PLUGINS; do\n    PLUGIN_DIR=$(dirname \"$(dirname \"$PLUGIN_JSON\")\")\n    RELATIVE_PATH=\"${PLUGIN_DIR#$REPO_ROOT/}\"\n\n    # Check if this plugin is referenced in marketplace.json\n    if [[ -f \"$MARKETPLACE_FILE\" ]]; then\n        if ! jq -r '.plugins[].source' \"$MARKETPLACE_FILE\" | grep -q \"$RELATIVE_PATH\"; then\n            log_error \"Orphaned plugin not in marketplace.json: $RELATIVE_PATH\"\n        fi\n    fi\ndone\n\nlog_success \"Orphan check complete\"\n\necho \"\"\n\n# ---------------------------------------------\n# 4. Validate product folder structure\n# ---------------------------------------------\necho \"4. Checking product folder structure...\"\n\n# Get all top-level directories that could be products (excluding hidden and special)\nfor DIR in \"$REPO_ROOT\"/*/; do\n    DIR_NAME=$(basename \"$DIR\")\n\n    # Skip special directories\n    if [[ \"$DIR_NAME\" == \"scripts\" ]] || [[ \"$DIR_NAME\" == \"node_modules\" ]] || [[ \"$DIR_NAME\" =~ ^\\. ]]; then\n        continue\n    fi\n\n    # Check if this is a product folder (contains plugin subdirectories)\n    HAS_PLUGINS=false\n    for SUBDIR in \"$DIR\"/*/; do\n        if [[ -d \"$SUBDIR/.claude-plugin\" ]]; then\n            HAS_PLUGINS=true\n            break\n        fi\n    done\n\n    if [[ \"$HAS_PLUGINS\" == true ]]; then\n        log_success \"Valid product folder: $DIR_NAME\"\n    fi\ndone\n\necho \"\"\necho \"==========================================\"\necho \"Validation complete\"\necho \"==========================================\"\n\nif [[ \"$ERRORS\" -gt 0 ]]; then\n    echo -e \"${RED}Found $ERRORS error(s)${NC}\"\n    exit 1\nelse\n    echo -e \"${GREEN}All checks passed!${NC}\"\n    exit 0\nfi\n"
  },
  {
    "path": "terraform/README.md",
    "content": "# Terraform Skills\n\nAgent skills for Terraform infrastructure-as-code development.\n\n## Plugins\n\n### terraform-code-generation\n\nSkills for generating and validating Terraform HCL code.\n\n| Skill | Description |\n|-------|-------------|\n| terraform-style-guide  | Generate Terraform HCL code following HashiCorp style conventions |\n| terraform-test         | Writing and running `.tftest.hcl` test files |\n| azure-verified-modules | Azure Verified Modules (AVM) requirements and certification |\n| terraform-search-import | Discover existing resources with Terraform Search and bulk import |\n\n### terraform-module-generation\n\nSkills for creating and refactoring Terraform modules.\n\n| Skill | Description |\n|-------|-------------|\n| refactor-module  | Transform monolithic configs into reusable modules |\n| terraform-stacks | Multi-region/environment orchestration with Terraform Stacks |\n\n### terraform-provider-development\n\nSkills for developing Terraform providers.\n\n| Skill | Description |\n|-------|-------------|\n| new-terraform-provider | Scaffold a new Terraform provider |\n| run-acceptance-tests   | Run and debug provider acceptance tests |\n| provider-actions       | Implement provider actions (lifecycle operations) |\n| provider-resources     | Implement resources and data sources |\n| provider-test-patterns | Acceptance test patterns for terraform-plugin-testing |\n\n## Installation\n\n### Claude Code Plugin\n\n```bash\nclaude plugin marketplace add hashicorp/agent-skills\n\nclaude plugin install terraform-code-generation@hashicorp\nclaude plugin install terraform-module-generation@hashicorp\nclaude plugin install terraform-provider-development@hashicorp\n```\n\n### Individual Skills\n\n```bash\n# Code generation\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-test\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/azure-verified-modules\nnpx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-search-import\n\n# Module generation\nnpx skills add hashicorp/agent-skills/terraform/module-generation/skills/refactor-module\nnpx skills add hashicorp/agent-skills/terraform/module-generation/skills/terraform-stacks\n\n# Provider development\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/new-terraform-provider\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/run-acceptance-tests\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-actions\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-resources\nnpx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-test-patterns\n```\n\n## MCP Server\n\nRelevant Terraform plugins include the `terraform-mcp-server` which provides access to Terraform Cloud/Enterprise APIs. Set the following environment variables:\n\n```bash\nexport TFE_TOKEN=\"your-terraform-cloud-token\"\nexport TFE_ADDRESS=\"https://app.terraform.io\"  # or your TFE instance\n```\n\n## Structure\n\n```\nterraform/\n├── code-generation/\n│   ├── .claude-plugin/plugin.json\n│   └── skills/\n│       ├── terraform-style-guide/\n│       ├── terraform-test/\n│       ├── azure-verified-modules/\n│       └── terraform-search-import/\n├── module-generation/\n│   ├── .claude-plugin/plugin.json\n│   └── skills/\n│       ├── terraform-stacks/\n│       └── refactor-module/\n└── provider-development/\n    ├── .claude-plugin/plugin.json\n    └── skills/\n        ├── new-terraform-provider/\n        ├── provider-actions/\n        ├── provider-resources/\n        ├── run-acceptance-tests/\n        └── provider-test-patterns/\n```\n\n## References\n\n- [Terraform Documentation](https://developer.hashicorp.com/terraform)\n- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Terraform MCP Server](https://github.com/hashicorp/terraform-mcp-server)\n"
  },
  {
    "path": "terraform/code-generation/.claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"terraform-code-generation\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform code generation skills for Claude Code, including HCL generation, style guides, and testing.\",\n  \"author\": {\n    \"name\": \"HashiCorp\",\n    \"url\": \"https://github.com/hashicorp\"\n  },\n  \"homepage\": \"https://developer.hashicorp.com/terraform/language\",\n  \"repository\": \"https://github.com/hashicorp/agent-skills\",\n  \"license\": \"MPL-2.0\",\n  \"keywords\": [\"terraform\", \"hcl\", \"infrastructure\", \"iac\", \"testing\", \"style-guide\", \"search\", \"import\", \"discovery\"],\n  \"mcpServers\": {\n    \"terraform\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\", \"TFE_TOKEN\",\n        \"-e\", \"TFE_ADDRESS\",\n        \"hashicorp/terraform-mcp-server\"\n      ],\n      \"env\": {\n        \"TFE_TOKEN\": \"${TFE_TOKEN}\",\n        \"TFE_ADDRESS\": \"${TFE_ADDRESS}\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "terraform/code-generation/skills/azure-verified-modules/SKILL.md",
    "content": "---\nname: azure-verified-modules\ndescription: Azure Verified Modules (AVM) requirements and best practices for developing certified Azure Terraform modules. Use when creating or reviewing Azure modules that need AVM certification.\n---\n\n# Azure Verified Modules (AVM) Requirements\n\nThis guide covers the mandatory requirements for Azure Verified Modules certification. These requirements ensure consistency, quality, and maintainability across Azure Terraform modules.\n\n**References:**\n- [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/)\n- [AVM Terraform Requirements](https://azure.github.io/Azure-Verified-Modules/specs/terraform/)\n\n## Table of Contents\n\n- [Module Cross-Referencing](#module-cross-referencing)\n- [Azure Provider Requirements](#azure-provider-requirements)\n- [Code Style Standards](#code-style-standards)\n- [Variable Requirements](#variable-requirements)\n- [Output Requirements](#output-requirements)\n- [Local Values Standards](#local-values-standards)\n- [Terraform Configuration Requirements](#terraform-configuration-requirements)\n- [Testing Requirements](#testing-requirements)\n- [Documentation Requirements](#documentation-requirements)\n- [Breaking Changes & Feature Management](#breaking-changes--feature-management)\n- [Contribution Standards](#contribution-standards)\n- [Compliance Checklist](#compliance-checklist)\n\n---\n\n## Module Cross-Referencing\n\n**Severity:** MUST | **Requirement:** TFFR1\n\nWhen building Resource or Pattern modules, module owners **MAY** cross-reference other modules. However:\n\n- Modules **MUST** be referenced using HashiCorp Terraform registry reference to a pinned version\n  - Example: `source = \"Azure/xxx/azurerm\"` with `version = \"1.2.3\"`\n- Modules **MUST NOT** use git references (e.g., `git::https://xxx.yyy/xxx.git` or `github.com/xxx/yyy`)\n- Modules **MUST NOT** contain references to non-AVM modules\n\n---\n\n## Azure Provider Requirements\n\n**Severity:** MUST | **Requirement:** TFFR3\n\nAuthors **MUST** only use the following Azure providers:\n\n| Provider | Min Version | Max Version |\n|----------|-------------|-------------|\n| azapi    | >= 2.0      | < 3.0       |\n| azurerm  | >= 4.0      | < 5.0       |\n\n**Requirements:**\n\n- Authors **MAY** select either Azurerm, Azapi, or both providers\n- **MUST** use `required_providers` block to enforce provider versions\n- **SHOULD** use pessimistic version constraint operator (`~>`)\n\n**Example:**\n\n```hcl\nterraform {\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp/azurerm\"\n      version = \"~> 4.0\"\n    }\n    azapi = {\n      source  = \"Azure/azapi\"\n      version = \"~> 2.0\"\n    }\n  }\n}\n```\n\n---\n\n## Code Style Standards\n\n### Lower snake_casing\n\n**Severity:** MUST | **Requirement:** TFNFR4\n\n**MUST** use lower snake_casing for:\n\n- Locals\n- Variables\n- Outputs\n- Resources (symbolic names)\n- Modules (symbolic names)\n\nExample: `snake_casing_example`\n\n### Resource & Data Source Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR6\n\n- Resources that are depended on **SHOULD** come first\n- Resources with dependencies **SHOULD** be defined close to each other\n\n### Count & for_each Usage\n\n**Severity:** MUST | **Requirement:** TFNFR7\n\n- Use `count` for conditional resource creation\n- **MUST** use `map(xxx)` or `set(xxx)` as resource's `for_each` collection\n- The map's key or set's element **MUST** be static literals\n\n**Example:**\n\n```hcl\nresource \"azurerm_subnet\" \"pair\" {\n  for_each             = var.subnet_map  # map(string)\n  name                 = \"${each.value}-pair\"\n  resource_group_name  = azurerm_resource_group.example.name\n  virtual_network_name = azurerm_virtual_network.example.name\n  address_prefixes     = [\"10.0.1.0/24\"]\n}\n```\n\n### Resource & Data Block Internal Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR8\n\n**Order within resource/data blocks:**\n\n1. **Meta-arguments (top)**:\n   - `provider`\n   - `count`\n   - `for_each`\n\n2. **Arguments/blocks (middle, alphabetical)**:\n   - Required arguments\n   - Optional arguments\n   - Required nested blocks\n   - Optional nested blocks\n\n3. **Meta-arguments (bottom)**:\n   - `depends_on`\n   - `lifecycle` (with sub-order: `create_before_destroy`, `ignore_changes`, `prevent_destroy`)\n\nSeparate sections with blank lines.\n\n### Module Block Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR9\n\n**Order within module blocks:**\n\n1. **Top meta-arguments**:\n   - `source`\n   - `version`\n   - `count`\n   - `for_each`\n\n2. **Arguments (alphabetical)**:\n   - Required arguments\n   - Optional arguments\n\n3. **Bottom meta-arguments**:\n   - `depends_on`\n   - `providers`\n\n### Lifecycle ignore_changes Syntax\n\n**Severity:** MUST | **Requirement:** TFNFR10\n\nThe `ignore_changes` attribute **MUST NOT** be enclosed in double quotes.\n\n**Good:**\n\n```hcl\nlifecycle {\n  ignore_changes = [tags]\n}\n```\n\n**Bad:**\n\n```hcl\nlifecycle {\n  ignore_changes = [\"tags\"]\n}\n```\n\n### Null Comparison for Conditional Creation\n\n**Severity:** SHOULD | **Requirement:** TFNFR11\n\nFor parameters requiring conditional resource creation, wrap with `object` type to avoid \"known after apply\" issues during plan stage.\n\n**Recommended:**\n\n```hcl\nvariable \"security_group\" {\n  type = object({\n    id = string\n  })\n  default = null\n}\n```\n\n### Dynamic Blocks for Optional Nested Objects\n\n**Severity:** MUST | **Requirement:** TFNFR12\n\nNested blocks under conditions **MUST** use this pattern:\n\n```hcl\ndynamic \"identity\" {\n  for_each = <condition> ? [<some_item>] : []\n\n  content {\n    # block content\n  }\n}\n```\n\n### Default Values with coalesce/try\n\n**Severity:** SHOULD | **Requirement:** TFNFR13\n\n**Good:**\n\n```hcl\ncoalesce(var.new_network_security_group_name, \"${var.subnet_name}-nsg\")\n```\n\n**Bad:**\n\n```hcl\nvar.new_network_security_group_name == null ? \"${var.subnet_name}-nsg\" : var.new_network_security_group_name\n```\n\n### Provider Declarations in Modules\n\n**Severity:** MUST | **Requirement:** TFNFR27\n\n- `provider` **MUST NOT** be declared in modules (except for `configuration_aliases`)\n- `provider` blocks in modules **MUST** only use `alias`\n- Provider configurations **SHOULD** be passed in by module users\n\n---\n\n## Variable Requirements\n\n### Not Allowed Variables\n\n**Severity:** MUST | **Requirement:** TFNFR14\n\nModule owners **MUST NOT** add variables like `enabled` or `module_depends_on` to control entire module operation. Boolean feature toggles for specific resources are acceptable.\n\n### Variable Definition Order\n\n**Severity:** SHOULD | **Requirement:** TFNFR15\n\nVariables **SHOULD** follow this order:\n\n1. All required fields (alphabetical)\n2. All optional fields (alphabetical)\n\n### Variable Naming Rules\n\n**Severity:** SHOULD | **Requirement:** TFNFR16\n\n- Follow [HashiCorp's naming rules](https://www.terraform.io/docs/extend/best-practices/naming.html)\n- Feature switches **SHOULD** use positive statements: `xxx_enabled` instead of `xxx_disabled`\n\n### Variables with Descriptions\n\n**Severity:** SHOULD | **Requirement:** TFNFR17\n\n- `description` **SHOULD** precisely describe the parameter's purpose and expected data type\n- Target audience is module users, not developers\n- For `object` types, use HEREDOC format\n\n### Variables with Types\n\n**Severity:** MUST | **Requirement:** TFNFR18\n\n- `type` **MUST** be defined for every variable\n- `type` **SHOULD** be as precise as possible\n- `any` **MAY** only be used with adequate reasons\n- Use `bool` instead of `string`/`number` for true/false values\n- Use concrete `object` instead of `map(any)`\n\n### Sensitive Data Variables\n\n**Severity:** SHOULD | **Requirement:** TFNFR19\n\nIf a variable's type is `object` and contains sensitive fields, the entire variable **SHOULD** be `sensitive = true`, or extract sensitive fields into separate variables.\n\n### Non-Nullable Defaults for Collections\n\n**Severity:** SHOULD | **Requirement:** TFNFR20\n\nNullable **SHOULD** be set to `false` for collection values (sets, maps, lists) when using them in loops. For scalar values, null may have semantic meaning.\n\n### Discourage Nullability by Default\n\n**Severity:** MUST | **Requirement:** TFNFR21\n\n`nullable = true` **MUST** be avoided unless there's a specific semantic need for null values.\n\n### Avoid sensitive = false\n\n**Severity:** MUST | **Requirement:** TFNFR22\n\n`sensitive = false` **MUST** be avoided (this is the default).\n\n### Sensitive Default Value Conditions\n\n**Severity:** MUST | **Requirement:** TFNFR23\n\nA default value **MUST NOT** be set for sensitive inputs (e.g., default passwords).\n\n### Handling Deprecated Variables\n\n**Severity:** MUST | **Requirement:** TFNFR24\n\n- Move deprecated variables to `deprecated_variables.tf`\n- Annotate with `DEPRECATED` at the beginning of description\n- Declare the replacement's name\n- Clean up during major version releases\n\n---\n\n## Output Requirements\n\n### Additional Terraform Outputs\n\n**Severity:** SHOULD | **Requirement:** TFFR2\n\nAuthors **SHOULD NOT** output entire resource objects as these may contain sensitive data and the schema can change with API or provider versions.\n\n**Best Practices:**\n\n- Output *computed* attributes of resources as discrete outputs (anti-corruption layer pattern)\n- **SHOULD NOT** output values that are already inputs (except `name`)\n- Use `sensitive = true` for sensitive attributes\n- For resources deployed with `for_each`, output computed attributes in a map structure\n\n**Examples:**\n\n```hcl\n# Single resource computed attribute\noutput \"foo\" {\n  description = \"MyResource foo attribute\"\n  value       = azurerm_resource_myresource.foo\n}\n\n# for_each resources\noutput \"childresource_foos\" {\n  description = \"MyResource children's foo attributes\"\n  value = {\n    for key, value in azurerm_resource_mychildresource : key => value.foo\n  }\n}\n\n# Sensitive output\noutput \"bar\" {\n  description = \"MyResource bar attribute\"\n  value       = azurerm_resource_myresource.bar\n  sensitive   = true\n}\n```\n\n### Sensitive Data Outputs\n\n**Severity:** MUST | **Requirement:** TFNFR29\n\nOutputs containing confidential data **MUST** be declared with `sensitive = true`.\n\n### Handling Deprecated Outputs\n\n**Severity:** MUST | **Requirement:** TFNFR30\n\n- Move deprecated outputs to `deprecated_outputs.tf`\n- Define new outputs in `outputs.tf`\n- Clean up during major version releases\n\n---\n\n## Local Values Standards\n\n### locals.tf Organization\n\n**Severity:** MAY | **Requirement:** TFNFR31\n\n- `locals.tf` **SHOULD** only contain `locals` blocks\n- **MAY** declare `locals` blocks next to resources for advanced scenarios\n\n### Alphabetical Local Arrangement\n\n**Severity:** MUST | **Requirement:** TFNFR32\n\nExpressions in `locals` blocks **MUST** be arranged alphabetically.\n\n### Precise Local Types\n\n**Severity:** SHOULD | **Requirement:** TFNFR33\n\nUse precise types (e.g., `number` for age, not `string`).\n\n---\n\n## Terraform Configuration Requirements\n\n### Terraform Version Requirements\n\n**Severity:** MUST | **Requirement:** TFNFR25\n\n**`terraform.tf` requirements:**\n\n- **MUST** contain only one `terraform` block\n- First line **MUST** define `required_version`\n- **MUST** include minimum version constraint\n- **MUST** include maximum major version constraint\n- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format\n\n**Example:**\n\n```hcl\nterraform {\n  required_version = \"~> 1.6\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp/azurerm\"\n      version = \"~> 4.0\"\n    }\n  }\n}\n```\n\n### Providers in required_providers\n\n**Severity:** MUST | **Requirement:** TFNFR26\n\n- `terraform` block **MUST** contain `required_providers` block\n- Each provider **MUST** specify `source` and `version`\n- Providers **SHOULD** be sorted alphabetically\n- Only include directly required providers\n- `source` **MUST** be in format `namespace/name`\n- `version` **MUST** include minimum and maximum major version constraints\n- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format\n\n---\n\n## Testing Requirements\n\n### Test Tooling\n\n**Severity:** MUST | **Requirement:** TFNFR5\n\n**Required testing tools for AVM:**\n\n- Terraform (`terraform validate/fmt/test`)\n- terrafmt\n- Checkov\n- tflint (with azurerm ruleset)\n- Go (optional for custom tests)\n\n### Test Provider Configuration\n\n**Severity:** SHOULD | **Requirement:** TFNFR36\n\nFor robust testing, `prevent_deletion_if_contains_resources` **SHOULD** be explicitly set to `false` in test provider configurations.\n\n---\n\n## Documentation Requirements\n\n### Module Documentation Generation\n\n**Severity:** MUST | **Requirement:** TFNFR2\n\n- Documentation **MUST** be automatically generated via [Terraform Docs](https://github.com/terraform-docs/terraform-docs)\n- A `.terraform-docs.yml` file **MUST** be present in the module root\n\n---\n\n## Breaking Changes & Feature Management\n\n### Using Feature Toggles\n\n**Severity:** MUST | **Requirement:** TFNFR34\n\nNew resources added in minor/patch versions **MUST** have a toggle variable to avoid creation by default:\n\n```hcl\nvariable \"create_route_table\" {\n  type     = bool\n  default  = false\n  nullable = false\n}\n\nresource \"azurerm_route_table\" \"this\" {\n  count = var.create_route_table ? 1 : 0\n  # ...\n}\n```\n\n### Reviewing Potential Breaking Changes\n\n**Severity:** MUST | **Requirement:** TFNFR35\n\n**Breaking changes requiring caution:**\n\n**Resource blocks:**\n\n1. Adding new resource without conditional creation\n2. Adding arguments with non-default values\n3. Adding nested blocks without `dynamic`\n4. Renaming resources without `moved` blocks\n5. Changing `count` to `for_each` or vice versa\n\n**Variable/Output blocks:**\n\n1. Deleting/renaming variables\n2. Changing variable `type`\n3. Changing variable `default` values\n4. Changing `nullable` to false\n5. Changing `sensitive` from false to true\n6. Adding variables without `default`\n7. Deleting outputs\n8. Changing output `value`\n9. Changing output `sensitive` value\n\n---\n\n## Contribution Standards\n\n### GitHub Repository Branch Protection\n\n**Severity:** MUST | **Requirement:** TFNFR3\n\nModule owners **MUST** set branch protection policies on the default branch (typically `main`):\n\n1. Require Pull Request before merging\n2. Require approval of most recent reviewable push\n3. Dismiss stale PR approvals when new commits are pushed\n4. Require linear history\n5. Prevent force pushes\n6. Not allow deletions\n7. Require CODEOWNERS review\n8. No bypassing settings allowed\n9. Enforce for administrators\n\n---\n\n## Compliance Checklist\n\nUse this checklist when developing or reviewing Azure Verified Modules:\n\n### Module Structure\n- [ ] Module cross-references use registry sources with pinned versions\n- [ ] Azure providers (azurerm/azapi) versions meet AVM requirements\n- [ ] `.terraform-docs.yml` present in module root\n- [ ] CODEOWNERS file present\n\n### Code Style\n- [ ] All names use lower snake_casing\n- [ ] Resources ordered with dependencies first\n- [ ] `for_each` uses `map()` or `set()` with static keys\n- [ ] Resource/data/module blocks follow proper internal ordering\n- [ ] `ignore_changes` not quoted\n- [ ] Dynamic blocks used for conditional nested objects\n- [ ] `coalesce()` or `try()` used for default values\n\n### Variables\n- [ ] No `enabled` or `module_depends_on` variables\n- [ ] Variables ordered: required (alphabetical) then optional (alphabetical)\n- [ ] All variables have precise types (avoid `any`)\n- [ ] All variables have descriptions\n- [ ] Collections have `nullable = false`\n- [ ] No `sensitive = false` declarations\n- [ ] No default values for sensitive inputs\n- [ ] Deprecated variables moved to `deprecated_variables.tf`\n\n### Outputs\n- [ ] Outputs use anti-corruption layer pattern (discrete attributes)\n- [ ] Sensitive outputs marked `sensitive = true`\n- [ ] Deprecated outputs moved to `deprecated_outputs.tf`\n\n### Terraform Configuration\n- [ ] `terraform.tf` has version constraints (`~>` format)\n- [ ] `required_providers` block present with all providers\n- [ ] No `provider` declarations in module (except aliases)\n- [ ] Locals arranged alphabetically\n\n### Testing & Quality\n- [ ] Required testing tools configured\n- [ ] New resources have feature toggles\n- [ ] Breaking changes reviewed and documented\n\n---\n\n## Summary Statistics\n\n- **Functional Requirements:** 3\n- **Non-Functional Requirements:** 34\n- **Total Requirements:** 37\n\n### By Severity\n- **MUST:** 21 requirements\n- **SHOULD:** 14 requirements\n- **MAY:** 2 requirements\n\n---\n\n*Based on: Azure Verified Modules - Terraform Requirements*\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/SKILL.md",
    "content": "---\nname: terraform-search-import\ndescription: Discover existing cloud resources using Terraform Search queries and bulk import them into Terraform management. Use when bringing unmanaged infrastructure under Terraform control, auditing cloud resources, or migrating to IaC.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.1.0\"\ncompatibility: Requires Terraform >= 1.14 and providers with list resource support (always use latest provider version)\n---\n\n# Terraform Search and Bulk Import\n\nDiscover existing cloud resources using declarative queries and generate configuration for bulk import into Terraform state.\n\n**References:**\n- [Terraform Search - list block](https://developer.hashicorp.com/terraform/language/block/tfquery/list)\n- [Bulk Import](https://developer.hashicorp.com/terraform/language/import/bulk)\n\n## When to Use\n\n- Bringing unmanaged resources under Terraform control\n- Auditing existing cloud infrastructure\n- Migrating from manual provisioning to IaC\n- Discovering resources across multiple regions/accounts\n\n## IMPORTANT: Check Provider Support First\n\n**BEFORE starting, you MUST verify the target resource type is supported:**\n\n```bash\n# Check what list resources are available\n./scripts/list_resources.sh aws      # Specific provider\n./scripts/list_resources.sh          # All configured providers\n```\n\n## Decision Tree\n\n1. **Identify target resource type** (e.g., aws_s3_bucket, aws_instance)\n2. **Check if supported**: Run `./scripts/list_resources.sh <provider>`\n3. **Choose workflow**:\n   - ** If supported**: Check for terraform version available.\n   - ** If terraform version is above 1.14.0** Use Terraform Search workflow (below)\n   - ** If not supported or terraform version is below 1.14.0 **: Use Manual Discovery workflow (see [references/MANUAL-IMPORT.md](references/MANUAL-IMPORT.md))\n   \n   **Note**: The list of supported resources is rapidly expanding. Always verify current support before using manual import.\n\n## Prerequisites\n\nBefore writing queries, verify the provider supports list resources for your target resource type.\n\n### Discover Available List Resources\n\nRun the helper script to extract supported list resources from your provider:\n\n```bash\n# From a directory with provider configuration (runs terraform init if needed)\n./scripts/list_resources.sh aws      # Specific provider\n./scripts/list_resources.sh          # All configured providers\n```\n\nOr manually query the provider schema:\n\n```bash\nterraform providers schema -json | jq '.provider_schemas | to_entries | map({key: (.key | split(\"/\")[-1]), value: (.value.list_resource_schemas // {} | keys)})'\n```\n\nTerraform Search requires an initialized working directory. Ensure you have a configuration with the required provider before running queries:\n\n```hcl\n# terraform.tf\nterraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 6.0\"\n    }\n  }\n}\n```\n\nRun `terraform init` to download the provider, then proceed with queries.\n\n## Terraform Search Workflow (Supported Resources Only)\n\n1. Create `.tfquery.hcl` files with `list` blocks defining search queries\n2. Run `terraform query` to discover matching resources\n3. Generate configuration with `-generate-config-out=<file>`\n4. Review and refine generated `resource` and `import` blocks\n5. Run `terraform plan` and `terraform apply` to import\n\n## Query File Structure\n\nQuery files use `.tfquery.hcl` extension and support:\n- `provider` blocks for authentication\n- `list` blocks for resource discovery\n- `variable` and `locals` blocks for parameterization\n\n```hcl\n# discovery.tfquery.hcl\nprovider \"aws\" {\n  region = \"us-west-2\"\n}\n\nlist \"aws_instance\" \"all\" {\n  provider = aws\n}\n```\n\n## List Block Syntax\n\n```hcl\nlist \"<list_type>\" \"<symbolic_name>\" {\n  provider = <provider_reference>  # Required\n\n  # Optional: filter configuration (provider-specific)\n  # The `config` block schema is provider-specific. Discover available options using `terraform providers schema -json | jq '.provider_schemas.\"registry.terraform.io/hashicorp/<provider>\".list_resource_schemas.\"<resource_type>\"'`\n\n  config {\n    filter {\n      name   = \"<filter_name>\"\n      values = [\"<value1>\", \"<value2>\"]\n    }\n    region = \"<region>\"  # AWS-specific\n  }\n  # Optional: limit results\n  limit = 100\n}\n```\n\n## Supported List Resources\n\nProvider support for list resources varies by version. **Always check what's available for your specific provider version using the discovery script.**\n\n## Query Examples\n\n### Basic Discovery\n\n```hcl\n# Find all EC2 instances in configured region\nlist \"aws_instance\" \"all\" {\n  provider = aws\n}\n```\n\n### Filtered Discovery\n\n```hcl\n# Find instances by tag\nlist \"aws_instance\" \"production\" {\n  provider = aws\n  \n  config {\n    filter {\n      name   = \"tag:Environment\"\n      values = [\"production\"]\n    }\n  }\n}\n\n# Find instances by type\nlist \"aws_instance\" \"large\" {\n  provider = aws\n  \n  config {\n    filter {\n      name   = \"instance-type\"\n      values = [\"t3.large\", \"t3.xlarge\"]\n    }\n  }\n}\n```\n\n### Multi-Region Discovery\n\n```hcl\nprovider \"aws\" {\n  region = \"us-west-2\"\n}\n\nlocals {\n  regions = [\"us-west-2\", \"us-east-1\", \"eu-west-1\"]\n}\n\nlist \"aws_instance\" \"all_regions\" {\n  for_each = toset(local.regions)\n  provider = aws\n  \n  config {\n    region = each.value\n  }\n}\n```\n\n### Parameterized Queries\n\n```hcl\nvariable \"target_environment\" {\n  type    = string\n  default = \"staging\"\n}\n\nlist \"aws_instance\" \"by_env\" {\n  provider = aws\n  \n  config {\n    filter {\n      name   = \"tag:Environment\"\n      values = [var.target_environment]\n    }\n  }\n}\n```\n\n## Running Queries\n\n```bash\n# Execute queries and display results\nterraform query\n\n# Generate configuration file\nterraform query -generate-config-out=imported.tf\n\n# Pass variables\nterraform query -var='target_environment=production'\n```\n\n## Query Output Format\n\n```\nlist.aws_instance.all   account_id=123456789012,id=i-0abc123,region=us-west-2   web-server\n```\n\nColumns: `<query_address>   <identity_attributes>   <name_tag>`\n\n## Generated Configuration\n\nThe `-generate-config-out` flag creates:\n\n```hcl\n# __generated__ by Terraform\nresource \"aws_instance\" \"all_0\" {\n  ami           = \"ami-0c55b159cbfafe1f0\"\n  instance_type = \"t2.micro\"\n  # ... all attributes\n}\n\nimport {\n  to       = aws_instance.all_0\n  provider = aws\n  identity = {\n    account_id = \"123456789012\"\n    id         = \"i-0abc123\"\n    region     = \"us-west-2\"\n  }\n}\n```\n\n## Post-Generation Cleanup\n\nGenerated configuration includes all attributes. Clean up by:\n\n1. Remove computed/read-only attributes\n2. Replace hardcoded values with variables\n3. Add proper resource naming\n4. Organize into appropriate files\n\n```hcl\n# Before: generated\nresource \"aws_instance\" \"all_0\" {\n  ami                    = \"ami-0c55b159cbfafe1f0\"\n  instance_type          = \"t2.micro\"\n  arn                    = \"arn:aws:ec2:...\"  # Remove - computed\n  id                     = \"i-0abc123\"        # Remove - computed\n  # ... many more attributes\n}\n\n# After: cleaned\nresource \"aws_instance\" \"web_server\" {\n  ami           = var.ami_id\n  instance_type = var.instance_type\n  subnet_id     = var.subnet_id\n  \n  tags = {\n    Name        = \"web-server\"\n    Environment = var.environment\n  }\n}\n```\n\n## Import by Identity\n\nGenerated imports use identity-based import (Terraform 1.12+):\n\n```hcl\nimport {\n  to       = aws_instance.web\n  provider = aws\n  identity = {\n    account_id = \"123456789012\"\n    id         = \"i-0abc123\"\n    region     = \"us-west-2\"\n  }\n}\n```\n\n## Best Practices\n\n### Query Design\n- Start broad, then add filters to narrow results\n- Use `limit` to prevent overwhelming output\n- Test queries before generating configuration\n\n### Configuration Management\n- Review all generated code before applying\n- Remove unnecessary default values\n- Use consistent naming conventions\n- Add proper variable abstraction\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| \"No list resources found\" | Check provider version supports list resources |\n| Query returns empty | Verify region and filter values |\n| Generated config has errors | Remove computed attributes, fix deprecated arguments |\n| Import fails | Ensure resource not already in state |\n\n## Complete Example\n\n```hcl\n# main.tf - Initialize provider\nterraform {\n  required_version = \">= 1.14\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 6.0\"  # Always use latest version\n    }\n  }\n}\n\n# discovery.tfquery.hcl - Define queries\nprovider \"aws\" {\n  region = \"us-west-2\"\n}\n\nlist \"aws_instance\" \"team_instances\" {\n  provider = aws\n  \n  config {\n    filter {\n      name   = \"tag:Owner\"\n      values = [\"platform\"]\n    }\n    filter {\n      name   = \"instance-state-name\"\n      values = [\"running\"]\n    }\n  }\n  \n  limit = 50\n}\n```\n\n```bash\n# Execute workflow\nterraform init\nterraform query\nterraform query -generate-config-out=generated.tf\n# Review and clean generated.tf\nterraform plan\nterraform apply\n```\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/references/MANUAL-IMPORT.md",
    "content": "# Manual Terraform Import Reference\n\nUse this workflow when your target resource type isn't supported by Terraform Search.\n\n## 1. Discover Resources Using Provider CLI\n\nAWS CLI examples:\n\n```bash\n# RDS instances (not yet supported by Terraform Search)\naws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'\n\n# DynamoDB tables (not yet supported by Terraform Search)\naws dynamodb list-tables --query 'TableNames[]'\n\n# API Gateway REST APIs (not yet supported by Terraform Search)\naws apigateway get-rest-apis --query 'items[].id'\n\n# SNS topics (not yet supported by Terraform Search)\naws sns list-topics --query 'Topics[].TopicArn'\n```\n\n## 2. Create Resource Blocks Manually\n\n```hcl\n# Example for RDS instance\nresource \"aws_db_instance\" \"existing_db\" {\n  identifier = \"my-existing-db\"\n  # Add other required attributes\n}\n\n# Example for DynamoDB table\nresource \"aws_dynamodb_table\" \"existing_table\" {\n  name = \"my-existing-table\"\n  # Add other required attributes\n}\n\n# Example for SNS topic\nresource \"aws_sns_topic\" \"existing_topic\" {\n  name = \"my-existing-topic\"\n}\n```\n\n## 3. Create Import Blocks (Config-Driven Import)\n\n```hcl\n# Example for RDS instance\nresource \"aws_db_instance\" \"existing_db\" {\n  identifier = \"my-existing-db\"\n  # Add other required attributes\n}\n\nimport {\n  to = aws_db_instance.existing_db\n  id = \"my-existing-db\"\n}\n\n# Example for DynamoDB table\nresource \"aws_dynamodb_table\" \"existing_table\" {\n  name = \"my-existing-table\"\n  # Add other required attributes\n}\n\nimport {\n  to = aws_dynamodb_table.existing_table\n  id = \"my-existing-table\"\n}\n```\n\n## 4. Run Import Plan\n\n```bash\n# Plan the import to see what will happen\nterraform plan\n\n# Apply to import the resources\nterraform apply\n```\n\n## Bulk Import Script Example\n\nFor multiple resources of the same type:\n\n```bash\n#!/bin/bash\n# bulk-import-dynamodb.sh\n\n# Get all table names\ntables=$(aws dynamodb list-tables --query 'TableNames[]' --output text)\n\n# Generate import configuration\ncat > dynamodb-imports.tf << 'EOF'\n# DynamoDB Table Resources and Imports\nEOF\n\nfor table in $tables; do\n  # Create resource and import blocks\n  cat >> dynamodb-imports.tf << EOF\nresource \"aws_dynamodb_table\" \"table_${table//[-.]/_}\" {\n  name = \"$table\"\n}\n\nimport {\n  to = aws_dynamodb_table.table_${table//[-.]/_}\n  id = \"$table\"\n}\n\nEOF\ndone\n\necho \"Generated dynamodb-imports.tf with import blocks\"\necho \"Run 'terraform plan' to review, then 'terraform apply' to import\"\n```\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/scripts/list_resources.sh",
    "content": "#!/bin/bash\n# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\n# Extract list resources supported by Terraform providers\n# Usage: ./list_resources.sh [provider_name]\n# Requires: terraform, jq\n# Note: Run from an initialized Terraform directory (terraform init)\n\nset -e\n\nPROVIDER=$1\n\n# Ensure terraform is initialized\nif [ ! -d \".terraform\" ]; then\n    echo \"Initializing Terraform...\" >&2\n    terraform init -upgrade > /dev/null 2>&1\nfi\n\n# Get provider schema and extract list_resource_schemas\nif [ -n \"$PROVIDER\" ]; then\n    # Specific provider\n    provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep \"/${PROVIDER}$\" || true)\n    if [ -n \"$provider_key\" ]; then\n        terraform providers schema -json 2>/dev/null | jq -r \\\n            \"{\\\"$PROVIDER\\\": (.provider_schemas.\\\"${provider_key}\\\" | .list_resource_schemas // {} | keys | sort)}\"\n    else\n        echo \"{\\\"$PROVIDER\\\": []}\"\n    fi\nelse\n    # All providers\n    terraform providers schema -json 2>/dev/null | jq -r '\n        .provider_schemas\n        | to_entries\n        | map({key: (.key | split(\"/\")[-1]), value: (.value.list_resource_schemas // {} | keys | sort)})\n        | from_entries\n    '\nfi\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-style-guide/SECURITY.md",
    "content": "---\nname: terraform-style-guide-security\ndescription: Generate Terraform HCL code following HashiCorp's security practices\n---\n\n# Terraform Style Guide - Security\n\nWhen generating code, apply security hardening:\n\n- Enable encryption at rest by default\n- Configure private networking where applicable\n- Apply principle of least privilege for security groups\n- Enable logging and monitoring\n- Never hardcode credentials or secrets\n- Mark sensitive outputs with `sensitive = true`\n- Use `ephemeral` resources and write-only attributes\n  for sensitive data when possible\n\n## Example: Secure S3 Bucket\n\n```hcl\nresource \"aws_s3_bucket\" \"data\" {\n  bucket = \"${var.project}-${var.environment}-data\"\n  tags   = local.common_tags\n}\n\nresource \"aws_s3_bucket_versioning\" \"data\" {\n  bucket = aws_s3_bucket.data.id\n\n  versioning_configuration {\n    status = \"Enabled\"\n  }\n}\n\nresource \"aws_s3_bucket_server_side_encryption_configuration\" \"data\" {\n  bucket = aws_s3_bucket.data.id\n\n  rule {\n    apply_server_side_encryption_by_default {\n      sse_algorithm     = \"aws:kms\"\n      kms_master_key_id = aws_kms_key.s3.arn\n    }\n  }\n}\n\nresource \"aws_s3_bucket_public_access_block\" \"data\" {\n  bucket = aws_s3_bucket.data.id\n\n  block_public_acls       = true\n  block_public_policy     = true\n  ignore_public_acls      = true\n  restrict_public_buckets = true\n}\n```\n\n## Ephemeral resources\n\nEphemeral resources prevent sensitive data being stored in state.\nFor more information on ephemeral resources, see the\n[Terraform documentation](https://developer.hashicorp.com/terraform/language/block/ephemeral).\n\nBefore you generate code for an ephemeral resource, check that the Terraform\nversion is greater than or equal to 1.11.0.\n\nThen, follow this priority order for managing sensitive attributes:\n\n1. **First priority: Native secrets manager integration**\n   If a resource has the ability to automatically manage a sensitive attribute by\n   storing it in a secrets manager (e.g., AWS Secrets Manager, Azure Key Vault),\n   use that configuration. This is the preferred approach.\n\n   ```hcl\n   # Bad\n   resource \"aws_rds_cluster\" \"example\" {\n     cluster_identifier = \"example\"\n     database_name      = \"test\"\n     master_username    = \"test\"\n     master_password    = var.db_master_password\n   }\n\n   # Good, managed by AWS Secrets Manager by default\n   resource \"aws_rds_cluster\" \"test\" {\n     cluster_identifier          = \"example\"\n     database_name               = \"test\"\n     manage_master_user_password = true\n     master_username             = \"test\"\n   }\n   ```\n\n2. **Second priority: Write-only attributes with ephemeral resources**\n   If a resource has a write-only attribute but no native secrets manager integration,\n   use an `ephemeral` resource for the sensitive data and pass that to the write-only\n   attribute. Default the write-only version to 1.\n\n   ```hcl\n   # Bad\n   resource \"random_password\" \"password\" {\n     length           = 16\n     special          = true\n     override_special = \"!#$%&*()-_=+[]{}<>:?\"\n   }\n \n   resource \"vault_kv_secret_v2\" \"example\" {\n     mount               = vault_mount.kvv2.path\n     name                = \"secret\"\n \n     data_json = jsonencode(\n       {\n         password = \"${random_password.password.result}\",\n       }\n     )\n   }\n \n   # Good\n   ephemeral \"random_password\" \"password\" {\n     length           = 16\n     special          = true\n     override_special = \"!#$%&*()-_=+[]{}<>:?\"\n   }\n \n   resource \"vault_kv_secret_v2\" \"example\" {\n     mount               = vault_mount.kvv2.path\n     name                = \"secret\"\n \n     data_json_wo = jsonencode(\n       {\n         password = \"${ephemeral.random_password.password.result}\",\n       }\n     )\n     data_json_wo_version = 1\n   }\n   ```\n\n   If you need to retrieve a secret from a secrets manager to pass\n   to a resource, use the `ephemeral` version of the resource to\n   retrieve the secret and pass it to another resource.\n   \n   ```hcl\n   # Good\n   ephemeral \"vault_kv_secret_v2\" \"db_secret\" {\n     mount = vault_mount.kvv2.path\n     mount_id = vault_mount.kvv2.id\n     name = vault_kv_secret_v2.db_root.name\n   }\n   \n   resource \"vault_database_secret_backend_connection\" \"postgres\" {\n     backend       = vault_mount.db.path\n     name          = \"postrgres-db\"\n     allowed_roles = [\"*\"]\n   \n     postgresql {\n       connection_url = \"postgresql://{{username}}:{{password}}@localhost:5432/postgres\"\n       password_authentication = \"\"\n       username = \"postgres\"\n       password_wo = tostring(ephemeral.vault_kv_secret_v2.db_secret.data.password)\n       password_wo_version = 1\n     }\n   }\n   ```\n\n3. **Last resort: Regular resources**\n   Only use a regular resource that has sensitive data written to state if neither of the above\n   options are available, resource does not offer a write-only attribute or ephemeral resource\n   alternative, or the Terraform version is less than 1.11.0."
  },
  {
    "path": "terraform/code-generation/skills/terraform-style-guide/SKILL.md",
    "content": "---\nname: terraform-style-guide\ndescription: Generate Terraform HCL code following HashiCorp's official style conventions and best practices. Use when writing, reviewing, or generating Terraform configurations.\n---\n\n# Terraform Style Guide\n\nGenerate and maintain Terraform code following HashiCorp's official style conventions and best practices.\n\n**Reference:** [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)\n\n## Code Generation Strategy\n\nWhen generating Terraform code:\n\n1. Start with provider configuration and version constraints\n2. Create data sources before dependent resources\n3. Build resources in dependency order\n4. Add outputs for key resource attributes\n5. Use variables for all configurable values\n\n## File Organization\n\n| File | Purpose |\n|------|---------|\n| `terraform.tf` | Terraform and provider version requirements |\n| `providers.tf` | Provider configurations |\n| `main.tf` | Primary resources and data sources |\n| `variables.tf` | Input variable declarations (alphabetical) |\n| `outputs.tf` | Output value declarations (alphabetical) |\n| `locals.tf` | Local value declarations |\n\n### Example Structure\n\n```hcl\n# terraform.tf\nterraform {\n  required_version = \">= 1.14\"\n\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 6.0\"\n    }\n  }\n}\n\n# variables.tf\nvariable \"environment\" {\n  description = \"Target deployment environment\"\n  type        = string\n\n  validation {\n    condition     = contains([\"dev\", \"staging\", \"prod\"], var.environment)\n    error_message = \"Environment must be dev, staging, or prod.\"\n  }\n}\n\n# locals.tf\nlocals {\n  common_tags = {\n    Environment = var.environment\n    ManagedBy   = \"Terraform\"\n  }\n}\n\n# main.tf\nresource \"aws_vpc\" \"main\" {\n  cidr_block           = var.vpc_cidr\n  enable_dns_hostnames = true\n\n  tags = merge(local.common_tags, {\n    Name = \"${var.project_name}-${var.environment}-vpc\"\n  })\n}\n\n# outputs.tf\noutput \"vpc_id\" {\n  description = \"ID of the created VPC\"\n  value       = aws_vpc.main.id\n}\n```\n\n## Code Formatting\n\n### Indentation and Alignment\n\n- Use **two spaces** per nesting level (no tabs)\n- Align equals signs for consecutive arguments\n\n```hcl\nresource \"aws_instance\" \"web\" {\n  ami           = \"ami-0c55b159cbfafe1f0\"\n  instance_type = \"t2.micro\"\n  subnet_id     = \"subnet-12345678\"\n\n  tags = {\n    Name        = \"web-server\"\n    Environment = \"production\"\n  }\n}\n```\n\n### Block Organization\n\nArguments precede blocks, with meta-arguments first:\n\n```hcl\nresource \"aws_instance\" \"example\" {\n  # Meta-arguments\n  count = 3\n\n  # Arguments\n  ami           = \"ami-0c55b159cbfafe1f0\"\n  instance_type = \"t2.micro\"\n\n  # Blocks\n  root_block_device {\n    volume_size = 20\n  }\n\n  # Lifecycle last\n  lifecycle {\n    create_before_destroy = true\n  }\n}\n```\n\n## Naming Conventions\n\n- Use **lowercase with underscores** for all names\n- Use **descriptive nouns** excluding the resource type\n- Be specific and meaningful\n- Resource names must be singular, not plural\n- Default to `main` for resources where a specific descriptive name is redundant or unavailable, provided only one instance exists\n\n```hcl\n# Bad\nresource \"aws_instance\" \"webAPI-aws-instance\" {}\nresource \"aws_instance\" \"web_apis\" {}\nvariable \"name\" {}\n\n# Good\nresource \"aws_instance\" \"web_api\" {}\nresource \"aws_vpc\" \"main\" {}\nvariable \"application_name\" {}\n```\n\n## Variables\n\nEvery variable must include `type` and `description`:\n\n```hcl\nvariable \"instance_type\" {\n  description = \"EC2 instance type for the web server\"\n  type        = string\n  default     = \"t2.micro\"\n\n  validation {\n    condition     = contains([\"t2.micro\", \"t2.small\", \"t2.medium\"], var.instance_type)\n    error_message = \"Instance type must be t2.micro, t2.small, or t2.medium.\"\n  }\n}\n\nvariable \"database_password\" {\n  description = \"Password for the database admin user\"\n  type        = string\n  sensitive   = true\n}\n```\n\n## Outputs\n\nEvery output must include `description`:\n\n```hcl\noutput \"instance_id\" {\n  description = \"ID of the EC2 instance\"\n  value       = aws_instance.web.id\n}\n\noutput \"database_password\" {\n  description = \"Database administrator password\"\n  value       = aws_db_instance.main.password\n  sensitive   = true\n}\n```\n\n## Dynamic Resource Creation\n\n### Prefer for_each over count\n\n```hcl\n# Bad - count for multiple resources\nresource \"aws_instance\" \"web\" {\n  count = var.instance_count\n  tags  = { Name = \"web-${count.index}\" }\n}\n\n# Good - for_each with named instances\nvariable \"instance_names\" {\n  type    = set(string)\n  default = [\"web-1\", \"web-2\", \"web-3\"]\n}\n\nresource \"aws_instance\" \"web\" {\n  for_each = var.instance_names\n  tags     = { Name = each.key }\n}\n```\n\n### count for Conditional Creation\n\n```hcl\nresource \"aws_cloudwatch_metric_alarm\" \"cpu\" {\n  count = var.enable_monitoring ? 1 : 0\n\n  alarm_name = \"high-cpu-usage\"\n  threshold  = 80\n}\n```\n\n## Security Best Practices\n\nRefer to SECURITY.md. It includes guidance on encrypting resources,\npreventing sensitive data in state, and secure configurations.\n\n## Version Pinning\n\n```hcl\nterraform {\n  required_version = \">= 1.14\"\n\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 6.0\"\n    }\n  }\n}\n```\n\nUse the latest major version of each provider and the latest minor version of\nTerraform, unless otherwise constrained by a dependency lock file or by other\nmodules used by the configuration.\n\n**Version constraint operators:**\n- `= 1.0.0` - Exact version\n- `>= 1.0.0` - Greater than or equal\n- `~> 1.0` - Allow rightmost component to increment\n- `>= 1.0, < 2.0` - Version range\n\n## Provider Configuration\n\n```hcl\nprovider \"aws\" {\n  region = \"us-west-2\"\n\n  default_tags {\n    tags = {\n      ManagedBy = \"Terraform\"\n      Project   = var.project_name\n    }\n  }\n}\n\n# Aliased provider for multi-region\nprovider \"aws\" {\n  alias  = \"east\"\n  region = \"us-east-1\"\n}\n```\n\n## Version Control\n\n**Never commit:**\n- `terraform.tfstate`, `terraform.tfstate.backup`\n- `.terraform/` directory\n- `*.tfplan`\n- `.tfvars` files with sensitive data\n\n**Always commit:**\n- All `.tf` configuration files\n- `.terraform.lock.hcl` (dependency lock file)\n\n## Validation Tools\n\nRun before committing:\n\n```bash\nterraform fmt -recursive\nterraform validate\n```\n\nAdditional tools:\n- `tflint` - Linting and best practices\n- `checkov` / `tfsec` - Security scanning\n\n## Code Review Checklist\n\n- [ ] Code formatted with `terraform fmt`\n- [ ] Configuration validated with `terraform validate`\n- [ ] Files organized according to standard structure\n- [ ] All variables have type and description\n- [ ] All outputs have descriptions\n- [ ] Resource names use descriptive nouns with underscores\n- [ ] Version constraints pinned explicitly\n- [ ] Sensitive values marked with `sensitive = true`\n- [ ] No hardcoded credentials or secrets\n- [ ] Security best practices applied\n\n---\n\n*Based on: [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)*\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/SKILL.md",
    "content": "---\nname: terraform-test\ndescription: Comprehensive guide for writing and running Terraform tests. Use when creating test files (.tftest.hcl), writing test scenarios with run blocks, validating infrastructure behavior with assertions, mocking providers and data sources, testing module outputs and resource configurations, or troubleshooting Terraform test syntax and execution.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.2\"\n---\n\n# Terraform Test\n\nTerraform's built-in testing framework validates that configuration updates don't introduce breaking changes. Tests run against temporary resources, protecting existing infrastructure and state files.\n\n## Reference Files\n\n- `references/MOCK_PROVIDERS.md` — Mock provider syntax, common defaults, when to use mocks (Terraform 1.7.0+ only — skip if the user's version is below 1.7)\n- `references/CI_CD.md` — GitHub Actions and GitLab CI pipeline examples\n- `references/EXAMPLES.md` — Complete example test suite (unit, integration, and mock tests for a VPC module)\n\nRead the relevant reference file when the user asks about mocking, CI/CD integration, or wants a full example.\n\n## Core Concepts\n\n- **Test file** (`.tftest.hcl` / `.tftest.json`): Contains `run` blocks that validate your configuration\n- **Run block**: A single test scenario with optional variables, providers, and assertions\n- **Assert block**: Conditions that must be true for the test to pass\n- **Mock provider**: Simulates provider behavior without real infrastructure (Terraform 1.7.0+)\n- **Test modes**: `apply` (default, creates real resources) or `plan` (validates logic only)\n\n## File Structure\n\n```\nmy-module/\n├── main.tf\n├── variables.tf\n├── outputs.tf\n└── tests/\n    ├── defaults_unit_test.tftest.hcl         # plan mode — fast, no resources\n    ├── validation_unit_test.tftest.hcl        # plan mode\n    └── full_stack_integration_test.tftest.hcl # apply mode — creates real resources\n```\n\nUse `*_unit_test.tftest.hcl` for plan-mode tests and `*_integration_test.tftest.hcl` for apply-mode tests so they can be filtered separately in CI.\n\n## Test File Structure\n\n```hcl\n# Optional: test-wide settings\ntest {\n  parallel = true  # Enable parallel execution for all run blocks (default: false)\n}\n\n# Optional: file-level variables (highest precedence, override all other sources)\nvariables {\n  aws_region    = \"us-west-2\"\n  instance_type = \"t2.micro\"\n}\n\n# Optional: provider configuration\nprovider \"aws\" {\n  region = var.aws_region\n}\n\n# Required: at least one run block\nrun \"test_default_configuration\" {\n  command = plan\n\n  assert {\n    condition     = aws_instance.example.instance_type == \"t2.micro\"\n    error_message = \"Instance type should be t2.micro by default\"\n  }\n}\n```\n\n## Run Block\n\n```hcl\nrun \"test_name\" {\n  command  = plan  # or apply (default)\n  parallel = true  # optional, since v1.9.0\n\n  # Override file-level variables\n  variables {\n    instance_type = \"t3.large\"\n  }\n\n  # Reference a specific module\n  module {\n    source  = \"./modules/vpc\"  # local or registry only (not git/http)\n    version = \"5.0.0\"          # registry modules only\n  }\n\n  # Control state isolation\n  state_key = \"shared_state\"  # since v1.9.0\n\n  # Plan behavior\n  plan_options {\n    mode    = refresh-only  # or normal (default)\n    refresh = true\n    replace = [aws_instance.example]\n    target  = [aws_instance.example]\n  }\n\n  # Assertions\n  assert {\n    condition     = aws_instance.example.id != \"\"\n    error_message = \"Instance should have a valid ID\"\n  }\n\n  # Expected failures (test passes if these fail)\n  expect_failures = [\n    var.instance_count\n  ]\n}\n```\n\n## Common Test Patterns\n\n### Validate outputs\n\n```hcl\nrun \"test_outputs\" {\n  command = plan\n\n  assert {\n    condition     = output.vpc_id != null\n    error_message = \"VPC ID output must be defined\"\n  }\n\n  assert {\n    condition     = can(regex(\"^vpc-\", output.vpc_id))\n    error_message = \"VPC ID should start with 'vpc-'\"\n  }\n}\n```\n\n### Conditional resources\n\n```hcl\nrun \"test_nat_gateway_disabled\" {\n  command = plan\n\n  variables {\n    create_nat_gateway = false\n  }\n\n  assert {\n    condition     = length(aws_nat_gateway.main) == 0\n    error_message = \"NAT gateway should not be created when disabled\"\n  }\n}\n```\n\n### Resource counts\n\n```hcl\nrun \"test_resource_count\" {\n  command = plan\n\n  variables {\n    instance_count = 3\n  }\n\n  assert {\n    condition     = length(aws_instance.workers) == 3\n    error_message = \"Should create exactly 3 worker instances\"\n  }\n}\n```\n\n### Tags\n\n```hcl\nrun \"test_resource_tags\" {\n  command = plan\n\n  variables {\n    common_tags = {\n      Environment = \"production\"\n      ManagedBy   = \"Terraform\"\n    }\n  }\n\n  assert {\n    condition     = aws_instance.example.tags[\"Environment\"] == \"production\"\n    error_message = \"Environment tag should be set correctly\"\n  }\n\n  assert {\n    condition     = aws_instance.example.tags[\"ManagedBy\"] == \"Terraform\"\n    error_message = \"ManagedBy tag should be set correctly\"\n  }\n}\n```\n\n### Data sources\n\n```hcl\nrun \"test_data_source_lookup\" {\n  command = plan\n\n  assert {\n    condition     = data.aws_ami.ubuntu.id != \"\"\n    error_message = \"Should find a valid Ubuntu AMI\"\n  }\n\n  assert {\n    condition     = can(regex(\"^ami-\", data.aws_ami.ubuntu.id))\n    error_message = \"AMI ID should be in correct format\"\n  }\n}\n```\n\n### Validation rules\n\n```hcl\nrun \"test_invalid_environment\" {\n  command = plan\n\n  variables {\n    environment = \"invalid\"\n  }\n\n  expect_failures = [\n    var.environment\n  ]\n}\n```\n\n### Sequential tests with dependencies\n\n```hcl\nrun \"setup_vpc\" {\n  command = apply\n\n  assert {\n    condition     = output.vpc_id != \"\"\n    error_message = \"VPC should be created\"\n  }\n}\n\nrun \"test_subnet_in_vpc\" {\n  command = plan\n\n  variables {\n    vpc_id = run.setup_vpc.vpc_id\n  }\n\n  assert {\n    condition     = aws_subnet.example.vpc_id == run.setup_vpc.vpc_id\n    error_message = \"Subnet should be in the VPC from setup_vpc\"\n  }\n}\n```\n\n### Plan options (refresh-only, targeted)\n\n```hcl\nrun \"test_refresh_only\" {\n  command = plan\n\n  plan_options {\n    mode = refresh-only\n  }\n\n  assert {\n    condition     = aws_instance.example.tags[\"Environment\"] == \"production\"\n    error_message = \"Tags should be refreshed correctly\"\n  }\n}\n\nrun \"test_specific_resource\" {\n  command = plan\n\n  plan_options {\n    target = [aws_instance.example]\n  }\n\n  assert {\n    condition     = aws_instance.example.instance_type == \"t2.micro\"\n    error_message = \"Targeted resource should be planned\"\n  }\n}\n```\n\n### Parallel modules\n\n```hcl\nrun \"test_networking_module\" {\n  command  = plan\n  parallel = true\n\n  module {\n    source = \"./modules/networking\"\n  }\n\n  assert {\n    condition     = output.vpc_id != \"\"\n    error_message = \"VPC should be created\"\n  }\n}\n\nrun \"test_compute_module\" {\n  command  = plan\n  parallel = true\n\n  module {\n    source = \"./modules/compute\"\n  }\n\n  assert {\n    condition     = output.instance_id != \"\"\n    error_message = \"Instance should be created\"\n  }\n}\n```\n\n### State key sharing\n\n```hcl\nrun \"create_foundation\" {\n  command   = apply\n  state_key = \"foundation\"\n\n  assert {\n    condition     = aws_vpc.main.id != \"\"\n    error_message = \"Foundation VPC should be created\"\n  }\n}\n\nrun \"create_application\" {\n  command   = apply\n  state_key = \"foundation\"\n\n  variables {\n    vpc_id = run.create_foundation.vpc_id\n  }\n\n  assert {\n    condition     = aws_instance.app.vpc_id == run.create_foundation.vpc_id\n    error_message = \"Application should use foundation VPC\"\n  }\n}\n```\n\n### Cleanup ordering (S3 objects before bucket)\n\n```hcl\nrun \"create_bucket\" {\n  command = apply\n\n  assert {\n    condition     = aws_s3_bucket.example.id != \"\"\n    error_message = \"Bucket should be created\"\n  }\n}\n\nrun \"add_objects\" {\n  command = apply\n\n  assert {\n    condition     = length(aws_s3_object.files) > 0\n    error_message = \"Objects should be added\"\n  }\n}\n\n# Cleanup destroys in reverse: objects first, then bucket\n```\n\n### Multiple aliased providers\n\n```hcl\nprovider \"aws\" {\n  alias  = \"primary\"\n  region = \"us-west-2\"\n}\n\nprovider \"aws\" {\n  alias  = \"secondary\"\n  region = \"us-east-1\"\n}\n\nrun \"test_with_specific_provider\" {\n  command = plan\n\n  providers = {\n    aws = provider.aws.secondary\n  }\n\n  assert {\n    condition     = aws_instance.example.availability_zone == \"us-east-1a\"\n    error_message = \"Instance should be in us-east-1 region\"\n  }\n}\n```\n\n### Complex conditions\n\n```hcl\nassert {\n  condition = alltrue([\n    for subnet in aws_subnet.private :\n    can(regex(\"^10\\\\.0\\\\.\", subnet.cidr_block))\n  ])\n  error_message = \"All private subnets should use 10.0.0.0/8 CIDR range\"\n}\n```\n\n## Cleanup\n\nResources are destroyed in **reverse run block order** after test completion. This matters for dependencies (e.g., S3 objects before bucket). Use `terraform test -no-cleanup` to skip cleanup for debugging.\n\n## Running Tests\n\n```bash\nterraform test                                        # all tests\nterraform test tests/defaults.tftest.hcl             # specific file\nterraform test -filter=test_vpc_configuration        # by run block name\nterraform test -test-directory=integration-tests     # custom directory\nterraform test -verbose                              # detailed output\nterraform test -no-cleanup                           # skip resource cleanup\n```\n\n## Best Practices\n\n1. **Naming**: `*_unit_test.tftest.hcl` for plan mode, `*_integration_test.tftest.hcl` for apply mode\n2. **Test naming**: Use descriptive run block names that explain the scenario being tested\n3. **Default to plan**: Use `command = plan` unless you need to test real resource behavior\n4. **Use mocks** for external dependencies — faster and no credentials needed (see `references/MOCK_PROVIDERS.md`)\n5. **Error messages**: Make them specific enough to diagnose failures without running the test again\n6. **Negative tests**: Use `expect_failures` to verify validation rules reject bad inputs\n7. **Variable coverage**: Test different variable combinations to validate all code paths — test variables have the highest precedence and override all other sources\n8. **Module sources**: Test files only support local paths and registry modules — not git or HTTP URLs\n9. **Parallel execution**: Use `parallel = true` for independent tests with different state files\n10. **Cleanup**: Integration tests destroy resources in reverse run block order automatically; use `-no-cleanup` for debugging\n11. **CI/CD**: Run unit tests on every PR, integration tests on merge (see `references/CI_CD.md`)\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| Assertion failures | Use `-verbose` to see actual vs expected values |\n| Missing credentials | Use mock providers for unit tests |\n| Unsupported module source | Convert git/HTTP sources to local modules |\n| Tests interfering | Use `state_key` or separate modules for isolation |\n| Slow tests | Use `command = plan` and mocks; run integration tests separately |\n\n## References\n\n- [Terraform Testing Documentation](https://developer.hashicorp.com/terraform/language/tests)\n- [Terraform Test Command](https://developer.hashicorp.com/terraform/cli/commands/test)\n- [Testing Best Practices](https://developer.hashicorp.com/terraform/language/tests/best-practices)\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/CI_CD.md",
    "content": "# CI/CD Integration\n\n## GitHub Actions\n\n```yaml\nname: Terraform Tests\n\non:\n  pull_request:\n    branches: [ main ]\n  push:\n    branches: [ main ]\n\njobs:\n  unit-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: hashicorp/setup-terraform@v3\n        with:\n          terraform_version: 1.9.0\n\n      - run: terraform fmt -check -recursive\n      - run: terraform init\n      - run: terraform validate\n      - name: Run unit tests (plan mode, no credentials needed)\n        run: terraform test -filter=unit_test -verbose\n\n  integration-tests:\n    runs-on: ubuntu-latest\n    needs: unit-tests\n    if: github.ref == 'refs/heads/main'\n    steps:\n      - uses: actions/checkout@v4\n      - uses: hashicorp/setup-terraform@v3\n        with:\n          terraform_version: 1.9.0\n\n      - run: terraform init\n      - name: Run integration tests\n        run: terraform test -filter=integration_test -verbose\n        env:\n          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n```\n\n## GitLab CI\n\n```yaml\nstages:\n  - validate\n  - test\n\nterraform-unit-tests:\n  image: hashicorp/terraform:1.9\n  stage: validate\n  before_script:\n    - terraform init\n  script:\n    - terraform fmt -check -recursive\n    - terraform validate\n    - terraform test -filter=unit_test -verbose\n\nterraform-integration-tests:\n  image: hashicorp/terraform:1.9\n  stage: test\n  before_script:\n    - terraform init\n  script:\n    - terraform test -filter=integration_test -verbose\n  only:\n    - main\n```\n\n## Recommended CI Strategy\n\n- Run unit tests (plan mode + mock tests) on every PR — fast, no credentials needed\n- Run integration tests only on merge to main or nightly — requires cloud credentials\n- Use `-filter=unit_test` / `-filter=integration_test` to separate test types based on naming convention\n- Store cloud credentials as CI secrets, never in code\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/EXAMPLES.md",
    "content": "# Example Test Suite\n\nComplete example testing a VPC module with unit, integration, and mock tests.\n\n## Unit Tests (Plan Mode)\n\n```hcl\n# tests/vpc_module_unit_test.tftest.hcl\n\nvariables {\n  environment = \"test\"\n  aws_region  = \"us-west-2\"\n}\n\nrun \"test_defaults\" {\n  command = plan\n\n  variables {\n    vpc_cidr = \"10.0.0.0/16\"\n    vpc_name = \"test-vpc\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.cidr_block == \"10.0.0.0/16\"\n    error_message = \"VPC CIDR should match input\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.enable_dns_hostnames == true\n    error_message = \"DNS hostnames should be enabled by default\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.tags[\"Name\"] == \"test-vpc\"\n    error_message = \"VPC name tag should match input\"\n  }\n}\n\nrun \"test_subnets\" {\n  command = plan\n\n  variables {\n    vpc_cidr        = \"10.0.0.0/16\"\n    vpc_name        = \"test-vpc\"\n    public_subnets  = [\"10.0.1.0/24\", \"10.0.2.0/24\"]\n    private_subnets = [\"10.0.10.0/24\", \"10.0.11.0/24\"]\n  }\n\n  assert {\n    condition     = length(aws_subnet.public) == 2\n    error_message = \"Should create 2 public subnets\"\n  }\n\n  assert {\n    condition     = length(aws_subnet.private) == 2\n    error_message = \"Should create 2 private subnets\"\n  }\n\n  assert {\n    condition = alltrue([\n      for subnet in aws_subnet.private :\n      subnet.map_public_ip_on_launch == false\n    ])\n    error_message = \"Private subnets should not assign public IPs\"\n  }\n}\n\nrun \"test_outputs\" {\n  command = plan\n\n  variables {\n    vpc_cidr = \"10.0.0.0/16\"\n    vpc_name = \"test-vpc\"\n  }\n\n  assert {\n    condition     = output.vpc_id != \"\"\n    error_message = \"VPC ID output should not be empty\"\n  }\n\n  assert {\n    condition     = can(regex(\"^vpc-\", output.vpc_id))\n    error_message = \"VPC ID should have correct format\"\n  }\n\n  assert {\n    condition     = output.vpc_cidr == \"10.0.0.0/16\"\n    error_message = \"VPC CIDR output should match input\"\n  }\n}\n\nrun \"test_invalid_cidr\" {\n  command = plan\n\n  variables {\n    vpc_cidr = \"invalid\"\n    vpc_name = \"test-vpc\"\n  }\n\n  expect_failures = [\n    var.vpc_cidr\n  ]\n}\n```\n\n## Integration Tests (Apply Mode)\n\n```hcl\n# tests/vpc_module_integration_test.tftest.hcl\n\nvariables {\n  environment = \"integration-test\"\n  aws_region  = \"us-west-2\"\n}\n\nrun \"integration_test_vpc_creation\" {\n  # command defaults to apply — creates real AWS resources\n\n  variables {\n    vpc_cidr = \"10.100.0.0/16\"\n    vpc_name = \"integration-test-vpc\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.id != \"\"\n    error_message = \"VPC should be created with valid ID\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.state == \"available\"\n    error_message = \"VPC should be in available state\"\n  }\n}\n```\n\n## Mock Tests (Plan Mode, No Credentials)\n\n```hcl\n# tests/vpc_module_mock_test.tftest.hcl\n\nmock_provider \"aws\" {\n  mock_resource \"aws_instance\" {\n    defaults = {\n      id            = \"i-1234567890abcdef0\"\n      instance_type = \"t2.micro\"\n      ami           = \"ami-12345678\"\n      public_ip     = \"203.0.113.1\"\n      private_ip    = \"10.0.1.100\"\n    }\n  }\n\n  mock_resource \"aws_vpc\" {\n    defaults = {\n      id                   = \"vpc-12345678\"\n      cidr_block           = \"10.0.0.0/16\"\n      enable_dns_hostnames = true\n      enable_dns_support   = true\n    }\n  }\n\n  mock_resource \"aws_subnet\" {\n    defaults = {\n      id                      = \"subnet-12345678\"\n      vpc_id                  = \"vpc-12345678\"\n      cidr_block              = \"10.0.1.0/24\"\n      availability_zone       = \"us-west-2a\"\n      map_public_ip_on_launch = false\n    }\n  }\n\n  mock_data \"aws_ami\" {\n    defaults = {\n      id   = \"ami-0c55b159cbfafe1f0\"\n      name = \"ubuntu-focal-20.04-amd64\"\n    }\n  }\n\n  mock_data \"aws_availability_zones\" {\n    defaults = {\n      names = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n    }\n  }\n}\n\nrun \"test_instance_with_mocks\" {\n  command = plan\n\n  variables {\n    instance_type = \"t2.micro\"\n    ami_id        = \"ami-12345678\"\n  }\n\n  assert {\n    condition     = aws_instance.example.instance_type == \"t2.micro\"\n    error_message = \"Instance type should match input variable\"\n  }\n\n  assert {\n    condition     = aws_instance.example.id == \"i-1234567890abcdef0\"\n    error_message = \"Mock should return consistent instance ID\"\n  }\n}\n\nrun \"test_data_source_with_mocks\" {\n  command = plan\n\n  assert {\n    condition     = data.aws_ami.ubuntu.id == \"ami-0c55b159cbfafe1f0\"\n    error_message = \"Mock data source should return predictable AMI ID\"\n  }\n\n  assert {\n    condition     = length(data.aws_availability_zones.available.names) == 3\n    error_message = \"Should return 3 mocked availability zones\"\n  }\n\n  assert {\n    condition     = contains(data.aws_availability_zones.available.names, \"us-west-2a\")\n    error_message = \"Should include us-west-2a in mocked zones\"\n  }\n}\n\nrun \"test_outputs_with_mocks\" {\n  command = plan\n\n  assert {\n    condition     = output.vpc_id == \"vpc-12345678\"\n    error_message = \"VPC ID output should match mocked value\"\n  }\n\n  assert {\n    condition     = can(regex(\"^vpc-\", output.vpc_id))\n    error_message = \"VPC ID output should have correct format\"\n  }\n}\n\nrun \"test_conditional_resources_with_mocks\" {\n  command = plan\n\n  variables {\n    create_bastion     = true\n    create_nat_gateway = false\n  }\n\n  assert {\n    condition     = length(aws_instance.bastion) == 1\n    error_message = \"Bastion should be created when enabled\"\n  }\n\n  assert {\n    condition     = length(aws_nat_gateway.nat) == 0\n    error_message = \"NAT gateway should not be created when disabled\"\n  }\n}\n\nrun \"test_tag_inheritance_with_mocks\" {\n  command = plan\n\n  variables {\n    common_tags = {\n      Environment = \"test\"\n      ManagedBy   = \"Terraform\"\n    }\n  }\n\n  assert {\n    condition = alltrue([\n      for key in keys(var.common_tags) :\n      contains(keys(aws_instance.example.tags), key)\n    ])\n    error_message = \"All common tags should be present on instance\"\n  }\n}\n\nrun \"test_invalid_cidr_with_mocks\" {\n  command = plan\n\n  variables {\n    vpc_cidr = \"invalid\"\n  }\n\n  expect_failures = [\n    var.vpc_cidr\n  ]\n}\n\nrun \"setup_vpc_with_mocks\" {\n  command = plan\n\n  variables {\n    vpc_cidr = \"10.0.0.0/16\"\n    vpc_name = \"test-vpc\"\n  }\n\n  assert {\n    condition     = aws_vpc.main.cidr_block == \"10.0.0.0/16\"\n    error_message = \"VPC CIDR should match input\"\n  }\n}\n\nrun \"test_subnet_references_vpc_with_mocks\" {\n  command = plan\n\n  variables {\n    vpc_id      = run.setup_vpc_with_mocks.vpc_id\n    subnet_cidr = \"10.0.1.0/24\"\n  }\n\n  assert {\n    condition     = aws_subnet.example.vpc_id == run.setup_vpc_with_mocks.vpc_id\n    error_message = \"Subnet should reference VPC from previous run\"\n  }\n}\n```\n"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/MOCK_PROVIDERS.md",
    "content": "# Mock Providers\n\nMock providers simulate provider behavior without creating real infrastructure (Terraform 1.7.0+). Use them for fast, credential-free unit tests.\n\n## Basic Mock Provider\n\n```hcl\nmock_provider \"aws\" {\n  mock_resource \"aws_instance\" {\n    defaults = {\n      id            = \"i-1234567890abcdef0\"\n      instance_type = \"t2.micro\"\n      ami           = \"ami-12345678\"\n      public_ip     = \"203.0.113.1\"\n      private_ip    = \"10.0.1.100\"\n    }\n  }\n\n  mock_data \"aws_ami\" {\n    defaults = {\n      id = \"ami-0c55b159cbfafe1f0\"\n    }\n  }\n\n  mock_data \"aws_availability_zones\" {\n    defaults = {\n      names = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n    }\n  }\n}\n\nrun \"test_with_mocks\" {\n  command = plan  # Mocks only work with plan mode\n\n  assert {\n    condition     = aws_instance.example.id == \"i-1234567890abcdef0\"\n    error_message = \"Mock instance ID should match\"\n  }\n}\n```\n\n## Aliased Mock Provider\n\n```hcl\nmock_provider \"aws\" {\n  alias = \"mocked\"\n\n  mock_resource \"aws_s3_bucket\" {\n    defaults = {\n      id  = \"test-bucket-12345\"\n      arn = \"arn:aws:s3:::test-bucket-12345\"\n    }\n  }\n}\n\nrun \"test_with_aliased_mock\" {\n  command = plan\n\n  providers = {\n    aws = provider.aws.mocked\n  }\n\n  assert {\n    condition     = aws_s3_bucket.example.id == \"test-bucket-12345\"\n    error_message = \"Bucket ID should match mock\"\n  }\n}\n```\n\n## Common Mock Defaults\n\n```hcl\nmock_provider \"aws\" {\n  mock_resource \"aws_instance\" {\n    defaults = {\n      id                          = \"i-1234567890abcdef0\"\n      arn                         = \"arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0\"\n      instance_type               = \"t2.micro\"\n      ami                         = \"ami-12345678\"\n      availability_zone           = \"us-west-2a\"\n      subnet_id                   = \"subnet-12345678\"\n      vpc_security_group_ids      = [\"sg-12345678\"]\n      associate_public_ip_address = true\n      public_ip                   = \"203.0.113.1\"\n      private_ip                  = \"10.0.1.100\"\n      tags                        = {}\n    }\n  }\n\n  mock_resource \"aws_vpc\" {\n    defaults = {\n      id                   = \"vpc-12345678\"\n      arn                  = \"arn:aws:ec2:us-west-2:123456789012:vpc/vpc-12345678\"\n      cidr_block           = \"10.0.0.0/16\"\n      enable_dns_hostnames = true\n      enable_dns_support   = true\n      instance_tenancy     = \"default\"\n      tags                 = {}\n    }\n  }\n\n  mock_resource \"aws_subnet\" {\n    defaults = {\n      id                      = \"subnet-12345678\"\n      arn                     = \"arn:aws:ec2:us-west-2:123456789012:subnet/subnet-12345678\"\n      vpc_id                  = \"vpc-12345678\"\n      cidr_block              = \"10.0.1.0/24\"\n      availability_zone       = \"us-west-2a\"\n      map_public_ip_on_launch = false\n      tags                    = {}\n    }\n  }\n\n  mock_resource \"aws_s3_bucket\" {\n    defaults = {\n      id                 = \"test-bucket-12345\"\n      arn                = \"arn:aws:s3:::test-bucket-12345\"\n      bucket             = \"test-bucket-12345\"\n      bucket_domain_name = \"test-bucket-12345.s3.amazonaws.com\"\n      region             = \"us-west-2\"\n      tags               = {}\n    }\n  }\n\n  mock_data \"aws_ami\" {\n    defaults = {\n      id                  = \"ami-0c55b159cbfafe1f0\"\n      name                = \"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210430\"\n      architecture        = \"x86_64\"\n      root_device_type    = \"ebs\"\n      virtualization_type = \"hvm\"\n    }\n  }\n\n  mock_data \"aws_availability_zones\" {\n    defaults = {\n      names    = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n      zone_ids = [\"usw2-az1\", \"usw2-az2\", \"usw2-az3\"]\n    }\n  }\n\n  mock_data \"aws_vpc\" {\n    defaults = {\n      id                   = \"vpc-12345678\"\n      cidr_block           = \"10.0.0.0/16\"\n      enable_dns_hostnames = true\n      enable_dns_support   = true\n    }\n  }\n}\n```\n\n## When to Use Mocks\n\n**Good fit:**\n- Testing Terraform logic, conditionals, `for_each`/`count` expressions\n- Validating variable transformations and output calculations\n- Local development without cloud credentials\n- Fast CI/CD feedback loops\n\n**Not a good fit:**\n- Validating actual provider API behavior\n- Testing real resource creation side effects\n- End-to-end integration testing\n\n## Limitations\n\n- **Plan mode only** — mocks don't work with `command = apply`\n- Mock defaults may not reflect real computed attribute values\n- Mocks need manual updates when provider schemas change\n- Can't test real resource dependencies or timing\n"
  },
  {
    "path": "terraform/module-generation/.claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"terraform-module-generation\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform module generation and refactoring skills for Claude Code, including module design and Terraform Stacks.\",\n  \"author\": {\n    \"name\": \"HashiCorp\",\n    \"url\": \"https://github.com/hashicorp\"\n  },\n  \"homepage\": \"https://developer.hashicorp.com/terraform/language/modules\",\n  \"repository\": \"https://github.com/hashicorp/agent-skills\",\n  \"license\": \"MPL-2.0\",\n  \"keywords\": [\"terraform\", \"modules\", \"infrastructure\", \"iac\", \"stacks\", \"refactoring\"],\n  \"mcpServers\": {\n    \"terraform\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\", \"TFE_TOKEN\",\n        \"-e\", \"TFE_ADDRESS\",\n        \"hashicorp/terraform-mcp-server\"\n      ],\n      \"env\": {\n        \"TFE_TOKEN\": \"${TFE_TOKEN}\",\n        \"TFE_ADDRESS\": \"${TFE_ADDRESS}\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "terraform/module-generation/skills/refactor-module/SKILL.md",
    "content": "---\nname: refactor-module\ndescription: Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\n# Skill: Refactor Module\n\n## Overview\nThis skill guides AI agents in transforming monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.\n\n## Capability Statement\nThe agent will analyze existing Terraform code and systematically refactor it into well-structured modules with:\n- Clear interface contracts (variables and outputs)\n- Proper encapsulation and abstraction\n- Versioning and documentation\n- Testing frameworks\n- Migration path for existing state\n\n## Prerequisites\n- Existing Terraform configuration to refactor\n- Understanding of resource dependencies\n- Access to current state file (for migration planning)\n- Knowledge of module registry patterns\n\n## Input Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `source_directory` | string | Yes | Path to existing Terraform configuration |\n| `module_name` | string | Yes | Name for the new module |\n| `abstraction_level` | string | No | \"simple\", \"intermediate\", \"advanced\" (default: intermediate) |\n| `preserve_state` | boolean | Yes | Whether to maintain state compatibility |\n| `target_registry` | string | No | Target module registry (local, private, public) |\n\n## Execution Steps\n\n### 1. Analysis Phase\n```markdown\n**Identify Refactoring Candidates**\n- Group resources by logical function\n- Identify repeated patterns\n- Map resource dependencies\n- Detect configuration coupling\n- Analyze variable usage patterns\n\n**Complexity Assessment**\n- Count resource relationships\n- Measure variable propagation depth\n- Identify cross-resource references\n- Evaluate state migration complexity\n```\n\n### 2. Module Design\n\n#### Interface Design\n```hcl\n# Define clear input contract\nvariable \"network_config\" {\n  description = \"Network configuration parameters\"\n  type = object({\n    cidr_block         = string\n    availability_zones = list(string)\n    enable_nat         = bool\n  })\n  \n  validation {\n    condition     = can(cidrhost(var.network_config.cidr_block, 0))\n    error_message = \"CIDR block must be valid IPv4 CIDR.\"\n  }\n}\n\n# Define output contract\noutput \"vpc_id\" {\n  description = \"ID of the created VPC\"\n  value       = aws_vpc.main.id\n}\n\noutput \"private_subnet_ids\" {\n  description = \"List of private subnet IDs\"\n  value       = { for k, v in aws_subnet.private : k => v.id }\n}\n```\n\n#### Encapsulation Strategy\n```markdown\n**What to Include in Module:**\n- Tightly coupled resources (VPC + subnets)\n- Resources with shared lifecycle\n- Configuration with clear boundaries\n\n**What to Keep Separate:**\n- Cross-cutting concerns (monitoring, tagging)\n- Resources with different lifecycles\n- Provider-specific configurations\n```\n\n### 3. Code Transformation\n\n#### Before: Monolithic Configuration\n```hcl\n# main.tf (monolithic)\nresource \"aws_vpc\" \"main\" {\n  cidr_block = \"10.0.0.0/16\"\n  enable_dns_hostnames = true\n  \n  tags = {\n    Name = \"production-vpc\"\n    Environment = \"prod\"\n  }\n}\n\nresource \"aws_subnet\" \"public_1\" {\n  vpc_id            = aws_vpc.main.id\n  cidr_block        = \"10.0.1.0/24\"\n  availability_zone = \"us-east-1a\"\n  \n  tags = {\n    Name = \"public-subnet-1\"\n    Type = \"public\"\n  }\n}\n\nresource \"aws_subnet\" \"public_2\" {\n  vpc_id            = aws_vpc.main.id\n  cidr_block        = \"10.0.2.0/24\"\n  availability_zone = \"us-east-1b\"\n  \n  tags = {\n    Name = \"public-subnet-2\"\n    Type = \"public\"\n  }\n}\n\nresource \"aws_internet_gateway\" \"main\" {\n  vpc_id = aws_vpc.main.id\n  \n  tags = {\n    Name = \"production-igw\"\n  }\n}\n\n# ... more repetitive subnet and routing resources\n```\n\n#### After: Modular Structure\n```hcl\n# modules/vpc/main.tf\nlocals {\n  subnet_count = length(var.availability_zones)\n}\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block           = var.cidr_block\n  enable_dns_hostnames = var.enable_dns_hostnames\n  enable_dns_support   = var.enable_dns_support\n  \n  tags = merge(\n    var.tags,\n    {\n      Name = var.name\n    }\n  )\n}\n\nresource \"aws_subnet\" \"public\" {\n  for_each = var.create_public_subnets ? toset(var.availability_zones) : []\n  \n  vpc_id                  = aws_vpc.main.id\n  cidr_block              = cidrsubnet(var.cidr_block, 8, index(var.availability_zones, each.value))\n  availability_zone       = each.value\n  map_public_ip_on_launch = true\n  \n  tags = merge(\n    var.tags,\n    {\n      Name = \"${var.name}-public-${each.value}\"\n      Type = \"public\"\n    }\n  )\n}\n\nresource \"aws_internet_gateway\" \"main\" {\n  count  = var.create_public_subnets ? 1 : 0\n  vpc_id = aws_vpc.main.id\n  \n  tags = merge(\n    var.tags,\n    {\n      Name = \"${var.name}-igw\"\n    }\n  )\n}\n\n# modules/vpc/variables.tf\nvariable \"name\" {\n  description = \"Name prefix for all resources\"\n  type        = string\n}\n\nvariable \"cidr_block\" {\n  description = \"CIDR block for the VPC\"\n  type        = string\n  \n  validation {\n    condition     = can(cidrhost(var.cidr_block, 0))\n    error_message = \"Must be a valid IPv4 CIDR block.\"\n  }\n}\n\nvariable \"availability_zones\" {\n  description = \"List of availability zones\"\n  type        = list(string)\n}\n\nvariable \"create_public_subnets\" {\n  description = \"Whether to create public subnets\"\n  type        = bool\n  default     = true\n}\n\nvariable \"enable_dns_hostnames\" {\n  description = \"Enable DNS hostnames in the VPC\"\n  type        = bool\n  default     = true\n}\n\nvariable \"enable_dns_support\" {\n  description = \"Enable DNS support in the VPC\"\n  type        = bool\n  default     = true\n}\n\nvariable \"tags\" {\n  description = \"Tags to apply to all resources\"\n  type        = map(string)\n  default     = {}\n}\n\n# modules/vpc/outputs.tf\noutput \"vpc_id\" {\n  description = \"ID of the VPC\"\n  value       = aws_vpc.main.id\n}\n\noutput \"vpc_cidr_block\" {\n  description = \"CIDR block of the VPC\"\n  value       = aws_vpc.main.cidr_block\n}\n\noutput \"public_subnet_ids\" {\n  description = \"Map of availability zones to public subnet IDs\"\n  value       = { for k, v in aws_subnet.public : k => v.id }\n}\n\noutput \"internet_gateway_id\" {\n  description = \"ID of the internet gateway\"\n  value       = try(aws_internet_gateway.main[0].id, null)\n}\n\n# Root configuration using module\nmodule \"vpc\" {\n  source = \"./modules/vpc\"\n  \n  name               = \"production\"\n  cidr_block         = \"10.0.0.0/16\"\n  availability_zones = [\"us-east-1a\", \"us-east-1b\", \"us-east-1c\"]\n  \n  tags = {\n    Environment = \"production\"\n    ManagedBy   = \"Terraform\"\n  }\n}\n```\n\n### 4. State Migration\n\n#### Generate Migration Plan\n```hcl\n# migration.tf\n# Use moved blocks for state refactoring (Terraform 1.1+)\n\nmoved {\n  from = aws_vpc.main\n  to   = module.vpc.aws_vpc.main\n}\n\nmoved {\n  from = aws_subnet.public_1\n  to   = module.vpc.aws_subnet.public[\"us-east-1a\"]\n}\n\nmoved {\n  from = aws_subnet.public_2\n  to   = module.vpc.aws_subnet.public[\"us-east-1b\"]\n}\n\nmoved {\n  from = aws_internet_gateway.main\n  to   = module.vpc.aws_internet_gateway.main[0]\n}\n```\n\n#### Manual State Migration (Pre-1.1)\n```bash\n# Generate state migration commands\nterraform state mv aws_vpc.main module.vpc.aws_vpc.main\nterraform state mv aws_subnet.public_1 'module.vpc.aws_subnet.public[\"us-east-1a\"]'\nterraform state mv aws_subnet.public_2 'module.vpc.aws_subnet.public[\"us-east-1b\"]'\nterraform state mv aws_internet_gateway.main 'module.vpc.aws_internet_gateway.main[0]'\n```\n\n### 5. Module Documentation\n\n```markdown\n# VPC Module\n\n## Overview\nCreates a VPC with configurable public and private subnets across multiple availability zones.\n\n## Features\n- Multi-AZ subnet deployment\n- Optional NAT gateway configuration\n- VPC Flow Logs integration\n- Customizable CIDR allocation\n\n## Usage\n\n\\`\\`\\`hcl\nmodule \"vpc\" {\n  source = \"./modules/vpc\"\n  \n  name               = \"my-vpc\"\n  cidr_block         = \"10.0.0.0/16\"\n  availability_zones = [\"us-east-1a\", \"us-east-1b\"]\n  \n  create_public_subnets  = true\n  create_private_subnets = true\n  enable_nat_gateway     = true\n  \n  tags = {\n    Environment = \"production\"\n  }\n}\n\\`\\`\\`\n\n## Requirements\n\n| Name | Version |\n|------|---------|\n| terraform | >= 1.5.0 |\n| aws | ~> 5.0 |\n\n## Inputs\n\n| Name | Description | Type | Default | Required |\n|------|-------------|------|---------|----------|\n| name | Name prefix for resources | `string` | n/a | yes |\n| cidr_block | VPC CIDR block | `string` | n/a | yes |\n| availability_zones | List of AZs | `list(string)` | n/a | yes |\n\n## Outputs\n\n| Name | Description |\n|------|-------------|\n| vpc_id | VPC identifier |\n| public_subnet_ids | Map of public subnet IDs |\n| private_subnet_ids | Map of private subnet IDs |\n\n## Examples\n\nSee [examples/](./examples/) directory for complete usage examples.\n```\n\n### 6. Testing\n\nUse skill terraform-test\n\n**Test File**: A `.tftest.hcl` or `.tftest.json` file containing test configuration and run blocks that validate your Terraform configuration.\n\n**Test Block**: Optional configuration block that defines test-wide settings (available since Terraform 1.6.0).\n\n**Run Block**: Defines a single test scenario with optional variables, provider configurations, and assertions. Each test file requires at least one run block.\n\n**Assert Block**: Contains conditions that must evaluate to true for the test to pass. Failed assertions cause the test to fail.\n\n**Mock Provider**: Simulates provider behavior without creating real infrastructure (available since Terraform 1.7.0).\n\n**Test Modes**: Tests run in apply mode (default, creates real infrastructure) or plan mode (validates logic without creating resources).\n\n#### File Structure\n\nTerraform test files use the `.tftest.hcl` or `.tftest.json` extension and are typically organized in a `tests/` directory. Use clear naming conventions to distinguish between unit tests (plan mode) and integration tests (apply mode):\n\n```\nmy-module/\n├── main.tf\n├── variables.tf\n├── outputs.tf\n└── tests/\n    ├── unit_test.tftest.hcl      # Unit test (plan mode)\n    └── integration_test.tftest.hcl  # Integration test (apply mode - creates real resources)\n```\n\n## Refactoring Patterns\n\n### Pattern 1: Resource Grouping\nExtract related resources into cohesive modules:\n- Networking (VPC, Subnets, Route Tables)\n- Compute (ASG, Launch Templates, Load Balancers)\n- Data (RDS, ElastiCache, S3)\n\n### Pattern 2: Configuration Layering\n```hcl\n# Base module with defaults\nmodule \"vpc_base\" {\n  source = \"./modules/vpc-base\"\n  # Minimal required inputs\n}\n\n# Environment-specific wrapper\nmodule \"vpc_prod\" {\n  source = \"./modules/vpc-production\"\n  # Inherits from base, adds prod-specific config\n}\n```\n\n### Pattern 3: Composition\n```hcl\n# Small, focused modules\nmodule \"vpc\" {\n  source = \"./modules/vpc\"\n}\n\nmodule \"security_groups\" {\n  source = \"./modules/security-groups\"\n  vpc_id = module.vpc.vpc_id\n}\n\nmodule \"application\" {\n  source     = \"./modules/application\"\n  vpc_id     = module.vpc.vpc_id\n  subnet_ids = module.vpc.private_subnet_ids\n  sg_ids     = module.security_groups.app_sg_ids\n}\n```\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n```hcl\n# ❌ Don't create overly generic modules\nvariable \"resources\" {\n  type = map(map(any))  # Too flexible, hard to validate\n}\n\n# ✅ Do use specific, typed interfaces\nvariable \"database_config\" {\n  type = object({\n    engine         = string\n    instance_class = string\n  })\n}\n```\n\n### 2. Tight Coupling\n```hcl\n# ❌ Don't couple modules through direct references\n# module A\noutput \"instance_id\" { value = aws_instance.app.id }\n\n# module B (in same config)\nresource \"aws_eip\" \"app\" {\n  instance = module.a.instance_id  # Tight coupling\n}\n\n# ✅ Do pass dependencies through root module\nmodule \"compute\" {\n  source = \"./modules/compute\"\n}\n\nresource \"aws_eip\" \"app\" {\n  instance = module.compute.instance_id\n}\n```\n\n### 3. State Migration Errors\nAlways test migration in non-production first:\n```bash\n# Create plan to verify no changes after migration\nterraform plan -out=migration.tfplan\n\n# Review carefully\nterraform show migration.tfplan\n\n# Apply only if plan shows no changes\nterraform apply migration.tfplan\n```\n\n## Version Control Strategy\n\n```hcl\n# Use semantic versioning for modules\nmodule \"vpc\" {\n  source  = \"git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0\"\n  version = \"~> 1.2\"\n}\n\n# Pin to specific versions in production\n# Use version ranges in development\n```\n\n## Success Criteria\n\n- [ ] Module has single, well-defined responsibility\n- [ ] All variables have descriptions and types\n- [ ] Validation rules prevent invalid configurations\n- [ ] Outputs provide sufficient information for consumers\n- [ ] Documentation includes usage examples\n- [ ] Tests verify module behavior\n- [ ] State migration completed without resource recreation\n- [ ] No plan differences after refactoring\n\n## Related Skills\n- [Terraform code generation](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/terraform-style-guide/SKILL.md) - Style guide for the new Terraform Module\n- [Azure Verified Modules](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/azure-verified-modules/SKILL.md) - Recommended module specifications for Azure\n\n## Resources\n- [Terraform Module Development](https://developer.hashicorp.com/terraform/language/modules/develop)\n- [Module Best Practices](https://developer.hashicorp.com/terraform/cloud-docs/registry/design)\n\n## Revision History\n\n| Version | Date | Changes |\n|---------|------|---------|\n| 1.0.0 | 2025-11-07 | Initial skill definition |\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/SKILL.md",
    "content": "---\nname: terraform-stacks\ndescription: Comprehensive guide for working with HashiCorp Terraform Stacks. Use when creating, modifying, or validating Terraform Stack configurations (.tfcomponent.hcl, .tfdeploy.hcl files), working with stack components and deployments from local modules, public registry, or private registry sources, managing multi-region or multi-environment infrastructure, or troubleshooting Terraform Stacks syntax and structure.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\n# Terraform Stacks\n\nTerraform Stacks simplify infrastructure provisioning and management at scale by providing a configuration layer above traditional Terraform modules. Stacks enable declarative orchestration of multiple components across environments, regions, and cloud accounts.\n\n## Core Concepts\n\n**Stack**: A complete unit of infrastructure composed of components and deployments that can be managed together.\n\n**Component**: An abstraction around a Terraform module that defines infrastructure pieces. Each component specifies a source module, inputs, and providers.\n\n**Deployment**: An instance of all components in a stack with specific input values. Use deployments for different environments (dev/staging/prod), regions, or cloud accounts.\n\n**Stack Language**: A separate HCL-based language (not regular Terraform HCL) with distinct blocks and file extensions.\n\n## File Structure\n\nTerraform Stacks use specific file extensions:\n\n- **Component configuration**: `.tfcomponent.hcl`\n- **Deployment configuration**: `.tfdeploy.hcl`\n- **Provider lock file**: `.terraform.lock.hcl` (generated by CLI)\n\nAll configuration files must be at the root level of the Stack repository. HCP Terraform processes all files in dependency order.\n\n### Recommended File Organization\n\n```\nmy-stack/\n├── .terraform-version               # The required Terraform version for this Stack\n├── variables.tfcomponent.hcl        # Variable declarations\n├── providers.tfcomponent.hcl        # Provider configurations\n├── components.tfcomponent.hcl       # Component definitions\n├── outputs.tfcomponent.hcl          # Stack outputs\n├── deployments.tfdeploy.hcl         # Deployment definitions\n├── .terraform.lock.hcl              # Provider lock file (generated)\n└── modules/                         # Local modules (optional - only if using local modules)\n    ├── s3/\n    └── compute/\n```\n\n**Note**: The `modules/` directory is only required when using local module sources. Components can reference modules from:\n- Local file paths: `./modules/vpc`\n- Public registry: `terraform-aws-modules/vpc/aws`\n- Private registry: `app.terraform.io/<org-name>/vpc/aws`\n- Git: `git::https://github.com/org/repo.git//path?ref=v1.0.0`\n\nHCP Terraform processes all `.tfcomponent.hcl` and `.tfdeploy.hcl` files in dependency order.\n\n## Required Terraform version (.terraform-version)\n\nUse Terraform v1.13.x or later to access the Stacks CLI plugin and to run\nterraform stacks CLI commands. Begin by adding a .terraform-version file to\nyour Stack's root directory to specify the Terraform version required for your\nStack. For example, the following file specifies Terraform v1.14.5:\n\n```\n1.14.5\n```\n\n## Component Configuration (.tfcomponent.hcl)\n\n### Variable Block\n\nDeclare input variables for the Stack configuration. Variables must define a `type` field and do not support the `validation` argument.\n\n```hcl\nvariable \"aws_region\" {\n  type        = string\n  description = \"AWS region for deployments\"\n  default     = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n  type        = string\n  description = \"OIDC identity token\"\n  ephemeral   = true  # Does not persist to state file\n}\n\nvariable \"instance_count\" {\n  type     = number\n  nullable = false\n}\n```\n\n**Important**: Use `ephemeral = true` for credentials and tokens (identity tokens, API keys, passwords) to prevent them from persisting in state files. Use `stable` for longer-lived values like license keys that need to persist across runs.\n\n### Required Providers Block\n\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 6.0\"\n  }\n  random = {\n    source  = \"hashicorp/random\"\n    version = \"~> 3.5.0\"\n  }\n}\n```\n\n### Provider Block\n\nProvider blocks differ from traditional Terraform:\n\n1. Support `for_each` meta-argument\n2. Define aliases in the block header (not as an argument)\n3. Accept configuration through a `config` block\n\n**Single Provider Configuration:**\n\n```hcl\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n**Multiple Provider Configurations with for_each:**\n\n```hcl\nprovider \"aws\" \"configurations\" {\n  for_each = var.regions\n\n  config {\n    region = each.value\n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n**Authentication Best Practice**: Use **workload identity** (OIDC) as the preferred authentication method for Stacks. This approach:\n- Avoids long-lived static credentials\n- Provides temporary, scoped credentials per deployment run\n- Integrates with cloud provider IAM (AWS IAM Roles, Azure Managed Identities, GCP Service Accounts)\n- Eliminates need for platform-managed environment variables\n\nConfigure workload identity using `identity_token` blocks and `assume_role_with_web_identity` in provider configuration. For detailed setup instructions for AWS, Azure, and GCP, see: https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials\n\n### Component Block\n\nEach Stack requires at least one component block. Add a component for each module to include in the Stack. Components reference modules from local paths, registries, or Git.\n\n```hcl\ncomponent \"vpc\" {\n  source  = \"app.terraform.io/my-org/vpc/aws\"  # Local, registry, or Git URL\n  version = \"2.1.0\"          # For registry modules\n\n  inputs = {\n    cidr_block  = var.vpc_cidr\n    name_prefix = var.name_prefix\n  }\n\n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\nSee `references/component-blocks.md` for examples of dependencies, for_each, public registry modules, Git sources, and more.\n\n**Key Points:**\n- Reference outputs: `component.<name>.<output>` or `component.<name>[key].<output>` for for_each\n- Dependencies inferred automatically from component references\n- Aggregate with for expressions: `[for x in component.s3 : x.bucket_name]`\n- For components with `for_each`, reference specific instances: `component.<name>[each.value].<output>`\n- Provider references are normal values: `provider.<type>.<alias>` or `provider.<type>.<alias>[each.value]`\n\n### Output Block\n\nOutputs require a `type` argument and do not support `preconditions`:\n\n```hcl\noutput \"vpc_id\" {\n  type        = string\n  description = \"VPC ID\"\n  value       = component.vpc.vpc_id\n}\n\noutput \"endpoint_urls\" {\n  type      = map(string)\n  value     = {\n    for region, comp in component.api : region => comp.endpoint_url\n  }\n  sensitive = false\n}\n```\n\n### Locals Block\n\nLocals blocks work the same in both `.tfcomponent.hcl` and `.tfdeploy.hcl` files:\n\n```hcl\nlocals {\n  common_tags = {\n    Environment = var.environment\n    ManagedBy   = \"Terraform Stacks\"\n    Project     = var.project_name\n  }\n\n  region_config = {\n    for region in var.regions : region => {\n      name_suffix = \"${var.environment}-${region}\"\n    }\n  }\n}\n```\n\n### Removed Block\n\nUse to safely remove components from a Stack. HCP Terraform requires the component's providers to remove it.\n\n```hcl\nremoved {\n  from   = component.old_component\n  source = \"./modules/old-module\"\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\n## Deployment Configuration (.tfdeploy.hcl)\n\n### Identity Token Block\n\nGenerate JWT tokens for OIDC authentication with cloud providers:\n\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nidentity_token \"azure\" {\n  audience = [\"api://AzureADTokenExchange\"]\n}\n```\n\nReference tokens in deployments using `identity_token.<name>.jwt`\n\n### Store Block\n\nAccess HCP Terraform variable sets within Stack deployments:\n\n```hcl\nstore \"varset\" \"aws_credentials\" {\n  id       = \"varset-ABC123\"  # Alternatively use: name = \"varset_name\"\n  source   = \"tfc-cloud-shared\"\n  category = \"terraform\"      # Alternatively use: category = \"env\" for environment variables\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_access_key = store.varset.aws_credentials.AWS_ACCESS_KEY_ID\n  }\n}\n```\n\nUse to centralize credentials and share variables across Stacks. See `references/deployment-blocks.md` for details.\n\n### Deployment Block\n\nDefine deployment instances (minimum 1, maximum 20 per Stack):\n\n```hcl\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    instance_count = 3\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Create multiple deployments for different environments\ndeployment \"development\" {\n  inputs = {\n    aws_region     = \"us-east-1\"\n    instance_count = 1\n    name_suffix    = \"dev\"\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n```\n\n**To destroy a deployment**: Set `destroy = true`, upload configuration, approve destroy run, then remove the deployment block. See `references/deployment-blocks.md` for details.\n\n### Deployment Group Block\n\nGroup deployments together for shared settings (HCP Terraform Premium tier feature). Free/standard tiers use default groups named `{deployment-name}_default`.\n\n```hcl\ndeployment_group \"canary\" {\n  auto_approve_checks = [deployment_auto_approve.safe_changes]\n}\n\ndeployment \"dev\" {\n  inputs = { /* ... */ }\n  deployment_group = deployment_group.canary\n}\n```\n\nMultiple deployments can reference the same group. See `references/deployment-blocks.md` for details.\n\n### Deployment Auto-Approve Block\n\nDefine rules to automatically approve deployment plans (HCP Terraform Premium tier feature):\n\n```hcl\ndeployment_auto_approve \"safe_changes\" {\n  deployment_group = deployment_group.canary\n\n  check {\n    condition = context.plan.changes.remove == 0\n    reason    = \"Cannot auto-approve plans with resource deletions\"\n  }\n}\n```\n\n**Available context variables**: `context.plan.applyable`, `context.plan.changes.add/change/remove/total`, `context.success`\n\n**Note:** `orchestrate` blocks are deprecated. Use `deployment_group` and `deployment_auto_approve` instead.\n\nSee `references/deployment-blocks.md` for all context variables and patterns.\n\n### Publish Output and Upstream Input Blocks\n\nLink Stacks together by publishing outputs from one Stack and consuming them in another:\n\n```hcl\n# In network Stack - publish outputs\npublish_output \"vpc_id_network\" {\n  type  = string\n  value = deployment.network.vpc_id\n}\n\n# In application Stack - consume outputs\nupstream_input \"network_stack\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/networking-stack\"\n}\n\ndeployment \"app\" {\n  inputs = {\n    vpc_id = upstream_input.network_stack.vpc_id_network\n  }\n}\n```\n\nSee `references/linked-stacks.md` for complete documentation and examples.\n\n## Terraform Stacks CLI\n\n**Note**: Terraform Stacks is Generally Available (GA) as of Terraform CLI v1.13+. Stacks now count toward Resources Under Management (RUM) for HCP Terraform billing.\n\n### Initialize and Validate\n\n```bash\nterraform stacks init              # Download providers, modules, generate lock file\nterraform stacks providers-lock    # Regenerate lock file (add platforms if needed)\nterraform stacks validate          # Check syntax without uploading\n```\n\n### Deployment Workflow\n\n**Important**: No `plan` or `apply` commands. Upload configuration triggers deployment runs automatically.\n\n```bash\n# 1. Upload configuration (triggers deployment runs)\nterraform stacks configuration upload\n\n# 2. Monitor deployments\nterraform stacks deployment-run list                          # List runs (non-interactive)\nterraform stacks deployment-group watch -deployment-group=... # Stream status updates\n\n# 3. Approve deployments (if auto-approve not configured)\nterraform stacks deployment-run approve-all-plans -deployment-run-id=...\nterraform stacks deployment-group approve-all-plans -deployment-group=...\nterraform stacks deployment-run cancel -deployment-run-id=...  # Cancel if needed\n```\n\n### Configuration Management\n\n```bash\nterraform stacks configuration list                    # List configuration versions\nterraform stacks configuration fetch -configuration-id=...  # Download configuration\nterraform stacks configuration watch                   # Monitor upload status\n```\n\n### Other Commands\n\n```bash\nterraform stacks create              # Create new Stack (interactive)\nterraform stacks fmt                 # Format Stack files\nterraform stacks list                # Show all Stacks\nterraform stacks version             # Display version\nterraform stacks deployment-group rerun -deployment-group=...  # Rerun deployment\n```\n\n## Monitoring Deployments with HCP Terraform API\n\nFor programmatic monitoring in automation, CI/CD, or non-interactive environments (like AI agents), use the HCP Terraform API instead of CLI watch commands. The API provides endpoints for:\n\n- Configuration status and validation\n- Deployment group summaries\n- Deployment run status\n- Deployment step details (plan/apply)\n- Error diagnostics with file locations and code snippets\n- Stack outputs via artifacts endpoint\n\n**Key points:**\n- CLI watch commands stream indefinitely and don't work in automation\n- Use artifacts endpoint to retrieve Stack outputs: `GET /api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description`\n- Diagnostics endpoint requires `stack_deployment_step_id` query parameter\n- Artifacts endpoint returns HTTP 307 redirect (use `curl -L`)\n\nFor complete API workflow, authentication, polling best practices, and example scripts, see `references/api-monitoring.md`.\n\n## Common Patterns\n\n**Component Dependencies**: Dependencies are automatically inferred when one component references another's output (e.g., `subnet_ids = component.vpc.private_subnet_ids`).\n\n**Multi-Region Deployment**: Use `for_each` on providers and components to deploy across multiple regions. Each region gets its own provider configuration and component instances.\n\n**Deferred Changes**: Stacks support deferred changes to handle dependencies where values are only known after apply. This enables complex multi-component deployments where some resources depend on runtime values from other components (cluster endpoints, generated passwords, etc.).\n\nFor complete examples including multi-region deployments, component dependencies, deferred changes patterns, and linked Stacks, see `references/examples.md`.\n\n## Best Practices\n\n1. **Component Granularity**: Create components for logical infrastructure units that share a lifecycle\n2. **Module Compatibility**:\n   - Modules used with Stacks cannot include provider blocks (configure providers in Stack configuration)\n   - **Test public registry modules** before using in production Stacks - some modules may have compatibility issues\n   - Consider using raw resources for critical infrastructure if module compatibility is uncertain\n   - Example: Some terraform-aws-modules versions have been found to have compatibility issues with Stacks (e.g., ALB and ECS modules)\n3. **State Isolation**: Each deployment has its own isolated state\n4. **Input Variables**: Use variables for values that differ across deployments; use locals for shared values\n5. **Provider Lock Files**: Always generate and commit `.terraform.lock.hcl` to version control\n6. **Naming Conventions**: Use descriptive names for components and deployments\n7. **Deployment Groups**: You can organize deployments into deployment groups. Deployment groups enable auto-approval rules, logical organization, and provide a foundation for scaling. Deployment groups are an HCP Terraform Premium tier feature\n8. **Testing**: Test Stack configurations in dev/staging deployments before production\n\n## Troubleshooting\n\n**Circular Dependencies**: Refactor to break circular references or use intermediate components.\n\n**Deployment Destruction**: Cannot destroy from UI. Set `destroy = true` in deployment block, upload configuration, and HCP Terraform creates a destroy run.\n\n**Empty Diagnostics**: Add required `stack_deployment_step_id` query parameter to diagnostics API requests.\n\n**Module Compatibility**: Test public registry modules before production use. Some modules may have compatibility issues with Stacks.\n\n## References\n\nFor detailed documentation, see:\n- `references/component-blocks.md` - Complete component block reference with all arguments and syntax\n- `references/deployment-blocks.md` - Complete deployment block reference with all configuration options\n- `references/linked-stacks.md` - Publish outputs and upstream inputs for linking Stacks together\n- `references/examples.md` - Complete working examples for multi-region and component dependencies\n- `references/api-monitoring.md` - Full API workflow for programmatic monitoring and automation\n- `references/troubleshooting.md` - Detailed troubleshooting guide for common issues and solutions\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/api-monitoring.md",
    "content": "# API Monitoring Reference\n\nComplete guide for monitoring Terraform Stack deployments using the HCP Terraform API. Use this approach for automation, CI/CD pipelines, and non-interactive environments like AI agents.\n\n## Table of Contents\n\n1. [When to Use the API](#when-to-use-the-api)\n2. [Authentication](#authentication)\n3. [API Monitoring Workflow](#api-monitoring-workflow)\n4. [Detailed Endpoint Reference](#detailed-endpoint-reference)\n5. [Notes for AI Agents and Automation](#notes-for-ai-agents-and-automation)\n\n## When to Use the API\n\nUse the HCP Terraform API instead of CLI commands when:\n- Running in non-interactive environments (CI/CD, automation scripts)\n- Building tools or integrations that need programmatic access\n- Monitoring multiple Stacks simultaneously\n- Implementing custom retry logic or error handling\n- Working in environments where streaming CLI commands don't work\n\n**CLI commands that don't work in automation:**\n- `terraform stacks deployment-run watch` - Streams output, blocks indefinitely\n- `terraform stacks deployment-group watch` - Streams output, blocks indefinitely\n- `terraform stacks configuration watch` - Streams output, blocks indefinitely\n\n## Authentication\n\n### Extract API Token from Credentials File\n\n```bash\nTOKEN=$(jq -r '.credentials[\"app.terraform.io\"].token' ~/.terraform.d/credentials.tfrc.json)\n```\n\n### Alternative: Use Environment Variable\n\n```bash\nexport TFC_TOKEN=\"your-token-here\"\nTOKEN=$TFC_TOKEN\n```\n\n### API Request Headers\n\nAll API requests require these headers:\n\n```bash\n-H \"Authorization: Bearer $TOKEN\"\n-H \"Content-Type: application/vnd.api+json\"\n```\n\n## API Monitoring Workflow\n\nAfter uploading a configuration with `terraform stacks configuration upload`, follow this sequence to monitor deployment progress:\n\n### Step 1: Get Configuration Status\n\n**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}`\n\n**Purpose:** Verify configuration upload completed successfully and get the configuration details.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/vnd.api+json\" \\\n  \"https://app.terraform.io/api/v2/stack-configurations/{configuration-id}\" | jq '.'\n```\n\n**Response Fields:**\n- `attributes.status` - Configuration processing status (pending/completed)\n- `attributes.sequence-number` - Version number of this configuration\n- `attributes.components-detected` - Number of components found\n- `attributes.deployments-detected` - Number of deployments found\n\n**Example Response:**\n\n```json\n{\n  \"data\": {\n    \"id\": \"stc-ABC123\",\n    \"type\": \"stack-configurations\",\n    \"attributes\": {\n      \"status\": \"completed\",\n      \"sequence-number\": 5,\n      \"components-detected\": 3,\n      \"deployments-detected\": 2,\n      \"created-at\": \"2024-01-15T10:30:00.000Z\",\n      \"updated-at\": \"2024-01-15T10:30:45.000Z\"\n    }\n  }\n}\n```\n\n### Step 2: Get Deployment Group Summaries\n\n**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries`\n\n**Purpose:** Get list of deployment groups, their IDs, and current status summary.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/vnd.api+json\" \\\n  \"https://app.terraform.io/api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Deployment group ID (needed for next step)\n- `attributes.name` - Deployment group name (e.g., `dev_default`)\n- `attributes.status` - Overall status (running/succeeded/failed)\n- `attributes.status-counts` - Breakdown of deployment statuses\n\n**Example Response:**\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"sdg-XYZ789\",\n      \"type\": \"stack-deployment-group-summaries\",\n      \"attributes\": {\n        \"name\": \"dev_default\",\n        \"status\": \"running\",\n        \"status-counts\": {\n          \"pending\": 0,\n          \"running\": 1,\n          \"succeeded\": 1,\n          \"failed\": 0\n        }\n      }\n    }\n  ]\n}\n```\n\n### Step 3: Get Deployment Runs\n\n**Endpoint:** `GET /api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs`\n\n**Purpose:** Get list of deployment runs for a specific group with their current status.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/vnd.api+json\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Deployment run ID (needed for next step)\n- `attributes.status` - Current status (planning/planned/applying/applied/failed)\n- `attributes.created-at` - Run start time\n- `attributes.updated-at` - Last update time\n\n**Example Response:**\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"sdr-123ABC\",\n      \"type\": \"stack-deployment-runs\",\n      \"attributes\": {\n        \"status\": \"planning\",\n        \"created-at\": \"2024-01-15T10:31:00.000Z\",\n        \"updated-at\": \"2024-01-15T10:31:15.000Z\"\n      }\n    }\n  ]\n}\n```\n\n### Step 4: Get Deployment Steps\n\n**Endpoint:** `GET /api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps`\n\n**Purpose:** Get detailed information about individual plan and apply steps.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/vnd.api+json\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Step ID (needed for diagnostics and outputs)\n- `attributes.operation-type` - Type of operation (plan/apply)\n- `attributes.status` - Step status (running/completed/failed)\n- `attributes.component-name` - Which component is being processed\n\n**Example Response:**\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"sds-PlanStep123\",\n      \"type\": \"stack-deployment-steps\",\n      \"attributes\": {\n        \"operation-type\": \"plan\",\n        \"status\": \"completed\",\n        \"component-name\": \"vpc\",\n        \"created-at\": \"2024-01-15T10:31:05.000Z\",\n        \"completed-at\": \"2024-01-15T10:31:30.000Z\"\n      }\n    },\n    {\n      \"id\": \"sds-ApplyStep456\",\n      \"type\": \"stack-deployment-steps\",\n      \"attributes\": {\n        \"operation-type\": \"apply\",\n        \"status\": \"running\",\n        \"component-name\": \"vpc\",\n        \"created-at\": \"2024-01-15T10:32:00.000Z\"\n      }\n    }\n  ]\n}\n```\n\n### Step 5: Get Error Diagnostics (When Deployment Fails)\n\n**Endpoint:** `GET /api/v2/stack-deployment-steps/{step-id}/stack-diagnostics`\n\n**Purpose:** Retrieve detailed error messages when a deployment step fails.\n\n**Critical:** The `stack_deployment_step_id` query parameter is **required**. Without it, the API returns empty results.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/vnd.api+json\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | jq '.'\n```\n\n**Response Fields:**\n- `attributes.severity` - Diagnostic level (error/warning)\n- `attributes.summary` - Brief error description\n- `attributes.detail` - Detailed error message\n- `attributes.diags` - Array of diagnostic objects with file locations and code snippets\n\n**Example Response (Error with Details):**\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"stf-ErrorExampleId\",\n      \"type\": \"stack-diagnostics\",\n      \"attributes\": {\n        \"severity\": \"error\",\n        \"summary\": \"Diagnostics reported\",\n        \"detail\": \"2 errors\",\n        \"diags\": [\n          {\n            \"summary\": \"Unsupported attribute\",\n            \"detail\": \"This object does not have an attribute named \\\"target_id\\\".\",\n            \"range\": {\n              \"filename\": \"main.tf\",\n              \"start\": {\n                \"line\": 634,\n                \"column\": 33\n              },\n              \"end\": {\n                \"line\": 634,\n                \"column\": 43\n              },\n              \"source\": \"registry.terraform.io/terraform-aws-modules/alb/aws@9.17.0//main.tf\"\n            },\n            \"snippet\": {\n              \"code\": \"  target_id         = each.value.target_id\",\n              \"context\": \"resource \\\"aws_lb_target_group_attachment\\\" \\\"this\\\"\"\n            }\n          },\n          {\n            \"summary\": \"Invalid reference\",\n            \"detail\": \"A reference to a resource type must be followed by at least one attribute access.\",\n            \"range\": {\n              \"filename\": \"main.tf\",\n              \"start\": {\n                \"line\": 142,\n                \"column\": 15\n              },\n              \"end\": {\n                \"line\": 142,\n                \"column\": 28\n              },\n              \"source\": \"local-module//main.tf\"\n            },\n            \"snippet\": {\n              \"code\": \"  vpc_id = aws_vpc.main\",\n              \"context\": \"resource \\\"aws_subnet\\\" \\\"private\\\"\"\n            }\n          }\n        ],\n        \"acknowledged\": false,\n        \"created-at\": \"2024-01-15T10:32:15.000Z\"\n      }\n    }\n  ]\n}\n```\n\n**Parsing Diagnostics:**\n\nExtract error information with jq:\n\n```bash\n# Get error summaries\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | \\\n  jq -r '.data[].attributes.diags[]? | \"\\(.summary): \\(.detail)\"'\n\n# Get file locations\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | \\\n  jq -r '.data[].attributes.diags[]? | \"\\(.range.filename):\\(.range.start.line)\"'\n```\n\n### Step 6: Get Stack Outputs (After Successful Deployment)\n\n**Endpoint:** `GET /api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description`\n\n**Purpose:** Retrieve Stack outputs after a successful deployment completes.\n\n**Important Notes:**\n- This endpoint returns HTTP 307 redirect - use `curl -L` to follow redirects automatically\n- This is currently the **only way** to retrieve Stack outputs programmatically\n- This endpoint is **not documented** in public API documentation\n- You need the final apply step ID from Step 4\n\n**Request:**\n\n```bash\ncurl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description\"\n```\n\n**Response Structure:**\n\nThe artifact response includes an `.outputs` object where each output contains a `change.after` property with the actual output value:\n\n```json\n{\n  \"outputs\": {\n    \"alb_url\": {\n      \"change\": {\n        \"actions\": [\"no-op\"],\n        \"before\": \"http://my-alb-123456789.us-west-2.elb.amazonaws.com\",\n        \"after\": \"http://my-alb-123456789.us-west-2.elb.amazonaws.com\",\n        \"after_unknown\": false,\n        \"before_sensitive\": false,\n        \"after_sensitive\": false\n      },\n      \"type\": \"string\"\n    },\n    \"ecr_repository_url\": {\n      \"change\": {\n        \"actions\": [\"no-op\"],\n        \"before\": \"123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\",\n        \"after\": \"123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\",\n        \"after_unknown\": false,\n        \"before_sensitive\": false,\n        \"after_sensitive\": false\n      },\n      \"type\": \"string\"\n    }\n  }\n}\n```\n\n**Extract Only Output Values:**\n\n```bash\ncurl -L -s --header \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description\" | \\\n  jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n```\n\n**Example Output:**\n\n```\nalb_url: http://my-alb-123456789.us-west-2.elb.amazonaws.com\necr_repository_url: 123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\n```\n\n## Detailed Endpoint Reference\n\n### Available Artifact Types\n\nThe artifacts endpoint accepts these `name` parameter values:\n\n- `plan-description` - Terraform plan output in JSON format\n- `plan-debug-log` - Detailed debug logs from plan operation\n- `apply-description` - Terraform apply output including outputs (JSON format)\n- `apply-debug-log` - Detailed debug logs from apply operation\n\n### Polling Best Practices\n\n**Recommended polling intervals:**\n- Configuration status: Check every 5 seconds until status is \"completed\"\n- Deployment runs: Check every 10 seconds during active deployment\n- Deployment steps: Check every 10 seconds for individual step status\n\n**Implement exponential backoff:**\n\n```bash\n# Example polling script with backoff\nRETRY_COUNT=0\nMAX_RETRIES=30\nBACKOFF=5\n\nwhile [ $RETRY_COUNT -lt $MAX_RETRIES ]; do\n  STATUS=$(curl -s -H \"Authorization: Bearer $TOKEN\" \\\n    \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}\" | \\\n    jq -r '.data.attributes.status')\n\n  if [ \"$STATUS\" = \"applied\" ] || [ \"$STATUS\" = \"failed\" ]; then\n    echo \"Deployment finished with status: $STATUS\"\n    break\n  fi\n\n  echo \"Current status: $STATUS. Waiting ${BACKOFF}s...\"\n  sleep $BACKOFF\n  RETRY_COUNT=$((RETRY_COUNT + 1))\ndone\n```\n\n## Notes for AI Agents and Automation\n\n### CLI Command Limitations\n\n**These CLI commands DO NOT work in automation:**\n- `terraform stacks deployment-run watch` - Streams output, blocks indefinitely\n- `terraform stacks deployment-group watch` - Streams output, blocks indefinitely\n- `terraform stacks configuration watch` - Streams output, blocks indefinitely\n\n**Solution:** Use API polling instead of watch commands.\n\n### No Direct Output Command\n\nThere is currently no CLI command to retrieve Stack outputs. You must:\n1. Use API to get deployment steps\n2. Find the final apply step ID\n3. Request the `apply-description` artifact\n4. Parse JSON to extract outputs\n\n### Handling Redirects\n\nThe artifacts endpoint returns HTTP 307 redirect to the actual artifact location. Ensure your HTTP client follows redirects:\n\n**curl:** Use `-L` flag\n**Python requests:** Set `allow_redirects=True` (default)\n**Node.js fetch:** Set `redirect: 'follow'` (default)\n\n### Error Handling\n\n**Common API errors:**\n\n- **401 Unauthorized:** Invalid or expired token - refresh credentials\n- **404 Not Found:** Invalid ID or resource doesn't exist yet - retry with backoff\n- **429 Too Many Requests:** Rate limited - implement exponential backoff\n- **Empty diagnostics:** Missing required `stack_deployment_step_id` query parameter\n\n### Complete Monitoring Script Example\n\n```bash\n#!/bin/bash\n\n# Configuration\nTOKEN=$(jq -r '.credentials[\"app.terraform.io\"].token' ~/.terraform.d/credentials.tfrc.json)\nCONFIG_ID=\"stc-ABC123\"\nBASE_URL=\"https://app.terraform.io/api/v2\"\n\n# Helper function\napi_get() {\n  curl -s -H \"Authorization: Bearer $TOKEN\" \\\n    -H \"Content-Type: application/vnd.api+json\" \\\n    \"$1\"\n}\n\n# 1. Wait for configuration to complete\necho \"Checking configuration status...\"\nwhile true; do\n  STATUS=$(api_get \"$BASE_URL/stack-configurations/$CONFIG_ID\" | jq -r '.data.attributes.status')\n  [ \"$STATUS\" = \"completed\" ] && break\n  echo \"Configuration status: $STATUS. Waiting...\"\n  sleep 5\ndone\n\n# 2. Get deployment groups\necho \"Getting deployment groups...\"\nGROUP_ID=$(api_get \"$BASE_URL/stack-configurations/$CONFIG_ID/stack-deployment-group-summaries\" | \\\n  jq -r '.data[0].id')\n\n# 3. Get deployment run\necho \"Getting deployment run...\"\nRUN_ID=$(api_get \"$BASE_URL/stack-deployment-groups/$GROUP_ID/stack-deployment-runs\" | \\\n  jq -r '.data[0].id')\n\n# 4. Monitor deployment run\necho \"Monitoring deployment run: $RUN_ID\"\nwhile true; do\n  STATUS=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID\" | jq -r '.data.attributes.status')\n  echo \"Deployment status: $STATUS\"\n\n  if [ \"$STATUS\" = \"applied\" ]; then\n    echo \"Deployment succeeded!\"\n\n    # 5. Get outputs from final apply step\n    APPLY_STEP=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID/stack-deployment-steps\" | \\\n      jq -r '.data[] | select(.attributes[\"operation-type\"] == \"apply\") | .id' | tail -1)\n\n    echo \"Retrieving outputs from step: $APPLY_STEP\"\n    curl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n      \"$BASE_URL/stack-deployment-steps/$APPLY_STEP/artifacts?name=apply-description\" | \\\n      jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n    break\n  fi\n\n  if [ \"$STATUS\" = \"failed\" ]; then\n    echo \"Deployment failed!\"\n\n    # Get error diagnostics\n    FAILED_STEP=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID/stack-deployment-steps\" | \\\n      jq -r '.data[] | select(.attributes.status == \"failed\") | .id' | head -1)\n\n    echo \"Error diagnostics from step: $FAILED_STEP\"\n    api_get \"$BASE_URL/stack-deployment-steps/$FAILED_STEP/stack-diagnostics?stack_deployment_step_id=$FAILED_STEP\" | \\\n      jq -r '.data[].attributes.diags[]? | \"\\(.summary): \\(.detail)\"'\n    exit 1\n  fi\n\n  sleep 10\ndone\n```\n\nThis script demonstrates a complete monitoring workflow from configuration upload to output retrieval with error handling.\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/component-blocks.md",
    "content": "# Component Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack component configuration files (`.tfcomponent.hcl`).\n\n## Table of Contents\n\n1. [Variable Block](#variable-block)\n2. [Required Providers Block](#required-providers-block)\n3. [Provider Block](#provider-block)\n4. [Component Block](#component-block)\n5. [Output Block](#output-block)\n6. [Locals Block](#locals-block)\n7. [Removed Block](#removed-block)\n\n## Variable Block\n\nDeclares input variables for Stack configuration.\n\n### Syntax\n\n```hcl\nvariable \"variable_name\" {\n  type        = <type>\n  description = \"<description>\"\n  default     = <value>\n  sensitive   = <bool>\n  nullable    = <bool>\n  ephemeral   = <bool>\n}\n```\n\n### Arguments\n\n- **type** (required): Data type (string, number, bool, list, map, object, set, tuple, any)\n- **description** (optional): Variable description\n- **default** (optional): Default value\n- **sensitive** (optional, default false): Mark as sensitive to redact from logs\n- **nullable** (optional, default true): Whether null is allowed\n- **ephemeral** (optional, default false): Do not persist to state file\n\n### Differences from Traditional Terraform\n\n- **type** is required (not optional)\n- **validation** argument is not supported\n\n### Examples\n\n```hcl\nvariable \"aws_region\" {\n  type        = string\n  description = \"AWS region for infrastructure\"\n  default     = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n  type        = string\n  description = \"OIDC identity token\"\n  ephemeral   = true\n}\n\nvariable \"subnet_config\" {\n  type = object({\n    cidr_block           = string\n    availability_zone    = string\n    map_public_ip        = bool\n  })\n}\n```\n\nFor complete variable examples in context, see `examples.md`.\n\n## Required Providers Block\n\nDeclares provider dependencies.\n\n### Syntax\n\n```hcl\nrequired_providers {\n  <provider_name> = {\n    source  = \"<source>\"\n    version = \"<version_constraint>\"\n  }\n}\n```\n\n### Arguments\n\n- **source** (required): Provider source address (e.g., \"hashicorp/aws\")\n- **version** (optional): Version constraint (e.g., \"~> 5.0\")\n\n### Examples\n\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n  \n  random = {\n    source  = \"hashicorp/random\"\n    version = \"~> 3.5.0\"\n  }\n  \n  azurerm = {\n    source  = \"hashicorp/azurerm\"\n    version = \">= 3.0\"\n  }\n}\n```\n\n## Provider Block\n\nConfigures provider instances.\n\n### Syntax\n\n```hcl\nprovider \"<provider_type>\" \"<alias>\" {\n  for_each = <map_or_set>  # Optional\n  \n  config {\n    <provider_arguments>\n  }\n}\n```\n\n### Arguments\n\n- **provider_type** (label 1, required): Provider type (e.g., \"aws\", \"azurerm\")\n- **alias** (label 2, required): Unique identifier for this provider configuration\n- **for_each** (optional): Create multiple provider instances from a map or set\n- **config** (required): Nested block containing provider-specific configuration\n\n### Key Differences from Traditional Terraform\n\n1. Alias is defined in block header, not as an argument\n2. Configuration goes in a nested `config` block\n3. Supports `for_each` meta-argument\n4. Provider configurations are treated as first-class values\n\n### Example\n\n```hcl\nprovider \"aws\" \"main\" {\n  config {\n    region = var.aws_region\n\n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\nFor complete provider examples including for_each and multi-cloud patterns, see `examples.md`.\n\n## Component Block\n\nDefines infrastructure components to include in the Stack.\n\n### Syntax\n\n```hcl\ncomponent \"<component_name>\" {\n  for_each = <map_or_set>  # Optional\n  \n  source = \"<module_source>\"\n  \n  inputs = {\n    <input_name> = <value>\n  }\n  \n  providers = {\n    <provider_local_name> = provider.<type>.<alias>[<key>]\n  }\n}\n```\n\n### Arguments\n\n- **component_name** (label, required): Unique identifier for this component\n- **for_each** (optional): Create multiple component instances\n- **source** (required): Module source (see [Source Argument](#source-argument) below)\n- **version** (optional): Version constraint for registry-based sources only\n- **inputs** (required): Map of input variables for the module\n- **providers** (required): Map of provider configurations\n\n### Source Argument\n\nThe `source` argument accepts the same module sources as traditional Terraform configurations.\n\n**Local File Path:**\n```hcl\nsource = \"./modules/vpc\"\nsource = \"../shared-modules/networking\"\n```\n\n**Public Terraform Registry:**\n```hcl\nsource = \"terraform-aws-modules/vpc/aws\"\nsource = \"hashicorp/consul/aws\"\n```\nFormat: `<NAMESPACE>/<NAME>/<PROVIDER>`\n\n**Private HCP Terraform Registry:**\n```hcl\nsource = \"app.terraform.io/my-org/vpc/aws\"\nsource = \"app.terraform.io/example-corp/networking/azurerm\"\n```\nFormat: `<HOSTNAME>/<ORGANIZATION>/<MODULE_NAME>/<PROVIDER_NAME>`\n\n- **HCP Terraform (SaaS)**: Use hostname `app.terraform.io`\n- **Terraform Enterprise**: Use your instance hostname (e.g., `terraform.mycompany.com`)\n- **Generic hostname**: Use `localterraform.com` for deployments spanning multiple Terraform Enterprise instances\n\n**Git Repository:**\n```hcl\nsource = \"git::https://github.com/org/repo.git//modules/vpc?ref=v1.0.0\"\nsource = \"git::ssh://git@github.com/org/repo.git//modules/vpc?ref=main\"\n```\n\n**HTTP/HTTPS Archive:**\n```hcl\nsource = \"https://example.com/modules/vpc-module.tar.gz\"\n```\n\n### Version Argument\n\nThe `version` argument is supported only for registry-based sources (public and private registries). Local file paths and Git sources do not support the `version` argument.\n\n```hcl\ncomponent \"vpc\" {\n  source  = \"app.terraform.io/my-org/vpc/aws\"\n  version = \"~> 2.0\"  # Semantic versioning constraint\n\n  inputs = {\n    cidr_block = var.vpc_cidr\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n```\n\n**Note**: Modules sourced from local file paths always share the same version as their caller and cannot have independent version constraints.\n\n### Component References\n\nAccess component outputs using: `component.<name>.<output>`\n\nFor components with `for_each`: `component.<name>[<key>].<output>`\n\n### Examples\n\n**Basic Component:**\n\n```hcl\ncomponent \"vpc\" {\n  source  = \"app.terraform.io/my-org/vpc/aws\"\n  version = \"2.1.0\"\n\n  inputs = {\n    cidr_block  = var.vpc_cidr\n    name_prefix = var.name_prefix\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n```\n\n**Component with Dependencies:**\n\n```hcl\ncomponent \"database\" {\n  source = \"./modules/rds\"\n\n  inputs = {\n    vpc_id             = component.vpc.vpc_id\n    subnet_ids         = component.vpc.private_subnet_ids\n    security_group_ids = [component.security.database_sg_id]\n    engine_version     = var.db_engine_version\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n```\n\nFor complete component examples including for_each, multi-region, public registry, and multi-provider patterns, see `examples.md`.\n\n## Output Block\n\nExposes values from Stack configuration.\n\n### Syntax\n\n```hcl\noutput \"<output_name>\" {\n  type        = <type>\n  description = \"<description>\"\n  value       = <expression>\n  sensitive   = <bool>\n  ephemeral   = <bool>\n}\n```\n\n### Arguments\n\n- **output_name** (label, required): Unique identifier for this output\n- **type** (required): Data type of the output\n- **description** (optional): Output description\n- **value** (required): Expression to output\n- **sensitive** (optional, default false): Mark as sensitive\n- **ephemeral** (optional, default false): Ephemeral value\n\n### Differences from Traditional Terraform\n\n- **type** is required\n- **precondition** block is not supported\n\n### Examples\n\n```hcl\noutput \"vpc_id\" {\n  type        = string\n  description = \"VPC ID\"\n  value       = component.vpc.vpc_id\n}\n\noutput \"instance_details\" {\n  type = object({\n    id         = string\n    public_ip  = string\n    private_ip = string\n  })\n  description = \"EC2 instance details\"\n  value = {\n    id         = component.compute.instance_id\n    public_ip  = component.compute.public_ip\n    private_ip = component.compute.private_ip\n  }\n}\n```\n\nFor complete output examples including sensitive outputs and for expressions, see `examples.md`.\n\n## Locals Block\n\nDefines local values for reuse within the Stack configuration.\n\n### Syntax\n\n```hcl\nlocals {\n  <name> = <expression>\n}\n```\n\n### Example\n\n```hcl\nlocals {\n  common_tags = {\n    Environment = var.environment\n    ManagedBy   = \"Terraform Stacks\"\n    Project     = var.project_name\n  }\n\n  name_prefix = \"${var.project_name}-${var.environment}\"\n\n  region_config = {\n    for region in var.regions : region => {\n      name_suffix    = region\n      instance_count = var.environment == \"prod\" ? 3 : 1\n    }\n  }\n}\n```\n\n## Removed Block\n\nDeclares components to be removed from the Stack.\n\n### Syntax\n\n```hcl\nremoved {\n  from   = component.<component_name>\n  source = \"<original_module_source>\"\n  \n  providers = {\n    <provider_name> = provider.<type>.<alias>\n  }\n}\n```\n\n### Arguments\n\n- **from** (required): Reference to the component being removed\n- **source** (required): Original module source\n- **providers** (required): Provider configurations needed for removal\n\n### Important Notes\n\n- Required for safe component removal\n- Must include all providers the component used\n- Do not remove providers before removing components that use them\n\n### Examples\n\n```hcl\nremoved {\n  from   = component.old_component\n  source = \"./modules/deprecated-module\"\n  \n  providers = {\n    aws = provider.aws.main\n  }\n}\n\nremoved {\n  from   = component.legacy_regional\n  source = \"registry.terraform.io/example/legacy/aws\"\n  \n  providers = {\n    aws    = provider.aws.main\n    random = provider.random.main\n  }\n}\n```\n\n## Provider References in Component Blocks\n\n### Single Provider\n\n```hcl\nproviders = {\n  aws = provider.aws.main\n}\n```\n\n### Multiple Providers\n\n```hcl\nproviders = {\n  aws    = provider.aws.main\n  random = provider.random.main\n  tls    = provider.tls.main\n}\n```\n\n### Provider from for_each\n\n```hcl\nproviders = {\n  aws = provider.aws.regional[each.value]\n}\n```\n\n### Aliased Providers in Module\n\nIf module requires specific provider aliases:\n\n```hcl\nproviders = {\n  aws.source = provider.aws.us_east\n  aws.dest   = provider.aws.eu_west\n}\n```\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/deployment-blocks.md",
    "content": "# Deployment Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack deployment configuration files (`.tfdeploy.hcl`).\n\n## Table of Contents\n\n1. [Identity Token Block](#identity-token-block)\n2. [Locals Block](#locals-block)\n3. [Deployment Block](#deployment-block)\n4. [Deployment Group Block](#deployment-group-block)\n5. [Deployment Auto-Approve Block](#deployment-auto-approve-block)\n\n**Note**: For Publish Output and Upstream Input blocks (linked Stacks), see `linked-stacks.md`.\n\n## Identity Token Block\n\nGenerates JWT tokens for OIDC authentication with cloud providers.\n\n### Syntax\n\n```hcl\nidentity_token \"<token_name>\" {\n  audience = [<audience_strings>]\n}\n```\n\n### Arguments\n\n- **token_name** (label, required): Unique identifier for this token\n- **audience** (required): List of audience strings for the JWT\n\n### Accessing Token\n\nReference the JWT using: `identity_token.<n>.jwt`\n\n### Cloud Provider Audiences\n\n**AWS:**\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n```\n\n**Azure:**\n```hcl\nidentity_token \"azure\" {\n  audience = [\"api://AzureADTokenExchange\"]\n}\n```\n\n**Google Cloud:**\n```hcl\nidentity_token \"gcp\" {\n  audience = [\"//iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID>\"]\n}\n```\n\n**Setup Documentation:** For detailed instructions on configuring OIDC/workload identity for each cloud provider (including IAM roles, trust policies, and federated credentials), see: https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials\n\n### Examples\n\n**Single Token:**\n\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\ndeployment \"production\" {\n  inputs = {\n    identity_token = identity_token.aws.jwt\n    role_arn       = var.role_arn\n  }\n}\n```\n\nFor complete working examples including multi-region identity token usage, see `examples.md`.\n\n## Locals Block\n\nDefines local values for reuse within deployment configuration.\n\n### Syntax\n\n```hcl\nlocals {\n  <n> = <expression>\n}\n```\n\n### Example\n\n```hcl\nlocals {\n  aws_regions = [\"us-west-1\", \"us-east-1\", \"eu-west-1\"]\n  role_arn    = \"arn:aws:iam::123456789012:role/hcp-terraform-stacks\"\n\n  common_inputs = {\n    project_name = \"my-app\"\n    environment  = \"production\"\n  }\n}\n```\n\n## Deployment Block\n\nDefines deployment instances of the Stack.\n\n### Syntax\n\n```hcl\ndeployment \"<deployment_name>\" {\n  inputs = {\n    <input_name> = <value>\n  }\n}\n```\n\n### Arguments\n\n- **deployment_name** (label, required): Unique identifier for this deployment\n- **inputs** (required): Map of input variable values\n- **destroy** (optional, default: false): Boolean flag to destroy this deployment\n\n### Constraints\n\n- Minimum 1 deployment per Stack\n- Maximum 20 deployments per Stack\n- No meta-arguments supported (no `for_each`, `count`)\n\n### Destroying a Deployment\n\nTo safely remove a deployment from your Stack:\n\n1. Set `destroy = true` in the deployment block\n2. Apply the plan through HCP Terraform\n3. After successful destruction, remove the deployment block from your configuration\n\n**Important**: Using the `destroy` argument ensures your configuration has the provider authentication necessary to properly destroy the deployment's resources.\n\n**Example:**\n```hcl\ndeployment \"old_environment\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    instance_count = 2\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n  destroy = true  # Mark for destruction\n}\n```\n\nAfter applying this plan and the deployment is destroyed, remove the entire `deployment \"old_environment\"` block from your configuration.\n\n### Examples\n\n**Single Deployment:**\n\n```hcl\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    instance_count = 5\n    instance_type  = \"t3.large\"\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n```\n\n**Using Locals for Multiple Deployments:**\n\n```hcl\nlocals {\n  common_inputs = {\n    role_arn       = \"arn:aws:iam::123456789012:role/terraform\"\n    identity_token = identity_token.aws.jwt\n    project_name   = \"my-app\"\n  }\n}\n\ndeployment \"dev\" {\n  inputs = merge(local.common_inputs, {\n    aws_region     = \"us-east-1\"\n    instance_count = 1\n    environment    = \"dev\"\n  })\n}\n\ndeployment \"prod\" {\n  inputs = merge(local.common_inputs, {\n    aws_region     = \"us-west-1\"\n    instance_count = 5\n    environment    = \"prod\"\n  })\n}\n```\n\nFor complete multi-environment and multi-region deployment examples, see `examples.md`.\n\n## Deployment Group Block\n\nGroups deployments together to configure shared settings and auto-approval rules (HCP Terraform Premium tier feature).\n\n### Syntax\n\n```hcl\ndeployment_group \"<group_name>\" {\n  deployments = [<deployment_references>]\n}\n```\n\n### Arguments\n\n- **group_name** (label, required): Unique identifier for this deployment group\n- **deployments** (required): List of deployment references to include in this group\n\n### Purpose\n\nDeployment groups allow you to:\n- Organize deployments logically (by environment, team, region, etc.)\n- Configure shared auto-approval rules for multiple deployments\n- Manage deployments more effectively at scale\n- Establish consistent configuration patterns across all Stacks\n\n### Examples\n\n**Single Deployment Group (Best Practice):**\n\n```hcl\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    instance_count = 5\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n```\n\n**Multiple Deployment Groups:**\n\n```hcl\ndeployment_group \"non_production\" {\n  deployments = [\n    deployment.development,\n    deployment.staging\n  ]\n}\n\ndeployment_group \"production\" {\n  deployments = [\n    deployment.prod_us_east,\n    deployment.prod_us_west,\n    deployment.prod_eu_west\n  ]\n}\n```\n\n## Deployment Auto-Approve Block\n\nDefines rules that automatically approve deployment plans based on specific conditions (HCP Terraform Premium feature).\n\n### Syntax\n\n```hcl\ndeployment_auto_approve \"<rule_name>\" {\n  deployment_group = deployment_group.<group_name>\n  \n  check {\n    condition = <boolean_expression>\n    reason    = \"<failure_message>\"\n  }\n}\n```\n\n### Arguments\n\n- **rule_name** (label, required): Unique identifier for this auto-approve rule\n- **deployment_group** (required): Reference to the deployment group this rule applies to\n- **check** (required, one or more): Condition that must be met for auto-approval\n\n### Context Variables\n\nAccess plan information through `context` object:\n\n- `context.plan.applyable` - Boolean: plan succeeded without errors\n- `context.plan.changes.add` - Number: resources to add\n- `context.plan.changes.change` - Number: resources to change\n- `context.plan.changes.remove` - Number: resources to remove\n- `context.plan.changes.import` - Number: resources to import\n\n### Important Notes\n\n- All checks must pass for auto-approval to occur\n- If any check fails, manual approval is required\n- HCP Terraform displays the failure reason from failed checks\n- Auto-approve rules only apply to deployments in the specified deployment group\n\n### Examples\n\n**Auto-approve Successful Plans:**\n\n```hcl\ndeployment_group \"canary\" {\n  deployments = [\n    deployment.dev,\n    deployment.staging\n  ]\n}\n\ndeployment_auto_approve \"applyable_plans\" {\n  deployment_group = deployment_group.canary\n  \n  check {\n    condition = context.plan.applyable\n    reason    = \"Plan must be applyable without errors\"\n  }\n}\n```\n\n**Auto-approve Non-Destructive Changes:**\n\n```hcl\ndeployment_group \"production\" {\n  deployments = [\n    deployment.prod_primary,\n    deployment.prod_secondary\n  ]\n}\n\ndeployment_auto_approve \"safe_production_changes\" {\n  deployment_group = deployment_group.production\n  \n  check {\n    condition = context.plan.changes.remove == 0\n    reason    = \"Production deletions require manual approval\"\n  }\n  \n  check {\n    condition = context.plan.applyable\n    reason    = \"Plan must be successful\"\n  }\n}\n```\n\n**Graduated Rollout Pattern:**\n\n```hcl\ndeployment_group \"canary\" {\n  deployments = [deployment.canary]\n}\n\ndeployment_group \"production\" {\n  deployments = [\n    deployment.prod_us,\n    deployment.prod_eu,\n    deployment.prod_asia\n  ]\n}\n\n# Canary auto-approves with strict checks\ndeployment_auto_approve \"canary_strict\" {\n  deployment_group = deployment_group.canary\n  \n  check {\n    condition = context.plan.changes.remove == 0\n    reason    = \"Canary cannot delete resources\"\n  }\n  \n  check {\n    condition = context.plan.changes.change <= 5\n    reason    = \"Canary limited to 5 resource changes\"\n  }\n  \n  check {\n    condition = context.plan.applyable\n    reason    = \"Plan must be applyable\"\n  }\n}\n\n# Production requires manual approval after canary validation\n```\n\nFor complete deployment configuration examples with all blocks, see `examples.md`.\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/examples.md",
    "content": "# Terraform Stacks Complete Examples\n\nComplete, working examples for common Terraform Stacks scenarios.\n\n## Table of Contents\n\n1. [Simple Single-Region Stack](#simple-single-region-stack)\n2. [Stack with Private Registry Modules](#stack-with-private-registry-modules)\n3. [Multi-Environment Stack](#multi-environment-stack)\n4. [Multi-Region Stack](#multi-region-stack)\n5. [Linked Stacks (Cross-Stack Dependencies)](#linked-stacks-cross-stack-dependencies)\n6. [Multi-Cloud Stack](#multi-cloud-stack)\n7. [Complete AWS Production Stack](#complete-aws-production-stack)\n8. [Destroying Deployments](#destroying-deployments)\n\n## Simple Single-Region Stack\n\nBasic Stack with a single environment deployment.\n\n### File Structure\n```\nsimple-stack/\n├── variables.tfcomponent.hcl\n├── providers.tfcomponent.hcl\n├── components.tfcomponent.hcl\n├── deployments.tfdeploy.hcl\n└── modules/\n    └── webapp/\n        ├── main.tf\n        ├── variables.tf\n        └── outputs.tf\n```\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n  type    = string\n  default = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n\nvariable \"app_name\" {\n  type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n}\n\nprovider \"aws\" \"main\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"webapp\" {\n  source = \"./modules/webapp\"\n  \n  inputs = {\n    app_name = var.app_name\n    region   = var.aws_region\n  }\n  \n  providers = {\n    aws = provider.aws.main\n  }\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    app_name       = \"my-webapp\"\n    role_arn       = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n```\n\n## Stack with Private Registry Modules\n\nExample Stack using modules from a private HCP Terraform registry, combining both private and public registry sources.\n\n### File Structure\n```\nprivate-registry-stack/\n├── variables.tfcomponent.hcl\n├── providers.tfcomponent.hcl\n├── components.tfcomponent.hcl\n├── outputs.tfcomponent.hcl\n└── deployments.tfdeploy.hcl\n```\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n  type    = string\n  default = \"us-west-2\"\n}\n\nvariable \"environment\" {\n  type = string\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n\nvariable \"vpc_cidr\" {\n  type    = string\n  default = \"10.0.0.0/16\"\n}\n\nvariable \"app_name\" {\n  type = string\n}\n\nvariable \"db_password\" {\n  type      = string\n  sensitive = true\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n  random = {\n    source  = \"hashicorp/random\"\n    version = \"~> 3.5.0\"\n  }\n}\n\nprovider \"aws\" \"main\" {\n  config {\n    region = var.aws_region\n\n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n\n    default_tags {\n      tags = {\n        Environment = var.environment\n        ManagedBy   = \"Terraform Stacks\"\n        Application = var.app_name\n      }\n    }\n  }\n}\n\nprovider \"random\" \"main\" {\n  config {}\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n  name_prefix = \"${var.app_name}-${var.environment}\"\n  common_tags = {\n    Project     = var.app_name\n    Environment = var.environment\n  }\n}\n\n# Using a private registry module for VPC\ncomponent \"vpc\" {\n  source  = \"app.terraform.io/my-org/vpc/aws\"\n  version = \"2.1.0\"\n\n  inputs = {\n    name_prefix         = local.name_prefix\n    cidr_block          = var.vpc_cidr\n    availability_zones  = [\"${var.aws_region}a\", \"${var.aws_region}b\", \"${var.aws_region}c\"]\n    enable_nat_gateway  = true\n    single_nat_gateway  = var.environment != \"prod\"\n    tags                = local.common_tags\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n\n# Using a private registry module for security groups\ncomponent \"security_groups\" {\n  source  = \"app.terraform.io/my-org/security-groups/aws\"\n  version = \"1.5.2\"\n\n  inputs = {\n    vpc_id      = component.vpc.vpc_id\n    name_prefix = local.name_prefix\n    environment = var.environment\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n\n# Using a public registry module for RDS\ncomponent \"database\" {\n  source  = \"terraform-aws-modules/rds/aws\"\n  version = \"~> 6.0\"\n\n  inputs = {\n    identifier           = \"${local.name_prefix}-db\"\n    engine               = \"postgres\"\n    engine_version       = \"15.3\"\n    family               = \"postgres15\"\n    major_engine_version = \"15\"\n    instance_class       = var.environment == \"prod\" ? \"db.t3.large\" : \"db.t3.micro\"\n\n    allocated_storage     = var.environment == \"prod\" ? 100 : 20\n    db_name               = replace(var.app_name, \"-\", \"_\")\n    username              = \"dbadmin\"\n    password              = var.db_password\n    port                  = 5432\n\n    db_subnet_group_name   = component.vpc.database_subnet_group_name\n    vpc_security_group_ids = [component.security_groups.database_sg_id]\n\n    backup_retention_period = var.environment == \"prod\" ? 30 : 7\n    skip_final_snapshot     = var.environment != \"prod\"\n    deletion_protection     = var.environment == \"prod\"\n\n    tags = local.common_tags\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n\n# Using a private registry module for application infrastructure\ncomponent \"application\" {\n  source  = \"app.terraform.io/my-org/ecs-application/aws\"\n  version = \"3.2.1\"\n\n  inputs = {\n    name_prefix           = local.name_prefix\n    vpc_id                = component.vpc.vpc_id\n    private_subnet_ids    = component.vpc.private_subnet_ids\n    public_subnet_ids     = component.vpc.public_subnet_ids\n    app_security_group_id = component.security_groups.app_sg_id\n\n    container_image       = \"my-org/my-app:latest\"\n    container_port        = 8080\n    desired_count         = var.environment == \"prod\" ? 3 : 1\n\n    environment_variables = {\n      ENVIRONMENT    = var.environment\n      DATABASE_HOST  = component.database.db_instance_endpoint\n      DATABASE_NAME  = component.database.db_instance_name\n    }\n\n    tags = local.common_tags\n  }\n\n  providers = {\n    aws = provider.aws.main\n  }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n  type        = string\n  description = \"VPC ID\"\n  value       = component.vpc.vpc_id\n}\n\noutput \"application_url\" {\n  type        = string\n  description = \"Application load balancer URL\"\n  value       = component.application.load_balancer_dns\n}\n\noutput \"database_endpoint\" {\n  type        = string\n  description = \"Database endpoint\"\n  value       = component.database.db_instance_endpoint\n  sensitive   = true\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"development\" {\n  inputs = {\n    aws_region     = \"us-west-2\"\n    environment    = \"dev\"\n    app_name       = \"myapp\"\n    vpc_cidr       = \"10.0.0.0/16\"\n    db_password    = \"dev-password-change-me\"\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-east-1\"\n    environment    = \"prod\"\n    app_name       = \"myapp\"\n    vpc_cidr       = \"10.1.0.0/16\"\n    db_password    = \"prod-password-use-secrets-manager\"\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"development\" {\n  deployments = [deployment.development]\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n```\n\n### Key Points\n\n- **Private registry modules** use the format `app.terraform.io/<org>/<module>/<provider>`\n- **Version constraints** ensure consistent module versions across environments\n- **Mixed sources**: Combining private registry modules (VPC, security groups, application) with public registry modules (RDS)\n- **Authentication**: HCP Terraform workspaces automatically authenticate to private registries; CLI users need credentials configured\n- **Terraform Enterprise**: Replace `app.terraform.io` with your instance hostname\n\n## Multi-Environment Stack\n\nStack with development, staging, and production deployments.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n  type = string\n}\n\nvariable \"environment\" {\n  type = string\n}\n\nvariable \"instance_count\" {\n  type = number\n}\n\nvariable \"instance_type\" {\n  type = string\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n}\n\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n    \n    default_tags {\n      tags = {\n        Environment = var.environment\n        ManagedBy   = \"Terraform Stacks\"\n      }\n    }\n  }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n  name_prefix = \"myapp-${var.environment}\"\n}\n\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  \n  inputs = {\n    name_prefix = local.name_prefix\n    cidr_block  = \"10.0.0.0/16\"\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"compute\" {\n  source = \"./modules/compute\"\n  \n  inputs = {\n    name_prefix    = local.name_prefix\n    vpc_id         = component.vpc.vpc_id\n    subnet_ids     = component.vpc.private_subnet_ids\n    instance_count = var.instance_count\n    instance_type  = var.instance_type\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n  type  = string\n  value = component.vpc.vpc_id\n}\n\noutput \"load_balancer_url\" {\n  type  = string\n  value = component.compute.load_balancer_url\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n  \n  environments = {\n    dev = {\n      region         = \"us-east-1\"\n      instance_count = 1\n      instance_type  = \"t3.micro\"\n    }\n    staging = {\n      region         = \"us-west-1\"\n      instance_count = 2\n      instance_type  = \"t3.small\"\n    }\n    prod = {\n      region         = \"us-west-1\"\n      instance_count = 5\n      instance_type  = \"t3.large\"\n    }\n  }\n}\n\ndeployment \"development\" {\n  inputs = {\n    aws_region     = local.environments.dev.region\n    environment    = \"dev\"\n    instance_count = local.environments.dev.instance_count\n    instance_type  = local.environments.dev.instance_type\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment \"staging\" {\n  inputs = {\n    aws_region     = local.environments.staging.region\n    environment    = \"staging\"\n    instance_count = local.environments.staging.instance_count\n    instance_type  = local.environments.staging.instance_type\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region     = local.environments.prod.region\n    environment    = \"prod\"\n    instance_count = local.environments.prod.instance_count\n    instance_type  = local.environments.prod.instance_type\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"development\" {\n  deployments = [deployment.development]\n}\n\ndeployment_group \"non_production\" {\n  deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n\n# Auto-approve dev deployments\ndeployment_auto_approve \"dev_auto\" {\n  deployment_group = deployment_group.development\n\n  check {\n    condition = context.plan.applyable\n    reason    = \"Development plans must be applyable\"\n  }\n}\n```\n\n## Multi-Region Stack\n\nStack that deploys identical infrastructure across multiple AWS regions.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"regions\" {\n  type = set(string)\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n\nvariable \"app_name\" {\n  type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n}\n\nprovider \"aws\" \"regional\" {\n  for_each = var.regions\n  \n  config {\n    region = each.value\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n    \n    default_tags {\n      tags = {\n        Region    = each.value\n        ManagedBy = \"Terraform Stacks\"\n        AppName   = var.app_name\n      }\n    }\n  }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"regional_infrastructure\" {\n  for_each = var.regions\n  \n  source = \"./modules/regional-infra\"\n  \n  inputs = {\n    region      = each.value\n    app_name    = var.app_name\n    name_suffix = each.value\n  }\n  \n  providers = {\n    aws = provider.aws.regional[each.value]\n  }\n}\n\ncomponent \"global_route53\" {\n  source = \"./modules/route53\"\n  \n  inputs = {\n    app_name     = var.app_name\n    domain_name  = \"example.com\"\n    regional_lbs = {\n      for region, comp in component.regional_infrastructure :\n      region => comp.load_balancer_dns\n    }\n  }\n  \n  # Use one region's provider for global resources\n  providers = {\n    aws = provider.aws.regional[\"us-west-1\"]\n  }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"regional_endpoints\" {\n  type = map(string)\n  value = {\n    for region, comp in component.regional_infrastructure :\n    region => comp.load_balancer_url\n  }\n}\n\noutput \"global_domain\" {\n  type  = string\n  value = component.global_route53.domain_name\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  regions = [\"us-west-1\", \"us-east-1\", \"eu-west-1\"]\n}\n\ndeployment \"multi_region_prod\" {\n  inputs = {\n    regions        = toset(local.regions)\n    app_name       = \"my-global-app\"\n    role_arn       = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"production\" {\n  deployments = [deployment.multi_region_prod]\n}\n```\n\n## Linked Stacks (Cross-Stack Dependencies)\n\nTwo Stacks where the application Stack depends on the network Stack.\n\n### Network Stack\n\n#### network-stack/variables.tfcomponent.hcl\n```hcl\nvariable \"vpc_cidr\" {\n  type = string\n}\n\nvariable \"environment\" {\n  type = string\n}\n\nvariable \"aws_region\" {\n  type = string\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n```\n\n#### network-stack/providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n}\n\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n#### network-stack/components.tfcomponent.hcl\n```hcl\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  \n  inputs = {\n    cidr_block  = var.vpc_cidr\n    environment = var.environment\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"security_groups\" {\n  source = \"./modules/security-groups\"\n  \n  inputs = {\n    vpc_id      = component.vpc.vpc_id\n    environment = var.environment\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\n#### network-stack/outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n  type  = string\n  value = component.vpc.vpc_id\n}\n\noutput \"private_subnet_ids\" {\n  type  = list(string)\n  value = component.vpc.private_subnet_ids\n}\n\noutput \"public_subnet_ids\" {\n  type  = list(string)\n  value = component.vpc.public_subnet_ids\n}\n\noutput \"app_security_group_id\" {\n  type  = string\n  value = component.security_groups.app_sg_id\n}\n```\n\n#### network-stack/deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"network\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    environment    = \"production\"\n    vpc_cidr       = \"10.0.0.0/16\"\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Publish outputs for other stacks\npublish_output \"vpc_id_network\" {\n  type  = string\n  value = deployment.network.vpc_id\n}\n\npublish_output \"private_subnet_ids\" {\n  type  = list(string)\n  value = deployment.network.private_subnet_ids\n}\n\npublish_output \"public_subnet_ids\" {\n  type  = list(string)\n  value = deployment.network.public_subnet_ids\n}\n\npublish_output \"app_security_group_id\" {\n  type  = string\n  value = deployment.network.app_security_group_id\n}\n\n# Deployment groups\ndeployment_group \"network\" {\n  deployments = [deployment.network]\n}\n```\n\n### Application Stack\n\n#### application-stack/variables.tfcomponent.hcl\n```hcl\nvariable \"vpc_id\" {\n  type = string\n}\n\nvariable \"subnet_ids\" {\n  type = list(string)\n}\n\nvariable \"security_group_id\" {\n  type = string\n}\n\nvariable \"instance_count\" {\n  type = number\n}\n\nvariable \"aws_region\" {\n  type = string\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n```\n\n#### application-stack/providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n}\n\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n#### application-stack/components.tfcomponent.hcl\n```hcl\ncomponent \"application\" {\n  source = \"./modules/app\"\n  \n  inputs = {\n    vpc_id            = var.vpc_id\n    subnet_ids        = var.subnet_ids\n    security_group_id = var.security_group_id\n    instance_count    = var.instance_count\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\n#### application-stack/deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\n# Reference the network stack\nupstream_input \"network\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/network-stack\"\n}\n\ndeployment \"application\" {\n  inputs = {\n    aws_region        = \"us-west-1\"\n    vpc_id            = upstream_input.network.vpc_id_network\n    subnet_ids        = upstream_input.network.private_subnet_ids\n    security_group_id = upstream_input.network.app_security_group_id\n    instance_count    = 3\n    role_arn          = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n    identity_token    = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"application\" {\n  deployments = [deployment.application]\n}\n```\n\n## Multi-Cloud Stack\n\nStack that deploys to both AWS and Azure.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n  type = string\n}\n\nvariable \"azure_location\" {\n  type = string\n}\n\nvariable \"aws_identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"aws_role_arn\" {\n  type = string\n}\n\nvariable \"azure_identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"azure_subscription_id\" {\n  type = string\n}\n\nvariable \"azure_tenant_id\" {\n  type = string\n}\n\nvariable \"azure_client_id\" {\n  type = string\n}\n\nvariable \"app_name\" {\n  type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n  azurerm = {\n    source  = \"hashicorp/azurerm\"\n    version = \"~> 3.0\"\n  }\n}\n\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.aws_role_arn\n      web_identity_token = var.aws_identity_token\n    }\n  }\n}\n\nprovider \"azurerm\" \"this\" {\n  config {\n    features {}\n    \n    subscription_id = var.azure_subscription_id\n    tenant_id       = var.azure_tenant_id\n    client_id       = var.azure_client_id\n    \n    use_oidc = true\n    oidc_token = var.azure_identity_token\n  }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"aws_infrastructure\" {\n  source = \"./modules/aws-infra\"\n  \n  inputs = {\n    region   = var.aws_region\n    app_name = var.app_name\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"azure_infrastructure\" {\n  source = \"./modules/azure-infra\"\n  \n  inputs = {\n    location = var.azure_location\n    app_name = var.app_name\n  }\n  \n  providers = {\n    azurerm = provider.azurerm.this\n  }\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nidentity_token \"azure\" {\n  audience = [\"api://AzureADTokenExchange\"]\n}\n\ndeployment \"multi_cloud\" {\n  inputs = {\n    aws_region             = \"us-west-1\"\n    azure_location         = \"westus2\"\n    app_name               = \"my-multi-cloud-app\"\n    aws_role_arn           = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n    aws_identity_token     = identity_token.aws.jwt\n    azure_subscription_id  = \"12345678-1234-1234-1234-123456789012\"\n    azure_tenant_id        = \"87654321-4321-4321-4321-210987654321\"\n    azure_client_id        = \"11111111-1111-1111-1111-111111111111\"\n    azure_identity_token   = identity_token.azure.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"multi_cloud\" {\n  deployments = [deployment.multi_cloud]\n}\n```\n\n## Complete AWS Production Stack\n\nFull production-grade Stack with VPC, RDS, ECS, and monitoring.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n  type        = string\n  description = \"AWS region\"\n}\n\nvariable \"environment\" {\n  type        = string\n  description = \"Environment name\"\n}\n\nvariable \"vpc_cidr\" {\n  type        = string\n  description = \"VPC CIDR block\"\n}\n\nvariable \"app_name\" {\n  type        = string\n  description = \"Application name\"\n}\n\nvariable \"db_instance_class\" {\n  type        = string\n  description = \"RDS instance class\"\n}\n\nvariable \"ecs_desired_count\" {\n  type        = number\n  description = \"Desired ECS task count\"\n}\n\nvariable \"identity_token\" {\n  type      = string\n  ephemeral = true\n}\n\nvariable \"role_arn\" {\n  type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"\n  }\n  random = {\n    source  = \"hashicorp/random\"\n    version = \"~> 3.5.0\"\n  }\n}\n\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    \n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n    \n    default_tags {\n      tags = {\n        Environment = var.environment\n        Application = var.app_name\n        ManagedBy   = \"Terraform Stacks\"\n      }\n    }\n  }\n}\n\nprovider \"random\" \"this\" {\n  config {}\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n  name_prefix = \"${var.app_name}-${var.environment}\"\n}\n\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  \n  inputs = {\n    name_prefix = local.name_prefix\n    cidr_block  = var.vpc_cidr\n    azs_count   = 3\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"security_groups\" {\n  source = \"./modules/security-groups\"\n  \n  inputs = {\n    name_prefix = local.name_prefix\n    vpc_id      = component.vpc.vpc_id\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"rds\" {\n  source = \"./modules/rds\"\n  \n  inputs = {\n    name_prefix        = local.name_prefix\n    instance_class     = var.db_instance_class\n    subnet_ids         = component.vpc.private_subnet_ids\n    security_group_ids = [component.security_groups.database_sg_id]\n  }\n  \n  providers = {\n    aws    = provider.aws.this\n    random = provider.random.this\n  }\n}\n\ncomponent \"ecs_cluster\" {\n  source = \"./modules/ecs-cluster\"\n  \n  inputs = {\n    name_prefix = local.name_prefix\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"ecs_service\" {\n  source = \"./modules/ecs-service\"\n  \n  inputs = {\n    name_prefix      = local.name_prefix\n    cluster_id       = component.ecs_cluster.cluster_id\n    desired_count    = var.ecs_desired_count\n    subnet_ids       = component.vpc.private_subnet_ids\n    security_group_id = component.security_groups.app_sg_id\n    database_endpoint = component.rds.endpoint\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"alb\" {\n  source = \"./modules/alb\"\n  \n  inputs = {\n    name_prefix       = local.name_prefix\n    vpc_id            = component.vpc.vpc_id\n    subnet_ids        = component.vpc.public_subnet_ids\n    security_group_id = component.security_groups.alb_sg_id\n    target_group_arn  = component.ecs_service.target_group_arn\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n\ncomponent \"cloudwatch\" {\n  source = \"./modules/cloudwatch\"\n  \n  inputs = {\n    name_prefix  = local.name_prefix\n    cluster_name = component.ecs_cluster.cluster_name\n    service_name = component.ecs_service.service_name\n  }\n  \n  providers = {\n    aws = provider.aws.this\n  }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"load_balancer_url\" {\n  type        = string\n  description = \"Application load balancer URL\"\n  value       = component.alb.dns_name\n}\n\noutput \"database_endpoint\" {\n  type        = string\n  description = \"RDS endpoint\"\n  value       = component.rds.endpoint\n  sensitive   = true\n}\n\noutput \"vpc_id\" {\n  type  = string\n  value = component.vpc.vpc_id\n}\n\noutput \"ecs_cluster_name\" {\n  type  = string\n  value = component.ecs_cluster.cluster_name\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"staging\" {\n  inputs = {\n    aws_region        = \"us-west-1\"\n    environment       = \"staging\"\n    app_name          = \"myapp\"\n    vpc_cidr          = \"10.1.0.0/16\"\n    db_instance_class = \"db.t3.small\"\n    ecs_desired_count = 2\n    role_arn          = local.role_arn\n    identity_token    = identity_token.aws.jwt\n  }\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region        = \"us-west-1\"\n    environment       = \"production\"\n    app_name          = \"myapp\"\n    vpc_cidr          = \"10.0.0.0/16\"\n    db_instance_class = \"db.r5.large\"\n    ecs_desired_count = 5\n    role_arn          = local.role_arn\n    identity_token    = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n  deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n\n# Auto-approve staging with safety checks\ndeployment_auto_approve \"staging_safe\" {\n  deployment_group = deployment_group.staging\n\n  check {\n    condition = context.plan.changes.remove == 0\n    reason    = \"Cannot auto-approve deletions in staging\"\n  }\n\n  check {\n    condition = context.plan.applyable\n    reason    = \"Plan must be applyable\"\n  }\n}\n```\n\n## Testing Configurations\n\n### Validate Stack Configuration\n```bash\nterraform stacks providers lock\nterraform stacks validate\n```\n\n### Plan Specific Deployment\n```bash\nterraform stacks plan --deployment=development\nterraform stacks plan --deployment=production\n```\n\n### Apply Deployment\n```bash\nterraform stacks apply --deployment=staging\n```\n\n## Destroying Deployments\n\nExample of safely removing a deployment from your Stack.\n\n### Scenario\n\nYou want to decommission the \"development\" deployment while keeping staging and production active.\n\n### Step 1: Mark Deployment for Destruction\n\nUpdate your `deployments.tfdeploy.hcl` file to set `destroy = true`:\n\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\n# Mark this deployment for destruction\ndeployment \"development\" {\n  inputs = {\n    aws_region     = \"us-east-1\"\n    environment    = \"dev\"\n    instance_count = 1\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n  destroy = true  # This tells HCP Terraform to destroy all resources\n}\n\n# Keep these deployments active\ndeployment \"staging\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    environment    = \"staging\"\n    instance_count = 2\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    environment    = \"prod\"\n    instance_count = 5\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n  deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n```\n\n### Step 2: Plan and Apply\n\n```bash\n# Review the destruction plan\nterraform stacks plan --deployment=development\n\n# Apply the destruction\nterraform stacks apply --deployment=development\n```\n\nHCP Terraform will destroy all resources in the development deployment.\n\n### Step 3: Remove the Deployment Block\n\nAfter the deployment is successfully destroyed, remove the entire deployment block from your configuration:\n\n```hcl\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n  role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\n# deployment \"development\" block has been removed\n\ndeployment \"staging\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    environment    = \"staging\"\n    instance_count = 2\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\ndeployment \"production\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    environment    = \"prod\"\n    instance_count = 5\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n  deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n  deployments = [deployment.production]\n}\n```\n\n### Important Notes\n\n- **Provider Authentication**: The `destroy` argument ensures your configuration retains the provider authentication needed to destroy resources\n- **Do Not Remove Immediately**: Don't remove the deployment block until after the destruction is complete\n- **Verify Before Removing**: Check HCP Terraform UI to confirm all resources are destroyed before removing the block\n- **Alternative**: You could manually destroy resources through HCP Terraform UI, but using `destroy = true` is the recommended approach for maintaining infrastructure-as-code practices\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/linked-stacks.md",
    "content": "# Linked Stacks Reference\n\nComplete reference for linking Terraform Stacks together using published outputs and upstream inputs.\n\n## Publish Output Block\n\nExports outputs from a Stack for consumption by other Stacks (linked Stacks).\n\n###Syntax\n\n```hcl\npublish_output \"<output_name>\" {\n  type  = <type>\n  value = <expression>\n}\n```\n\n### Arguments\n\n- **output_name** (label, required): Unique identifier for this published output\n- **type** (required): Data type of the output\n- **value** (required): Expression to export\n\n### Accessing Deployment Outputs\n\nReference deployment outputs using: `deployment.<deployment_name>.<output_name>`\n\n### Important Notes\n\n- Must apply the Stack's deployment configuration before downstream Stacks can reference outputs\n- Published outputs create a snapshot that other Stacks can read\n- Changes to published outputs automatically trigger runs in downstream Stacks\n\n### Examples\n\n**Basic Published Output:**\n\n```hcl\npublish_output \"vpc_id\" {\n  type  = string\n  value = deployment.network.vpc_id\n}\n\npublish_output \"subnet_ids\" {\n  type  = list(string)\n  value = deployment.network.private_subnet_ids\n}\n```\n\n**Multiple Deployment Outputs:**\n\n```hcl\npublish_output \"regional_vpc_ids\" {\n  type = map(string)\n  value = {\n    us_east = deployment.us_east.vpc_id\n    us_west = deployment.us_west.vpc_id\n    eu_west = deployment.eu_west.vpc_id\n  }\n}\n```\n\n**Complex Output:**\n\n```hcl\npublish_output \"database_config\" {\n  type = object({\n    endpoint = string\n    port     = number\n    name     = string\n  })\n  value = {\n    endpoint = deployment.production.db_endpoint\n    port     = deployment.production.db_port\n    name     = deployment.production.db_name\n  }\n}\n```\n\n**Regional Endpoints:**\n\n```hcl\npublish_output \"api_endpoints\" {\n  type = map(object({\n    url    = string\n    region = string\n  }))\n  value = {\n    for env in [\"dev\", \"staging\", \"prod\"] : env => {\n      url    = deployment[env].api_url\n      region = deployment[env].region\n    }\n  }\n}\n```\n\n## Upstream Input Block\n\nReferences published outputs from another Stack (linked Stacks).\n\n### Syntax\n\n```hcl\nupstream_input \"<input_name>\" {\n  type   = \"stack\"\n  source = \"<stack_address>\"\n}\n```\n\n### Arguments\n\n- **input_name** (label, required): Local name for this upstream input\n- **type** (required): Must be \"stack\"\n- **source** (required): Full Stack address in format: `app.terraform.io/<org>/<project>/<stack-name>`\n\n### Accessing Upstream Outputs\n\nReference upstream outputs using: `upstream_input.<input_name>.<output_name>`\n\n### Important Notes\n\n- Creates a dependency on the upstream Stack\n- Upstream Stack must have applied its deployment configuration\n- Changes in upstream Stack automatically trigger downstream Stack runs\n- Only works with Stacks in the same HCP Terraform project\n\n### Examples\n\n**Basic Upstream Reference:**\n\n```hcl\nupstream_input \"network\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/networking-stack\"\n}\n\ndeployment \"application\" {\n  inputs = {\n    vpc_id     = upstream_input.network.vpc_id\n    subnet_ids = upstream_input.network.subnet_ids\n  }\n}\n```\n\n**Multiple Upstream Stacks:**\n\n```hcl\nupstream_input \"network\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/network-stack\"\n}\n\nupstream_input \"database\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/database-stack\"\n}\n\ndeployment \"application\" {\n  inputs = {\n    vpc_id              = upstream_input.network.vpc_id\n    subnet_ids          = upstream_input.network.private_subnet_ids\n    database_endpoint   = upstream_input.database.endpoint\n    database_credentials = upstream_input.database.credentials\n  }\n}\n```\n\n**Regional Upstream Dependencies:**\n\n```hcl\nupstream_input \"regional_network\" {\n  type   = \"stack\"\n  source = \"app.terraform.io/my-org/my-project/regional-networks\"\n}\n\ndeployment \"us_east_app\" {\n  inputs = {\n    region     = \"us-east-1\"\n    vpc_id     = upstream_input.regional_network.regional_vpc_ids[\"us_east\"]\n    subnet_ids = upstream_input.regional_network.regional_subnet_ids[\"us_east\"]\n  }\n}\n```\n\n## Complete Working Example\n\nFor a complete example showing full Stack configurations with all files (variables, providers, components, outputs, deployments) for both upstream and downstream Stacks, see the \"Linked Stacks (Cross-Stack Dependencies)\" section in `examples.md`.\n"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/troubleshooting.md",
    "content": "# Troubleshooting Reference\n\nCommon issues and solutions when working with Terraform Stacks.\n\n## Table of Contents\n\n1. [Configuration Issues](#configuration-issues)\n2. [Deployment Issues](#deployment-issues)\n3. [Provider and Authentication Issues](#provider-and-authentication-issues)\n4. [Module Compatibility Issues](#module-compatibility-issues)\n5. [State and Dependency Issues](#state-and-dependency-issues)\n6. [API and CLI Issues](#api-and-cli-issues)\n\n## Configuration Issues\n\n### Circular Dependencies\n\n**Issue:** Component A references Component B, and Component B references Component A.\n\n**Error Message:**\n```\nError: Cycle detected in component dependencies\n```\n\n**Solutions:**\n\n1. **Break the circular reference** by refactoring components:\n\n```hcl\n# Before (circular dependency)\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  inputs = {\n    security_group_id = component.app.security_group_id  # References app\n  }\n}\n\ncomponent \"app\" {\n  source = \"./modules/app\"\n  inputs = {\n    vpc_id = component.vpc.vpc_id  # References vpc\n  }\n}\n\n# After (broken circular reference)\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  inputs = {\n    # Remove reference to app\n  }\n}\n\ncomponent \"security_group\" {\n  source = \"./modules/security-group\"\n  inputs = {\n    vpc_id = component.vpc.vpc_id\n  }\n}\n\ncomponent \"app\" {\n  source = \"./modules/app\"\n  inputs = {\n    vpc_id             = component.vpc.vpc_id\n    security_group_id  = component.security_group.id\n  }\n}\n```\n\n2. **Use intermediate components** to break the dependency chain\n3. **Refactor modules** to remove the circular dependency at the module level\n\n### Validation Errors on Variables\n\n**Issue:** Variable block validation errors during `terraform stacks validate`.\n\n**Error Message:**\n```\nError: Unsupported argument\n  on variables.tfcomponent.hcl line 5:\n  5:   validation {\n\nValidation blocks are not supported in Stack configurations\n```\n\n**Solution:** Remove `validation` blocks from variable declarations. Stacks do not support validation blocks:\n\n```hcl\n# Incorrect\nvariable \"instance_count\" {\n  type = number\n  validation {\n    condition     = var.instance_count > 0\n    error_message = \"Instance count must be positive\"\n  }\n}\n\n# Correct\nvariable \"instance_count\" {\n  type        = number\n  description = \"Number of instances (must be positive)\"\n}\n```\n\nMove validation logic into the underlying modules if needed.\n\n### Missing Type in Variable Declarations\n\n**Issue:** Variables fail validation when `type` is not specified.\n\n**Error Message:**\n```\nError: Missing required argument\n  on variables.tfcomponent.hcl line 3:\n  3: variable \"region\" {\n\nThe argument \"type\" is required in Stack variable declarations\n```\n\n**Solution:** Always specify `type` for variables - it's required in Stacks (unlike traditional Terraform):\n\n```hcl\n# Incorrect\nvariable \"region\" {\n  default = \"us-west-1\"\n}\n\n# Correct\nvariable \"region\" {\n  type    = string\n  default = \"us-west-1\"\n}\n```\n\n### Provider Configuration in Modules\n\n**Issue:** Modules with embedded provider blocks cause errors.\n\n**Error Message:**\n```\nError: Provider configuration not allowed in module\n\nModules used with Terraform Stacks cannot contain provider blocks\n```\n\n**Solution:**\n\n1. **Remove provider blocks from modules** - configure providers in Stack configuration instead\n2. **Use modules that don't contain provider blocks** (most public registry modules are compatible)\n3. **Fork and modify modules** if necessary to remove provider blocks\n\n## Deployment Issues\n\n### Cannot Destroy Deployment from UI\n\n**Issue:** The HCP Terraform UI doesn't provide an option to destroy Stack deployments.\n\n**Why:** Stack deployment destruction is only available through configuration, not the UI.\n\n**Solution:** Set `destroy = true` in the deployment block and upload the configuration:\n\n```hcl\ndeployment \"old_environment\" {\n  inputs = {\n    aws_region     = \"us-west-1\"\n    instance_count = 2\n    role_arn       = local.role_arn\n    identity_token = identity_token.aws.jwt\n  }\n\n  destroy = true  # Marks deployment for destruction\n}\n```\n\n**Workflow:**\n\n1. Add `destroy = true` to the deployment block\n2. Run `terraform stacks configuration upload`\n3. HCP Terraform creates a destroy run automatically\n4. Approve the destroy run (if auto-approve is not configured)\n5. After destruction completes, remove the deployment block entirely\n6. Upload configuration again to clean up the deployment definition\n\n**Important:** You cannot destroy deployments from the UI. This is by design to prevent accidental destruction.\n\n### Deployment Stuck in \"Planning\" State\n\n**Issue:** Deployment remains in \"planning\" state indefinitely.\n\n**Possible Causes:**\n\n1. **Provider authentication failed** - Check OIDC configuration and IAM roles\n2. **Module download failed** - Verify module sources are accessible\n3. **Provider version conflict** - Check `.terraform.lock.hcl` matches required providers\n\n**Diagnosis:**\n\n```bash\n# Get deployment step diagnostics\nterraform stacks deployment-run list\n# Note the run ID, then:\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps\" | \\\n  jq '.data[] | {id, status: .attributes.status, component: .attributes[\"component-name\"]}'\n```\n\n**Solutions:**\n\n1. Check diagnostics for the stuck step\n2. Verify provider authentication is configured correctly\n3. Ensure all module sources are accessible\n4. Check provider lock file matches required providers\n\n### Deployment Requires Approval But No Approval Prompt\n\n**Issue:** Deployment is waiting for approval but CLI doesn't show approval prompt.\n\n**Why:** CLI monitoring commands are non-blocking and don't automatically prompt for approval.\n\n**Solution:**\n\n**Option 1: Approve via CLI**\n```bash\n# Approve all pending plans in a deployment run\nterraform stacks deployment-run approve-all-plans -deployment-run-id=sdr-ABC123\n\n# Or approve all plans in a deployment group\nterraform stacks deployment-group approve-all-plans -deployment-group=canary\n```\n\n**Option 2: Configure auto-approve** (Premium feature)\n```hcl\ndeployment_auto_approve \"safe_changes\" {\n  deployment_group = deployment_group.canary\n\n  check {\n    condition = context.plan.applyable\n    reason    = \"Plan must be successful\"\n  }\n}\n```\n\n## Provider and Authentication Issues\n\n### OIDC Authentication Failing\n\n**Issue:** Provider authentication fails with OIDC/workload identity.\n\n**Error Messages:**\n```\nError: Error assuming role with web identity\nError: Failed to retrieve credentials\nError: Invalid identity token\n```\n\n**Diagnosis Steps:**\n\n1. **Verify identity token configuration:**\n\n```hcl\n# Check identity_token block exists\nidentity_token \"aws\" {\n  audience = [\"aws.workload.identity\"]\n}\n\n# Check deployment references the token\ndeployment \"production\" {\n  inputs = {\n    identity_token = identity_token.aws.jwt\n  }\n}\n```\n\n2. **Verify provider configuration:**\n\n```hcl\nprovider \"aws\" \"this\" {\n  config {\n    region = var.aws_region\n    assume_role_with_web_identity {\n      role_arn           = var.role_arn\n      web_identity_token = var.identity_token\n    }\n  }\n}\n```\n\n3. **Check IAM role trust policy:**\n\n**AWS - Verify trust policy includes HCP Terraform:**\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::<account-id>:oidc-provider/app.terraform.io\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"app.terraform.io:aud\": \"aws.workload.identity\"\n        },\n        \"StringLike\": {\n          \"app.terraform.io:sub\": \"organization:<org-name>:project:<project-name>:stack:<stack-name>:deployment:<deployment-name>\"\n        }\n      }\n    }\n  ]\n}\n```\n\n**Azure - Verify federated credential:**\n- Application ID matches the one in provider configuration\n- Subject matches: `organization:<org>:project:<project>:stack:<stack>:deployment:<deployment>`\n- Issuer is `https://app.terraform.io`\n\n**GCP - Verify workload identity pool:**\n- Provider configuration includes correct workload identity provider\n- Service account has necessary IAM permissions\n- Attribute mapping includes `google.subject` from token claims\n\n**Solutions:**\n\n1. Fix IAM role trust policy to include correct HCP Terraform OIDC provider\n2. Ensure audience matches between identity_token block and IAM trust policy\n3. Verify subject pattern matches your organization/project/stack/deployment names\n4. Check that the role_arn is correct in provider configuration\n\n### Provider Version Lock File Issues\n\n**Issue:** Provider version conflicts or \"could not retrieve provider\" errors.\n\n**Error Messages:**\n```\nError: Failed to install provider\nError: Provider version not found\nError: Checksum mismatch for provider\n```\n\n**Solutions:**\n\n1. **Regenerate provider lock file:**\n\n```bash\nterraform stacks providers-lock\n```\n\n2. **Add additional platforms** (if deploying from different OS):\n\n```bash\nterraform stacks providers-lock \\\n  -platform=linux_amd64 \\\n  -platform=darwin_amd64 \\\n  -platform=darwin_arm64\n```\n\n3. **Verify required_providers block:**\n\n```hcl\nrequired_providers {\n  aws = {\n    source  = \"hashicorp/aws\"\n    version = \"~> 5.7.0\"  # Ensure version constraint is valid\n  }\n}\n```\n\n4. **Commit `.terraform.lock.hcl`** to version control\n\n## Module Compatibility Issues\n\n### Public Registry Module Errors\n\n**Issue:** Modules from the Terraform public registry cause errors during plan or apply.\n\n**Common Errors:**\n```\nError: Unsupported attribute\nError: Invalid reference\nError: Missing required argument\n```\n\n**Known Problematic Modules:**\n- `terraform-aws-modules/alb/aws` - Some versions have compatibility issues\n- `terraform-aws-modules/ecs-service/aws` - May have issues with certain configurations\n\n**Solutions:**\n\n1. **Test modules in dev deployment first** before using in production\n\n2. **Check module compatibility** by reviewing recent issues on the module repository\n\n3. **Use specific module versions** rather than latest:\n\n```hcl\ncomponent \"alb\" {\n  source  = \"terraform-aws-modules/alb/aws\"\n  version = \"8.7.0\"  # Use specific version known to work\n  # ...\n}\n```\n\n4. **Consider using raw resources** for critical infrastructure:\n\n```hcl\n# Instead of using a module that has issues\ncomponent \"alb\" {\n  source = \"./modules/alb\"  # Create local module with raw resources\n  # ...\n}\n```\n\n5. **Fork and fix modules** if you have the resources to maintain them\n\n6. **Report compatibility issues** to module maintainers\n\n### Local Module Not Found\n\n**Issue:** Stack can't find local module sources.\n\n**Error Message:**\n```\nError: Module not found\n  Could not load module ./modules/vpc\n```\n\n**Solutions:**\n\n1. **Verify module path is relative** to Stack root:\n\n```hcl\n# Correct\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n}\n\n# Incorrect (absolute paths don't work)\ncomponent \"vpc\" {\n  source = \"/Users/username/project/modules/vpc\"\n}\n```\n\n2. **Ensure module directory exists** with proper structure:\n\n```\nmy-stack/\n├── components.tfcomponent.hcl\n└── modules/\n    └── vpc/\n        ├── main.tf\n        ├── variables.tf\n        └── outputs.tf\n```\n\n3. **Check file permissions** on module directories\n\n## State and Dependency Issues\n\n### Component Output Not Available\n\n**Issue:** Component output is not available to referencing component.\n\n**Error Message:**\n```\nError: Reference to unknown component\n  Component \"vpc\" has not been defined\n```\n\n**Solutions:**\n\n1. **Verify component exists** in configuration:\n\n```hcl\ncomponent \"vpc\" {\n  source = \"./modules/vpc\"\n  # Must define component before referencing it\n}\n\ncomponent \"app\" {\n  source = \"./modules/app\"\n  inputs = {\n    vpc_id = component.vpc.vpc_id  # Now valid\n  }\n}\n```\n\n2. **Check output is defined in module:**\n\n```hcl\n# In modules/vpc/outputs.tf\noutput \"vpc_id\" {\n  value = aws_vpc.main.id\n}\n```\n\n3. **For components with for_each**, reference specific instance:\n\n```hcl\ncomponent \"regional\" {\n  for_each = var.regions\n  # ...\n}\n\ncomponent \"app\" {\n  inputs = {\n    # Correct - reference specific instance\n    vpc_id = component.regional[\"us-west-1\"].vpc_id\n\n    # Incorrect - can't reference for_each component directly\n    # vpc_id = component.regional.vpc_id\n  }\n}\n```\n\n### Deferred Changes Not Converging\n\n**Issue:** Deployment with deferred changes doesn't complete after multiple iterations.\n\n**Error Message:**\n```\nError: Maximum deferred change iterations reached\n```\n\n**Cause:** Dependency cycle or values that never stabilize.\n\n**Solutions:**\n\n1. **Review component dependencies** for logical cycles\n2. **Check for computed values that change on every run**\n3. **Refactor to break dependency chain**\n4. **Consider multi-stage deployments** if resources truly can't be created together\n\n## API and CLI Issues\n\n### Empty Diagnostics Response\n\n**Issue:** API request for diagnostics returns empty results.\n\n**Request:**\n```bash\ncurl \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics\"\n```\n\n**Response:**\n```json\n{\n  \"data\": []\n}\n```\n\n**Solution:** Add required `stack_deployment_step_id` query parameter:\n\n```bash\ncurl \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\"\n```\n\n### Cannot Retrieve Stack Outputs\n\n**Issue:** No CLI command to retrieve Stack outputs after deployment.\n\n**Why:** Currently no direct CLI command for outputs retrieval.\n\n**Solution:** Use the artifacts API endpoint:\n\n```bash\n# Get final apply step ID first\nAPPLY_STEP=$(terraform stacks deployment-run list --json | \\\n  jq -r '.[0].deployment_steps[] | select(.operation_type == \"apply\") | .id' | tail -1)\n\n# Get outputs\ncurl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/$APPLY_STEP/artifacts?name=apply-description\" | \\\n  jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n```\n\n### CLI Watch Commands Hang in CI/CD\n\n**Issue:** Commands like `terraform stacks deployment-run watch` never return in CI/CD pipelines.\n\n**Why:** Watch commands stream output indefinitely and are designed for interactive use.\n\n**Solution:** Use API polling instead of watch commands. See `api-monitoring.md` for complete workflow.\n\n### Artifacts Endpoint Returns 404\n\n**Issue:** Request to artifacts endpoint returns 404 Not Found.\n\n**Possible Causes:**\n\n1. **Step hasn't completed yet** - wait for step status to be \"completed\"\n2. **Wrong artifact name** - use one of: plan-description, plan-debug-log, apply-description, apply-debug-log\n3. **Invalid step ID** - verify step ID from deployment-steps endpoint\n\n**Solution:**\n\n```bash\n# Check step status first\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}\" | \\\n  jq '.data.attributes.status'\n\n# Only request artifacts when status is \"completed\"\nif [ \"$STATUS\" = \"completed\" ]; then\n  curl -L -H \"Authorization: Bearer $TOKEN\" \\\n    \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description\"\nfi\n```\n\n### HTTP 307 Redirect Not Followed\n\n**Issue:** Artifacts endpoint returns redirect response instead of artifact content.\n\n**Why:** The endpoint returns HTTP 307 redirect to the actual artifact URL.\n\n**Solution:** Configure HTTP client to follow redirects:\n\n```bash\n# curl: Use -L flag\ncurl -L -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description\"\n\n# Python requests: allow_redirects=True (default)\nimport requests\nresponse = requests.get(url, headers=headers, allow_redirects=True)\n\n# Node.js fetch: redirect: 'follow' (default)\nconst response = await fetch(url, {\n  headers: headers,\n  redirect: 'follow'\n});\n```\n\n## Getting Additional Help\n\n### Enable Debug Logging\n\nFor more detailed error information, enable debug logging:\n\n```bash\n# CLI commands\nTF_LOG=DEBUG terraform stacks validate\nTF_LOG=DEBUG terraform stacks configuration upload\n\n# API artifacts\n# Request the debug-log artifact instead of description\ncurl -L -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-debug-log\"\n```\n\n### Check HCP Terraform Status\n\nIf experiencing widespread issues, check HCP Terraform status page:\n- https://status.hashicorp.com\n\n### Review Configuration Version\n\nList recent configurations to identify when issues started:\n\n```bash\nterraform stacks configuration list\n```\n\n### Contact Support\n\nFor issues not covered here:\n1. Gather relevant error messages and diagnostics\n2. Note the configuration sequence number\n3. Include deployment run IDs\n4. Contact HashiCorp Support with details\n"
  },
  {
    "path": "terraform/provider-development/.claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"terraform-provider-development\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform provider development skills for Claude Code, including resources, data sources, actions, and acceptance testing.\",\n  \"author\": {\n    \"name\": \"HashiCorp\",\n    \"url\": \"https://github.com/hashicorp\"\n  },\n  \"homepage\": \"https://developer.hashicorp.com/terraform/plugin/framework\",\n  \"repository\": \"https://github.com/hashicorp/agent-skills\",\n  \"license\": \"MPL-2.0\",\n  \"keywords\": [\"terraform\", \"provider\", \"plugin-framework\", \"resources\", \"testing\"]\n}\n"
  },
  {
    "path": "terraform/provider-development/skills/new-terraform-provider/SKILL.md",
    "content": "---\nname: new-terraform-provider\ndescription: Use this when scaffolding a new Terraform provider.\nlicense: MPL-2.0\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\nTo scaffold a new Terraform provider with Plugin Framework:\n\n1. If I am already in a Terraform provider workspace, then confirm that I want\n   to create a new workspace. If I do not want to create a new workspace, then\n   skip all remaining steps.\n1. Create a new workspace root directory. The root directory name should be\n   prefixed with \"terraform-provider-\". Perform all subsequent steps in this\n   new workspace.\n1. Initialize a new Go module..\n1. Run `go get -u github.com/hashicorp/terraform-plugin-framework@latest`.\n1. Write a main.go file that follows [the example](assets/main.go).\n1. Remove TODO comments from `main.go`\n1. Run `go mod tidy`\n1. Run `go build -o /dev/null`\n1. Run `go test ./...`\n\n"
  },
  {
    "path": "terraform/provider-development/skills/new-terraform-provider/assets/main.go",
    "content": "// Copyright IBM Corp. 2025, 2026\n// SPDX-License-Identifier: MPL-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"log\"\n\n\t\"example.org/terraform-provider-demo/internal/provider\"\n\t\"github.com/hashicorp/terraform-plugin-framework/providerserver\"\n)\n\nvar (\n\t// these will be set by the goreleaser configuration\n\t// to appropriate values for the compiled binary.\n\tversion string = \"dev\"\n\n\t// goreleaser can pass other information to the main package, such as the specific commit\n\t// https://goreleaser.com/cookbooks/using-main.version/\n)\n\nfunc main() {\n\tvar debug bool\n\n\tflag.BoolVar(&debug, \"debug\", false, \"set to true to run the provider with support for debuggers like delve\")\n\tflag.Parse()\n\n\topts := providerserver.ServeOpts{\n\t\t// TODO: Update this string with the published name of your provider.\n\t\t// Also update the tfplugindocs generate command to either remove the\n\t\t// -provider-name flag or set its value to the updated provider name.\n\t\tAddress: \"registry.terraform.io/example/demo\",\n\t\tDebug:   debug,\n\t}\n\n\terr := providerserver.Serve(context.Background(), provider.New(version), opts)\n\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-actions/SKILL.md",
    "content": "---\nname: provider-actions\ndescription: Implement Terraform Provider actions using the Plugin Framework. Use when developing imperative operations that execute at lifecycle events (before/after create, update, destroy).\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\n# Terraform Provider Actions Implementation Guide\n\n## Overview\n\nTerraform Actions enable imperative operations during the Terraform lifecycle. Actions are experimental features that allow performing provider operations at specific lifecycle events (before/after create, update, destroy).\n\n**References:**\n- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Terraform Actions RFC](https://github.com/hashicorp/terraform/blob/main/docs/plugin-protocol/actions.md)\n\n## File Structure\n\nActions follow the standard service package structure:\n\n```\ninternal/service/<service>/\n├── <action_name>_action.go       # Action implementation\n├── <action_name>_action_test.go  # Action tests\n└── service_package_gen.go        # Auto-generated service registration\n```\n\nDocumentation structure:\n```\nwebsite/docs/actions/\n└── <service>_<action_name>.html.markdown  # User-facing documentation\n```\n\nChangelog entry:\n```\n.changelog/\n└── <pr_number_or_description>.txt  # Release note entry\n```\n\n## Action Schema Definition\n\nActions use the Terraform Plugin Framework with a standard schema pattern:\n\n```go\nfunc (a *actionType) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {\n    resp.Schema = schema.Schema{\n        Attributes: map[string]schema.Attribute{\n            // Required configuration parameters\n            \"resource_id\": schema.StringAttribute{\n                Required:    true,\n                Description: \"ID of the resource to operate on\",\n            },\n            // Optional parameters with defaults\n            \"timeout\": schema.Int64Attribute{\n                Optional:    true,\n                Description: \"Operation timeout in seconds\",\n                Default:     int64default.StaticInt64(1800),\n                Computed:    true,\n            },\n        },\n    }\n}\n```\n\n### Common Schema Issues\n\n**Pay special attention to the schema definition** - common issues after a first draft:\n\n1. **Type Mismatches**\n   - Using `types.String` instead of `fwtypes.String` in model structs\n   - Using `types.StringType` instead of `fwtypes.StringType` in schema\n   - Mixing framework types with plugin-framework types\n\n2. **List/Map Element Types**\n   ```go\n   // WRONG - missing ElementType\n   \"items\": schema.ListAttribute{\n       Optional: true,\n   }\n\n   // CORRECT\n   \"items\": schema.ListAttribute{\n       Optional:    true,\n       ElementType: fwtypes.StringType,\n   }\n   ```\n\n3. **Computed vs Optional**\n   - Attributes with defaults must be both `Optional: true` and `Computed: true`\n   - Don't mark action inputs as `Computed` unless they have defaults\n\n4. **Validator Imports**\n   ```go\n   // Ensure proper imports\n   \"github.com/hashicorp/terraform-plugin-framework-validators/int64validator\"\n   \"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator\"\n   ```\n\n5. **Region/Provider Attribute**\n   - Use framework-provided region handling when available\n   - Don't manually define provider-specific config in schema if framework handles it\n\n6. **Nested Attributes**\n   - Use appropriate nested object types for complex structures\n   - Ensure nested types are properly defined\n\n### Schema Validation Checklist\n\nBefore submitting, verify:\n- [ ] All attributes have descriptions\n- [ ] List/Map attributes have ElementType defined\n- [ ] Validators are imported and applied correctly\n- [ ] Model struct uses correct framework types\n- [ ] Optional attributes with defaults are marked Computed\n- [ ] Code compiles without type errors\n- [ ] Run `go build` to catch type mismatches\n\n## Action Invoke Method\n\nThe Invoke method contains the action logic:\n\n```go\nfunc (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {\n    var data actionModel\n    resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)\n\n    // Create provider client\n    conn := a.Meta().Client(ctx)\n\n    // Progress updates for long-running operations\n    resp.Progress.Set(ctx, \"Starting operation...\")\n\n    // Implement action logic with error handling\n    // Use context for timeout management\n    // Poll for completion if async operation\n\n    resp.Progress.Set(ctx, \"Operation completed\")\n}\n```\n\n## Key Implementation Requirements\n\n### 1. Progress Reporting\n\n- Use `resp.SendProgress(action.InvokeProgressEvent{...})` for real-time updates\n- Provide meaningful progress messages during long operations\n- Update progress at key milestones\n- Include elapsed time for long operations\n\n### 2. Timeout Management\n\n- Always include configurable timeout parameter (default: 1800s)\n- Use `context.WithTimeout()` for API calls\n- Handle timeout errors gracefully\n- Validate timeout ranges (typically 60-7200 seconds)\n\n### 3. Error Handling\n\n- Add diagnostics with `resp.Diagnostics.AddError()`\n- Provide clear error messages with context\n- Include API error details when relevant\n- Map provider error types to user-friendly messages\n- Document all possible error cases\n\nExample error handling:\n```go\n// Handle specific errors\nvar notFound *types.ResourceNotFoundException\nif errors.As(err, &notFound) {\n    resp.Diagnostics.AddError(\n        \"Resource Not Found\",\n        fmt.Sprintf(\"Resource %s was not found\", resourceID),\n    )\n    return\n}\n\n// Generic error handling\nresp.Diagnostics.AddError(\n    \"Operation Failed\",\n    fmt.Sprintf(\"Could not complete operation for %s: %s\", resourceID, err),\n)\n```\n\n### 4. Provider SDK Integration\n\n- Use provider SDK clients from `a.Meta().<Service>Client(ctx)`\n- Handle pagination for list operations\n- Implement retry logic for transient failures\n- Use appropriate error types\n\n### 5. Parameter Validation\n\n- Use framework validators for input validation\n- Validate resource existence before operations\n- Check for conflicting parameters\n- Validate against provider naming requirements\n\n### 6. Polling and Waiting\n\nFor operations that require waiting for completion:\n\n```go\nresult, err := wait.WaitForStatus(ctx,\n    func(ctx context.Context) (wait.FetchResult[*ResourceType], error) {\n        // Fetch current status\n        resource, err := findResource(ctx, conn, id)\n        if err != nil {\n            return wait.FetchResult[*ResourceType]{}, err\n        }\n        return wait.FetchResult[*ResourceType]{\n            Status: wait.Status(resource.Status),\n            Value:  resource,\n        }, nil\n    },\n    wait.Options[*ResourceType]{\n        Timeout:            timeout,\n        Interval:           wait.FixedInterval(5 * time.Second),\n        SuccessStates:      []wait.Status{\"AVAILABLE\", \"COMPLETED\"},\n        TransitionalStates: []wait.Status{\"CREATING\", \"PENDING\"},\n        ProgressInterval:   30 * time.Second,\n        ProgressSink: func(fr wait.FetchResult[any], meta wait.ProgressMeta) {\n            resp.SendProgress(action.InvokeProgressEvent{\n                Message: fmt.Sprintf(\"Status: %s, Elapsed: %v\", fr.Status, meta.Elapsed.Round(time.Second)),\n            })\n        },\n    },\n)\n```\n\n## Common Action Patterns\n\n### Batch Operations\n- Process items in configurable batches\n- Report progress per batch\n- Handle partial failures gracefully\n- Support prefix/filter parameters\n\n### Command Execution\n- Submit command and get operation ID\n- Poll for completion status\n- Retrieve and report output\n- Handle timeout during polling\n- Validate resources exist before execution\n\n### Service Invocation\n- Invoke service with parameters\n- Wait for completion (if synchronous)\n- Return output/results\n- Handle service-specific errors\n\n### Resource State Changes\n- Validate current state\n- Apply state change\n- Poll for target state\n- Handle transitional states\n\n### Async Job Submission\n- Submit job with configuration\n- Get job ID\n- Optionally wait for completion\n- Report job status\n\n## Action Triggers\n\nActions are invoked via `action_trigger` lifecycle blocks in Terraform configurations:\n\n```hcl\naction \"provider_service_action\" \"name\" {\n  config {\n    parameter = value\n  }\n}\n\nresource \"terraform_data\" \"trigger\" {\n  lifecycle {\n    action_trigger {\n      events  = [after_create]\n      actions = [action.provider_service_action.name]\n    }\n  }\n}\n```\n\n### Available Trigger Events\n\n**Terraform 1.14.0 Supported Events:**\n- `before_create` - Before resource creation\n- `after_create` - After resource creation\n- `before_update` - Before resource update\n- `after_update` - After resource update\n\n**Not Supported in Terraform 1.14.0:**\n- `before_destroy` - Not available (will cause validation error)\n- `after_destroy` - Not available (will cause validation error)\n\n## Testing Actions\n\n### Acceptance Tests\n\n- Test action invocation with valid parameters\n- Test timeout scenarios\n- Test error conditions\n- Verify provider state changes\n- Test progress reporting\n- Test with custom parameters\n- Test trigger-based invocation\n\n### Test Pattern\n\n```go\nfunc TestAccServiceAction_basic(t *testing.T) {\n    ctx := acctest.Context(t)\n\n    resource.ParallelTest(t, resource.TestCase{\n        PreCheck:                 func() { acctest.PreCheck(ctx, t) },\n        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\n        TerraformVersionChecks: []tfversion.TerraformVersionCheck{\n            tfversion.SkipBelow(tfversion.Version1_14_0),\n        },\n        Steps: []resource.TestStep{\n            {\n                Config: testAccActionConfig_basic(),\n                Check: resource.ComposeTestCheckFunc(\n                    testAccCheckResourceExists(ctx, \"provider_resource.test\"),\n                ),\n            },\n        },\n    })\n}\n```\n\n### Test Cleanup with Sweep Functions\n\nAdd sweep functions to clean up test resources:\n\n```go\nfunc sweepResources(region string) error {\n    ctx := context.Background()\n    client := /* get client for region */\n\n    input := &service.ListInput{\n        // Filter for test resources\n    }\n\n    var sweeperErrs *multierror.Error\n\n    pages := service.NewListPaginator(client, input)\n    for pages.HasMorePages() {\n        page, err := pages.NextPage(ctx)\n        if err != nil {\n            sweeperErrs = multierror.Append(sweeperErrs, err)\n            continue\n        }\n\n        for _, item := range page.Items {\n            id := item.Id\n\n            // Skip non-test resources\n            if !strings.HasPrefix(id, \"tf-acc-test\") {\n                continue\n            }\n\n            _, err := client.Delete(ctx, &service.DeleteInput{\n                Id: id,\n            })\n            if err != nil {\n                sweeperErrs = multierror.Append(sweeperErrs, err)\n            }\n        }\n    }\n\n    return sweeperErrs.ErrorOrNil()\n}\n```\n\n### Testing Best Practices\n\n**Service-Specific Prerequisites**\n- Always check for service-specific prerequisites that must be met before actions can succeed\n- Document prerequisites in action documentation and test configurations\n\n**Error Pattern Matching**\n- Terraform wraps action errors with additional context\n- Use flexible regex patterns: `regexache.MustCompile(\\`(?s)Error Title.*key phrase\\`)`\n\n**Test Patterns Not Applicable to Actions**\n1. Actions trigger on lifecycle events, not config reapplication\n2. Before/After Destroy Tests: Not supported in Terraform 1.14.0\n\n### Running Tests\n\nCompile test to check for errors:\n```bash\ngo test -c -o /dev/null ./internal/service/<service>\n```\n\nRun specific action tests:\n```bash\nTF_ACC=1 go test ./internal/service/<service> -run TestAccServiceAction_ -v\n```\n\nRun sweep to clean up test resources:\n```bash\nTF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v\n```\n\n## Documentation Standards\n\nEach action documentation file must include:\n\n1. **Front Matter**\n   ```yaml\n   ---\n   subcategory: \"Service Name\"\n   layout: \"provider\"\n   page_title: \"Provider: provider_service_action\"\n   description: |-\n     Brief description of what the action does.\n   ---\n   ```\n\n2. **Header with Warnings**\n   - Beta/Alpha notice about experimental status\n   - Warning about potential unintended consequences\n   - Link to provider documentation\n\n3. **Example Usage**\n   - Basic usage example\n   - Advanced usage with all options\n   - Trigger-based example with `terraform_data`\n   - Real-world use case examples\n\n4. **Argument Reference**\n   - List all required and optional arguments\n   - Include descriptions and defaults\n   - Note any validation rules\n\n5. **Documentation Linting**\n   - Run `terrafmt fmt` before submission\n   - Verify with `terrafmt diff`\n\n## Changelog Entry Format\n\nCreate a changelog entry in `.changelog/` directory:\n\n```\n.changelog/<pr_number_or_description>.txt\n```\n\nContent format:\n```release-note:new-action\naction/provider_service_action: Brief description of the action\n```\n\n## Pre-Submission Checklist\n\nBefore submitting your action implementation:\n\n- [ ] Code compiles: `go build -o /dev/null .`\n- [ ] Tests compile: `go test -c -o /dev/null ./internal/service/<service>`\n- [ ] Code formatted: `make fmt`\n- [ ] Documentation formatted: `terrafmt fmt website/docs/actions/<action>.html.markdown`\n- [ ] Changelog entry created\n- [ ] Schema uses correct types\n- [ ] All List/Map attributes have ElementType\n- [ ] Progress updates implemented for long operations\n- [ ] Error messages include context and resource identifiers\n- [ ] Documentation includes multiple examples\n- [ ] Documentation includes prerequisites and warnings\n\n## References\n\n- [Terraform Plugin Framework Documentation](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Terraform Provider Development](https://developer.hashicorp.com/terraform/plugin)\n- [terraform-plugin-framework GitHub](https://github.com/hashicorp/terraform-plugin-framework)\n- [terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing)\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/SKILL.md",
    "content": "---\nname: provider-docs\ndescription: Create, update, and review Terraform provider documentation for Terraform Registry using HashiCorp-recommended patterns, tfplugindocs templates, and schema descriptions. Use when adding or changing provider configuration, resources, data sources, ephemeral resources, list resources, functions, or guides; when validating generated docs; and when troubleshooting missing or incorrect Registry documentation.\n---\n\n# Terraform Provider Docs\n\n## Follow This Workflow\n\n1. Confirm scope and documentation targets.\n- Map code changes to the exact doc targets: provider index, resources, data sources, ephemeral resources, list resources, functions, or guides.\n- Decide whether content should come from schema descriptions, templates, or both.\n\n2. Write schema descriptions first.\n- Add precise user-facing descriptions to schema fields so generated docs stay aligned with behavior.\n- Keep wording specific to argument purpose, constraints, defaults, and computed behavior.\n\n3. Add or update template files in `docs/`.\n- Create only files that map to implemented provider objects.\n- Use HashiCorp-recommended template paths:\n  - `docs/index.md.tmpl`\n  - `docs/data-sources/<name>.md.tmpl`\n  - `docs/resources/<name>.md.tmpl`\n  - `docs/ephemeral-resources/<name>.md.tmpl`\n  - `docs/list-resources/<name>.md.tmpl`\n  - `docs/functions/<name>.md.tmpl`\n  - `docs/guides/<name>.md.tmpl`\n- Keep templates focused on overview and examples; rely on generated sections for field-by-field details.\n\n4. Generate documentation with `tfplugindocs`.\n- Prefer repository defaults when configured:\n```bash\ngo generate ./...\n```\n- Otherwise run the generator directly:\n```bash\ngo run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name <provider_name>\n```\n- Re-run generation after every schema or template edit.\n\n5. Validate the generated markdown.\n- Verify files in `docs/` match the current provider implementation.\n- Verify examples are valid HCL and reflect current argument/attribute names.\n- Verify required/optional/computed semantics in docs match schema behavior.\n\n6. Apply Registry publication rules before release.\n- Use semantic version tags prefixed with `v` (for example `v1.2.3`).\n- Create release tags from the default branch.\n- Keep `terraform-registry-manifest.json` in the repository root.\n- Expect docs to be versioned in Registry and switchable with the version selector.\n\n7. Preview or troubleshoot publication when needed.\n- Use the HashiCorp preview process to inspect rendered docs before release when accuracy risk is high.\n- If docs are missing in Registry, check tag format, tag source branch, manifest file presence, and provider publication status.\n\n## Enforce Quality Bar\n\n- Keep documentation behaviorally accurate; never describe unsupported arguments or attributes.\n- Keep examples minimal, realistic, and runnable.\n- Keep terminology and naming consistent across provider, resources, and data sources.\n- Avoid duplicating generated argument/attribute blocks in manual templates.\n- Keep doc changes tied to the same PR as schema/API changes whenever possible.\n\n## Load References On Demand\n\n- Read `references/hashicorp-provider-docs.md` for source-backed rules and official links.\n- Load only the sections needed for the current change to keep context lean.\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/agents/openai.yaml",
    "content": "# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\ninterface:\n  display_name: \"Terraform Provider Docs\"\n  short_description: \"Best practices for Terraform provider docs\"\n  default_prompt: \"Use $terraform-provider-docs to create or update Terraform Registry provider documentation with HashiCorp-aligned structure and style.\"\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/references/hashicorp-provider-docs.md",
    "content": "# HashiCorp Provider Documentation Reference\n\nSource of truth for this skill:\n- https://developer.hashicorp.com/terraform/registry/providers/docs\n\n## Core Rules\n\n- Publish provider docs through Terraform Registry using `tfplugindocs`.\n- Generate provider docs from schema descriptions and markdown templates.\n- Store templates under the repository `docs/` directory with expected naming conventions.\n- Keep release tags and manifest metadata valid so Registry can render and display docs.\n\n## Template Paths\n\nUse these template paths when the corresponding provider objects exist:\n\n- `docs/index.md.tmpl`\n- `docs/data-sources/<name>.md.tmpl`\n- `docs/resources/<name>.md.tmpl`\n- `docs/ephemeral-resources/<name>.md.tmpl`\n- `docs/list-resources/<name>.md.tmpl`\n- `docs/functions/<name>.md.tmpl`\n- `docs/guides/<name>.md.tmpl`\n\n## Generation Workflow\n\nHashiCorp recommends wiring generator execution through `go generate`:\n\n```go\n//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name <provider_name>\n```\n\nRun from repository root:\n\n```bash\ngo generate ./...\n```\n\nAlternative direct execution:\n\n```bash\ngo run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name <provider_name>\n```\n\n## Release and Publication Constraints\n\n- Use semantic version tags prefixed with `v`.\n- Create tags from the default branch.\n- Keep `terraform-registry-manifest.json` in the repository root.\n- Understand docs appear by provider version in Registry once the provider release is published.\n\n## Preview and Troubleshooting\n\n- Use HashiCorp's preview process to verify rendering before release when needed.\n- If docs are missing or stale in Registry, verify:\n  - tag naming and tag branch source\n  - manifest file presence and validity\n  - provider version publication state\n\n## Related Canonical Pages\n\n- Provider docs guidance:\n  - https://developer.hashicorp.com/terraform/registry/providers/docs\n- Terraform Plugin Docs (`tfplugindocs`) source and usage:\n  - https://github.com/hashicorp/terraform-plugin-docs\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-resources/SKILL.md",
    "content": "---\nname: provider-resources\ndescription: Implement Terraform Provider resources and data sources using the Plugin Framework. Use when developing CRUD operations, schema design, state management, and acceptance testing for provider resources.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\n# Terraform Provider Resources Implementation Guide\n\n## Overview\n\nThis guide covers developing Terraform Provider resources and data sources using the Terraform Plugin Framework. Resources represent infrastructure objects that Terraform manages through Create, Read, Update, and Delete (CRUD) operations.\n\n**References:**\n- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Resource Development](https://developer.hashicorp.com/terraform/plugin/framework/resources)\n- [Data Source Development](https://developer.hashicorp.com/terraform/plugin/framework/data-sources)\n\n## File Structure\n\nResources follow the standard service package structure:\n\n```\ninternal/service/<service>/\n├── <resource_name>.go           # Resource implementation\n├── <resource_name>_test.go      # Acceptance tests\n├── <resource_name>_data_source.go    # Data source (if applicable)\n├── find.go                      # Finder functions\n├── exports_test.go              # Test exports\n└── service_package_gen.go       # Auto-generated registration\n```\n\nDocumentation structure:\n```\nwebsite/docs/r/\n└── <service>_<resource_name>.html.markdown  # Resource documentation\n\nwebsite/docs/d/\n└── <service>_<resource_name>.html.markdown  # Data source documentation\n```\n\n## Resource Structure\n\n### SDKv2 Resource Pattern\n\n```go\nfunc ResourceExample() *schema.Resource {\n    return &schema.Resource{\n        CreateWithoutTimeout: resourceExampleCreate,\n        ReadWithoutTimeout:   resourceExampleRead,\n        UpdateWithoutTimeout: resourceExampleUpdate,\n        DeleteWithoutTimeout: resourceExampleDelete,\n\n        Importer: &schema.ResourceImporter{\n            StateContext: schema.ImportStatePassthroughContext,\n        },\n\n        Schema: map[string]*schema.Schema{\n            \"name\": {\n                Type:         schema.TypeString,\n                Required:     true,\n                ForceNew:     true,\n                ValidateFunc: validation.StringLenBetween(1, 255),\n            },\n            \"arn\": {\n                Type:     schema.TypeString,\n                Computed: true,\n            },\n            \"tags\":     tftags.TagsSchema(),\n            \"tags_all\": tftags.TagsSchemaComputed(),\n        },\n\n        CustomizeDiff: verify.SetTagsDiff,\n    }\n}\n```\n\n### Plugin Framework Resource Pattern\n\n```go\ntype resourceExample struct {\n    framework.ResourceWithConfigure\n}\n\nfunc (r *resourceExample) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {\n    resp.TypeName = req.ProviderTypeName + \"_example\"\n}\n\nfunc (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {\n    resp.Schema = schema.Schema{\n        Attributes: map[string]schema.Attribute{\n            \"id\": framework.IDAttribute(),\n            \"name\": schema.StringAttribute{\n                Required: true,\n                PlanModifiers: []planmodifier.String{\n                    stringplanmodifier.RequiresReplace(),\n                },\n                Validators: []validator.String{\n                    stringvalidator.LengthBetween(1, 255),\n                },\n            },\n            \"arn\": schema.StringAttribute{\n                Computed: true,\n                PlanModifiers: []planmodifier.String{\n                    stringplanmodifier.UseStateForUnknown(),\n                },\n            },\n        },\n    }\n}\n```\n\n## CRUD Operations\n\n### Create Operation\n\n```go\nfunc (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {\n    var data resourceExampleModel\n    resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)\n    if resp.Diagnostics.HasError() {\n        return\n    }\n\n    conn := r.Meta().ExampleClient(ctx)\n\n    input := &example.CreateExampleInput{\n        Name: data.Name.ValueStringPointer(),\n    }\n\n    output, err := conn.CreateExample(ctx, input)\n    if err != nil {\n        resp.Diagnostics.AddError(\n            \"Error creating Example\",\n            fmt.Sprintf(\"Could not create example %s: %s\", data.Name.ValueString(), err),\n        )\n        return\n    }\n\n    data.ID = types.StringPointerValue(output.Id)\n    data.ARN = types.StringPointerValue(output.Arn)\n\n    resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)\n}\n```\n\n### Read Operation\n\n```go\nfunc (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {\n    var data resourceExampleModel\n    resp.Diagnostics.Append(req.State.Get(ctx, &data)...)\n    if resp.Diagnostics.HasError() {\n        return\n    }\n\n    conn := r.Meta().ExampleClient(ctx)\n\n    output, err := findExampleByID(ctx, conn, data.ID.ValueString())\n    if tfresource.NotFound(err) {\n        resp.Diagnostics.AddWarning(\n            \"Resource not found\",\n            fmt.Sprintf(\"Example %s not found, removing from state\", data.ID.ValueString()),\n        )\n        resp.State.RemoveResource(ctx)\n        return\n    }\n    if err != nil {\n        resp.Diagnostics.AddError(\n            \"Error reading Example\",\n            fmt.Sprintf(\"Could not read example %s: %s\", data.ID.ValueString(), err),\n        )\n        return\n    }\n\n    data.Name = types.StringPointerValue(output.Name)\n    data.ARN = types.StringPointerValue(output.Arn)\n\n    resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)\n}\n```\n\n### Update Operation\n\n```go\nfunc (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {\n    var plan, state resourceExampleModel\n    resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)\n    resp.Diagnostics.Append(req.State.Get(ctx, &state)...)\n    if resp.Diagnostics.HasError() {\n        return\n    }\n\n    conn := r.Meta().ExampleClient(ctx)\n\n    if !plan.Description.Equal(state.Description) {\n        input := &example.UpdateExampleInput{\n            Id:          plan.ID.ValueStringPointer(),\n            Description: plan.Description.ValueStringPointer(),\n        }\n\n        _, err := conn.UpdateExample(ctx, input)\n        if err != nil {\n            resp.Diagnostics.AddError(\n                \"Error updating Example\",\n                fmt.Sprintf(\"Could not update example %s: %s\", plan.ID.ValueString(), err),\n            )\n            return\n        }\n    }\n\n    resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)\n}\n```\n\n### Delete Operation\n\n```go\nfunc (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {\n    var data resourceExampleModel\n    resp.Diagnostics.Append(req.State.Get(ctx, &data)...)\n    if resp.Diagnostics.HasError() {\n        return\n    }\n\n    conn := r.Meta().ExampleClient(ctx)\n\n    _, err := conn.DeleteExample(ctx, &example.DeleteExampleInput{\n        Id: data.ID.ValueStringPointer(),\n    })\n\n    if tfresource.NotFound(err) {\n        return\n    }\n\n    if err != nil {\n        resp.Diagnostics.AddError(\n            \"Error deleting Example\",\n            fmt.Sprintf(\"Could not delete example %s: %s\", data.ID.ValueString(), err),\n        )\n        return\n    }\n}\n```\n\n## Schema Design\n\n### Attribute Types\n\n| Terraform Type | Framework Type | Use Case |\n|----------------|----------------|----------|\n| `string` | `schema.StringAttribute` | Names, ARNs, IDs |\n| `number` | `schema.Int64Attribute`, `schema.Float64Attribute` | Counts, sizes |\n| `bool` | `schema.BoolAttribute` | Feature flags |\n| `list` | `schema.ListAttribute` | Ordered collections |\n| `set` | `schema.SetAttribute` | Unordered unique items |\n| `map` | `schema.MapAttribute` | Key-value pairs |\n| `object` | `schema.SingleNestedAttribute` | Complex nested config |\n\n### Plan Modifiers\n\n```go\n// Force replacement when value changes\nstringplanmodifier.RequiresReplace()\n\n// Preserve unknown value during plan\nstringplanmodifier.UseStateForUnknown()\n\n// Custom plan modifier\nstringplanmodifier.RequiresReplaceIf(\n    func(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {\n        // Custom logic\n    },\n    \"description\",\n    \"markdown description\",\n)\n```\n\n### Validators\n\n```go\n// String validators\nstringvalidator.LengthBetween(1, 255)\nstringvalidator.RegexMatches(regexp.MustCompile(`^[a-z0-9-]+$`), \"must be lowercase alphanumeric with hyphens\")\nstringvalidator.OneOf(\"option1\", \"option2\", \"option3\")\n\n// Int64 validators\nint64validator.Between(1, 100)\nint64validator.AtLeast(1)\nint64validator.AtMost(1000)\n\n// List validators\nlistvalidator.SizeAtLeast(1)\nlistvalidator.SizeAtMost(10)\n```\n\n### Sensitive Attributes\n\n```go\n\"password\": schema.StringAttribute{\n    Required:  true,\n    Sensitive: true,\n    Validators: []validator.String{\n        stringvalidator.LengthAtLeast(8),\n    },\n}\n```\n\n## State Management\n\n### Handling Resource Not Found\n\n```go\nfunc findExampleByID(ctx context.Context, conn *example.Client, id string) (*example.Example, error) {\n    input := &example.GetExampleInput{\n        Id: &id,\n    }\n\n    output, err := conn.GetExample(ctx, input)\n    if err != nil {\n        var notFound *types.ResourceNotFoundException\n        if errors.As(err, &notFound) {\n            return nil, &retry.NotFoundError{\n                LastError:   err,\n                LastRequest: input,\n            }\n        }\n        return nil, err\n    }\n\n    if output == nil || output.Example == nil {\n        return nil, tfresource.NewEmptyResultError(input)\n    }\n\n    return output.Example, nil\n}\n```\n\n### Waiting for Resource States\n\n```go\nfunc waitExampleCreated(ctx context.Context, conn *example.Client, id string, timeout time.Duration) (*example.Example, error) {\n    stateConf := &retry.StateChangeConf{\n        Pending: []string{\"CREATING\", \"PENDING\"},\n        Target:  []string{\"ACTIVE\", \"AVAILABLE\"},\n        Refresh: statusExample(ctx, conn, id),\n        Timeout: timeout,\n    }\n\n    outputRaw, err := stateConf.WaitForStateContext(ctx)\n    if output, ok := outputRaw.(*example.Example); ok {\n        return output, err\n    }\n\n    return nil, err\n}\n\nfunc statusExample(ctx context.Context, conn *example.Client, id string) retry.StateRefreshFunc {\n    return func() (interface{}, string, error) {\n        output, err := findExampleByID(ctx, conn, id)\n        if tfresource.NotFound(err) {\n            return nil, \"\", nil\n        }\n        if err != nil {\n            return nil, \"\", err\n        }\n        return output, string(output.Status), nil\n    }\n}\n```\n\n## Testing\n\n### Basic Acceptance Test\n\n```go\nfunc TestAccExampleResource_basic(t *testing.T) {\n    ctx := acctest.Context(t)\n    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n    resourceName := \"provider_example.test\"\n\n    resource.ParallelTest(t, resource.TestCase{\n        PreCheck:                 func() { acctest.PreCheck(ctx, t) },\n        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\n        CheckDestroy:             testAccCheckExampleDestroy(ctx),\n        Steps: []resource.TestStep{\n            {\n                Config: testAccExampleConfig_basic(rName),\n                Check: resource.ComposeTestCheckFunc(\n                    testAccCheckExampleExists(ctx, resourceName),\n                    resource.TestCheckResourceAttr(resourceName, \"name\", rName),\n                    resource.TestCheckResourceAttrSet(resourceName, \"arn\"),\n                ),\n            },\n            {\n                ResourceName:      resourceName,\n                ImportState:       true,\n                ImportStateVerify: true,\n            },\n        },\n    })\n}\n```\n\n### Disappears Test\n\n```go\nfunc TestAccExampleResource_disappears(t *testing.T) {\n    ctx := acctest.Context(t)\n    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n    resourceName := \"provider_example.test\"\n\n    resource.ParallelTest(t, resource.TestCase{\n        PreCheck:                 func() { acctest.PreCheck(ctx, t) },\n        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\n        CheckDestroy:             testAccCheckExampleDestroy(ctx),\n        Steps: []resource.TestStep{\n            {\n                Config: testAccExampleConfig_basic(rName),\n                Check: resource.ComposeTestCheckFunc(\n                    testAccCheckExampleExists(ctx, resourceName),\n                    acctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExample(), resourceName),\n                ),\n                ExpectNonEmptyPlan: true,\n            },\n        },\n    })\n}\n```\n\n### Test Helper Functions\n\n```go\nfunc testAccCheckExampleExists(ctx context.Context, name string) resource.TestCheckFunc {\n    return func(s *terraform.State) error {\n        rs, ok := s.RootModule().Resources[name]\n        if !ok {\n            return fmt.Errorf(\"Not found: %s\", name)\n        }\n\n        conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)\n        _, err := findExampleByID(ctx, conn, rs.Primary.ID)\n\n        return err\n    }\n}\n\nfunc testAccCheckExampleDestroy(ctx context.Context) resource.TestCheckFunc {\n    return func(s *terraform.State) error {\n        conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)\n\n        for _, rs := range s.RootModule().Resources {\n            if rs.Type != \"provider_example\" {\n                continue\n            }\n\n            _, err := findExampleByID(ctx, conn, rs.Primary.ID)\n            if tfresource.NotFound(err) {\n                continue\n            }\n            if err != nil {\n                return err\n            }\n\n            return fmt.Errorf(\"Example %s still exists\", rs.Primary.ID)\n        }\n\n        return nil\n    }\n}\n```\n\n### Running Tests\n\n```bash\n# Compile tests\ngo test -c -o /dev/null ./internal/service/<service>\n\n# Run acceptance tests\nTF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v -timeout 60m\n\n# Run with specific provider version\nTF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v\n\n# Run sweeper to clean up\nTF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v\n```\n\n## Error Handling\n\n### Common Error Patterns\n\n```go\n// Handle specific API errors\nvar notFound *types.ResourceNotFoundException\nif errors.As(err, &notFound) {\n    // Resource doesn't exist\n}\n\nvar conflict *types.ConflictException\nif errors.As(err, &conflict) {\n    // Resource state conflict\n}\n\nvar throttle *types.ThrottlingException\nif errors.As(err, &throttle) {\n    // Rate limited - SDK handles retry\n}\n```\n\n### Diagnostics\n\n```go\n// Add error\nresp.Diagnostics.AddError(\n    \"Error creating resource\",\n    fmt.Sprintf(\"Could not create resource: %s\", err),\n)\n\n// Add warning\nresp.Diagnostics.AddWarning(\n    \"Resource modified outside Terraform\",\n    \"Resource was modified outside of Terraform, state may be inconsistent\",\n)\n\n// Add attribute error\nresp.Diagnostics.AddAttributeError(\n    path.Root(\"name\"),\n    \"Invalid name\",\n    \"Name must be lowercase alphanumeric\",\n)\n```\n\n## Documentation Standards\n\n### Resource Documentation\n\n```markdown\n---\nsubcategory: \"Service Name\"\nlayout: \"provider\"\npage_title: \"Provider: provider_example\"\ndescription: |-\n  Manages an Example resource.\n---\n\n# Resource: provider_example\n\nManages an Example resource.\n\n## Example Usage\n\n### Basic Usage\n\n\\```hcl\nresource \"provider_example\" \"example\" {\n  name = \"my-example\"\n}\n\\```\n\n## Argument Reference\n\n* `name` - (Required) Name of the example.\n* `description` - (Optional) Description of the example.\n\n## Attribute Reference\n\n* `id` - ID of the example.\n* `arn` - ARN of the example.\n\n## Import\n\nExample can be imported using the ID:\n\n\\```\n$ terraform import provider_example.example example-id-12345\n\\```\n```\n\n## Pre-Submission Checklist\n\n- [ ] Code compiles without errors\n- [ ] All tests pass locally\n- [ ] Resource has all CRUD operations implemented\n- [ ] Import is implemented and tested\n- [ ] Disappears test is included\n- [ ] Documentation is complete with examples\n- [ ] Error messages are clear and actionable\n- [ ] Sensitive attributes are marked\n- [ ] Plan modifiers are appropriate\n- [ ] Validators cover edge cases\n\n## References\n\n- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)\n- [Terraform Plugin SDKv2](https://developer.hashicorp.com/terraform/plugin/sdkv2)\n- [Acceptance Testing](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests)\n- [terraform-plugin-framework GitHub](https://github.com/hashicorp/terraform-plugin-framework)\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/SKILL.md",
    "content": "---\nname: provider-test-patterns\ndescription: >-\n  Terraform provider acceptance test patterns using terraform-plugin-testing\n  with the Plugin Framework. Covers test structure, TestCase/TestStep fields,\n  ConfigStateChecks with custom statecheck.StateCheck implementations,\n  plan checks, CompareValue for cross-step assertions, config helpers,\n  import testing with ImportStateKind, sweepers, and scenario patterns\n  (basic, update, disappears, validation, regression), and ephemeral resource\n  testing with the echoprovider package. Use when writing, reviewing, or\n  debugging provider acceptance tests, including questions about statecheck,\n  plancheck, TestCheckFunc, CheckDestroy, ExpectError, import state\n  verification, ephemeral resources, or how to structure test files.\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\n# Provider Acceptance Test Patterns\n\nPatterns for writing acceptance tests using\n[terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing)\nwith the [Plugin Framework](https://github.com/hashicorp/terraform-plugin-framework).\n\nSource: [HashiCorp Testing Patterns](https://developer.hashicorp.com/terraform/plugin/testing/testing-patterns)\n\n**References** (load when needed):\n- `references/checks.md` — statecheck, plancheck, knownvalue types, tfjsonpath, comparers\n- `references/sweepers.md` — sweeper setup, TestMain, dependencies\n- `references/ephemeral.md` — ephemeral resource testing, echoprovider, multi-step patterns\n\n---\n\n## Test Lifecycle\n\nThe framework runs each TestStep through: **plan → apply → refresh → final\nplan**. If the final plan shows a diff, the test fails (unless\n`ExpectNonEmptyPlan` is set). After all steps, destroy runs followed by\n`CheckDestroy`. This means every test automatically verifies that\nconfigurations apply cleanly and produce no drift — no assertions needed for\nthat.\n\n---\n\n## Test Function Structure\n\n```go\nfunc TestAccExample_basic(t *testing.T) {\n    var widget example.Widget\n    rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)\n    resourceName := \"example_widget.test\"\n\n    resource.ParallelTest(t, resource.TestCase{\n        PreCheck:                 func() { testAccPreCheck(t) },\n        ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,\n        CheckDestroy:             testAccCheckExampleDestroy,\n        Steps: []resource.TestStep{\n            {\n                Config: testAccExampleConfig_basic(rName),\n                ConfigStateChecks: []statecheck.StateCheck{\n                    stateCheckExampleExists(resourceName, &widget),\n                    statecheck.ExpectKnownValue(resourceName,\n                        tfjsonpath.New(\"name\"), knownvalue.StringExact(rName)),\n                    statecheck.ExpectKnownValue(resourceName,\n                        tfjsonpath.New(\"id\"), knownvalue.NotNull()),\n                },\n            },\n        },\n    })\n}\n```\n\nUse `resource.ParallelTest` by default. Use `resource.Test` only when tests\nshare state or cannot run concurrently.\n\n---\n\n## Provider Factory\n\n```go\n// provider_test.go — Plugin Framework with Protocol 6 (use Protocol5 variant if needed)\nvar testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){\n    \"example\": providerserver.NewProtocol6WithError(New(\"test\")()),\n}\n```\n\n---\n\n## TestCase Fields\n\n| Field | Purpose |\n|-------|---------|\n| `PreCheck` | `func()` — verify prerequisites (env vars, API access) |\n| `ProtoV6ProviderFactories` | Plugin Framework provider factories |\n| `CheckDestroy` | `TestCheckFunc` — verify resources destroyed after all steps |\n| `Steps` | `[]TestStep` — sequential test operations |\n| `TerraformVersionChecks` | `[]tfversion.TerraformVersionCheck` — gate by CLI version |\n\n---\n\n## TestStep Fields\n\n### Config Mode\n\n| Field | Purpose |\n|-------|---------|\n| `Config` | Inline HCL string to apply |\n| `ConfigStateChecks` | `[]statecheck.StateCheck` — modern assertions (preferred) |\n| `ConfigPlanChecks` | `resource.ConfigPlanChecks{PreApply: []plancheck.PlanCheck{...}}` |\n| `ExpectError` | `*regexp.Regexp` — expect failure matching pattern |\n| `ExpectNonEmptyPlan` | `bool` — expect non-empty plan after apply |\n| `PlanOnly` | `bool` — plan without applying |\n| `Destroy` | `bool` — run destroy step |\n| `PreConfig` | `func()` — setup before step |\n\n### Import Mode\n\n| Field | Purpose |\n|-------|---------|\n| `ImportState` | `true` to enable import mode |\n| `ImportStateVerify` | Verify imported state matches prior state |\n| `ImportStateVerifyIgnore` | `[]string` — attributes to skip during verify |\n| `ImportStateKind` | `resource.ImportBlockWithID` — import block generation |\n| `ResourceName` | Resource address to import |\n| `ImportStateId` | Override the ID used for import |\n\n---\n\n## Check Functions\n\n### Modern: ConfigStateChecks (preferred)\n\nType-safe with aggregated error reporting. Compose built-in checks with custom\n`statecheck.StateCheck` implementations. See `references/checks.md` for full\nknownvalue types, tfjsonpath navigation, and comparers.\n\n```go\nConfigStateChecks: []statecheck.StateCheck{\n    stateCheckExampleExists(resourceName, &widget),\n    statecheck.ExpectKnownValue(resourceName,\n        tfjsonpath.New(\"name\"), knownvalue.StringExact(\"my-widget\")),\n    statecheck.ExpectKnownValue(resourceName,\n        tfjsonpath.New(\"enabled\"), knownvalue.Bool(true)),\n    statecheck.ExpectKnownValue(resourceName,\n        tfjsonpath.New(\"id\"), knownvalue.NotNull()),\n    statecheck.ExpectSensitiveValue(resourceName,\n        tfjsonpath.New(\"api_key\")),\n},\n```\n\nDo not mix `Check` (legacy) and `ConfigStateChecks` in the same step.\n\n### Legacy: Check (for CheckDestroy and migration)\n\n`CheckDestroy` on `TestCase` requires `TestCheckFunc`. The `Check` field on\n`TestStep` also accepts `TestCheckFunc` but prefer `ConfigStateChecks` for new\ntests.\n\n```go\nCheck: resource.ComposeAggregateTestCheckFunc(\n    resource.TestCheckResourceAttr(name, \"key\", \"expected\"),\n    resource.TestCheckResourceAttrSet(name, \"id\"),\n    resource.TestCheckNoResourceAttr(name, \"removed\"),\n    resource.TestMatchResourceAttr(name, \"url\", regexp.MustCompile(`^https://`)),\n    resource.TestCheckResourceAttrPair(res1, \"ref_id\", res2, \"id\"),\n),\n```\n\n`ComposeAggregateTestCheckFunc` reports all errors; `ComposeTestCheckFunc`\nfails fast on the first.\n\n---\n\n## Config Helpers\n\nUse numbered format verbs — `%[1]q` for quoted strings, `%[1]s` for raw:\n\n```go\nfunc testAccExampleConfig_basic(rName string) string {\n    return fmt.Sprintf(`\nresource \"example_widget\" \"test\" {\n  name = %[1]q\n}\n`, rName)\n}\n\nfunc testAccExampleConfig_full(rName, description string) string {\n    return fmt.Sprintf(`\nresource \"example_widget\" \"test\" {\n  name        = %[1]q\n  description = %[2]q\n  enabled     = true\n}\n`, rName, description)\n}\n```\n\n---\n\n## Scenario Patterns\n\n### Basic + Update (combine in one test — updates are supersets of basic)\n\n```go\nSteps: []resource.TestStep{\n    {\n        Config: testAccExampleConfig_basic(rName),\n        ConfigStateChecks: []statecheck.StateCheck{\n            stateCheckExampleExists(resourceName, &widget),\n            statecheck.ExpectKnownValue(resourceName,\n                tfjsonpath.New(\"name\"), knownvalue.StringExact(rName)),\n        },\n    },\n    {\n        Config: testAccExampleConfig_full(rName, \"updated\"),\n        ConfigStateChecks: []statecheck.StateCheck{\n            stateCheckExampleExists(resourceName, &widget),\n            statecheck.ExpectKnownValue(resourceName,\n                tfjsonpath.New(\"description\"), knownvalue.StringExact(\"updated\")),\n        },\n    },\n},\n```\n\n### Import\n\nAfter a config step, verify import produces identical state. Use\n`ImportStateKind` for import block generation:\n\n```go\n{\n    ResourceName:      resourceName,\n    ImportState:       true,\n    ImportStateVerify: true,\n    ImportStateKind:   resource.ImportBlockWithID,\n},\n```\n\n### Disappears (resource deleted externally)\n\n```go\n{\n    Config: testAccExampleConfig_basic(rName),\n    ConfigStateChecks: []statecheck.StateCheck{\n        stateCheckExampleExists(resourceName, &widget),\n        stateCheckExampleDisappears(resourceName),\n    },\n    ExpectNonEmptyPlan: true,\n},\n```\n\n### Validation (expect error)\n\n```go\n{\n    Config:      testAccExampleConfig_invalidName(\"\"),\n    ExpectError: regexp.MustCompile(`name must not be empty`),\n},\n```\n\n### Regression (two-commit workflow)\n\nA proper bug fix uses at least two commits: first commit the regression test\n(which fails, confirming the bug), then commit the fix (test passes). This\nlets reviewers independently verify the test reproduces the issue by checking\nout the first commit, then advancing to the fix.\n\nName and document regression tests to identify the issue they fix. Include a\nlink to the original bug report when possible.\n\n```go\n// TestAccExample_regressionGH1234 verifies fix for https://github.com/org/repo/issues/1234\nfunc TestAccExample_regressionGH1234(t *testing.T) {\n    rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)\n    resourceName := \"example_widget.test\"\n\n    resource.ParallelTest(t, resource.TestCase{\n        PreCheck:                 func() { testAccPreCheck(t) },\n        ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,\n        CheckDestroy:             testAccCheckExampleDestroy,\n        Steps: []resource.TestStep{\n            {\n                // Reproduce the issue: this config triggered the bug\n                Config: testAccExampleConfig_regressionGH1234(rName),\n                ConfigStateChecks: []statecheck.StateCheck{\n                    stateCheckExampleExists(resourceName, nil),\n                    statecheck.ExpectKnownValue(resourceName,\n                        tfjsonpath.New(\"computed_field\"), knownvalue.NotNull()),\n                },\n            },\n        },\n    })\n}\n```\n\n---\n\n## Helper Functions\n\n### Custom StateCheck: Exists\n\nImplement `statecheck.StateCheck` for API existence verification. Separate the\nexists check into its own function for reuse across steps — the source\nrecommends this as a design principle:\n\n```go\ntype exampleExistsCheck struct {\n    resourceAddress string\n    widget          *example.Widget\n}\n\nfunc (e exampleExistsCheck) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) {\n    r, err := stateResourceAtAddress(req.State, e.resourceAddress)\n    if err != nil {\n        resp.Error = err\n        return\n    }\n\n    id, ok := r.AttributeValues[\"id\"].(string)\n    if !ok {\n        resp.Error = fmt.Errorf(\"no id found for %s\", e.resourceAddress)\n        return\n    }\n\n    conn := testAccAPIClient()\n    widget, err := conn.GetWidget(id)\n    if err != nil {\n        resp.Error = fmt.Errorf(\"%s not found via API: %w\", e.resourceAddress, err)\n        return\n    }\n\n    if e.widget != nil {\n        *e.widget = *widget\n    }\n}\n\nfunc stateCheckExampleExists(name string, widget *example.Widget) statecheck.StateCheck {\n    return exampleExistsCheck{resourceAddress: name, widget: widget}\n}\n```\n\n### Custom StateCheck: Disappears\n\nDelete a resource via API to simulate external deletion:\n\n```go\ntype exampleDisappearsCheck struct {\n    resourceAddress string\n}\n\nfunc (e exampleDisappearsCheck) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) {\n    r, err := stateResourceAtAddress(req.State, e.resourceAddress)\n    if err != nil {\n        resp.Error = err\n        return\n    }\n\n    id := r.AttributeValues[\"id\"].(string)\n    conn := testAccAPIClient()\n    resp.Error = conn.DeleteWidget(id)\n}\n\nfunc stateCheckExampleDisappears(name string) statecheck.StateCheck {\n    return exampleDisappearsCheck{resourceAddress: name}\n}\n```\n\n### State Resource Lookup (shared utility)\n\n```go\nfunc stateResourceAtAddress(state *tfjson.State, address string) (*tfjson.StateResource, error) {\n    if state == nil || state.Values == nil || state.Values.RootModule == nil {\n        return nil, fmt.Errorf(\"no state available\")\n    }\n    for _, r := range state.Values.RootModule.Resources {\n        if r.Address == address {\n            return r, nil\n        }\n    }\n    return nil, fmt.Errorf(\"not found in state: %s\", address)\n}\n```\n\n### Destroy Check (TestCheckFunc — required by CheckDestroy)\n\n```go\nfunc testAccCheckExampleDestroy(s *terraform.State) error {\n    conn := testAccAPIClient()\n    for _, rs := range s.RootModule().Resources {\n        if rs.Type != \"example_widget\" {\n            continue\n        }\n        _, err := conn.GetWidget(rs.Primary.ID)\n        if err == nil {\n            return fmt.Errorf(\"widget %s still exists\", rs.Primary.ID)\n        }\n        if !isNotFoundError(err) {\n            return err\n        }\n    }\n    return nil\n}\n```\n\n### PreCheck\n\n```go\nfunc testAccPreCheck(t *testing.T) {\n    t.Helper()\n    if os.Getenv(\"EXAMPLE_API_KEY\") == \"\" {\n        t.Fatal(\"EXAMPLE_API_KEY must be set for acceptance tests\")\n    }\n}\n```"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/checks.md",
    "content": "# State Checks and Plan Checks Reference\n\nDetailed reference for `statecheck` and `plancheck` packages from\n`terraform-plugin-testing`. Read this when writing assertions for test steps.\n\nSource: [State Checks](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/state-checks/resource),\n[Plan Checks](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/plan-checks)\n\n---\n\n## Table of Contents\n\n1. [State Checks](#state-checks)\n2. [Known Value Types](#known-value-types)\n3. [tfjsonpath Navigation](#tfjsonpath-navigation)\n4. [Value Comparers](#value-comparers)\n5. [Plan Checks](#plan-checks)\n\n---\n\n## State Checks\n\nUse via `ConfigStateChecks` field on `TestStep`. All assertion errors are\naggregated and reported together.\n\n### ExpectKnownValue\n\nAssert an attribute has a specific type and value:\n\n```go\nstatecheck.ExpectKnownValue(\"example_widget.test\",\n    tfjsonpath.New(\"name\"),\n    knownvalue.StringExact(\"my-widget\"))\n```\n\n### ExpectSensitiveValue\n\nAssert an attribute is marked sensitive (requires Terraform 1.4.6+):\n\n```go\nTerraformVersionChecks: []tfversion.TerraformVersionCheck{\n    tfversion.SkipBelow(tfversion.Version1_4_6),\n},\n// ...\nstatecheck.ExpectSensitiveValue(\"example_widget.test\",\n    tfjsonpath.New(\"api_key\"))\n```\n\n### CompareValue\n\nCompare the same attribute across sequential test steps:\n\n```go\ncompareValuesSame := statecheck.CompareValue(compare.ValuesSame())\n\nSteps: []resource.TestStep{\n    {\n        Config: testAccConfig_v1(rName),\n        ConfigStateChecks: []statecheck.StateCheck{\n            compareValuesSame.AddStateValue(\"example_widget.test\",\n                tfjsonpath.New(\"id\")),\n        },\n    },\n    {\n        Config: testAccConfig_v2(rName),\n        ConfigStateChecks: []statecheck.StateCheck{\n            compareValuesSame.AddStateValue(\"example_widget.test\",\n                tfjsonpath.New(\"id\")),\n        },\n    },\n},\n```\n\n### CompareValuePairs\n\nCompare attributes between two resources:\n\n```go\nstatecheck.CompareValuePairs(\n    \"example_widget.test\", tfjsonpath.New(\"vpc_id\"),\n    \"example_vpc.test\", tfjsonpath.New(\"id\"),\n    compare.ValuesSame())\n```\n\n### CompareValueCollection\n\nCheck if a value exists in a collection attribute:\n\n```go\nstatecheck.CompareValueCollection(\n    \"example_widget.test\", tfjsonpath.New(\"tags\"),\n    \"example_widget.test\", tfjsonpath.New(\"name\"),\n    compare.ValuesSame())\n```\n\n---\n\n## Known Value Types\n\nUse with `ExpectKnownValue` to assert attribute values:\n\n| Type | Example |\n|------|---------|\n| `knownvalue.StringExact(\"value\")` | Exact string match |\n| `knownvalue.StringRegexp(regexp.MustCompile(`^arn:`))` | Regex match |\n| `knownvalue.Bool(true)` | Boolean value |\n| `knownvalue.Int64Exact(42)` | Exact int64 |\n| `knownvalue.Float64Exact(3.14)` | Exact float64 |\n| `knownvalue.NotNull()` | Value is set (not null) |\n| `knownvalue.Null()` | Value is null |\n| `knownvalue.ListExact([]knownvalue.Check{...})` | Exact list match |\n| `knownvalue.ListPartial(map[int]knownvalue.Check{0: ...})` | Partial list match |\n| `knownvalue.ListSizeExact(3)` | List has N elements |\n| `knownvalue.SetExact([]knownvalue.Check{...})` | Exact set match |\n| `knownvalue.SetPartial([]knownvalue.Check{...})` | Set contains items |\n| `knownvalue.SetSizeExact(2)` | Set has N elements |\n| `knownvalue.MapExact(map[string]knownvalue.Check{...})` | Exact map match |\n| `knownvalue.MapPartial(map[string]knownvalue.Check{...})` | Map contains keys |\n| `knownvalue.MapSizeExact(1)` | Map has N keys |\n| `knownvalue.ObjectExact(map[string]knownvalue.Check{...})` | Exact object match |\n| `knownvalue.ObjectPartial(map[string]knownvalue.Check{...})` | Object has attributes |\n| `knownvalue.Float32Exact(1.5)` | Exact float32 |\n| `knownvalue.Int32Exact(42)` | Exact int32 |\n| `knownvalue.NumberExact(big.NewFloat(42))` | Exact number (`*big.Float`) |\n| `knownvalue.TupleExact([]knownvalue.Check{...})` | Exact tuple match |\n| `knownvalue.TuplePartial(map[int]knownvalue.Check{0: ...})` | Partial tuple match |\n| `knownvalue.TupleSizeExact(3)` | Tuple has N elements |\n\n### Nested Value Example\n\n```go\nstatecheck.ExpectKnownValue(\"example_widget.test\",\n    tfjsonpath.New(\"settings\"),\n    knownvalue.ObjectExact(map[string]knownvalue.Check{\n        \"mode\":    knownvalue.StringExact(\"production\"),\n        \"enabled\": knownvalue.Bool(true),\n    }))\n```\n\n---\n\n## tfjsonpath Navigation\n\nNavigate nested attributes in state:\n\n```go\ntfjsonpath.New(\"attribute\")                   // top-level attribute\ntfjsonpath.New(\"block\").AtMapKey(\"key\")       // nested map/object key\ntfjsonpath.New(\"list_attr\").AtSliceIndex(0)   // list element by index\ntfjsonpath.New(\"block\").AtMapKey(\"nested\").AtMapKey(\"deep\") // deep nesting\n```\n\n---\n\n## Value Comparers\n\nUse with `CompareValue`, `CompareValuePairs`, `CompareValueCollection`:\n\n| Comparer | Purpose |\n|----------|---------|\n| `compare.ValuesSame()` | Values are identical |\n| `compare.ValuesDiffer()` | Values are different |\n\n---\n\n## Plan Checks\n\nUse via `ConfigPlanChecks` or `RefreshPlanChecks` on `TestStep`. Plan checks\ninspect the plan file at specific phases.\n\n### ConfigPlanChecks Phases\n\n```go\nConfigPlanChecks: resource.ConfigPlanChecks{\n    PreApply:  []plancheck.PlanCheck{...}, // after plan, before apply\n    PostApplyPreRefresh: []plancheck.PlanCheck{...}, // after apply, before refresh\n    PostApplyPostRefresh: []plancheck.PlanCheck{...}, // after refresh\n},\n```\n\n### Built-in Plan Checks\n\n```go\n// Expect no changes in plan\nplancheck.ExpectEmptyPlan()\n\n// Expect changes in plan\nplancheck.ExpectNonEmptyPlan()\n\n// Expect specific resource action\nplancheck.ExpectResourceAction(\"example_widget.test\", plancheck.ResourceActionCreate)\nplancheck.ExpectResourceAction(\"example_widget.test\", plancheck.ResourceActionUpdate)\nplancheck.ExpectResourceAction(\"example_widget.test\", plancheck.ResourceActionDestroy)\nplancheck.ExpectResourceAction(\"example_widget.test\", plancheck.ResourceActionNoop)\n\n// Expect known plan value\nplancheck.ExpectKnownValue(\"example_widget.test\",\n    tfjsonpath.New(\"name\"),\n    knownvalue.StringExact(\"my-widget\"))\n\n// Expect unknown (computed) value in plan\nplancheck.ExpectUnknownValue(\"example_widget.test\",\n    tfjsonpath.New(\"computed_field\"))\n\n// Expect sensitive value in plan\nplancheck.ExpectSensitiveValue(\"example_widget.test\",\n    tfjsonpath.New(\"api_key\"))\n```\n\n### No-Op After Update Example\n\nVerify that updating a config back to original values produces no diff:\n\n```go\nSteps: []resource.TestStep{\n    {\n        Config: testAccConfig_basic(rName),\n    },\n    {\n        Config: testAccConfig_updated(rName),\n    },\n    {\n        Config: testAccConfig_basic(rName),\n        ConfigPlanChecks: resource.ConfigPlanChecks{\n            PreApply: []plancheck.PlanCheck{\n                plancheck.ExpectEmptyPlan(),\n            },\n        },\n    },\n},\n```\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/ephemeral.md",
    "content": "# Ephemeral Resource Testing Reference\n\nTesting patterns for ephemeral resources using `terraform-plugin-testing`.\nEphemeral resources reference external data without persisting it to plan or\nstate artifacts, which means standard plan checks and state checks cannot\ndirectly assert on ephemeral resource data.\n\nSource: [Ephemeral Resource Acceptance Tests](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/ephemeral-resources)\n\n**Requires Terraform >= 1.10.0** — gate all ephemeral tests with\n`tfversion.SkipBelow(tfversion.Version1_10_0)`.\n\n---\n\n## Table of Contents\n\n1. [Testing Approaches](#testing-approaches)\n2. [Direct Integration Testing](#direct-integration-testing)\n3. [Echo Provider Pattern](#echo-provider-pattern)\n4. [Multi-Step Testing](#multi-step-testing)\n\n---\n\n## Testing Approaches\n\nTwo strategies for testing ephemeral resources:\n\n| Approach | When to use |\n|----------|-------------|\n| **Direct integration** | Verify the ephemeral resource successfully provides data to a dependent resource or provider |\n| **Echo provider** | Assert on specific attribute values using `ConfigStateChecks` via the `echoprovider` package |\n\n---\n\n## Direct Integration Testing\n\nTest that an ephemeral resource successfully provides data to a dependent\nresource. No direct assertions on ephemeral data — the test passes if the\ndependent resource applies cleanly.\n\n```go\nfunc TestExampleCloudSecret_DnsKerberos(t *testing.T) {\n    resource.UnitTest(t, resource.TestCase{\n        TerraformVersionChecks: []tfversion.TerraformVersionCheck{\n            tfversion.SkipBelow(tfversion.Version1_10_0),\n        },\n        ExternalProviders: map[string]resource.ExternalProvider{\n            \"dns\": {\n                Source: \"hashicorp/dns\",\n            },\n        },\n        ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){\n            \"examplecloud\": providerserver.NewProtocol5WithError(New()),\n        },\n        Steps: []resource.TestStep{\n            {\n                Config: `\nephemeral \"examplecloud_secret\" \"krb\" {\n  name = \"example_kerberos_user\"\n}\n\nprovider \"dns\" {\n  update {\n    server = \"ns.example.com\"\n    gssapi {\n      realm    = ephemeral.examplecloud_secret.krb.secret_data.realm\n      username = ephemeral.examplecloud_secret.krb.secret_data.username\n      password = ephemeral.examplecloud_secret.krb.secret_data.password\n    }\n  }\n}\n\nresource \"dns_a_record_set\" \"record_set\" {\n  zone = \"example.com.\"\n  addresses = [\"192.168.0.1\", \"192.168.0.2\", \"192.168.0.3\"]\n}\n                `,\n            },\n        },\n    })\n}\n```\n\n---\n\n## Echo Provider Pattern\n\nThe `echoprovider` package (Protocol V6) captures ephemeral data into a\nmanaged resource's state, making it assertable with standard\n`ConfigStateChecks`.\n\n### Setup\n\nRegister both your provider and the echo provider:\n\n```go\nimport (\n    \"github.com/hashicorp/terraform-plugin-testing/echoprovider\"\n)\n\nfunc TestExampleCloudSecret(t *testing.T) {\n    resource.UnitTest(t, resource.TestCase{\n        TerraformVersionChecks: []tfversion.TerraformVersionCheck{\n            tfversion.SkipBelow(tfversion.Version1_10_0),\n        },\n        ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){\n            \"examplecloud\": providerserver.NewProtocol5WithError(New()),\n        },\n        ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){\n            \"echo\": echoprovider.NewProviderServer(),\n        },\n        Steps: []resource.TestStep{\n            // test configurations\n        },\n    })\n}\n```\n\n### Config Pattern\n\nPass ephemeral data to the echo provider's `data` attribute, then assert on\nthe `echo` managed resource:\n\n```terraform\nephemeral \"examplecloud_secret\" \"krb\" {\n  name = \"example_kerberos_user\"\n}\n\nprovider \"echo\" {\n  data = ephemeral.examplecloud_secret.krb.secret_data\n}\n\nresource \"echo\" \"test_krb\" {}\n```\n\n### State Assertions\n\nAssert on the echo resource's `data` attribute using standard state checks:\n\n```go\nSteps: []resource.TestStep{\n    {\n        Config: `...`,\n        ConfigStateChecks: []statecheck.StateCheck{\n            statecheck.ExpectKnownValue(\"echo.test_krb\",\n                tfjsonpath.New(\"data\").AtMapKey(\"realm\"),\n                knownvalue.StringExact(\"EXAMPLE.COM\")),\n            statecheck.ExpectKnownValue(\"echo.test_krb\",\n                tfjsonpath.New(\"data\").AtMapKey(\"username\"),\n                knownvalue.StringExact(\"john-doe\")),\n            statecheck.ExpectKnownValue(\"echo.test_krb\",\n                tfjsonpath.New(\"data\").AtMapKey(\"password\"),\n                knownvalue.StringRegexp(regexp.MustCompile(`^.{12}$`))),\n        },\n    },\n},\n```\n\n---\n\n## Multi-Step Testing\n\nThe echo resource has special behavior to accommodate ephemeral data\nvariability:\n\n- During planning for new resources, the `data` attribute is marked unknown\n- Existing echo resources preserve prior state regardless of config changes\n- Refresh operations always return prior state\n\nBecause of this, **create new echo resource instances for each test step**\nrather than reusing the same one:\n\n```go\nSteps: []resource.TestStep{\n    {\n        Config: `\nephemeral \"examplecloud_secret\" \"krb\" {\n  name = \"user_one\"\n}\nprovider \"echo\" {\n  data = ephemeral.examplecloud_secret.krb\n}\nresource \"echo\" \"test_krb_one\" {}\n        `,\n        ConfigStateChecks: []statecheck.StateCheck{\n            statecheck.ExpectKnownValue(\"echo.test_krb_one\",\n                tfjsonpath.New(\"data\").AtMapKey(\"name\"),\n                knownvalue.StringExact(\"user_one\")),\n        },\n    },\n    {\n        Config: `\nephemeral \"examplecloud_secret\" \"krb\" {\n  name = \"user_two\"\n}\nprovider \"echo\" {\n  data = ephemeral.examplecloud_secret.krb\n}\nresource \"echo\" \"test_krb_two\" {}\n        `,\n        ConfigStateChecks: []statecheck.StateCheck{\n            statecheck.ExpectKnownValue(\"echo.test_krb_two\",\n                tfjsonpath.New(\"data\").AtMapKey(\"name\"),\n                knownvalue.StringExact(\"user_two\")),\n        },\n    },\n},\n```\n"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/sweepers.md",
    "content": "# Test Sweepers Reference\n\nSweepers clean up infrastructure resources that leak during acceptance tests —\nwhen test infrastructure fails to be destroyed due to API errors or test\nfailures.\n\nSource: [Sweepers](https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/sweepers)\n\n---\n\n## Setup\n\n### TestMain (required)\n\nAdd to a dedicated file (e.g., `sweep_test.go`):\n\n```go\nfunc TestMain(m *testing.M) {\n    resource.TestMain(m)\n}\n```\n\nThis parses the `-sweep` flag and invokes registered sweepers.\n\n### Register a Sweeper\n\nRegister in the test file for the resource being swept, using `init()`:\n\n```go\nfunc init() {\n    resource.AddTestSweepers(\"example_widget\", &resource.Sweeper{\n        Name: \"example_widget\",\n        F:    sweepWidgets,\n    })\n}\n\nfunc sweepWidgets(region string) error {\n    client, err := sharedClientForRegion(region)\n    if err != nil {\n        return fmt.Errorf(\"getting client: %w\", err)\n    }\n\n    conn := client.(*Client)\n    widgets, err := conn.ListWidgets()\n    if err != nil {\n        return fmt.Errorf(\"listing widgets: %w\", err)\n    }\n\n    for _, w := range widgets {\n        if !strings.HasPrefix(w.Name, \"test-acc\") {\n            continue\n        }\n        if err := conn.DeleteWidget(w.ID); err != nil {\n            log.Printf(\"[WARN] Failed to delete widget %s: %s\", w.ID, err)\n        }\n    }\n\n    return nil\n}\n```\n\nUse a consistent test name prefix (e.g., `\"test-acc\"`) to identify\ntest-created resources.\n\n### Dependencies\n\nWhen resources have ordering requirements (e.g., child resources must be\ndeleted before parents), the **parent** sweeper declares children as\ndependencies so they run first:\n\n```go\nresource.AddTestSweepers(\"example_widget\", &resource.Sweeper{\n    Name:         \"example_widget\",\n    Dependencies: []string{\"example_widget_child\"},\n    F:            sweepWidgets,\n})\n```\n\nDependencies run **before** the sweeper that declares them. In this example,\n`example_widget_child` is swept first, then `example_widget`.\n\n### Shared Client\n\nCreate a helper to build an API client for the sweep region:\n\n```go\nfunc sharedClientForRegion(region string) (any, error) {\n    // Build and return a configured API client\n    return NewClient(region)\n}\n```\n\n## Running Sweepers\n\n```bash\n# Run all sweepers for a region\nTF_ACC=1 go test ./internal/service/example -sweep=us-east-1 -v\n\n# Makefile target (common convention)\nmake sweep\n```\n"
  },
  {
    "path": "terraform/provider-development/skills/run-acceptance-tests/SKILL.md",
    "content": "---\nname: run-acceptance-tests\ndescription: Guide for running acceptance tests for a Terraform provider. Use this when asked to run an acceptance test or to run a test with the prefix `TestAcc`.\nlicense: MPL-2.0\nmetadata:\n  copyright: Copyright IBM Corp. 2026\n  version: \"0.0.1\"\n---\n\nAn acceptance test is a Go test function with the prefix `TestAcc`.\n\nTo run a focussed acceptance test named `TestAccFeatureHappyPath`:\n\n1. Run `go test -run=TestAccFeatureHappyPath` with the following environment\n   variables:\n   - `TF_ACC=1`\n   \n   Default to non-verbose test output.\n1. The acceptance tests may require additional environment variables for\n   specific providers. If the test output indicates missing environment\n   variables, then suggest how to set up these environment variables securely.\n\nTo diagnose a failing acceptance test, use these options, in order. These\noptions are cumulative: each option includes all the options above it.\n\n1. Run the test again. Use the `-count=1` option to ensure that `go test` does\n   not use a cached result.\n1. Offer verbose `go test` output. Use the `-v` option.\n1. Offer debug-level logging. Enable debug-level logging with the environment\n   variable `TF_LOG=debug`.\n1. Offer to persist the acceptance test's Terraform workspace. Enable\n   persistance with the environment variable `TF_ACC_WORKING_DIR_PERSIST=1`.\n\nA passing acceptance test may be a false negative. To \"flip\" a passing\nacceptance test named `TestAccFeatureHappyPath`:\n\n1. Edit the value of one of the TestCheckFuncs in one of the TestSteps in the\n   TestCase.\n1. Run the acceptance test. Expect the test to fail.\n1. If the test fails, then undo the edit and report a successful flip. Else,\n   keep the edit and report an unsuccessful flip.\n"
  }
]