Full Code of hashicorp/agent-skills for AI

main 43ca9b0cde13 cached
53 files
335.7 KB
84.7k tokens
1 symbols
1 requests
Download .txt
Showing preview only (357K chars total). Download the full file or copy to clipboard to get everything.
Repository: hashicorp/agent-skills
Branch: main
Commit: 43ca9b0cde13
Files: 53
Total size: 335.7 KB

Directory structure:
gitextract_z9sttyor/

├── .claude-plugin/
│   └── marketplace.json
├── .copywrite.hcl
├── .github/
│   ├── .tessl/
│   │   └── skill-review-cache.json
│   └── workflows/
│       ├── tessl-skill-review-comment.yml
│       ├── tessl-skill-review.yml
│       └── validate.yml
├── AGENTS.md
├── CHANGELOG.md
├── CODEOWNERS
├── LICENSE
├── README.md
├── packer/
│   ├── README.md
│   ├── builders/
│   │   ├── .claude-plugin/
│   │   │   └── plugin.json
│   │   └── skills/
│   │       ├── aws-ami-builder/
│   │       │   └── SKILL.md
│   │       ├── azure-image-builder/
│   │       │   └── SKILL.md
│   │       └── windows-builder/
│   │           └── SKILL.md
│   └── hcp/
│       ├── .claude-plugin/
│       │   └── plugin.json
│       └── skills/
│           └── push-to-registry/
│               └── SKILL.md
├── scripts/
│   └── validate-structure.sh
└── terraform/
    ├── README.md
    ├── code-generation/
    │   ├── .claude-plugin/
    │   │   └── plugin.json
    │   └── skills/
    │       ├── azure-verified-modules/
    │       │   └── SKILL.md
    │       ├── terraform-search-import/
    │       │   ├── SKILL.md
    │       │   ├── references/
    │       │   │   └── MANUAL-IMPORT.md
    │       │   └── scripts/
    │       │       └── list_resources.sh
    │       ├── terraform-style-guide/
    │       │   ├── SECURITY.md
    │       │   └── SKILL.md
    │       └── terraform-test/
    │           ├── SKILL.md
    │           └── references/
    │               ├── CI_CD.md
    │               ├── EXAMPLES.md
    │               └── MOCK_PROVIDERS.md
    ├── module-generation/
    │   ├── .claude-plugin/
    │   │   └── plugin.json
    │   └── skills/
    │       ├── refactor-module/
    │       │   └── SKILL.md
    │       └── terraform-stacks/
    │           ├── SKILL.md
    │           └── references/
    │               ├── api-monitoring.md
    │               ├── component-blocks.md
    │               ├── deployment-blocks.md
    │               ├── examples.md
    │               ├── linked-stacks.md
    │               └── troubleshooting.md
    └── provider-development/
        ├── .claude-plugin/
        │   └── plugin.json
        └── skills/
            ├── new-terraform-provider/
            │   ├── SKILL.md
            │   └── assets/
            │       └── main.go
            ├── provider-actions/
            │   └── SKILL.md
            ├── provider-docs/
            │   ├── SKILL.md
            │   ├── agents/
            │   │   └── openai.yaml
            │   └── references/
            │       └── hashicorp-provider-docs.md
            ├── provider-resources/
            │   └── SKILL.md
            ├── provider-test-patterns/
            │   ├── SKILL.md
            │   └── references/
            │       ├── checks.md
            │       ├── ephemeral.md
            │       └── sweepers.md
            └── run-acceptance-tests/
                └── SKILL.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .claude-plugin/marketplace.json
================================================
{
  "name": "hashicorp",
  "owner": {
    "name": "HashiCorp"
  },
  "metadata": {
    "description": "Official HashiCorp plugins and skills for Claude Code",
    "version": "1.0.0"
  },
  "plugins": [
    {
      "name": "terraform-code-generation",
      "source": "./terraform/code-generation",
      "description": "Terraform code generation skills including HCL generation, style guides, and testing.",
      "version": "1.0.0",
      "author": {
        "name": "HashiCorp"
      },
      "keywords": ["terraform", "hcl", "infrastructure", "iac", "testing", "style-guide"],
      "category": "integration",
      "license": "MPL-2.0",
      "strict": false
    },
    {
      "name": "terraform-module-generation",
      "source": "./terraform/module-generation",
      "description": "Terraform module generation and refactoring skills including module design and Terraform Stacks.",
      "version": "1.0.0",
      "author": {
        "name": "HashiCorp"
      },
      "keywords": ["terraform", "modules", "infrastructure", "iac", "stacks", "refactoring"],
      "category": "integration",
      "license": "MPL-2.0",
      "strict": false
    },
    {
      "name": "terraform-provider-development",
      "source": "./terraform/provider-development",
      "description": "Terraform provider development skills including resources, data sources, actions, and acceptance testing.",
      "version": "1.0.0",
      "author": {
        "name": "HashiCorp"
      },
      "keywords": ["terraform", "provider", "plugin-framework", "resources", "testing"],
      "category": "integration",
      "license": "MPL-2.0",
      "strict": false
    },
    {
      "name": "packer-builders",
      "source": "./packer/builders",
      "description": "Packer builder skills for AWS, Azure, and Windows image creation.",
      "version": "1.0.0",
      "author": {
        "name": "HashiCorp"
      },
      "keywords": ["packer", "aws", "azure", "windows", "ami", "image", "builder"],
      "category": "integration",
      "license": "MPL-2.0",
      "strict": false
    },
    {
      "name": "packer-hcp",
      "source": "./packer/hcp",
      "description": "HCP Packer registry integration for tracking and managing image metadata.",
      "version": "1.0.0",
      "author": {
        "name": "HashiCorp"
      },
      "keywords": ["packer", "hcp-packer", "registry", "metadata", "image-tracking"],
      "category": "integration",
      "license": "MPL-2.0",
      "strict": false
    }
  ]
}


================================================
FILE: .copywrite.hcl
================================================
schema_version = 1

project {
  license        = "MPL-2.0"
  copyright_year = 2025

  # (OPTIONAL) A list of globs that should not have copyright/license headers.
  # Supports doublestar glob patterns for more flexibility in defining which
  # files or folders should be ignored
  header_ignore = [
    # "vendor/**",
    # "**autogen**",
  ]
}


================================================
FILE: .github/.tessl/skill-review-cache.json
================================================
{
  "version": "1",
  "last_updated": "2026-02-24T00:00:00Z",
  "skills": {}
}


================================================
FILE: .github/workflows/tessl-skill-review-comment.yml
================================================
# Companion workflow to tessl-skill-eval.yml.
#
# The main workflow (Tessl Skill Review) runs on pull_request events, which
# don't have write access to the PR (especially for fork PRs). To work around
# this, the main workflow saves the review results and the PR number as
# artifacts. This workflow triggers on workflow_run (after the main workflow
# completes), downloads those artifacts, and posts/updates the PR comment
# with pull-requests: write permission.
#
# The PR number is read from the artifact file (pr-comment/pr_number) that
# was written by the main workflow using github.event.pull_request.number.
name: Post Tessl Review Comment

on:
  workflow_run:
    workflows: ["Tessl Skill Review"]
    types: [completed]

permissions:
  pull-requests: write

jobs:
  post-comment:
    name: Post PR Comment
    runs-on: ubuntu-latest
    if: github.event.workflow_run.event == 'pull_request'

    steps:
      # Download the artifact produced by the main workflow's review-skills job.
      # run-id ties this to the specific workflow run that triggered us.
      - name: Download skill-review-comment artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: skill-review-comment
          path: skill-review-comment
          run-id: ${{ github.event.workflow_run.id }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      # Read the PR number and comment body from the artifact, then create or
      # update the PR comment. Uses an HTML comment marker (<!-- tessl-skill-review -->)
      # to find and update an existing comment instead of posting duplicates.
      - name: Post skill review comment
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            const fs = require('fs');
            const prNumber = parseInt(fs.readFileSync('skill-review-comment/pr_number', 'utf8').trim());
            const body = fs.readFileSync('skill-review-comment/comment.md', 'utf8');
            const marker = '<!-- tessl-skill-review -->';

            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: prNumber,
            });

            const existing = comments.find(c => c.body.includes(marker));

            if (existing) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: existing.id,
                body: body,
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                body: body,
              });
            }


================================================
FILE: .github/workflows/tessl-skill-review.yml
================================================
name: Tessl Skill Review

on:
  pull_request:
    branches: [main]
    paths:
      - '**/SKILL.md'
      - '**/skills/**'
      - '.github/workflows/tessl-skill-review.yml'
  push:
    branches: [main]
    paths:
      - '**/SKILL.md'
      - '**/skills/**'
  workflow_dispatch:

permissions:
  contents: write  # Required for cache commits

jobs:
  review-skills:
    name: Review Skills
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: '20'

      - name: Install Tessl CLI
        run: npm install -g @tessl/cli

      - name: Detect changed skills
        id: detect
        env:
          EVENT_NAME: ${{ github.event_name }}
          BASE_REF: ${{ github.base_ref }}
        run: |
          if [[ "$EVENT_NAME" == "pull_request" ]]; then
            CHANGED_SKILLS=$(git diff --name-only --diff-filter=ACMR \
              "origin/${BASE_REF}"...HEAD \
              -- '**/SKILL.md' '**/skills/**' | \
              grep 'SKILL.md$' | \
              xargs -I {} dirname {} | \
              sort -u)
          else
            # workflow_dispatch: find all skills
            CHANGED_SKILLS=$(find . -name "SKILL.md" -not -path "./node_modules/*" -not -path "./.git/*" | \
              xargs -I {} dirname {} | \
              sed 's|^\\./||' | \
              sort -u)
          fi

          if [[ -z "$CHANGED_SKILLS" ]]; then
            echo "No skill changes detected."
            echo "skills=" >> "$GITHUB_OUTPUT"
          else
            echo "Skills to review:"
            echo "$CHANGED_SKILLS"
            EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
            echo "skills<<${EOF_MARKER}" >> "$GITHUB_OUTPUT"
            echo "$CHANGED_SKILLS" >> "$GITHUB_OUTPUT"
            echo "${EOF_MARKER}" >> "$GITHUB_OUTPUT"
          fi

      - name: Read review cache
        if: steps.detect.outputs.skills != ''
        id: cache
        run: |
          CACHE_FILE=".github/.tessl/skill-review-cache.json"

          if [[ -f "$CACHE_FILE" ]]; then
            echo "Cache file found, loading..."
            if CACHE_CONTENT=$(cat "$CACHE_FILE" 2>&1); then
              # Validate JSON
              if echo "$CACHE_CONTENT" | jq empty 2>/dev/null; then
                echo "cache_exists=true" >> "$GITHUB_OUTPUT"
                # Export cache to environment for review step
                EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
                echo "REVIEW_CACHE<<${EOF_MARKER}" >> "$GITHUB_ENV"
                echo "$CACHE_CONTENT" >> "$GITHUB_ENV"
                echo "${EOF_MARKER}" >> "$GITHUB_ENV"
              else
                echo "::warning::Cache file is invalid JSON, ignoring"
                echo "cache_exists=false" >> "$GITHUB_OUTPUT"
              fi
            else
              echo "::warning::Cache file exists but cannot be read: $CACHE_CONTENT"
              echo "cache_exists=false" >> "$GITHUB_OUTPUT"
            fi
          else
            echo "No cache file found, will create new one"
            echo "cache_exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Run skill reviews
        if: steps.detect.outputs.skills != ''
        id: review
        env:
          SKILLS: ${{ steps.detect.outputs.skills }}
          TESSL_API_KEY: ${{ secrets.TESSL_API_KEY }}
        run: |
          FAILED=0
          TABLE="| Skill | Status | Review Score | Change |"
          TABLE="${TABLE}\\n|-------|--------|--------------|--------|"
          DETAILS=""

          # Create temporary file for cache entries
          CACHE_FILE_TEMP="cache-entries.tsv"
          echo "Cache entries file: $CACHE_FILE_TEMP"

          while IFS= read -r dir; do
            [[ -z "$dir" ]] && continue
            echo "::group::Reviewing $dir"

            # Run review with --json flag
            JSON_OUTPUT=$(tessl skill review --json "$dir" 2>&1)
            echo "$JSON_OUTPUT"
            echo "::endgroup::"

            # Extract JSON (skip everything before first '{')
            JSON=$(echo "$JSON_OUTPUT" | sed -n '/{/,$p')

            # Look up previous score from cache
            PREV_SCORE=""
            PREV_DESC=""
            PREV_CONTENT=""
            if [[ -n "$REVIEW_CACHE" ]]; then
              CACHE_ENTRY=$(echo "$REVIEW_CACHE" | jq -r --arg path "$dir" '.skills[$path] // empty')
              if [[ -n "$CACHE_ENTRY" ]]; then
                PREV_SCORE=$(echo "$CACHE_ENTRY" | jq -r '.score // empty')
                PREV_DESC=$(echo "$CACHE_ENTRY" | jq -r '.dimensions.description // empty')
                PREV_CONTENT=$(echo "$CACHE_ENTRY" | jq -r '.dimensions.content // empty')
              fi
            fi

            # Validate PREV_SCORE is numeric
            if [[ -n "$PREV_SCORE" && ! "$PREV_SCORE" =~ ^[0-9]+$ ]]; then
              echo "::warning::Invalid previous score for $dir: $PREV_SCORE, ignoring"
              PREV_SCORE=""
            fi

            # Validate PREV_DESC and PREV_CONTENT are numeric
            if [[ -n "$PREV_DESC" && ! "$PREV_DESC" =~ ^[0-9]+$ ]]; then
              echo "::warning::Invalid previous description score for $dir: $PREV_DESC, ignoring"
              PREV_DESC=""
            fi
            if [[ -n "$PREV_CONTENT" && ! "$PREV_CONTENT" =~ ^[0-9]+$ ]]; then
              echo "::warning::Invalid previous content score for $dir: $PREV_CONTENT, ignoring"
              PREV_CONTENT=""
            fi

            # Extract fields via jq
            PASSED=$(echo "$JSON" | jq -r '.validation.overallPassed // false')

            # Calculate average score from all 8 dimensions
            AVG_SCORE=$(echo "$JSON" | jq -r '
              def avg(obj): (obj.scores | to_entries | map(.value.score) | add) / (obj.scores | length) * 100 / 3;
              (
                [(.descriptionJudge.evaluation | avg(.)), (.contentJudge.evaluation | avg(.))] | add / 2
              ) | round
            ')

            # Validate AVG_SCORE is numeric before arithmetic
            if [[ ! "$AVG_SCORE" =~ ^[0-9]+$ ]]; then
              echo "::error::Invalid average score calculated for $dir: $AVG_SCORE"
              AVG_SCORE=0
            fi

            # Calculate diff
            CHANGE=""
            if [[ -n "$PREV_SCORE" ]]; then
              DIFF=$((AVG_SCORE - PREV_SCORE))
              if [[ $DIFF -gt 0 ]]; then
                CHANGE="🔺 +${DIFF}% (was ${PREV_SCORE}%)"
              elif [[ $DIFF -lt 0 ]]; then
                CHANGE="🔻 ${DIFF}% (was ${PREV_SCORE}%)"
              else
                CHANGE="➡️ no change"
              fi
            fi

            # Build status column
            if [[ "$PASSED" == "true" ]]; then
              STATUS="✅ PASSED"
            else
              # Extract first validation error
              ERROR=$(echo "$JSON" | jq -r '
                .validation.checks
                | map(select(.status != "passed"))
                | first
                | .message // "Validation failed"
              ' | cut -c1-60)
              STATUS="❌ FAILED — ${ERROR}"
              FAILED=1
            fi

            DIR_DISPLAY=$(echo "$dir" | tr '|' '/')
            TABLE="${TABLE}\\n| \`${DIR_DISPLAY}\` | ${STATUS} | ${AVG_SCORE}% | ${CHANGE} |"

            # Calculate dimension scores for cache and details
            DESC_SCORE=$(echo "$JSON" | jq -r '
              (.descriptionJudge.evaluation.scores | to_entries | map(.value.score) | add) * 100 / ((.descriptionJudge.evaluation.scores | length) * 3) | round
            ')
            CONTENT_SCORE=$(echo "$JSON" | jq -r '
              (.contentJudge.evaluation.scores | to_entries | map(.value.score) | add) * 100 / ((.contentJudge.evaluation.scores | length) * 3) | round
            ')

            # Validate dimension scores
            if [[ ! "$DESC_SCORE" =~ ^[0-9]+$ ]]; then
              echo "::warning::Invalid description score for $dir: $DESC_SCORE, using 0"
              DESC_SCORE=0
            fi
            if [[ ! "$CONTENT_SCORE" =~ ^[0-9]+$ ]]; then
              echo "::warning::Invalid content score for $dir: $CONTENT_SCORE, using 0"
              CONTENT_SCORE=0
            fi

            # --- Extract detailed review for collapsible section ---
            DESC_EVAL=$(echo "$JSON" | jq -r '.descriptionJudge.evaluation |
              "  Description: " + ((.scores | to_entries | map(.value.score) | add) * 100 / ((.scores | length) * 3) | round | tostring) + "%\\n" +
              (.scores | to_entries | map("    \(.key): \(.value.score)/3 - \(.value.reasoning)") | join("\\n")) + "\\n\\n" +
              "    Assessment: " + .overall_assessment
            ')

            CONTENT_EVAL=$(echo "$JSON" | jq -r '.contentJudge.evaluation |
              "  Content: " + ((.scores | to_entries | map(.value.score) | add) * 100 / ((.scores | length) * 3) | round | tostring) + "%\\n" +
              (.scores | to_entries | map("    \(.key): \(.value.score)/3 - \(.value.reasoning)") | join("\\n")) + "\\n\\n" +
              "    Assessment: " + .overall_assessment
            ')

            # Extract suggestions
            SUGGESTIONS=$(echo "$JSON" | jq -r '
              [.descriptionJudge.evaluation.suggestions // [], .contentJudge.evaluation.suggestions // []]
              | flatten
              | map("- " + .)
              | join("\\n")
            ')

            # Build collapsible details block
            DETAILS="${DETAILS}\\n\\n<details>\\n<summary><strong>${DIR_DISPLAY}</strong> — ${AVG_SCORE}% (${STATUS#* })</summary>\\n\\n"

            # Show score comparison if previous exists (all three must be valid)
            if [[ -n "$PREV_SCORE" && -n "$PREV_DESC" && -n "$PREV_CONTENT" ]]; then
              DETAILS="${DETAILS}**Previous:** ${PREV_SCORE}% (Description: ${PREV_DESC}%, Content: ${PREV_CONTENT}%)\\n"
              DETAILS="${DETAILS}**Current:**  ${AVG_SCORE}% (Description: ${DESC_SCORE}%, Content: ${CONTENT_SCORE}%)\\n\\n"
              DETAILS="${DETAILS}---\\n\\n"
            fi

            DETAILS="${DETAILS}\`\`\`\\n${DESC_EVAL}\\n\\n${CONTENT_EVAL}\\n\`\`\`\\n"

            if [[ -n "$SUGGESTIONS" ]]; then
              DETAILS="${DETAILS}\\n**Suggestions:**\\n\\n${SUGGESTIONS}\\n"
            fi

            DETAILS="${DETAILS}\\n</details>"

            # Calculate content hash
            if [[ ! -f "$dir/SKILL.md" ]]; then
              echo "::error::SKILL.md not found for $dir"
              continue
            fi
            CONTENT_HASH=$(shasum -a 256 "$dir/SKILL.md" 2>&1)
            if [[ $? -ne 0 ]]; then
              echo "::error::Failed to calculate hash for $dir: $CONTENT_HASH"
              continue
            fi
            CONTENT_HASH="sha256:$(echo "$CONTENT_HASH" | awk '{print $1}')"

            # Build cache entry (compact to single line)
            if ! CACHE_ENTRY=$(jq -nc \
              --arg score "$AVG_SCORE" \
              --arg passed "$PASSED" \
              --arg hash "$CONTENT_HASH" \
              --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
              --arg desc "$DESC_SCORE" \
              --arg content "$CONTENT_SCORE" \
              '{
                score: ($score | tonumber),
                validation_passed: ($passed == "true"),
                content_hash: $hash,
                timestamp: $ts,
                dimensions: {
                  description: ($desc | tonumber),
                  content: ($content | tonumber)
                }
              }'); then
              echo "::error::Failed to build cache entry for $dir"
              continue
            fi

            # Write cache entry to file (tab-separated: path<tab>json)
            printf '%s\t%s\n' "$dir" "$CACHE_ENTRY" >> "$CACHE_FILE_TEMP"

          done <<< "$SKILLS"

          # Save cache entries file path for update step
          echo "CACHE_ENTRIES_FILE=$CACHE_FILE_TEMP" >> "$GITHUB_ENV"
          echo "Wrote $(wc -l < "$CACHE_FILE_TEMP") cache entries to $CACHE_FILE_TEMP"

          # Build PR comment body
          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._")

          EOF_MARKER=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
          echo "comment<<${EOF_MARKER}" >> "$GITHUB_OUTPUT"
          echo "$COMMENT_BODY" >> "$GITHUB_OUTPUT"
          echo "${EOF_MARKER}" >> "$GITHUB_OUTPUT"

          echo "$COMMENT_BODY" >> "$GITHUB_STEP_SUMMARY"

          if [[ "$FAILED" -eq 1 ]]; then
            echo "::error::One or more skills failed validation checks."
            exit 1
          fi

      - name: Update review cache
        if: always() && steps.review.outcome != 'skipped'
        id: update-cache
        run: |
          CACHE_FILE=".github/.tessl/skill-review-cache.json"
          mkdir -p .github/.tessl

          # Load existing cache or create new structure
          if [[ -f "$CACHE_FILE" ]]; then
            if CACHE=$(cat "$CACHE_FILE" 2>&1); then
              if ! echo "$CACHE" | jq empty 2>/dev/null; then
                echo "::warning::Cache file is invalid JSON, recreating"
                CACHE='{"version":"1","last_updated":"","skills":{}}'
              fi
            else
              echo "::warning::Cache file exists but cannot be read: $CACHE"
              CACHE='{"version":"1","last_updated":"","skills":{}}'
            fi
          else
            echo "Creating new cache file..."
            CACHE='{"version":"1","last_updated":"","skills":{}}'
          fi

          # Update timestamp
          TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
          if ! CACHE=$(echo "$CACHE" | jq --arg ts "$TIMESTAMP" '.last_updated = $ts'); then
            echo "::error::Failed to update cache timestamp"
            exit 1
          fi

          # Merge cache updates (using TAB delimiter)
          MERGED_COUNT=0
          FAILED_COUNT=0

          if [[ -f "$CACHE_ENTRIES_FILE" ]]; then
            while IFS=$'\t' read -r skill_path entry_json; do
              [[ -z "$skill_path" ]] && continue
              if NEW_CACHE=$(echo "$CACHE" | jq --arg path "$skill_path" --argjson entry "$entry_json" \
                '.skills[$path] = $entry' 2>&1); then
                CACHE="$NEW_CACHE"
                MERGED_COUNT=$((MERGED_COUNT + 1))
              else
                echo "::warning::Failed to merge cache entry for $skill_path: $NEW_CACHE"
                FAILED_COUNT=$((FAILED_COUNT + 1))
                continue
              fi
            done < "$CACHE_ENTRIES_FILE"
          fi

          # Write cache file
          if ! echo "$CACHE" | jq '.' > "$CACHE_FILE"; then
            echo "::error::Failed to write cache file"
            exit 1
          fi

          # Report accurate merge counts
          if [[ $FAILED_COUNT -gt 0 ]]; then
            echo "Cache updated with $MERGED_COUNT entries ($FAILED_COUNT failed)"
          else
            echo "Cache updated with $MERGED_COUNT entries"
          fi

      - name: Upload cache file
        if: always() && steps.update-cache.outcome == 'success'
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: skill-review-cache
          path: .github/.tessl/skill-review-cache.json

      - name: Upload cache entries artifact
        if: always() && steps.review.outcome != 'skipped'
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: cache-entries
          path: cache-entries.tsv
          retention-days: 1

      - name: Save PR comment artifact
        if: >-
          github.event_name == 'pull_request'
          && steps.detect.outputs.skills != ''
          && (steps.review.outcome == 'success' || steps.review.outputs.comment != '')
        env:
          COMMENT_BODY: ${{ steps.review.outputs.comment }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          mkdir -p pr-comment
          echo "$PR_NUMBER" > pr-comment/pr_number
          echo "$COMMENT_BODY" > pr-comment/comment.md

      - name: Upload PR comment artifact
        if: >-
          github.event_name == 'pull_request'
          && steps.detect.outputs.skills != ''
          && (steps.review.outcome == 'success' || steps.review.outputs.comment != '')
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: skill-review-comment
          path: pr-comment/

  commit-cache:
    name: Commit Cache
    runs-on: ubuntu-latest
    needs: review-skills
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Download cache file
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: skill-review-cache

      - name: Move cache to correct location
        run: |
          mkdir -p .github/.tessl
          mv skill-review-cache.json .github/.tessl/skill-review-cache.json

      - name: Check for cache changes
        id: check
        run: |
          if git diff --quiet HEAD .github/.tessl/skill-review-cache.json; then
            echo "changed=false" >> "$GITHUB_OUTPUT"
          else
            echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Commit cache
        if: steps.check.outputs.changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .github/.tessl/skill-review-cache.json
          git commit -m "chore: update skill review cache [skip ci]"
          if ! git push; then
            echo "::error::Failed to push cache update to main"
            exit 1
          fi


================================================
FILE: .github/workflows/validate.yml
================================================
name: Validate Structure

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  validate-structure:
    name: Validate Repository Structure
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      - name: Validate structure
        run: |
          chmod +x ./scripts/validate-structure.sh
          ./scripts/validate-structure.sh

  validate-json:
    name: Validate JSON Files
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      - name: Validate all JSON files
        run: |
          echo "Validating all JSON files..."
          ERRORS=0

          for file in $(find . -name "*.json" -not -path "./node_modules/*" -not -path "./.git/*"); do
            if ! jq empty "$file" 2>/dev/null; then
              echo "ERROR: Invalid JSON in $file"
              ERRORS=$((ERRORS + 1))
            else
              echo "OK: $file"
            fi
          done

          if [[ "$ERRORS" -gt 0 ]]; then
            echo "Found $ERRORS invalid JSON file(s)"
            exit 1
          fi

          echo "All JSON files are valid!"

  validate-skills:
    name: Validate SKILL.md Files
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      - name: Check SKILL.md frontmatter
        run: |
          echo "Validating SKILL.md files..."
          ERRORS=0

          for skill_file in $(find . -name "SKILL.md" -not -path "./node_modules/*"); do
            echo "Checking: $skill_file"

            # Check file starts with frontmatter
            if ! head -1 "$skill_file" | grep -q "^---"; then
              echo "ERROR: $skill_file does not start with frontmatter (---)"
              ERRORS=$((ERRORS + 1))
              continue
            fi

            # Extract frontmatter
            FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$skill_file" | sed '1d;$d')

            # Check for required fields
            if ! echo "$FRONTMATTER" | grep -q "^name:"; then
              echo "ERROR: $skill_file missing 'name' in frontmatter"
              ERRORS=$((ERRORS + 1))
            fi

            if ! echo "$FRONTMATTER" | grep -q "^description:"; then
              echo "ERROR: $skill_file missing 'description' in frontmatter"
              ERRORS=$((ERRORS + 1))
            fi
          done

          if [[ "$ERRORS" -gt 0 ]]; then
            echo "Found $ERRORS error(s) in SKILL.md files"
            exit 1
          fi

          echo "All SKILL.md files are valid!"

  validate-marketplace-references:
    name: Validate Marketplace References
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      - name: Check marketplace plugin references
        run: |
          echo "Validating marketplace.json plugin references..."
          ERRORS=0

          MARKETPLACE=".claude-plugin/marketplace.json"

          if [[ ! -f "$MARKETPLACE" ]]; then
            echo "ERROR: marketplace.json not found"
            exit 1
          fi

          # Check each plugin source path exists
          for source in $(jq -r '.plugins[].source' "$MARKETPLACE"); do
            # Remove leading ./
            path="${source#./}"

            if [[ ! -d "$path" ]]; then
              echo "ERROR: Plugin path does not exist: $path"
              ERRORS=$((ERRORS + 1))
            else
              echo "OK: $path exists"
            fi

            # Check plugin.json exists in that path
            if [[ ! -f "$path/.claude-plugin/plugin.json" ]]; then
              echo "ERROR: plugin.json not found in $path/.claude-plugin/"
              ERRORS=$((ERRORS + 1))
            else
              echo "OK: $path/.claude-plugin/plugin.json exists"
            fi

            # Check skills directory exists
            if [[ ! -d "$path/skills" ]]; then
              echo "ERROR: skills/ directory not found in $path"
              ERRORS=$((ERRORS + 1))
            else
              echo "OK: $path/skills/ exists"
            fi
          done

          if [[ "$ERRORS" -gt 0 ]]; then
            echo "Found $ERRORS error(s) in marketplace references"
            exit 1
          fi

          echo "All marketplace references are valid!"


================================================
FILE: AGENTS.md
================================================
# Agent Instructions

This repository contains agent skills and Claude Code plugins for HashiCorp products, including Terraform and Packer for infrastructure-as-code development.

## Repository Structure

```
agent-skills/
├── terraform/
│   ├── code-generation/
│   │   ├── .claude-plugin/plugin.json
│   │   └── skills/
│   │       ├── azure-verified-modules/
│   │       ├── terraform-style-guide/
│   │       ├── terraform-test/
│   │       └── terraform-search-import/
│   ├── module-generation/
│   │   ├── .claude-plugin/plugin.json
│   │   └── skills/
│   │       ├── refactor-module/
│   │       └── terraform-stacks/
│   └── provider-development/
│       ├── .claude-plugin/plugin.json
│       └── skills/
│           ├── new-terraform-provider/
│           ├── run-acceptance-tests/
│           ├── provider-actions/
│           └── provider-resources/
├── packer/
│   ├── builders/
│   │   ├── .claude-plugin/plugin.json
│   │   └── skills/
│   │       ├── aws-ami-builder/
│   │       ├── azure-image-builder/
│   │       └── windows-builder/
│   └── hcp/
│       ├── .claude-plugin/plugin.json
│       └── skills/
│           └── push-to-registry/
├── .claude-plugin/marketplace.json
├── README.md
└── AGENTS.md
```

## Installation Methods

### Method 1: Claude Code Plugin Installation

Install plugins using Claude Code CLI. First add the marketplace, then install plugins:

```bash
# Add the agent-skills marketplace
claude plugin marketplace add hashicorp/agent-skills

# Install plugins
claude plugin install terraform-code-generation@hashicorp
claude plugin install terraform-module-generation@hashicorp
claude plugin install terraform-provider-development@hashicorp
claude plugin install packer-builders@hashicorp
claude plugin install packer-hcp@hashicorp
```

Or use the interactive interface within Claude Code:
```
/plugin
```

This opens a tabbed interface where you can:
- **Discover**: Browse available plugins from all marketplaces
- **Installed**: View and manage installed plugins
- **Marketplaces**: Add, remove, or update marketplaces

Additional plugin management commands:
```bash
# Disable a plugin
claude plugin disable terraform-code-generation@hashicorp

# Re-enable a plugin
claude plugin enable terraform-code-generation@hashicorp

# Uninstall a plugin
claude plugin uninstall terraform-code-generation@hashicorp

# Update a plugin
claude plugin update terraform-code-generation@hashicorp
```

Installation scopes (use `--scope` flag):
- `user` (default): Available across all projects
- `project`: Shared with team via `.claude/settings.json`
- `local`: Project-specific, gitignored

### Method 2: Individual Skill Installation

Install individual skills using `npx skills add`:

```bash
# List all available skills
npx skills add hashicorp/agent-skills

# Code generation skills
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-test
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/azure-verified-modules
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-search-import

# Module generation skills
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/refactor-module
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/terraform-stacks

# Provider development skills
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/new-terraform-provider
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/run-acceptance-tests
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-actions
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-resources

# Packer builder skills
npx skills add hashicorp/agent-skills/packer/builders/skills/aws-ami-builder
npx skills add hashicorp/agent-skills/packer/builders/skills/azure-image-builder
npx skills add hashicorp/agent-skills/packer/builders/skills/windows-builder

# Packer HCP skills
npx skills add hashicorp/agent-skills/packer/hcp/skills/push-to-registry
```

Skills are installed to `~/.claude/skills/` or project `.claude/skills/` directory.

### Method 3: Manual Installation

Copy skills directly to your Claude Code configuration:

```bash
# Clone the repository
git clone https://github.com/hashicorp/agent-skills.git

# Copy a plugin to Claude Code plugins directory
cp -r agent-skills/terraform/code-generation ~/.claude/plugins/

# Or copy individual skills
cp -r agent-skills/terraform/code-generation/skills/terraform-style-guide ~/.claude/skills/
```

## Plugin Contents

### terraform-code-generation

Skills for generating and validating Terraform HCL code:

| Skill | Description |
|-------|-------------|
| `terraform-style-guide` | Generate Terraform HCL code following HashiCorp style conventions and best practices |
| `terraform-test` | Writing and running `.tftest.hcl` test files |
| `azure-verified-modules` | Azure Verified Modules (AVM) requirements and certification |
| `terraform-search-import` | Discover existing resources with Terraform Search and bulk import into state |

### terraform-module-generation

Skills for creating and refactoring Terraform modules:

| Skill | Description |
|-------|-------------|
| `refactor-module` | Transform monolithic configs into reusable modules |
| `terraform-stacks` | Multi-region/environment orchestration with Terraform Stacks |

### terraform-provider-development

Skills for developing Terraform providers:

| Skill | Description |
|-------|-------------|
| `new-terraform-provider` | Scaffold a new Terraform provider |
| `run-acceptance-tests` | Run and debug provider acceptance tests |
| `provider-actions` | Implement provider actions (lifecycle operations) |
| `provider-resources` | Implement resources and data sources |

### packer-builders

Skills for building images on AWS, Azure, and Windows:

| Skill | Description |
|-------|-------------|
| `aws-ami-builder`     | Build Amazon Machine Images (AMIs) with amazon-ebs builder |
| `azure-image-builder` | Build Azure managed images and Azure Compute Gallery images |
| `windows-builder`     | Platform-agnostic Windows image patterns with WinRM and PowerShell |

### packer-hcp

Skills for HCP Packer registry integration:

| Skill | Description |
|-------|-------------|
| `push-to-registry` | Configure hcp_packer_registry to push build metadata to HCP Packer |

## Skill Format

Each skill directory contains:
- `SKILL.md` - Main skill definition with YAML frontmatter (`name`, `description`)
- Optional `assets/`, `references/`, or `resources/` directories

### SKILL.md Frontmatter

```yaml
---
name: skill-name
description: Brief description of when to use this skill.
---
```

## When to Use Each Plugin

### terraform-code-generation
Use when:
- Writing new Terraform configurations
- Reviewing Terraform code for style compliance
- Creating test files for Terraform modules
- Generating HCL for specific providers

### terraform-module-generation
Use when:
- Refactoring existing Terraform code into modules
- Working with Terraform Stacks
- Designing module interfaces and outputs
- Managing multi-environment deployments

### terraform-provider-development
Use when:
- Creating a new Terraform provider
- Adding resources or data sources to an existing provider
- Implementing provider actions
- Running or debugging acceptance tests

### packer-builders
Use when:
- Building AWS AMIs with amazon-ebs builder
- Creating Azure managed images or Azure Compute Gallery images
- Building Windows images (AWS, Azure, VMware, etc.)
- Setting up WinRM and PowerShell provisioners
- Troubleshooting Windows-specific image build issues

### packer-hcp
Use when:
- Integrating Packer builds with HCP Packer registry
- Tracking image metadata and versions
- Setting up hcp_packer_registry block
- Configuring CI/CD to push to HCP Packer
- Querying HCP Packer images in Terraform

## MCP Server Configuration

All Terraform plugins include MCP server configuration for the Terraform MCP Server:

```json
{
  "mcpServers": {
    "terraform": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "TFE_TOKEN", "-e", "TFE_ADDRESS", "hashicorp/terraform-mcp-server"],
      "env": {
        "TFE_TOKEN": "${TFE_TOKEN}",
        "TFE_ADDRESS": "${TFE_ADDRESS}"
      }
    }
  }
}
```

Set environment variables for HCP Terraform integration:
- `TFE_TOKEN` - HCP Terraform API token
- `TFE_ADDRESS` - HCP Terraform address (optional, defaults to app.terraform.io)

## References

- [Terraform Documentation](https://developer.hashicorp.com/terraform)
- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)
- [Terraform MCP Server](https://github.com/hashicorp/terraform-mcp-server)
- [Packer Documentation](https://developer.hashicorp.com/packer)
- [HCP Packer](https://developer.hashicorp.com/hcp/docs/packer)
- [Packer HCL2 Configuration](https://developer.hashicorp.com/packer/guides/hcl)
- [Claude Code Plugins](https://docs.anthropic.com/claude-code/plugins)


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to the HashiCorp Agent Skills.

## Unreleased

### Added
- `terraform-search-import` skill for discovering existing resources with Terraform Search and bulk import

## 0.1.0

### Added
- 3 Claude Code plugins with 9 total skills
- `terraform-code-generation`: terraform-style-guide, terraform-test, azure-verified-modules
- `terraform-module-generation`: refactor-module, terraform-stacks
- `terraform-provider-development`: new-terraform-provider, run-acceptance-tests, provider-actions, provider-resources
- Marketplace manifest for Claude Code plugin installation
- Support for `npx add-skill` installation


================================================
FILE: CODEOWNERS
================================================
# Default owner
* @hashicorp/team-proj-mcp-servers


================================================
FILE: LICENSE
================================================
Mozilla Public License Version 2.0
==================================

1. Definitions
--------------

1.1. "Contributor"
    means each individual or legal entity that creates, contributes to
    the creation of, or owns Covered Software.

1.2. "Contributor Version"
    means the combination of the Contributions of others (if any) used
    by a Contributor and that particular Contributor's Contribution.

1.3. "Contribution"
    means Covered Software of a particular Contributor.

1.4. "Covered Software"
    means Source Code Form to which the initial Contributor has attached
    the notice in Exhibit A, the Executable Form of such Source Code
    Form, and Modifications of such Source Code Form, in each case
    including portions thereof.

1.5. "Incompatible With Secondary Licenses"
    means

    (a) that the initial Contributor has attached the notice described
        in Exhibit B to the Covered Software; or

    (b) that the Covered Software was made available under the terms of
        version 1.1 or earlier of the License, but not also under the
        terms of a Secondary License.

1.6. "Executable Form"
    means any form of the work other than Source Code Form.

1.7. "Larger Work"
    means a work that combines Covered Software with other material, in
    a separate file or files, that is not Covered Software.

1.8. "License"
    means this document.

1.9. "Licensable"
    means having the right to grant, to the maximum extent possible,
    whether at the time of the initial grant or subsequently, any and
    all of the rights conveyed by this License.

1.10. "Modifications"
    means any of the following:

    (a) any file in Source Code Form that results from an addition to,
        deletion from, or modification of the contents of Covered
        Software; or

    (b) any new file in Source Code Form that contains any Covered
        Software.

1.11. "Patent Claims" of a Contributor
    means any patent claim(s), including without limitation, method,
    process, and apparatus claims, in any patent Licensable by such
    Contributor that would be infringed, but for the grant of the
    License, by the making, using, selling, offering for sale, having
    made, import, or transfer of either its Contributions or its
    Contributor Version.

1.12. "Secondary License"
    means either the GNU General Public License, Version 2.0, the GNU
    Lesser General Public License, Version 2.1, the GNU Affero General
    Public License, Version 3.0, or any later versions of those
    licenses.

1.13. "Source Code Form"
    means the form of the work preferred for making modifications.

1.14. "You" (or "Your")
    means an individual or a legal entity exercising rights under this
    License. For legal entities, "You" includes any entity that
    controls, is controlled by, or is under common control with You. For
    purposes of this definition, "control" means (a) the power, direct
    or indirect, to cause the direction or management of such entity,
    whether by contract or otherwise, or (b) ownership of more than
    fifty percent (50%) of the outstanding shares or beneficial
    ownership of such entity.

2. License Grants and Conditions
--------------------------------

2.1. Grants

Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:

(a) under intellectual property rights (other than patent or trademark)
    Licensable by such Contributor to use, reproduce, make available,
    modify, display, perform, distribute, and otherwise exploit its
    Contributions, either on an unmodified basis, with Modifications, or
    as part of a Larger Work; and

(b) under Patent Claims of such Contributor to make, use, sell, offer
    for sale, have made, import, and otherwise transfer either its
    Contributions or its Contributor Version.

2.2. Effective Date

The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.

2.3. Limitations on Grant Scope

The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:

(a) for any code that a Contributor has removed from Covered Software;
    or

(b) for infringements caused by: (i) Your and any other third party's
    modifications of Covered Software, or (ii) the combination of its
    Contributions with other software (except as part of its Contributor
    Version); or

(c) under Patent Claims infringed by Covered Software in the absence of
    its Contributions.

This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).

2.4. Subsequent Licenses

No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).

2.5. Representation

Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.

2.6. Fair Use

This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.

2.7. Conditions

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.

3. Responsibilities
-------------------

3.1. Distribution of Source Form

All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.

3.2. Distribution of Executable Form

If You distribute Covered Software in Executable Form then:

(a) such Covered Software must also be made available in Source Code
    Form, as described in Section 3.1, and You must inform recipients of
    the Executable Form how they can obtain a copy of such Source Code
    Form by reasonable means in a timely manner, at a charge no more
    than the cost of distribution to the recipient; and

(b) You may distribute such Executable Form under the terms of this
    License, or sublicense it under different terms, provided that the
    license for the Executable Form does not attempt to limit or alter
    the recipients' rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).

3.4. Notices

You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.

4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------

If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.

5. Termination
--------------

5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.

************************************************************************
*                                                                      *
*  6. Disclaimer of Warranty                                           *
*  -------------------------                                           *
*                                                                      *
*  Covered Software is provided under this License on an "as is"       *
*  basis, without warranty of any kind, either expressed, implied, or  *
*  statutory, including, without limitation, warranties that the       *
*  Covered Software is free of defects, merchantable, fit for a        *
*  particular purpose or non-infringing. The entire risk as to the     *
*  quality and performance of the Covered Software is with You.        *
*  Should any Covered Software prove defective in any respect, You     *
*  (not any Contributor) assume the cost of any necessary servicing,   *
*  repair, or correction. This disclaimer of warranty constitutes an   *
*  essential part of this License. No use of any Covered Software is   *
*  authorized under this License except under this disclaimer.         *
*                                                                      *
************************************************************************

************************************************************************
*                                                                      *
*  7. Limitation of Liability                                          *
*  --------------------------                                          *
*                                                                      *
*  Under no circumstances and under no legal theory, whether tort      *
*  (including negligence), contract, or otherwise, shall any           *
*  Contributor, or anyone who distributes Covered Software as          *
*  permitted above, be liable to You for any direct, indirect,         *
*  special, incidental, or consequential damages of any character      *
*  including, without limitation, damages for lost profits, loss of    *
*  goodwill, work stoppage, computer failure or malfunction, or any    *
*  and all other commercial damages or losses, even if such party      *
*  shall have been informed of the possibility of such damages. This   *
*  limitation of liability shall not apply to liability for death or   *
*  personal injury resulting from such party's negligence to the       *
*  extent applicable law prohibits such limitation. Some               *
*  jurisdictions do not allow the exclusion or limitation of           *
*  incidental or consequential damages, so this exclusion and          *
*  limitation may not apply to You.                                    *
*                                                                      *
************************************************************************

8. Litigation
-------------

Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.

9. Miscellaneous
----------------

This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.

10. Versions of the License
---------------------------

10.1. New Versions

Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.

10.2. Effect of New Versions

You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.

10.3. Modified Versions

If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses

If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.

Exhibit A - Source Code Form License Notice
-------------------------------------------

  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at https://mozilla.org/MPL/2.0/.

If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.

You may add additional accurate notices of copyright ownership.

Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------

  This Source Code Form is "Incompatible With Secondary Licenses", as
  defined by the Mozilla Public License, v. 2.0.


================================================
FILE: README.md
================================================
# HashiCorp Agent Skills

A collection of Agent skills and Claude Code plugins for HashiCorp products.

| Product | Use cases |
|:--------|:----------|
| [Terraform](./terraform/) | Write HCL code, build modules, develop providers, and run tests |
| [Packer](./packer/) | Build machine images on AWS, Azure, and Windows; integrate with HCP Packer registry |

> **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.

## Installation

### Individual Skills

Install Agent Skills in GitHub Copilot, Claude Code, Opencode, Cursor, and more:

```bash
# List all skills
npx skills add hashicorp/agent-skills

# Install a specific skill
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide
```

### Claude Code Plugin

First, add the marketplace, then install plugins:

```bash
# Add the HashiCorp marketplace
claude plugin marketplace add hashicorp/agent-skills

# Install plugins
claude plugin install terraform-code-generation@hashicorp
claude plugin install terraform-module-generation@hashicorp
claude plugin install terraform-provider-development@hashicorp
claude plugin install packer-builders@hashicorp
claude plugin install packer-hcp@hashicorp
```

Or use the interactive interface:
```bash
/plugin
```

## Structure

```
agent-skills/
├── .claude-plugin/
│   └── marketplace.json
├── terraform/              # Terraform skills
├── packer/                 # Packer skills
├── <product>/              # Future products (Vault, Consul, etc.)
└── README.md
```

Each product folder contains plugins, and each plugin contains skills:

```
<product>/
└── <plugin>/
    ├── .claude-plugin/plugin.json
    └── skills/
        └── <skill>/
            └── SKILL.md
```

## License

MPL-2.0


================================================
FILE: packer/README.md
================================================
# Packer Skills

Agent skills for building machine images with Packer and HCP Packer.

## Plugins

### packer-builders

Skills for building images on AWS, Azure, and Windows.

| Skill | Description |
|-------|-------------|
| aws-ami-builder     | Build Amazon Machine Images (AMIs) with amazon-ebs builder |
| azure-image-builder | Build Azure managed images and Azure Compute Gallery images |
| windows-builder     | Platform-agnostic Windows image patterns with WinRM and PowerShell |

### packer-hcp

Skills for HCP Packer registry integration.

| Skill | Description |
|-------|-------------|
| push-to-registry | Configure hcp_packer_registry to push build metadata to HCP Packer |

## Installation

### Claude Code Plugin

```bash
claude plugin marketplace add hashicorp/agent-skills

claude plugin install packer-builders@hashicorp
claude plugin install packer-hcp@hashicorp
```

### Individual Skills

```bash
# Builders
npx skills add hashicorp/agent-skills/packer/builders/skills/aws-ami-builder
npx skills add hashicorp/agent-skills/packer/builders/skills/azure-image-builder
npx skills add hashicorp/agent-skills/packer/builders/skills/windows-builder

# HCP Packer
npx skills add hashicorp/agent-skills/packer/hcp/skills/push-to-registry
```

## Structure

```
packer/
├── builders/
│   ├── .claude-plugin/plugin.json
│   └── skills/
│       ├── aws-ami-builder/
│       ├── azure-image-builder/
│       └── windows-builder/
└── hcp/
    ├── .claude-plugin/plugin.json
    └── skills/
        └── push-to-registry/
```

## References

- [Packer Documentation](https://developer.hashicorp.com/packer)
- [HCP Packer](https://developer.hashicorp.com/hcp/docs/packer)
- [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)
- [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)


================================================
FILE: packer/builders/.claude-plugin/plugin.json
================================================
{
  "name": "packer-builders",
  "version": "1.0.0",
  "description": "Packer builder skills for AWS, Azure, and Windows image creation.",
  "author": {
    "name": "HashiCorp",
    "url": "https://github.com/hashicorp"
  },
  "homepage": "https://developer.hashicorp.com/packer",
  "repository": "https://github.com/hashicorp/agent-skills",
  "license": "MPL-2.0",
  "keywords": ["packer", "aws", "azure", "windows", "ami", "image", "builder"]
}


================================================
FILE: packer/builders/skills/aws-ami-builder/SKILL.md
================================================
---
name: aws-ami-builder
description: Build Amazon Machine Images (AMIs) with Packer using the amazon-ebs builder. Use when creating custom AMIs for EC2 instances.
---

# AWS AMI Builder

Build Amazon Machine Images (AMIs) using Packer's `amazon-ebs` builder.

**Reference:** [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)

> **Note:** Building AMIs incurs AWS costs (EC2 instances, EBS storage, data transfer). Builds typically take 10-30 minutes depending on provisioning complexity.

## Basic AMI Template

```hcl
packer {
  required_plugins {
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1.3"
    }
  }
}

variable "region" {
  type    = string
  default = "us-west-2"
}

locals {
  timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}

source "amazon-ebs" "ubuntu" {
  region        = var.region
  instance_type = "t3.micro"

  source_ami_filter {
    filters = {
      name                = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["099720109477"] # Canonical
  }

  ssh_username = "ubuntu"
  ami_name     = "my-app-${local.timestamp}"

  tags = {
    Name      = "my-app"
    BuildDate = local.timestamp
  }
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get upgrade -y",
    ]
  }
}
```

## Common Source AMI Filters

### Ubuntu 22.04 LTS
```hcl
source_ami_filter {
  filters = {
    name                = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
    root-device-type    = "ebs"
    virtualization-type = "hvm"
  }
  most_recent = true
  owners      = ["099720109477"] # Canonical
}
```

### Amazon Linux 2023
```hcl
source_ami_filter {
  filters = {
    name                = "al2023-ami-*-x86_64"
    root-device-type    = "ebs"
    virtualization-type = "hvm"
  }
  most_recent = true
  owners      = ["amazon"]
}
```

## Multi-Region AMI

```hcl
source "amazon-ebs" "ubuntu" {
  region        = "us-west-2"
  instance_type = "t3.micro"

  source_ami_filter {
    filters = {
      name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
    }
    most_recent = true
    owners      = ["099720109477"]
  }

  ssh_username = "ubuntu"
  ami_name     = "my-app-${local.timestamp}"

  # Copy to additional regions
  ami_regions = ["us-east-1", "us-east-2", "eu-west-1"]
}
```

## Authentication

Packer uses AWS credential resolution:

1. Environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
2. AWS credentials file: `~/.aws/credentials`
3. IAM instance profile (when running on EC2)

```bash
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_REGION="us-west-2"

packer build .
```

## Build Commands

```bash
# Initialize plugins
packer init .

# Validate template
packer validate .

# Build AMI
packer build .

# Build with variables
packer build -var "region=us-east-1" .
```

## Common Issues

**SSH Timeout**
- Ensure security group allows SSH (port 22)
- Verify subnet has internet access

**AMI Already Exists**
- AMI names must be unique
- Use timestamp in name: `my-app-${local.timestamp}`

**Volume Size Too Small**
- Check source AMI's volume size
- Set `launch_block_device_mappings.volume_size` accordingly

## References

- [Amazon EBS Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs)
- [AWS AMI Documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html)


================================================
FILE: packer/builders/skills/azure-image-builder/SKILL.md
================================================
---
name: azure-image-builder
description: Build Azure managed images and Azure Compute Gallery images with Packer. Use when creating custom images for Azure VMs.
---

# Azure Image Builder

Build Azure managed images and Azure Compute Gallery images using Packer's `azure-arm` builder.

**Reference:** [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)

> **Note:** Building Azure images incurs costs (compute, storage, data transfer). Builds typically take 15-45 minutes depending on provisioning and OS.

## Basic Managed Image

```hcl
packer {
  required_plugins {
    azure = {
      source  = "github.com/hashicorp/azure"
      version = "~> 2.0"
    }
  }
}

variable "client_id" {
  type      = string
  sensitive = true
}

variable "client_secret" {
  type      = string
  sensitive = true
}

variable "subscription_id" {
  type = string
}

variable "tenant_id" {
  type = string
}

variable "resource_group" {
  type    = string
  default = "packer-images-rg"
}

locals {
  timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}

source "azure-arm" "ubuntu" {
  client_id       = var.client_id
  client_secret   = var.client_secret
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id

  managed_image_resource_group_name = var.resource_group
  managed_image_name                = "my-app-${local.timestamp}"

  os_type         = "Linux"
  image_publisher = "Canonical"
  image_offer     = "0001-com-ubuntu-server-jammy"
  image_sku       = "22_04-lts-gen2"

  location = "East US"
  vm_size  = "Standard_B2s"

  azure_tags = {
    Name      = "my-app"
    BuildDate = local.timestamp
  }
}

build {
  sources = ["source.azure-arm.ubuntu"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get upgrade -y",
    ]
  }
}
```

## Azure Compute Gallery

```hcl
source "azure-arm" "ubuntu" {
  client_id       = var.client_id
  client_secret   = var.client_secret
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id

  os_type         = "Linux"
  image_publisher = "Canonical"
  image_offer     = "0001-com-ubuntu-server-jammy"
  image_sku       = "22_04-lts-gen2"

  location = "East US"
  vm_size  = "Standard_B2s"

  shared_image_gallery_destination {
    resource_group       = "gallery-rg"
    gallery_name         = "myImageGallery"
    image_name           = "ubuntu-webapp"
    image_version        = "1.0.${formatdate("YYYYMMDD", timestamp())}"
    replication_regions  = ["East US", "West US 2"]
    storage_account_type = "Standard_LRS"
  }
}
```

## Authentication

### Service Principal
```bash
# Create service principal
az ad sp create-for-rbac \
  --name "packer-sp" \
  --role Contributor \
  --scopes /subscriptions/<subscription-id>

# Set environment variables
export ARM_CLIENT_ID="<client-id>"
export ARM_CLIENT_SECRET="<client-secret>"
export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_TENANT_ID="<tenant-id>"
```

### Managed Identity
```hcl
source "azure-arm" "ubuntu" {
  use_azure_cli_auth = true
  subscription_id    = var.subscription_id
  # ... rest of configuration
}
```

## Build Commands

```bash
# Set authentication
export ARM_CLIENT_ID="your-client-id"
export ARM_CLIENT_SECRET="your-client-secret"
export ARM_SUBSCRIPTION_ID="your-subscription-id"
export ARM_TENANT_ID="your-tenant-id"

# Initialize plugins
packer init .

# Validate template
packer validate .

# Build image
packer build .
```

## Common Issues

**Authentication Failed**
- Verify service principal credentials
- Ensure Contributor role on resource group
- Check subscription and tenant IDs

**Compute Gallery Version Exists**
- Image versions are immutable
- Use unique version numbers with date/build number
- Cannot overwrite existing versions

**Timeout During Provisioning**
- Check network connectivity from build VM
- Verify NSG rules allow required traffic
- Increase timeout if needed

## References

- [Azure ARM Builder](https://developer.hashicorp.com/packer/integrations/hashicorp/azure/latest/components/builder/arm)
- [Azure Compute Gallery](https://learn.microsoft.com/en-us/azure/virtual-machines/azure-compute-gallery)


================================================
FILE: packer/builders/skills/windows-builder/SKILL.md
================================================
---
name: windows-builder
description: Build Windows images with Packer using WinRM communicator and PowerShell provisioners. Use when creating Windows AMIs, Azure images, or VMware templates.
---

# Windows Builder

Platform-agnostic patterns for building Windows images with Packer.

**Reference:** [Windows Builders](https://developer.hashicorp.com/packer/guides/windows)

> **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.

## WinRM Communicator Setup

Windows requires WinRM for Packer communication.

### AWS Example

```hcl
source "amazon-ebs" "windows" {
  region        = "us-west-2"
  instance_type = "t3.medium"

  source_ami_filter {
    filters = {
      name = "Windows_Server-2022-English-Full-Base-*"
    }
    most_recent = true
    owners      = ["amazon"]
  }

  ami_name = "windows-server-2022-${local.timestamp}"

  communicator   = "winrm"
  winrm_username = "Administrator"
  winrm_use_ssl  = true
  winrm_insecure = true
  winrm_timeout  = "15m"

  user_data_file = "scripts/setup-winrm.ps1"
}
```

### WinRM Setup Script (scripts/setup-winrm.ps1)

```powershell
<powershell>
# Configure WinRM
winrm quickconfig -q
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'

# Configure firewall
netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow
netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow

# Restart WinRM
net stop winrm
net start winrm
</powershell>
```

### Azure Example

```hcl
source "azure-arm" "windows" {
  client_id       = var.client_id
  client_secret   = var.client_secret
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id

  managed_image_resource_group_name = "images-rg"
  managed_image_name                = "windows-${local.timestamp}"

  os_type         = "Windows"
  image_publisher = "MicrosoftWindowsServer"
  image_offer     = "WindowsServer"
  image_sku       = "2022-datacenter-g2"

  location = "East US"
  vm_size  = "Standard_D2s_v3"

  # Azure auto-configures WinRM
  communicator   = "winrm"
  winrm_use_ssl  = true
  winrm_insecure = true
  winrm_timeout  = "15m"
  winrm_username = "packer"
}
```

## PowerShell Provisioners

### Install Software

```hcl
build {
  sources = ["source.amazon-ebs.windows"]

  # Install Chocolatey
  provisioner "powershell" {
    inline = [
      "Set-ExecutionPolicy Bypass -Scope Process -Force",
      "iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))"
    ]
  }

  # Install applications
  provisioner "powershell" {
    inline = [
      "choco install -y googlechrome",
      "choco install -y 7zip",
    ]
  }

  # Install IIS
  provisioner "powershell" {
    inline = [
      "Install-WindowsFeature -Name Web-Server -IncludeManagementTools"
    ]
  }
}
```

### Windows Updates

```hcl
provisioner "powershell" {
  inline = [
    "Install-PackageProvider -Name NuGet -Force",
    "Install-Module -Name PSWindowsUpdate -Force",
    "Import-Module PSWindowsUpdate",
    "Get-WindowsUpdate -Install -AcceptAll -AutoReboot",
  ]
  timeout = "2h"
}

# Wait for reboots
provisioner "windows-restart" {
  restart_timeout = "30m"
}
```

## Cleanup

```hcl
provisioner "powershell" {
  inline = [
    "# Clear temp files",
    "Remove-Item -Path 'C:\\Windows\\Temp\\*' -Recurse -Force -ErrorAction SilentlyContinue",
    "# Clear Windows Update cache",
    "Stop-Service -Name wuauserv -Force",
    "Remove-Item -Path 'C:\\Windows\\SoftwareDistribution\\*' -Recurse -Force -ErrorAction SilentlyContinue",
    "Start-Service -Name wuauserv",
  ]
}
```

## Common Issues

**WinRM Timeout**
- Increase `winrm_timeout` to 15m or more
- Verify security group allows ports 5985/5986
- Check user data script completed successfully

**PowerShell Execution Policy**
```hcl
provisioner "powershell" {
  inline = [
    "Set-ExecutionPolicy Bypass -Scope Process -Force",
    "# Your commands here",
  ]
}
```

**Long Build Times**
- Windows Updates can take 1-2 hours
- Use pre-patched base images when available
- Set provisioner `timeout = "2h"`

## References

- [Packer Windows Builders](https://developer.hashicorp.com/packer/guides/windows)
- [WinRM Communicator](https://developer.hashicorp.com/packer/docs/communicators/winrm)
- [PowerShell Provisioner](https://developer.hashicorp.com/packer/docs/provisioners/powershell)


================================================
FILE: packer/hcp/.claude-plugin/plugin.json
================================================
{
  "name": "packer-hcp",
  "version": "1.0.0",
  "description": "HCP Packer registry integration for tracking and managing image metadata.",
  "author": {
    "name": "HashiCorp",
    "url": "https://github.com/hashicorp"
  },
  "homepage": "https://developer.hashicorp.com/hcp/docs/packer",
  "repository": "https://github.com/hashicorp/agent-skills",
  "license": "MPL-2.0",
  "keywords": ["packer", "hcp-packer", "registry", "metadata", "image-tracking"]
}


================================================
FILE: packer/hcp/skills/push-to-registry/SKILL.md
================================================
---
name: push-to-registry
description: 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.
---

# Push to HCP Packer Registry

Configure Packer templates to push build metadata to HCP Packer registry.

**Reference:** [HCP Packer Registry](https://developer.hashicorp.com/hcp/docs/packer)

> **Note:** HCP Packer is free for basic use. Builds push metadata only (not actual images), adding minimal overhead (<1 minute).

## Basic Registry Configuration

```hcl
packer {
  required_version = ">= 1.7.7"
}

variable "image_name" {
  type    = string
  default = "web-server"
}

locals {
  timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}

source "amazon-ebs" "ubuntu" {
  region        = "us-west-2"
  instance_type = "t3.micro"

  source_ami_filter {
    filters = {
      name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
    }
    most_recent = true
    owners      = ["099720109477"]
  }

  ssh_username = "ubuntu"
  ami_name     = "${var.image_name}-${local.timestamp}"
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  hcp_packer_registry {
    bucket_name = var.image_name
    description = "Ubuntu 22.04 base image for web servers"

    bucket_labels = {
      "os"   = "ubuntu"
      "team" = "platform"
    }

    build_labels = {
      "build-time" = local.timestamp
    }
  }

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get upgrade -y",
    ]
  }
}
```

## Authentication

Set environment variables before building:

```bash
export HCP_CLIENT_ID="your-service-principal-client-id"
export HCP_CLIENT_SECRET="your-service-principal-secret"
export HCP_ORGANIZATION_ID="your-org-id"
export HCP_PROJECT_ID="your-project-id"

packer build .
```

### Create HCP Service Principal

1. Navigate to HCP → Access Control (IAM)
2. Create Service Principal
3. Grant "Contributor" role on project
4. Generate client secret
5. Save client ID and secret

## Registry Configuration Options

### bucket_name (required)
The image identifier. Must stay consistent across builds!

```hcl
bucket_name = "web-server"  # Keep this constant
```

### bucket_labels (optional)
Metadata at bucket level. Updates with each build.

```hcl
bucket_labels = {
  "os"        = "ubuntu"
  "team"      = "platform"
  "component" = "web"
}
```

### build_labels (optional)
Metadata for each iteration. Immutable after build completes.

```hcl
build_labels = {
  "build-time" = local.timestamp
  "git-commit" = var.git_commit
}
```

## CI/CD Integration

### GitHub Actions

```yaml
name: Build and Push to HCP Packer

on:
  push:
    branches: [main]

env:
  HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }}
  HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }}
  HCP_ORGANIZATION_ID: ${{ secrets.HCP_ORGANIZATION_ID }}
  HCP_PROJECT_ID: ${{ secrets.HCP_PROJECT_ID }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-packer@main

      - name: Build and push
        run: |
          packer init .
          packer build \
            -var "git_commit=${{ github.sha }}" \
            .
```

## Querying in Terraform

```hcl
data "hcp_packer_artifact" "ubuntu" {
  bucket_name  = "web-server"
  channel_name = "production"
  platform     = "aws"
  region       = "us-west-2"
}

resource "aws_instance" "web" {
  ami           = data.hcp_packer_artifact.ubuntu.external_identifier
  instance_type = "t3.micro"

  tags = {
    PackerBucket = data.hcp_packer_artifact.ubuntu.bucket_name
  }
}
```

## Common Issues

**Authentication Failed**
- Verify HCP_CLIENT_ID and HCP_CLIENT_SECRET
- Ensure service principal has Contributor role
- Check organization and project IDs

**Bucket Name Mismatch**
- Keep `bucket_name` consistent across builds
- Don't include timestamps in bucket_name
- Creates new bucket if name changes

**Build Fails**
- Packer fails immediately if can't push metadata
- Prevents drift between artifacts and registry
- Check network connectivity to HCP API

## Best Practices

- **Consistent bucket names** - Never change for same image type
- **Meaningful labels** - Use for versions, teams, compliance
- **CI/CD automation** - Automate builds and registry pushes
- **Immutable build labels** - Put changing data (git SHA, date) in build_labels

## References

- [HCP Packer Documentation](https://developer.hashicorp.com/hcp/docs/packer)
- [hcp_packer_registry Block](https://developer.hashicorp.com/packer/docs/templates/hcl_templates/blocks/build/hcp_packer_registry)
- [HCP Terraform Provider](https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_artifact)


================================================
FILE: scripts/validate-structure.sh
================================================
#!/bin/bash
# Copyright IBM Corp. 2025, 2026
# SPDX-License-Identifier: MPL-2.0

#
# Validates the agent-skills repository structure
# Ensures all plugins and skills follow the expected format
#

set -e

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ERRORS=0

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log_error() {
    echo -e "${RED}ERROR:${NC} $1"
    ERRORS=$((ERRORS + 1))
}

log_success() {
    echo -e "${GREEN}OK:${NC} $1"
}

log_info() {
    echo -e "${YELLOW}INFO:${NC} $1"
}

# Check if jq is available
if ! command -v jq &> /dev/null; then
    echo "jq is required but not installed. Please install jq."
    exit 1
fi

echo "=========================================="
echo "Validating agent-skills repository structure"
echo "=========================================="
echo ""

# ---------------------------------------------
# 1. Validate marketplace.json
# ---------------------------------------------
echo "1. Checking marketplace.json..."

MARKETPLACE_FILE="$REPO_ROOT/.claude-plugin/marketplace.json"

if [[ ! -f "$MARKETPLACE_FILE" ]]; then
    log_error "marketplace.json not found at $MARKETPLACE_FILE"
else
    # Check if valid JSON
    if ! jq empty "$MARKETPLACE_FILE" 2>/dev/null; then
        log_error "marketplace.json is not valid JSON"
    else
        log_success "marketplace.json is valid JSON"

        # Check required fields
        NAME=$(jq -r '.name // empty' "$MARKETPLACE_FILE")
        if [[ -z "$NAME" ]]; then
            log_error "marketplace.json missing 'name' field"
        else
            log_success "marketplace.json has name: $NAME"
        fi

        OWNER=$(jq -r '.owner.name // empty' "$MARKETPLACE_FILE")
        if [[ -z "$OWNER" ]]; then
            log_error "marketplace.json missing 'owner.name' field"
        else
            log_success "marketplace.json has owner: $OWNER"
        fi

        # Check plugins array exists
        PLUGINS_COUNT=$(jq '.plugins | length' "$MARKETPLACE_FILE")
        if [[ "$PLUGINS_COUNT" -eq 0 ]]; then
            log_error "marketplace.json has no plugins defined"
        else
            log_success "marketplace.json has $PLUGINS_COUNT plugin(s) defined"
        fi
    fi
fi

echo ""

# ---------------------------------------------
# 2. Validate each plugin referenced in marketplace
# ---------------------------------------------
echo "2. Checking plugins referenced in marketplace.json..."

if [[ -f "$MARKETPLACE_FILE" ]]; then
    PLUGIN_SOURCES=$(jq -r '.plugins[].source' "$MARKETPLACE_FILE" 2>/dev/null)

    for SOURCE in $PLUGIN_SOURCES; do
        # Remove leading ./ if present
        SOURCE_PATH="${SOURCE#./}"
        PLUGIN_DIR="$REPO_ROOT/$SOURCE_PATH"
        PLUGIN_JSON="$PLUGIN_DIR/.claude-plugin/plugin.json"

        echo ""
        log_info "Checking plugin: $SOURCE_PATH"

        # Check plugin directory exists
        if [[ ! -d "$PLUGIN_DIR" ]]; then
            log_error "Plugin directory not found: $PLUGIN_DIR"
            continue
        else
            log_success "Plugin directory exists"
        fi

        # Check plugin.json exists
        if [[ ! -f "$PLUGIN_JSON" ]]; then
            log_error "plugin.json not found: $PLUGIN_JSON"
            continue
        else
            log_success "plugin.json exists"
        fi

        # Validate plugin.json
        if ! jq empty "$PLUGIN_JSON" 2>/dev/null; then
            log_error "plugin.json is not valid JSON: $PLUGIN_JSON"
            continue
        else
            log_success "plugin.json is valid JSON"
        fi

        # Check required fields in plugin.json
        PLUGIN_NAME=$(jq -r '.name // empty' "$PLUGIN_JSON")
        if [[ -z "$PLUGIN_NAME" ]]; then
            log_error "plugin.json missing 'name' field"
        else
            log_success "plugin.json has name: $PLUGIN_NAME"
        fi

        PLUGIN_VERSION=$(jq -r '.version // empty' "$PLUGIN_JSON")
        if [[ -z "$PLUGIN_VERSION" ]]; then
            log_error "plugin.json missing 'version' field"
        else
            log_success "plugin.json has version: $PLUGIN_VERSION"
        fi

        PLUGIN_DESC=$(jq -r '.description // empty' "$PLUGIN_JSON")
        if [[ -z "$PLUGIN_DESC" ]]; then
            log_error "plugin.json missing 'description' field"
        else
            log_success "plugin.json has description"
        fi

        # Check skills directory exists
        SKILLS_DIR="$PLUGIN_DIR/skills"
        if [[ ! -d "$SKILLS_DIR" ]]; then
            log_error "skills/ directory not found in plugin: $PLUGIN_DIR"
            continue
        else
            log_success "skills/ directory exists"
        fi

        # Validate each skill
        SKILL_COUNT=0
        for SKILL_DIR in "$SKILLS_DIR"/*/; do
            if [[ -d "$SKILL_DIR" ]]; then
                SKILL_NAME=$(basename "$SKILL_DIR")
                SKILL_MD="$SKILL_DIR/SKILL.md"

                if [[ ! -f "$SKILL_MD" ]]; then
                    log_error "SKILL.md not found for skill: $SKILL_NAME"
                else
                    # Check SKILL.md has frontmatter
                    if ! head -1 "$SKILL_MD" | grep -q "^---"; then
                        log_error "SKILL.md missing frontmatter (---) for skill: $SKILL_NAME"
                    else
                        # Extract and validate frontmatter
                        FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$SKILL_MD" | sed '1d;$d')

                        # Check for name field
                        if ! echo "$FRONTMATTER" | grep -q "^name:"; then
                            log_error "SKILL.md missing 'name' in frontmatter for skill: $SKILL_NAME"
                        fi

                        # Check for description field
                        if ! echo "$FRONTMATTER" | grep -q "^description:"; then
                            log_error "SKILL.md missing 'description' in frontmatter for skill: $SKILL_NAME"
                        fi

                        if echo "$FRONTMATTER" | grep -q "^name:" && echo "$FRONTMATTER" | grep -q "^description:"; then
                            log_success "SKILL.md valid for skill: $SKILL_NAME"
                        fi
                    fi
                fi
                SKILL_COUNT=$((SKILL_COUNT + 1))
            fi
        done

        if [[ "$SKILL_COUNT" -eq 0 ]]; then
            log_error "No skills found in $SKILLS_DIR"
        else
            log_success "Found $SKILL_COUNT skill(s) in plugin"
        fi
    done
fi

echo ""

# ---------------------------------------------
# 3. Check for orphaned plugins (not in marketplace)
# ---------------------------------------------
echo "3. Checking for orphaned plugins..."

# Find all plugin.json files
FOUND_PLUGINS=$(find "$REPO_ROOT" -path "*/.claude-plugin/plugin.json" -not -path "$REPO_ROOT/.claude-plugin/*" 2>/dev/null)

for PLUGIN_JSON in $FOUND_PLUGINS; do
    PLUGIN_DIR=$(dirname "$(dirname "$PLUGIN_JSON")")
    RELATIVE_PATH="${PLUGIN_DIR#$REPO_ROOT/}"

    # Check if this plugin is referenced in marketplace.json
    if [[ -f "$MARKETPLACE_FILE" ]]; then
        if ! jq -r '.plugins[].source' "$MARKETPLACE_FILE" | grep -q "$RELATIVE_PATH"; then
            log_error "Orphaned plugin not in marketplace.json: $RELATIVE_PATH"
        fi
    fi
done

log_success "Orphan check complete"

echo ""

# ---------------------------------------------
# 4. Validate product folder structure
# ---------------------------------------------
echo "4. Checking product folder structure..."

# Get all top-level directories that could be products (excluding hidden and special)
for DIR in "$REPO_ROOT"/*/; do
    DIR_NAME=$(basename "$DIR")

    # Skip special directories
    if [[ "$DIR_NAME" == "scripts" ]] || [[ "$DIR_NAME" == "node_modules" ]] || [[ "$DIR_NAME" =~ ^\. ]]; then
        continue
    fi

    # Check if this is a product folder (contains plugin subdirectories)
    HAS_PLUGINS=false
    for SUBDIR in "$DIR"/*/; do
        if [[ -d "$SUBDIR/.claude-plugin" ]]; then
            HAS_PLUGINS=true
            break
        fi
    done

    if [[ "$HAS_PLUGINS" == true ]]; then
        log_success "Valid product folder: $DIR_NAME"
    fi
done

echo ""
echo "=========================================="
echo "Validation complete"
echo "=========================================="

if [[ "$ERRORS" -gt 0 ]]; then
    echo -e "${RED}Found $ERRORS error(s)${NC}"
    exit 1
else
    echo -e "${GREEN}All checks passed!${NC}"
    exit 0
fi


================================================
FILE: terraform/README.md
================================================
# Terraform Skills

Agent skills for Terraform infrastructure-as-code development.

## Plugins

### terraform-code-generation

Skills for generating and validating Terraform HCL code.

| Skill | Description |
|-------|-------------|
| terraform-style-guide  | Generate Terraform HCL code following HashiCorp style conventions |
| terraform-test         | Writing and running `.tftest.hcl` test files |
| azure-verified-modules | Azure Verified Modules (AVM) requirements and certification |
| terraform-search-import | Discover existing resources with Terraform Search and bulk import |

### terraform-module-generation

Skills for creating and refactoring Terraform modules.

| Skill | Description |
|-------|-------------|
| refactor-module  | Transform monolithic configs into reusable modules |
| terraform-stacks | Multi-region/environment orchestration with Terraform Stacks |

### terraform-provider-development

Skills for developing Terraform providers.

| Skill | Description |
|-------|-------------|
| new-terraform-provider | Scaffold a new Terraform provider |
| run-acceptance-tests   | Run and debug provider acceptance tests |
| provider-actions       | Implement provider actions (lifecycle operations) |
| provider-resources     | Implement resources and data sources |
| provider-test-patterns | Acceptance test patterns for terraform-plugin-testing |

## Installation

### Claude Code Plugin

```bash
claude plugin marketplace add hashicorp/agent-skills

claude plugin install terraform-code-generation@hashicorp
claude plugin install terraform-module-generation@hashicorp
claude plugin install terraform-provider-development@hashicorp
```

### Individual Skills

```bash
# Code generation
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-test
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/azure-verified-modules
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-search-import

# Module generation
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/refactor-module
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/terraform-stacks

# Provider development
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/new-terraform-provider
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/run-acceptance-tests
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-actions
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-resources
npx skills add hashicorp/agent-skills/terraform/provider-development/skills/provider-test-patterns
```

## MCP Server

Relevant Terraform plugins include the `terraform-mcp-server` which provides access to Terraform Cloud/Enterprise APIs. Set the following environment variables:

```bash
export TFE_TOKEN="your-terraform-cloud-token"
export TFE_ADDRESS="https://app.terraform.io"  # or your TFE instance
```

## Structure

```
terraform/
├── code-generation/
│   ├── .claude-plugin/plugin.json
│   └── skills/
│       ├── terraform-style-guide/
│       ├── terraform-test/
│       ├── azure-verified-modules/
│       └── terraform-search-import/
├── module-generation/
│   ├── .claude-plugin/plugin.json
│   └── skills/
│       ├── terraform-stacks/
│       └── refactor-module/
└── provider-development/
    ├── .claude-plugin/plugin.json
    └── skills/
        ├── new-terraform-provider/
        ├── provider-actions/
        ├── provider-resources/
        ├── run-acceptance-tests/
        └── provider-test-patterns/
```

## References

- [Terraform Documentation](https://developer.hashicorp.com/terraform)
- [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework)
- [Terraform MCP Server](https://github.com/hashicorp/terraform-mcp-server)


================================================
FILE: terraform/code-generation/.claude-plugin/plugin.json
================================================
{
  "name": "terraform-code-generation",
  "version": "1.0.0",
  "description": "Terraform code generation skills for Claude Code, including HCL generation, style guides, and testing.",
  "author": {
    "name": "HashiCorp",
    "url": "https://github.com/hashicorp"
  },
  "homepage": "https://developer.hashicorp.com/terraform/language",
  "repository": "https://github.com/hashicorp/agent-skills",
  "license": "MPL-2.0",
  "keywords": ["terraform", "hcl", "infrastructure", "iac", "testing", "style-guide", "search", "import", "discovery"],
  "mcpServers": {
    "terraform": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e", "TFE_TOKEN",
        "-e", "TFE_ADDRESS",
        "hashicorp/terraform-mcp-server"
      ],
      "env": {
        "TFE_TOKEN": "${TFE_TOKEN}",
        "TFE_ADDRESS": "${TFE_ADDRESS}"
      }
    }
  }
}


================================================
FILE: terraform/code-generation/skills/azure-verified-modules/SKILL.md
================================================
---
name: azure-verified-modules
description: 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.
---

# Azure Verified Modules (AVM) Requirements

This guide covers the mandatory requirements for Azure Verified Modules certification. These requirements ensure consistency, quality, and maintainability across Azure Terraform modules.

**References:**
- [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/)
- [AVM Terraform Requirements](https://azure.github.io/Azure-Verified-Modules/specs/terraform/)

## Table of Contents

- [Module Cross-Referencing](#module-cross-referencing)
- [Azure Provider Requirements](#azure-provider-requirements)
- [Code Style Standards](#code-style-standards)
- [Variable Requirements](#variable-requirements)
- [Output Requirements](#output-requirements)
- [Local Values Standards](#local-values-standards)
- [Terraform Configuration Requirements](#terraform-configuration-requirements)
- [Testing Requirements](#testing-requirements)
- [Documentation Requirements](#documentation-requirements)
- [Breaking Changes & Feature Management](#breaking-changes--feature-management)
- [Contribution Standards](#contribution-standards)
- [Compliance Checklist](#compliance-checklist)

---

## Module Cross-Referencing

**Severity:** MUST | **Requirement:** TFFR1

When building Resource or Pattern modules, module owners **MAY** cross-reference other modules. However:

- Modules **MUST** be referenced using HashiCorp Terraform registry reference to a pinned version
  - Example: `source = "Azure/xxx/azurerm"` with `version = "1.2.3"`
- Modules **MUST NOT** use git references (e.g., `git::https://xxx.yyy/xxx.git` or `github.com/xxx/yyy`)
- Modules **MUST NOT** contain references to non-AVM modules

---

## Azure Provider Requirements

**Severity:** MUST | **Requirement:** TFFR3

Authors **MUST** only use the following Azure providers:

| Provider | Min Version | Max Version |
|----------|-------------|-------------|
| azapi    | >= 2.0      | < 3.0       |
| azurerm  | >= 4.0      | < 5.0       |

**Requirements:**

- Authors **MAY** select either Azurerm, Azapi, or both providers
- **MUST** use `required_providers` block to enforce provider versions
- **SHOULD** use pessimistic version constraint operator (`~>`)

**Example:**

```hcl
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
    azapi = {
      source  = "Azure/azapi"
      version = "~> 2.0"
    }
  }
}
```

---

## Code Style Standards

### Lower snake_casing

**Severity:** MUST | **Requirement:** TFNFR4

**MUST** use lower snake_casing for:

- Locals
- Variables
- Outputs
- Resources (symbolic names)
- Modules (symbolic names)

Example: `snake_casing_example`

### Resource & Data Source Ordering

**Severity:** SHOULD | **Requirement:** TFNFR6

- Resources that are depended on **SHOULD** come first
- Resources with dependencies **SHOULD** be defined close to each other

### Count & for_each Usage

**Severity:** MUST | **Requirement:** TFNFR7

- Use `count` for conditional resource creation
- **MUST** use `map(xxx)` or `set(xxx)` as resource's `for_each` collection
- The map's key or set's element **MUST** be static literals

**Example:**

```hcl
resource "azurerm_subnet" "pair" {
  for_each             = var.subnet_map  # map(string)
  name                 = "${each.value}-pair"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]
}
```

### Resource & Data Block Internal Ordering

**Severity:** SHOULD | **Requirement:** TFNFR8

**Order within resource/data blocks:**

1. **Meta-arguments (top)**:
   - `provider`
   - `count`
   - `for_each`

2. **Arguments/blocks (middle, alphabetical)**:
   - Required arguments
   - Optional arguments
   - Required nested blocks
   - Optional nested blocks

3. **Meta-arguments (bottom)**:
   - `depends_on`
   - `lifecycle` (with sub-order: `create_before_destroy`, `ignore_changes`, `prevent_destroy`)

Separate sections with blank lines.

### Module Block Ordering

**Severity:** SHOULD | **Requirement:** TFNFR9

**Order within module blocks:**

1. **Top meta-arguments**:
   - `source`
   - `version`
   - `count`
   - `for_each`

2. **Arguments (alphabetical)**:
   - Required arguments
   - Optional arguments

3. **Bottom meta-arguments**:
   - `depends_on`
   - `providers`

### Lifecycle ignore_changes Syntax

**Severity:** MUST | **Requirement:** TFNFR10

The `ignore_changes` attribute **MUST NOT** be enclosed in double quotes.

**Good:**

```hcl
lifecycle {
  ignore_changes = [tags]
}
```

**Bad:**

```hcl
lifecycle {
  ignore_changes = ["tags"]
}
```

### Null Comparison for Conditional Creation

**Severity:** SHOULD | **Requirement:** TFNFR11

For parameters requiring conditional resource creation, wrap with `object` type to avoid "known after apply" issues during plan stage.

**Recommended:**

```hcl
variable "security_group" {
  type = object({
    id = string
  })
  default = null
}
```

### Dynamic Blocks for Optional Nested Objects

**Severity:** MUST | **Requirement:** TFNFR12

Nested blocks under conditions **MUST** use this pattern:

```hcl
dynamic "identity" {
  for_each = <condition> ? [<some_item>] : []

  content {
    # block content
  }
}
```

### Default Values with coalesce/try

**Severity:** SHOULD | **Requirement:** TFNFR13

**Good:**

```hcl
coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")
```

**Bad:**

```hcl
var.new_network_security_group_name == null ? "${var.subnet_name}-nsg" : var.new_network_security_group_name
```

### Provider Declarations in Modules

**Severity:** MUST | **Requirement:** TFNFR27

- `provider` **MUST NOT** be declared in modules (except for `configuration_aliases`)
- `provider` blocks in modules **MUST** only use `alias`
- Provider configurations **SHOULD** be passed in by module users

---

## Variable Requirements

### Not Allowed Variables

**Severity:** MUST | **Requirement:** TFNFR14

Module owners **MUST NOT** add variables like `enabled` or `module_depends_on` to control entire module operation. Boolean feature toggles for specific resources are acceptable.

### Variable Definition Order

**Severity:** SHOULD | **Requirement:** TFNFR15

Variables **SHOULD** follow this order:

1. All required fields (alphabetical)
2. All optional fields (alphabetical)

### Variable Naming Rules

**Severity:** SHOULD | **Requirement:** TFNFR16

- Follow [HashiCorp's naming rules](https://www.terraform.io/docs/extend/best-practices/naming.html)
- Feature switches **SHOULD** use positive statements: `xxx_enabled` instead of `xxx_disabled`

### Variables with Descriptions

**Severity:** SHOULD | **Requirement:** TFNFR17

- `description` **SHOULD** precisely describe the parameter's purpose and expected data type
- Target audience is module users, not developers
- For `object` types, use HEREDOC format

### Variables with Types

**Severity:** MUST | **Requirement:** TFNFR18

- `type` **MUST** be defined for every variable
- `type` **SHOULD** be as precise as possible
- `any` **MAY** only be used with adequate reasons
- Use `bool` instead of `string`/`number` for true/false values
- Use concrete `object` instead of `map(any)`

### Sensitive Data Variables

**Severity:** SHOULD | **Requirement:** TFNFR19

If a variable's type is `object` and contains sensitive fields, the entire variable **SHOULD** be `sensitive = true`, or extract sensitive fields into separate variables.

### Non-Nullable Defaults for Collections

**Severity:** SHOULD | **Requirement:** TFNFR20

Nullable **SHOULD** be set to `false` for collection values (sets, maps, lists) when using them in loops. For scalar values, null may have semantic meaning.

### Discourage Nullability by Default

**Severity:** MUST | **Requirement:** TFNFR21

`nullable = true` **MUST** be avoided unless there's a specific semantic need for null values.

### Avoid sensitive = false

**Severity:** MUST | **Requirement:** TFNFR22

`sensitive = false` **MUST** be avoided (this is the default).

### Sensitive Default Value Conditions

**Severity:** MUST | **Requirement:** TFNFR23

A default value **MUST NOT** be set for sensitive inputs (e.g., default passwords).

### Handling Deprecated Variables

**Severity:** MUST | **Requirement:** TFNFR24

- Move deprecated variables to `deprecated_variables.tf`
- Annotate with `DEPRECATED` at the beginning of description
- Declare the replacement's name
- Clean up during major version releases

---

## Output Requirements

### Additional Terraform Outputs

**Severity:** SHOULD | **Requirement:** TFFR2

Authors **SHOULD NOT** output entire resource objects as these may contain sensitive data and the schema can change with API or provider versions.

**Best Practices:**

- Output *computed* attributes of resources as discrete outputs (anti-corruption layer pattern)
- **SHOULD NOT** output values that are already inputs (except `name`)
- Use `sensitive = true` for sensitive attributes
- For resources deployed with `for_each`, output computed attributes in a map structure

**Examples:**

```hcl
# Single resource computed attribute
output "foo" {
  description = "MyResource foo attribute"
  value       = azurerm_resource_myresource.foo
}

# for_each resources
output "childresource_foos" {
  description = "MyResource children's foo attributes"
  value = {
    for key, value in azurerm_resource_mychildresource : key => value.foo
  }
}

# Sensitive output
output "bar" {
  description = "MyResource bar attribute"
  value       = azurerm_resource_myresource.bar
  sensitive   = true
}
```

### Sensitive Data Outputs

**Severity:** MUST | **Requirement:** TFNFR29

Outputs containing confidential data **MUST** be declared with `sensitive = true`.

### Handling Deprecated Outputs

**Severity:** MUST | **Requirement:** TFNFR30

- Move deprecated outputs to `deprecated_outputs.tf`
- Define new outputs in `outputs.tf`
- Clean up during major version releases

---

## Local Values Standards

### locals.tf Organization

**Severity:** MAY | **Requirement:** TFNFR31

- `locals.tf` **SHOULD** only contain `locals` blocks
- **MAY** declare `locals` blocks next to resources for advanced scenarios

### Alphabetical Local Arrangement

**Severity:** MUST | **Requirement:** TFNFR32

Expressions in `locals` blocks **MUST** be arranged alphabetically.

### Precise Local Types

**Severity:** SHOULD | **Requirement:** TFNFR33

Use precise types (e.g., `number` for age, not `string`).

---

## Terraform Configuration Requirements

### Terraform Version Requirements

**Severity:** MUST | **Requirement:** TFNFR25

**`terraform.tf` requirements:**

- **MUST** contain only one `terraform` block
- First line **MUST** define `required_version`
- **MUST** include minimum version constraint
- **MUST** include maximum major version constraint
- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format

**Example:**

```hcl
terraform {
  required_version = "~> 1.6"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}
```

### Providers in required_providers

**Severity:** MUST | **Requirement:** TFNFR26

- `terraform` block **MUST** contain `required_providers` block
- Each provider **MUST** specify `source` and `version`
- Providers **SHOULD** be sorted alphabetically
- Only include directly required providers
- `source` **MUST** be in format `namespace/name`
- `version` **MUST** include minimum and maximum major version constraints
- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format

---

## Testing Requirements

### Test Tooling

**Severity:** MUST | **Requirement:** TFNFR5

**Required testing tools for AVM:**

- Terraform (`terraform validate/fmt/test`)
- terrafmt
- Checkov
- tflint (with azurerm ruleset)
- Go (optional for custom tests)

### Test Provider Configuration

**Severity:** SHOULD | **Requirement:** TFNFR36

For robust testing, `prevent_deletion_if_contains_resources` **SHOULD** be explicitly set to `false` in test provider configurations.

---

## Documentation Requirements

### Module Documentation Generation

**Severity:** MUST | **Requirement:** TFNFR2

- Documentation **MUST** be automatically generated via [Terraform Docs](https://github.com/terraform-docs/terraform-docs)
- A `.terraform-docs.yml` file **MUST** be present in the module root

---

## Breaking Changes & Feature Management

### Using Feature Toggles

**Severity:** MUST | **Requirement:** TFNFR34

New resources added in minor/patch versions **MUST** have a toggle variable to avoid creation by default:

```hcl
variable "create_route_table" {
  type     = bool
  default  = false
  nullable = false
}

resource "azurerm_route_table" "this" {
  count = var.create_route_table ? 1 : 0
  # ...
}
```

### Reviewing Potential Breaking Changes

**Severity:** MUST | **Requirement:** TFNFR35

**Breaking changes requiring caution:**

**Resource blocks:**

1. Adding new resource without conditional creation
2. Adding arguments with non-default values
3. Adding nested blocks without `dynamic`
4. Renaming resources without `moved` blocks
5. Changing `count` to `for_each` or vice versa

**Variable/Output blocks:**

1. Deleting/renaming variables
2. Changing variable `type`
3. Changing variable `default` values
4. Changing `nullable` to false
5. Changing `sensitive` from false to true
6. Adding variables without `default`
7. Deleting outputs
8. Changing output `value`
9. Changing output `sensitive` value

---

## Contribution Standards

### GitHub Repository Branch Protection

**Severity:** MUST | **Requirement:** TFNFR3

Module owners **MUST** set branch protection policies on the default branch (typically `main`):

1. Require Pull Request before merging
2. Require approval of most recent reviewable push
3. Dismiss stale PR approvals when new commits are pushed
4. Require linear history
5. Prevent force pushes
6. Not allow deletions
7. Require CODEOWNERS review
8. No bypassing settings allowed
9. Enforce for administrators

---

## Compliance Checklist

Use this checklist when developing or reviewing Azure Verified Modules:

### Module Structure
- [ ] Module cross-references use registry sources with pinned versions
- [ ] Azure providers (azurerm/azapi) versions meet AVM requirements
- [ ] `.terraform-docs.yml` present in module root
- [ ] CODEOWNERS file present

### Code Style
- [ ] All names use lower snake_casing
- [ ] Resources ordered with dependencies first
- [ ] `for_each` uses `map()` or `set()` with static keys
- [ ] Resource/data/module blocks follow proper internal ordering
- [ ] `ignore_changes` not quoted
- [ ] Dynamic blocks used for conditional nested objects
- [ ] `coalesce()` or `try()` used for default values

### Variables
- [ ] No `enabled` or `module_depends_on` variables
- [ ] Variables ordered: required (alphabetical) then optional (alphabetical)
- [ ] All variables have precise types (avoid `any`)
- [ ] All variables have descriptions
- [ ] Collections have `nullable = false`
- [ ] No `sensitive = false` declarations
- [ ] No default values for sensitive inputs
- [ ] Deprecated variables moved to `deprecated_variables.tf`

### Outputs
- [ ] Outputs use anti-corruption layer pattern (discrete attributes)
- [ ] Sensitive outputs marked `sensitive = true`
- [ ] Deprecated outputs moved to `deprecated_outputs.tf`

### Terraform Configuration
- [ ] `terraform.tf` has version constraints (`~>` format)
- [ ] `required_providers` block present with all providers
- [ ] No `provider` declarations in module (except aliases)
- [ ] Locals arranged alphabetically

### Testing & Quality
- [ ] Required testing tools configured
- [ ] New resources have feature toggles
- [ ] Breaking changes reviewed and documented

---

## Summary Statistics

- **Functional Requirements:** 3
- **Non-Functional Requirements:** 34
- **Total Requirements:** 37

### By Severity
- **MUST:** 21 requirements
- **SHOULD:** 14 requirements
- **MAY:** 2 requirements

---

*Based on: Azure Verified Modules - Terraform Requirements*


================================================
FILE: terraform/code-generation/skills/terraform-search-import/SKILL.md
================================================
---
name: terraform-search-import
description: 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.
metadata:
  copyright: Copyright IBM Corp. 2026
  version: "0.1.0"
compatibility: Requires Terraform >= 1.14 and providers with list resource support (always use latest provider version)
---

# Terraform Search and Bulk Import

Discover existing cloud resources using declarative queries and generate configuration for bulk import into Terraform state.

**References:**
- [Terraform Search - list block](https://developer.hashicorp.com/terraform/language/block/tfquery/list)
- [Bulk Import](https://developer.hashicorp.com/terraform/language/import/bulk)

## When to Use

- Bringing unmanaged resources under Terraform control
- Auditing existing cloud infrastructure
- Migrating from manual provisioning to IaC
- Discovering resources across multiple regions/accounts

## IMPORTANT: Check Provider Support First

**BEFORE starting, you MUST verify the target resource type is supported:**

```bash
# Check what list resources are available
./scripts/list_resources.sh aws      # Specific provider
./scripts/list_resources.sh          # All configured providers
```

## Decision Tree

1. **Identify target resource type** (e.g., aws_s3_bucket, aws_instance)
2. **Check if supported**: Run `./scripts/list_resources.sh <provider>`
3. **Choose workflow**:
   - ** If supported**: Check for terraform version available.
   - ** If terraform version is above 1.14.0** Use Terraform Search workflow (below)
   - ** If not supported or terraform version is below 1.14.0 **: Use Manual Discovery workflow (see [references/MANUAL-IMPORT.md](references/MANUAL-IMPORT.md))
   
   **Note**: The list of supported resources is rapidly expanding. Always verify current support before using manual import.

## Prerequisites

Before writing queries, verify the provider supports list resources for your target resource type.

### Discover Available List Resources

Run the helper script to extract supported list resources from your provider:

```bash
# From a directory with provider configuration (runs terraform init if needed)
./scripts/list_resources.sh aws      # Specific provider
./scripts/list_resources.sh          # All configured providers
```

Or manually query the provider schema:

```bash
terraform providers schema -json | jq '.provider_schemas | to_entries | map({key: (.key | split("/")[-1]), value: (.value.list_resource_schemas // {} | keys)})'
```

Terraform Search requires an initialized working directory. Ensure you have a configuration with the required provider before running queries:

```hcl
# terraform.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}
```

Run `terraform init` to download the provider, then proceed with queries.

## Terraform Search Workflow (Supported Resources Only)

1. Create `.tfquery.hcl` files with `list` blocks defining search queries
2. Run `terraform query` to discover matching resources
3. Generate configuration with `-generate-config-out=<file>`
4. Review and refine generated `resource` and `import` blocks
5. Run `terraform plan` and `terraform apply` to import

## Query File Structure

Query files use `.tfquery.hcl` extension and support:
- `provider` blocks for authentication
- `list` blocks for resource discovery
- `variable` and `locals` blocks for parameterization

```hcl
# discovery.tfquery.hcl
provider "aws" {
  region = "us-west-2"
}

list "aws_instance" "all" {
  provider = aws
}
```

## List Block Syntax

```hcl
list "<list_type>" "<symbolic_name>" {
  provider = <provider_reference>  # Required

  # Optional: filter configuration (provider-specific)
  # 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>"'`

  config {
    filter {
      name   = "<filter_name>"
      values = ["<value1>", "<value2>"]
    }
    region = "<region>"  # AWS-specific
  }
  # Optional: limit results
  limit = 100
}
```

## Supported List Resources

Provider support for list resources varies by version. **Always check what's available for your specific provider version using the discovery script.**

## Query Examples

### Basic Discovery

```hcl
# Find all EC2 instances in configured region
list "aws_instance" "all" {
  provider = aws
}
```

### Filtered Discovery

```hcl
# Find instances by tag
list "aws_instance" "production" {
  provider = aws
  
  config {
    filter {
      name   = "tag:Environment"
      values = ["production"]
    }
  }
}

# Find instances by type
list "aws_instance" "large" {
  provider = aws
  
  config {
    filter {
      name   = "instance-type"
      values = ["t3.large", "t3.xlarge"]
    }
  }
}
```

### Multi-Region Discovery

```hcl
provider "aws" {
  region = "us-west-2"
}

locals {
  regions = ["us-west-2", "us-east-1", "eu-west-1"]
}

list "aws_instance" "all_regions" {
  for_each = toset(local.regions)
  provider = aws
  
  config {
    region = each.value
  }
}
```

### Parameterized Queries

```hcl
variable "target_environment" {
  type    = string
  default = "staging"
}

list "aws_instance" "by_env" {
  provider = aws
  
  config {
    filter {
      name   = "tag:Environment"
      values = [var.target_environment]
    }
  }
}
```

## Running Queries

```bash
# Execute queries and display results
terraform query

# Generate configuration file
terraform query -generate-config-out=imported.tf

# Pass variables
terraform query -var='target_environment=production'
```

## Query Output Format

```
list.aws_instance.all   account_id=123456789012,id=i-0abc123,region=us-west-2   web-server
```

Columns: `<query_address>   <identity_attributes>   <name_tag>`

## Generated Configuration

The `-generate-config-out` flag creates:

```hcl
# __generated__ by Terraform
resource "aws_instance" "all_0" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  # ... all attributes
}

import {
  to       = aws_instance.all_0
  provider = aws
  identity = {
    account_id = "123456789012"
    id         = "i-0abc123"
    region     = "us-west-2"
  }
}
```

## Post-Generation Cleanup

Generated configuration includes all attributes. Clean up by:

1. Remove computed/read-only attributes
2. Replace hardcoded values with variables
3. Add proper resource naming
4. Organize into appropriate files

```hcl
# Before: generated
resource "aws_instance" "all_0" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t2.micro"
  arn                    = "arn:aws:ec2:..."  # Remove - computed
  id                     = "i-0abc123"        # Remove - computed
  # ... many more attributes
}

# After: cleaned
resource "aws_instance" "web_server" {
  ami           = var.ami_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id
  
  tags = {
    Name        = "web-server"
    Environment = var.environment
  }
}
```

## Import by Identity

Generated imports use identity-based import (Terraform 1.12+):

```hcl
import {
  to       = aws_instance.web
  provider = aws
  identity = {
    account_id = "123456789012"
    id         = "i-0abc123"
    region     = "us-west-2"
  }
}
```

## Best Practices

### Query Design
- Start broad, then add filters to narrow results
- Use `limit` to prevent overwhelming output
- Test queries before generating configuration

### Configuration Management
- Review all generated code before applying
- Remove unnecessary default values
- Use consistent naming conventions
- Add proper variable abstraction

## Troubleshooting

| Issue | Solution |
|-------|----------|
| "No list resources found" | Check provider version supports list resources |
| Query returns empty | Verify region and filter values |
| Generated config has errors | Remove computed attributes, fix deprecated arguments |
| Import fails | Ensure resource not already in state |

## Complete Example

```hcl
# main.tf - Initialize provider
terraform {
  required_version = ">= 1.14"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"  # Always use latest version
    }
  }
}

# discovery.tfquery.hcl - Define queries
provider "aws" {
  region = "us-west-2"
}

list "aws_instance" "team_instances" {
  provider = aws
  
  config {
    filter {
      name   = "tag:Owner"
      values = ["platform"]
    }
    filter {
      name   = "instance-state-name"
      values = ["running"]
    }
  }
  
  limit = 50
}
```

```bash
# Execute workflow
terraform init
terraform query
terraform query -generate-config-out=generated.tf
# Review and clean generated.tf
terraform plan
terraform apply
```


================================================
FILE: terraform/code-generation/skills/terraform-search-import/references/MANUAL-IMPORT.md
================================================
# Manual Terraform Import Reference

Use this workflow when your target resource type isn't supported by Terraform Search.

## 1. Discover Resources Using Provider CLI

AWS CLI examples:

```bash
# RDS instances (not yet supported by Terraform Search)
aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'

# DynamoDB tables (not yet supported by Terraform Search)
aws dynamodb list-tables --query 'TableNames[]'

# API Gateway REST APIs (not yet supported by Terraform Search)
aws apigateway get-rest-apis --query 'items[].id'

# SNS topics (not yet supported by Terraform Search)
aws sns list-topics --query 'Topics[].TopicArn'
```

## 2. Create Resource Blocks Manually

```hcl
# Example for RDS instance
resource "aws_db_instance" "existing_db" {
  identifier = "my-existing-db"
  # Add other required attributes
}

# Example for DynamoDB table
resource "aws_dynamodb_table" "existing_table" {
  name = "my-existing-table"
  # Add other required attributes
}

# Example for SNS topic
resource "aws_sns_topic" "existing_topic" {
  name = "my-existing-topic"
}
```

## 3. Create Import Blocks (Config-Driven Import)

```hcl
# Example for RDS instance
resource "aws_db_instance" "existing_db" {
  identifier = "my-existing-db"
  # Add other required attributes
}

import {
  to = aws_db_instance.existing_db
  id = "my-existing-db"
}

# Example for DynamoDB table
resource "aws_dynamodb_table" "existing_table" {
  name = "my-existing-table"
  # Add other required attributes
}

import {
  to = aws_dynamodb_table.existing_table
  id = "my-existing-table"
}
```

## 4. Run Import Plan

```bash
# Plan the import to see what will happen
terraform plan

# Apply to import the resources
terraform apply
```

## Bulk Import Script Example

For multiple resources of the same type:

```bash
#!/bin/bash
# bulk-import-dynamodb.sh

# Get all table names
tables=$(aws dynamodb list-tables --query 'TableNames[]' --output text)

# Generate import configuration
cat > dynamodb-imports.tf << 'EOF'
# DynamoDB Table Resources and Imports
EOF

for table in $tables; do
  # Create resource and import blocks
  cat >> dynamodb-imports.tf << EOF
resource "aws_dynamodb_table" "table_${table//[-.]/_}" {
  name = "$table"
}

import {
  to = aws_dynamodb_table.table_${table//[-.]/_}
  id = "$table"
}

EOF
done

echo "Generated dynamodb-imports.tf with import blocks"
echo "Run 'terraform plan' to review, then 'terraform apply' to import"
```


================================================
FILE: terraform/code-generation/skills/terraform-search-import/scripts/list_resources.sh
================================================
#!/bin/bash
# Copyright IBM Corp. 2025, 2026
# SPDX-License-Identifier: MPL-2.0

# Extract list resources supported by Terraform providers
# Usage: ./list_resources.sh [provider_name]
# Requires: terraform, jq
# Note: Run from an initialized Terraform directory (terraform init)

set -e

PROVIDER=$1

# Ensure terraform is initialized
if [ ! -d ".terraform" ]; then
    echo "Initializing Terraform..." >&2
    terraform init -upgrade > /dev/null 2>&1
fi

# Get provider schema and extract list_resource_schemas
if [ -n "$PROVIDER" ]; then
    # Specific provider
    provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true)
    if [ -n "$provider_key" ]; then
        terraform providers schema -json 2>/dev/null | jq -r \
            "{\"$PROVIDER\": (.provider_schemas.\"${provider_key}\" | .list_resource_schemas // {} | keys | sort)}"
    else
        echo "{\"$PROVIDER\": []}"
    fi
else
    # All providers
    terraform providers schema -json 2>/dev/null | jq -r '
        .provider_schemas
        | to_entries
        | map({key: (.key | split("/")[-1]), value: (.value.list_resource_schemas // {} | keys | sort)})
        | from_entries
    '
fi


================================================
FILE: terraform/code-generation/skills/terraform-style-guide/SECURITY.md
================================================
---
name: terraform-style-guide-security
description: Generate Terraform HCL code following HashiCorp's security practices
---

# Terraform Style Guide - Security

When generating code, apply security hardening:

- Enable encryption at rest by default
- Configure private networking where applicable
- Apply principle of least privilege for security groups
- Enable logging and monitoring
- Never hardcode credentials or secrets
- Mark sensitive outputs with `sensitive = true`
- Use `ephemeral` resources and write-only attributes
  for sensitive data when possible

## Example: Secure S3 Bucket

```hcl
resource "aws_s3_bucket" "data" {
  bucket = "${var.project}-${var.environment}-data"
  tags   = local.common_tags
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "data" {
  bucket = aws_s3_bucket.data.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

## Ephemeral resources

Ephemeral resources prevent sensitive data being stored in state.
For more information on ephemeral resources, see the
[Terraform documentation](https://developer.hashicorp.com/terraform/language/block/ephemeral).

Before you generate code for an ephemeral resource, check that the Terraform
version is greater than or equal to 1.11.0.

Then, follow this priority order for managing sensitive attributes:

1. **First priority: Native secrets manager integration**
   If a resource has the ability to automatically manage a sensitive attribute by
   storing it in a secrets manager (e.g., AWS Secrets Manager, Azure Key Vault),
   use that configuration. This is the preferred approach.

   ```hcl
   # Bad
   resource "aws_rds_cluster" "example" {
     cluster_identifier = "example"
     database_name      = "test"
     master_username    = "test"
     master_password    = var.db_master_password
   }

   # Good, managed by AWS Secrets Manager by default
   resource "aws_rds_cluster" "test" {
     cluster_identifier          = "example"
     database_name               = "test"
     manage_master_user_password = true
     master_username             = "test"
   }
   ```

2. **Second priority: Write-only attributes with ephemeral resources**
   If a resource has a write-only attribute but no native secrets manager integration,
   use an `ephemeral` resource for the sensitive data and pass that to the write-only
   attribute. Default the write-only version to 1.

   ```hcl
   # Bad
   resource "random_password" "password" {
     length           = 16
     special          = true
     override_special = "!#$%&*()-_=+[]{}<>:?"
   }
 
   resource "vault_kv_secret_v2" "example" {
     mount               = vault_mount.kvv2.path
     name                = "secret"
 
     data_json = jsonencode(
       {
         password = "${random_password.password.result}",
       }
     )
   }
 
   # Good
   ephemeral "random_password" "password" {
     length           = 16
     special          = true
     override_special = "!#$%&*()-_=+[]{}<>:?"
   }
 
   resource "vault_kv_secret_v2" "example" {
     mount               = vault_mount.kvv2.path
     name                = "secret"
 
     data_json_wo = jsonencode(
       {
         password = "${ephemeral.random_password.password.result}",
       }
     )
     data_json_wo_version = 1
   }
   ```

   If you need to retrieve a secret from a secrets manager to pass
   to a resource, use the `ephemeral` version of the resource to
   retrieve the secret and pass it to another resource.
   
   ```hcl
   # Good
   ephemeral "vault_kv_secret_v2" "db_secret" {
     mount = vault_mount.kvv2.path
     mount_id = vault_mount.kvv2.id
     name = vault_kv_secret_v2.db_root.name
   }
   
   resource "vault_database_secret_backend_connection" "postgres" {
     backend       = vault_mount.db.path
     name          = "postrgres-db"
     allowed_roles = ["*"]
   
     postgresql {
       connection_url = "postgresql://{{username}}:{{password}}@localhost:5432/postgres"
       password_authentication = ""
       username = "postgres"
       password_wo = tostring(ephemeral.vault_kv_secret_v2.db_secret.data.password)
       password_wo_version = 1
     }
   }
   ```

3. **Last resort: Regular resources**
   Only use a regular resource that has sensitive data written to state if neither of the above
   options are available, resource does not offer a write-only attribute or ephemeral resource
   alternative, or the Terraform version is less than 1.11.0.

================================================
FILE: terraform/code-generation/skills/terraform-style-guide/SKILL.md
================================================
---
name: terraform-style-guide
description: Generate Terraform HCL code following HashiCorp's official style conventions and best practices. Use when writing, reviewing, or generating Terraform configurations.
---

# Terraform Style Guide

Generate and maintain Terraform code following HashiCorp's official style conventions and best practices.

**Reference:** [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)

## Code Generation Strategy

When generating Terraform code:

1. Start with provider configuration and version constraints
2. Create data sources before dependent resources
3. Build resources in dependency order
4. Add outputs for key resource attributes
5. Use variables for all configurable values

## File Organization

| File | Purpose |
|------|---------|
| `terraform.tf` | Terraform and provider version requirements |
| `providers.tf` | Provider configurations |
| `main.tf` | Primary resources and data sources |
| `variables.tf` | Input variable declarations (alphabetical) |
| `outputs.tf` | Output value declarations (alphabetical) |
| `locals.tf` | Local value declarations |

### Example Structure

```hcl
# terraform.tf
terraform {
  required_version = ">= 1.14"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

# variables.tf
variable "environment" {
  description = "Target deployment environment"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

# locals.tf
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

# main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-vpc"
  })
}

# outputs.tf
output "vpc_id" {
  description = "ID of the created VPC"
  value       = aws_vpc.main.id
}
```

## Code Formatting

### Indentation and Alignment

- Use **two spaces** per nesting level (no tabs)
- Align equals signs for consecutive arguments

```hcl
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = "subnet-12345678"

  tags = {
    Name        = "web-server"
    Environment = "production"
  }
}
```

### Block Organization

Arguments precede blocks, with meta-arguments first:

```hcl
resource "aws_instance" "example" {
  # Meta-arguments
  count = 3

  # Arguments
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  # Blocks
  root_block_device {
    volume_size = 20
  }

  # Lifecycle last
  lifecycle {
    create_before_destroy = true
  }
}
```

## Naming Conventions

- Use **lowercase with underscores** for all names
- Use **descriptive nouns** excluding the resource type
- Be specific and meaningful
- Resource names must be singular, not plural
- Default to `main` for resources where a specific descriptive name is redundant or unavailable, provided only one instance exists

```hcl
# Bad
resource "aws_instance" "webAPI-aws-instance" {}
resource "aws_instance" "web_apis" {}
variable "name" {}

# Good
resource "aws_instance" "web_api" {}
resource "aws_vpc" "main" {}
variable "application_name" {}
```

## Variables

Every variable must include `type` and `description`:

```hcl
variable "instance_type" {
  description = "EC2 instance type for the web server"
  type        = string
  default     = "t2.micro"

  validation {
    condition     = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
    error_message = "Instance type must be t2.micro, t2.small, or t2.medium."
  }
}

variable "database_password" {
  description = "Password for the database admin user"
  type        = string
  sensitive   = true
}
```

## Outputs

Every output must include `description`:

```hcl
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id
}

output "database_password" {
  description = "Database administrator password"
  value       = aws_db_instance.main.password
  sensitive   = true
}
```

## Dynamic Resource Creation

### Prefer for_each over count

```hcl
# Bad - count for multiple resources
resource "aws_instance" "web" {
  count = var.instance_count
  tags  = { Name = "web-${count.index}" }
}

# Good - for_each with named instances
variable "instance_names" {
  type    = set(string)
  default = ["web-1", "web-2", "web-3"]
}

resource "aws_instance" "web" {
  for_each = var.instance_names
  tags     = { Name = each.key }
}
```

### count for Conditional Creation

```hcl
resource "aws_cloudwatch_metric_alarm" "cpu" {
  count = var.enable_monitoring ? 1 : 0

  alarm_name = "high-cpu-usage"
  threshold  = 80
}
```

## Security Best Practices

Refer to SECURITY.md. It includes guidance on encrypting resources,
preventing sensitive data in state, and secure configurations.

## Version Pinning

```hcl
terraform {
  required_version = ">= 1.14"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}
```

Use the latest major version of each provider and the latest minor version of
Terraform, unless otherwise constrained by a dependency lock file or by other
modules used by the configuration.

**Version constraint operators:**
- `= 1.0.0` - Exact version
- `>= 1.0.0` - Greater than or equal
- `~> 1.0` - Allow rightmost component to increment
- `>= 1.0, < 2.0` - Version range

## Provider Configuration

```hcl
provider "aws" {
  region = "us-west-2"

  default_tags {
    tags = {
      ManagedBy = "Terraform"
      Project   = var.project_name
    }
  }
}

# Aliased provider for multi-region
provider "aws" {
  alias  = "east"
  region = "us-east-1"
}
```

## Version Control

**Never commit:**
- `terraform.tfstate`, `terraform.tfstate.backup`
- `.terraform/` directory
- `*.tfplan`
- `.tfvars` files with sensitive data

**Always commit:**
- All `.tf` configuration files
- `.terraform.lock.hcl` (dependency lock file)

## Validation Tools

Run before committing:

```bash
terraform fmt -recursive
terraform validate
```

Additional tools:
- `tflint` - Linting and best practices
- `checkov` / `tfsec` - Security scanning

## Code Review Checklist

- [ ] Code formatted with `terraform fmt`
- [ ] Configuration validated with `terraform validate`
- [ ] Files organized according to standard structure
- [ ] All variables have type and description
- [ ] All outputs have descriptions
- [ ] Resource names use descriptive nouns with underscores
- [ ] Version constraints pinned explicitly
- [ ] Sensitive values marked with `sensitive = true`
- [ ] No hardcoded credentials or secrets
- [ ] Security best practices applied

---

*Based on: [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)*


================================================
FILE: terraform/code-generation/skills/terraform-test/SKILL.md
================================================
---
name: terraform-test
description: 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.
metadata:
  copyright: Copyright IBM Corp. 2026
  version: "0.0.2"
---

# Terraform Test

Terraform'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.

## Reference Files

- `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)
- `references/CI_CD.md` — GitHub Actions and GitLab CI pipeline examples
- `references/EXAMPLES.md` — Complete example test suite (unit, integration, and mock tests for a VPC module)

Read the relevant reference file when the user asks about mocking, CI/CD integration, or wants a full example.

## Core Concepts

- **Test file** (`.tftest.hcl` / `.tftest.json`): Contains `run` blocks that validate your configuration
- **Run block**: A single test scenario with optional variables, providers, and assertions
- **Assert block**: Conditions that must be true for the test to pass
- **Mock provider**: Simulates provider behavior without real infrastructure (Terraform 1.7.0+)
- **Test modes**: `apply` (default, creates real resources) or `plan` (validates logic only)

## File Structure

```
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
    ├── defaults_unit_test.tftest.hcl         # plan mode — fast, no resources
    ├── validation_unit_test.tftest.hcl        # plan mode
    └── full_stack_integration_test.tftest.hcl # apply mode — creates real resources
```

Use `*_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.

## Test File Structure

```hcl
# Optional: test-wide settings
test {
  parallel = true  # Enable parallel execution for all run blocks (default: false)
}

# Optional: file-level variables (highest precedence, override all other sources)
variables {
  aws_region    = "us-west-2"
  instance_type = "t2.micro"
}

# Optional: provider configuration
provider "aws" {
  region = var.aws_region
}

# Required: at least one run block
run "test_default_configuration" {
  command = plan

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Instance type should be t2.micro by default"
  }
}
```

## Run Block

```hcl
run "test_name" {
  command  = plan  # or apply (default)
  parallel = true  # optional, since v1.9.0

  # Override file-level variables
  variables {
    instance_type = "t3.large"
  }

  # Reference a specific module
  module {
    source  = "./modules/vpc"  # local or registry only (not git/http)
    version = "5.0.0"          # registry modules only
  }

  # Control state isolation
  state_key = "shared_state"  # since v1.9.0

  # Plan behavior
  plan_options {
    mode    = refresh-only  # or normal (default)
    refresh = true
    replace = [aws_instance.example]
    target  = [aws_instance.example]
  }

  # Assertions
  assert {
    condition     = aws_instance.example.id != ""
    error_message = "Instance should have a valid ID"
  }

  # Expected failures (test passes if these fail)
  expect_failures = [
    var.instance_count
  ]
}
```

## Common Test Patterns

### Validate outputs

```hcl
run "test_outputs" {
  command = plan

  assert {
    condition     = output.vpc_id != null
    error_message = "VPC ID output must be defined"
  }

  assert {
    condition     = can(regex("^vpc-", output.vpc_id))
    error_message = "VPC ID should start with 'vpc-'"
  }
}
```

### Conditional resources

```hcl
run "test_nat_gateway_disabled" {
  command = plan

  variables {
    create_nat_gateway = false
  }

  assert {
    condition     = length(aws_nat_gateway.main) == 0
    error_message = "NAT gateway should not be created when disabled"
  }
}
```

### Resource counts

```hcl
run "test_resource_count" {
  command = plan

  variables {
    instance_count = 3
  }

  assert {
    condition     = length(aws_instance.workers) == 3
    error_message = "Should create exactly 3 worker instances"
  }
}
```

### Tags

```hcl
run "test_resource_tags" {
  command = plan

  variables {
    common_tags = {
      Environment = "production"
      ManagedBy   = "Terraform"
    }
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Environment tag should be set correctly"
  }

  assert {
    condition     = aws_instance.example.tags["ManagedBy"] == "Terraform"
    error_message = "ManagedBy tag should be set correctly"
  }
}
```

### Data sources

```hcl
run "test_data_source_lookup" {
  command = plan

  assert {
    condition     = data.aws_ami.ubuntu.id != ""
    error_message = "Should find a valid Ubuntu AMI"
  }

  assert {
    condition     = can(regex("^ami-", data.aws_ami.ubuntu.id))
    error_message = "AMI ID should be in correct format"
  }
}
```

### Validation rules

```hcl
run "test_invalid_environment" {
  command = plan

  variables {
    environment = "invalid"
  }

  expect_failures = [
    var.environment
  ]
}
```

### Sequential tests with dependencies

```hcl
run "setup_vpc" {
  command = apply

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_subnet_in_vpc" {
  command = plan

  variables {
    vpc_id = run.setup_vpc.vpc_id
  }

  assert {
    condition     = aws_subnet.example.vpc_id == run.setup_vpc.vpc_id
    error_message = "Subnet should be in the VPC from setup_vpc"
  }
}
```

### Plan options (refresh-only, targeted)

```hcl
run "test_refresh_only" {
  command = plan

  plan_options {
    mode = refresh-only
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Tags should be refreshed correctly"
  }
}

run "test_specific_resource" {
  command = plan

  plan_options {
    target = [aws_instance.example]
  }

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Targeted resource should be planned"
  }
}
```

### Parallel modules

```hcl
run "test_networking_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/networking"
  }

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_compute_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/compute"
  }

  assert {
    condition     = output.instance_id != ""
    error_message = "Instance should be created"
  }
}
```

### State key sharing

```hcl
run "create_foundation" {
  command   = apply
  state_key = "foundation"

  assert {
    condition     = aws_vpc.main.id != ""
    error_message = "Foundation VPC should be created"
  }
}

run "create_application" {
  command   = apply
  state_key = "foundation"

  variables {
    vpc_id = run.create_foundation.vpc_id
  }

  assert {
    condition     = aws_instance.app.vpc_id == run.create_foundation.vpc_id
    error_message = "Application should use foundation VPC"
  }
}
```

### Cleanup ordering (S3 objects before bucket)

```hcl
run "create_bucket" {
  command = apply

  assert {
    condition     = aws_s3_bucket.example.id != ""
    error_message = "Bucket should be created"
  }
}

run "add_objects" {
  command = apply

  assert {
    condition     = length(aws_s3_object.files) > 0
    error_message = "Objects should be added"
  }
}

# Cleanup destroys in reverse: objects first, then bucket
```

### Multiple aliased providers

```hcl
provider "aws" {
  alias  = "primary"
  region = "us-west-2"
}

provider "aws" {
  alias  = "secondary"
  region = "us-east-1"
}

run "test_with_specific_provider" {
  command = plan

  providers = {
    aws = provider.aws.secondary
  }

  assert {
    condition     = aws_instance.example.availability_zone == "us-east-1a"
    error_message = "Instance should be in us-east-1 region"
  }
}
```

### Complex conditions

```hcl
assert {
  condition = alltrue([
    for subnet in aws_subnet.private :
    can(regex("^10\\.0\\.", subnet.cidr_block))
  ])
  error_message = "All private subnets should use 10.0.0.0/8 CIDR range"
}
```

## Cleanup

Resources 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.

## Running Tests

```bash
terraform test                                        # all tests
terraform test tests/defaults.tftest.hcl             # specific file
terraform test -filter=test_vpc_configuration        # by run block name
terraform test -test-directory=integration-tests     # custom directory
terraform test -verbose                              # detailed output
terraform test -no-cleanup                           # skip resource cleanup
```

## Best Practices

1. **Naming**: `*_unit_test.tftest.hcl` for plan mode, `*_integration_test.tftest.hcl` for apply mode
2. **Test naming**: Use descriptive run block names that explain the scenario being tested
3. **Default to plan**: Use `command = plan` unless you need to test real resource behavior
4. **Use mocks** for external dependencies — faster and no credentials needed (see `references/MOCK_PROVIDERS.md`)
5. **Error messages**: Make them specific enough to diagnose failures without running the test again
6. **Negative tests**: Use `expect_failures` to verify validation rules reject bad inputs
7. **Variable coverage**: Test different variable combinations to validate all code paths — test variables have the highest precedence and override all other sources
8. **Module sources**: Test files only support local paths and registry modules — not git or HTTP URLs
9. **Parallel execution**: Use `parallel = true` for independent tests with different state files
10. **Cleanup**: Integration tests destroy resources in reverse run block order automatically; use `-no-cleanup` for debugging
11. **CI/CD**: Run unit tests on every PR, integration tests on merge (see `references/CI_CD.md`)

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Assertion failures | Use `-verbose` to see actual vs expected values |
| Missing credentials | Use mock providers for unit tests |
| Unsupported module source | Convert git/HTTP sources to local modules |
| Tests interfering | Use `state_key` or separate modules for isolation |
| Slow tests | Use `command = plan` and mocks; run integration tests separately |

## References

- [Terraform Testing Documentation](https://developer.hashicorp.com/terraform/language/tests)
- [Terraform Test Command](https://developer.hashicorp.com/terraform/cli/commands/test)
- [Testing Best Practices](https://developer.hashicorp.com/terraform/language/tests/best-practices)


================================================
FILE: terraform/code-generation/skills/terraform-test/references/CI_CD.md
================================================
# CI/CD Integration

## GitHub Actions

```yaml
name: Terraform Tests

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.0

      - run: terraform fmt -check -recursive
      - run: terraform init
      - run: terraform validate
      - name: Run unit tests (plan mode, no credentials needed)
        run: terraform test -filter=unit_test -verbose

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.0

      - run: terraform init
      - name: Run integration tests
        run: terraform test -filter=integration_test -verbose
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

## GitLab CI

```yaml
stages:
  - validate
  - test

terraform-unit-tests:
  image: hashicorp/terraform:1.9
  stage: validate
  before_script:
    - terraform init
  script:
    - terraform fmt -check -recursive
    - terraform validate
    - terraform test -filter=unit_test -verbose

terraform-integration-tests:
  image: hashicorp/terraform:1.9
  stage: test
  before_script:
    - terraform init
  script:
    - terraform test -filter=integration_test -verbose
  only:
    - main
```

## Recommended CI Strategy

- Run unit tests (plan mode + mock tests) on every PR — fast, no credentials needed
- Run integration tests only on merge to main or nightly — requires cloud credentials
- Use `-filter=unit_test` / `-filter=integration_test` to separate test types based on naming convention
- Store cloud credentials as CI secrets, never in code


================================================
FILE: terraform/code-generation/skills/terraform-test/references/EXAMPLES.md
================================================
# Example Test Suite

Complete example testing a VPC module with unit, integration, and mock tests.

## Unit Tests (Plan Mode)

```hcl
# tests/vpc_module_unit_test.tftest.hcl

variables {
  environment = "test"
  aws_region  = "us-west-2"
}

run "test_defaults" {
  command = plan

  variables {
    vpc_cidr = "10.0.0.0/16"
    vpc_name = "test-vpc"
  }

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR should match input"
  }

  assert {
    condition     = aws_vpc.main.enable_dns_hostnames == true
    error_message = "DNS hostnames should be enabled by default"
  }

  assert {
    condition     = aws_vpc.main.tags["Name"] == "test-vpc"
    error_message = "VPC name tag should match input"
  }
}

run "test_subnets" {
  command = plan

  variables {
    vpc_cidr        = "10.0.0.0/16"
    vpc_name        = "test-vpc"
    public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
    private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
  }

  assert {
    condition     = length(aws_subnet.public) == 2
    error_message = "Should create 2 public subnets"
  }

  assert {
    condition     = length(aws_subnet.private) == 2
    error_message = "Should create 2 private subnets"
  }

  assert {
    condition = alltrue([
      for subnet in aws_subnet.private :
      subnet.map_public_ip_on_launch == false
    ])
    error_message = "Private subnets should not assign public IPs"
  }
}

run "test_outputs" {
  command = plan

  variables {
    vpc_cidr = "10.0.0.0/16"
    vpc_name = "test-vpc"
  }

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC ID output should not be empty"
  }

  assert {
    condition     = can(regex("^vpc-", output.vpc_id))
    error_message = "VPC ID should have correct format"
  }

  assert {
    condition     = output.vpc_cidr == "10.0.0.0/16"
    error_message = "VPC CIDR output should match input"
  }
}

run "test_invalid_cidr" {
  command = plan

  variables {
    vpc_cidr = "invalid"
    vpc_name = "test-vpc"
  }

  expect_failures = [
    var.vpc_cidr
  ]
}
```

## Integration Tests (Apply Mode)

```hcl
# tests/vpc_module_integration_test.tftest.hcl

variables {
  environment = "integration-test"
  aws_region  = "us-west-2"
}

run "integration_test_vpc_creation" {
  # command defaults to apply — creates real AWS resources

  variables {
    vpc_cidr = "10.100.0.0/16"
    vpc_name = "integration-test-vpc"
  }

  assert {
    condition     = aws_vpc.main.id != ""
    error_message = "VPC should be created with valid ID"
  }

  assert {
    condition     = aws_vpc.main.state == "available"
    error_message = "VPC should be in available state"
  }
}
```

## Mock Tests (Plan Mode, No Credentials)

```hcl
# tests/vpc_module_mock_test.tftest.hcl

mock_provider "aws" {
  mock_resource "aws_instance" {
    defaults = {
      id            = "i-1234567890abcdef0"
      instance_type = "t2.micro"
      ami           = "ami-12345678"
      public_ip     = "203.0.113.1"
      private_ip    = "10.0.1.100"
    }
  }

  mock_resource "aws_vpc" {
    defaults = {
      id                   = "vpc-12345678"
      cidr_block           = "10.0.0.0/16"
      enable_dns_hostnames = true
      enable_dns_support   = true
    }
  }

  mock_resource "aws_subnet" {
    defaults = {
      id                      = "subnet-12345678"
      vpc_id                  = "vpc-12345678"
      cidr_block              = "10.0.1.0/24"
      availability_zone       = "us-west-2a"
      map_public_ip_on_launch = false
    }
  }

  mock_data "aws_ami" {
    defaults = {
      id   = "ami-0c55b159cbfafe1f0"
      name = "ubuntu-focal-20.04-amd64"
    }
  }

  mock_data "aws_availability_zones" {
    defaults = {
      names = ["us-west-2a", "us-west-2b", "us-west-2c"]
    }
  }
}

run "test_instance_with_mocks" {
  command = plan

  variables {
    instance_type = "t2.micro"
    ami_id        = "ami-12345678"
  }

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Instance type should match input variable"
  }

  assert {
    condition     = aws_instance.example.id == "i-1234567890abcdef0"
    error_message = "Mock should return consistent instance ID"
  }
}

run "test_data_source_with_mocks" {
  command = plan

  assert {
    condition     = data.aws_ami.ubuntu.id == "ami-0c55b159cbfafe1f0"
    error_message = "Mock data source should return predictable AMI ID"
  }

  assert {
    condition     = length(data.aws_availability_zones.available.names) == 3
    error_message = "Should return 3 mocked availability zones"
  }

  assert {
    condition     = contains(data.aws_availability_zones.available.names, "us-west-2a")
    error_message = "Should include us-west-2a in mocked zones"
  }
}

run "test_outputs_with_mocks" {
  command = plan

  assert {
    condition     = output.vpc_id == "vpc-12345678"
    error_message = "VPC ID output should match mocked value"
  }

  assert {
    condition     = can(regex("^vpc-", output.vpc_id))
    error_message = "VPC ID output should have correct format"
  }
}

run "test_conditional_resources_with_mocks" {
  command = plan

  variables {
    create_bastion     = true
    create_nat_gateway = false
  }

  assert {
    condition     = length(aws_instance.bastion) == 1
    error_message = "Bastion should be created when enabled"
  }

  assert {
    condition     = length(aws_nat_gateway.nat) == 0
    error_message = "NAT gateway should not be created when disabled"
  }
}

run "test_tag_inheritance_with_mocks" {
  command = plan

  variables {
    common_tags = {
      Environment = "test"
      ManagedBy   = "Terraform"
    }
  }

  assert {
    condition = alltrue([
      for key in keys(var.common_tags) :
      contains(keys(aws_instance.example.tags), key)
    ])
    error_message = "All common tags should be present on instance"
  }
}

run "test_invalid_cidr_with_mocks" {
  command = plan

  variables {
    vpc_cidr = "invalid"
  }

  expect_failures = [
    var.vpc_cidr
  ]
}

run "setup_vpc_with_mocks" {
  command = plan

  variables {
    vpc_cidr = "10.0.0.0/16"
    vpc_name = "test-vpc"
  }

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR should match input"
  }
}

run "test_subnet_references_vpc_with_mocks" {
  command = plan

  variables {
    vpc_id      = run.setup_vpc_with_mocks.vpc_id
    subnet_cidr = "10.0.1.0/24"
  }

  assert {
    condition     = aws_subnet.example.vpc_id == run.setup_vpc_with_mocks.vpc_id
    error_message = "Subnet should reference VPC from previous run"
  }
}
```


================================================
FILE: terraform/code-generation/skills/terraform-test/references/MOCK_PROVIDERS.md
================================================
# Mock Providers

Mock providers simulate provider behavior without creating real infrastructure (Terraform 1.7.0+). Use them for fast, credential-free unit tests.

## Basic Mock Provider

```hcl
mock_provider "aws" {
  mock_resource "aws_instance" {
    defaults = {
      id            = "i-1234567890abcdef0"
      instance_type = "t2.micro"
      ami           = "ami-12345678"
      public_ip     = "203.0.113.1"
      private_ip    = "10.0.1.100"
    }
  }

  mock_data "aws_ami" {
    defaults = {
      id = "ami-0c55b159cbfafe1f0"
    }
  }

  mock_data "aws_availability_zones" {
    defaults = {
      names = ["us-west-2a", "us-west-2b", "us-west-2c"]
    }
  }
}

run "test_with_mocks" {
  command = plan  # Mocks only work with plan mode

  assert {
    condition     = aws_instance.example.id == "i-1234567890abcdef0"
    error_message = "Mock instance ID should match"
  }
}
```

## Aliased Mock Provider

```hcl
mock_provider "aws" {
  alias = "mocked"

  mock_resource "aws_s3_bucket" {
    defaults = {
      id  = "test-bucket-12345"
      arn = "arn:aws:s3:::test-bucket-12345"
    }
  }
}

run "test_with_aliased_mock" {
  command = plan

  providers = {
    aws = provider.aws.mocked
  }

  assert {
    condition     = aws_s3_bucket.example.id == "test-bucket-12345"
    error_message = "Bucket ID should match mock"
  }
}
```

## Common Mock Defaults

```hcl
mock_provider "aws" {
  mock_resource "aws_instance" {
    defaults = {
      id                          = "i-1234567890abcdef0"
      arn                         = "arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0"
      instance_type               = "t2.micro"
      ami                         = "ami-12345678"
      availability_zone           = "us-west-2a"
      subnet_id                   = "subnet-12345678"
      vpc_security_group_ids      = ["sg-12345678"]
      associate_public_ip_address = true
      public_ip                   = "203.0.113.1"
      private_ip                  = "10.0.1.100"
      tags                        = {}
    }
  }

  mock_resource "aws_vpc" {
    defaults = {
      id                   = "vpc-12345678"
      arn                  = "arn:aws:ec2:us-west-2:123456789012:vpc/vpc-12345678"
      cidr_block           = "10.0.0.0/16"
      enable_dns_hostnames = true
      enable_dns_support   = true
      instance_tenancy     = "default"
      tags                 = {}
    }
  }

  mock_resource "aws_subnet" {
    defaults = {
      id                      = "subnet-12345678"
      arn                     = "arn:aws:ec2:us-west-2:123456789012:subnet/subnet-12345678"
      vpc_id                  = "vpc-12345678"
      cidr_block              = "10.0.1.0/24"
      availability_zone       = "us-west-2a"
      map_public_ip_on_launch = false
      tags                    = {}
    }
  }

  mock_resource "aws_s3_bucket" {
    defaults = {
      id                 = "test-bucket-12345"
      arn                = "arn:aws:s3:::test-bucket-12345"
      bucket             = "test-bucket-12345"
      bucket_domain_name = "test-bucket-12345.s3.amazonaws.com"
      region             = "us-west-2"
      tags               = {}
    }
  }

  mock_data "aws_ami" {
    defaults = {
      id                  = "ami-0c55b159cbfafe1f0"
      name                = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210430"
      architecture        = "x86_64"
      root_device_type    = "ebs"
      virtualization_type = "hvm"
    }
  }

  mock_data "aws_availability_zones" {
    defaults = {
      names    = ["us-west-2a", "us-west-2b", "us-west-2c"]
      zone_ids = ["usw2-az1", "usw2-az2", "usw2-az3"]
    }
  }

  mock_data "aws_vpc" {
    defaults = {
      id                   = "vpc-12345678"
      cidr_block           = "10.0.0.0/16"
      enable_dns_hostnames = true
      enable_dns_support   = true
    }
  }
}
```

## When to Use Mocks

**Good fit:**
- Testing Terraform logic, conditionals, `for_each`/`count` expressions
- Validating variable transformations and output calculations
- Local development without cloud credentials
- Fast CI/CD feedback loops

**Not a good fit:**
- Validating actual provider API behavior
- Testing real resource creation side effects
- End-to-end integration testing

## Limitations

- **Plan mode only** — mocks don't work with `command = apply`
- Mock defaults may not reflect real computed attribute values
- Mocks need manual updates when provider schemas change
- Can't test real resource dependencies or timing


================================================
FILE: terraform/module-generation/.claude-plugin/plugin.json
================================================
{
  "name": "terraform-module-generation",
  "version": "1.0.0",
  "description": "Terraform module generation and refactoring skills for Claude Code, including module design and Terraform Stacks.",
  "author": {
    "name": "HashiCorp",
    "url": "https://github.com/hashicorp"
  },
  "homepage": "https://developer.hashicorp.com/terraform/language/modules",
  "repository": "https://github.com/hashicorp/agent-skills",
  "license": "MPL-2.0",
  "keywords": ["terraform", "modules", "infrastructure", "iac", "stacks", "refactoring"],
  "mcpServers": {
    "terraform": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e", "TFE_TOKEN",
        "-e", "TFE_ADDRESS",
        "hashicorp/terraform-mcp-server"
      ],
      "env": {
        "TFE_TOKEN": "${TFE_TOKEN}",
        "TFE_ADDRESS": "${TFE_ADDRESS}"
      }
    }
  }
}


================================================
FILE: terraform/module-generation/skills/refactor-module/SKILL.md
================================================
---
name: refactor-module
description: Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.
metadata:
  copyright: Copyright IBM Corp. 2026
  version: "0.0.1"
---

# Skill: Refactor Module

## Overview
This skill guides AI agents in transforming monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.

## Capability Statement
The agent will analyze existing Terraform code and systematically refactor it into well-structured modules with:
- Clear interface contracts (variables and outputs)
- Proper encapsulation and abstraction
- Versioning and documentation
- Testing frameworks
- Migration path for existing state

## Prerequisites
- Existing Terraform configuration to refactor
- Understanding of resource dependencies
- Access to current state file (for migration planning)
- Knowledge of module registry patterns

## Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `source_directory` | string | Yes | Path to existing Terraform configuration |
| `module_name` | string | Yes | Name for the new module |
| `abstraction_level` | string | No | "simple", "intermediate", "advanced" (default: intermediate) |
| `preserve_state` | boolean | Yes | Whether to maintain state compatibility |
| `target_registry` | string | No | Target module registry (local, private, public) |

## Execution Steps

### 1. Analysis Phase
```markdown
**Identify Refactoring Candidates**
- Group resources by logical function
- Identify repeated patterns
- Map resource dependencies
- Detect configuration coupling
- Analyze variable usage patterns

**Complexity Assessment**
- Count resource relationships
- Measure variable propagation depth
- Identify cross-resource references
- Evaluate state migration complexity
```

### 2. Module Design

#### Interface Design
```hcl
# Define clear input contract
variable "network_config" {
  description = "Network configuration parameters"
  type = object({
    cidr_block         = string
    availability_zones = list(string)
    enable_nat         = bool
  })
  
  validation {
    condition     = can(cidrhost(var.network_config.cidr_block, 0))
    error_message = "CIDR block must be valid IPv4 CIDR."
  }
}

# Define output contract
output "vpc_id" {
  description = "ID of the created VPC"
  value       = aws_vpc.main.id
}

output "private_subnet_ids" {
  description = "List of private subnet IDs"
  value       = { for k, v in aws_subnet.private : k => v.id }
}
```

#### Encapsulation Strategy
```markdown
**What to Include in Module:**
- Tightly coupled resources (VPC + subnets)
- Resources with shared lifecycle
- Configuration with clear boundaries

**What to Keep Separate:**
- Cross-cutting concerns (monitoring, tagging)
- Resources with different lifecycles
- Provider-specific configurations
```

### 3. Code Transformation

#### Before: Monolithic Configuration
```hcl
# main.tf (monolithic)
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  enable_dns_hostnames = true
  
  tags = {
    Name = "production-vpc"
    Environment = "prod"
  }
}

resource "aws_subnet" "public_1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
  
  tags = {
    Name = "public-subnet-1"
    Type = "public"
  }
}

resource "aws_subnet" "public_2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1b"
  
  tags = {
    Name = "public-subnet-2"
    Type = "public"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = {
    Name = "production-igw"
  }
}

# ... more repetitive subnet and routing resources
```

#### After: Modular Structure
```hcl
# modules/vpc/main.tf
locals {
  subnet_count = length(var.availability_zones)
}

resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = var.enable_dns_support
  
  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
}

resource "aws_subnet" "public" {
  for_each = var.create_public_subnets ? toset(var.availability_zones) : []
  
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.cidr_block, 8, index(var.availability_zones, each.value))
  availability_zone       = each.value
  map_public_ip_on_launch = true
  
  tags = merge(
    var.tags,
    {
      Name = "${var.name}-public-${each.value}"
      Type = "public"
    }
  )
}

resource "aws_internet_gateway" "main" {
  count  = var.create_public_subnets ? 1 : 0
  vpc_id = aws_vpc.main.id
  
  tags = merge(
    var.tags,
    {
      Name = "${var.name}-igw"
    }
  )
}

# modules/vpc/variables.tf
variable "name" {
  description = "Name prefix for all resources"
  type        = string
}

variable "cidr_block" {
  description = "CIDR block for the VPC"
  type        = string
  
  validation {
    condition     = can(cidrhost(var.cidr_block, 0))
    error_message = "Must be a valid IPv4 CIDR block."
  }
}

variable "availability_zones" {
  description = "List of availability zones"
  type        = list(string)
}

variable "create_public_subnets" {
  description = "Whether to create public subnets"
  type        = bool
  default     = true
}

variable "enable_dns_hostnames" {
  description = "Enable DNS hostnames in the VPC"
  type        = bool
  default     = true
}

variable "enable_dns_support" {
  description = "Enable DNS support in the VPC"
  type        = bool
  default     = true
}

variable "tags" {
  description = "Tags to apply to all resources"
  type        = map(string)
  default     = {}
}

# modules/vpc/outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "vpc_cidr_block" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.main.cidr_block
}

output "public_subnet_ids" {
  description = "Map of availability zones to public subnet IDs"
  value       = { for k, v in aws_subnet.public : k => v.id }
}

output "internet_gateway_id" {
  description = "ID of the internet gateway"
  value       = try(aws_internet_gateway.main[0].id, null)
}

# Root configuration using module
module "vpc" {
  source = "./modules/vpc"
  
  name               = "production"
  cidr_block         = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
  
  tags = {
    Environment = "production"
    ManagedBy   = "Terraform"
  }
}
```

### 4. State Migration

#### Generate Migration Plan
```hcl
# migration.tf
# Use moved blocks for state refactoring (Terraform 1.1+)

moved {
  from = aws_vpc.main
  to   = module.vpc.aws_vpc.main
}

moved {
  from = aws_subnet.public_1
  to   = module.vpc.aws_subnet.public["us-east-1a"]
}

moved {
  from = aws_subnet.public_2
  to   = module.vpc.aws_subnet.public["us-east-1b"]
}

moved {
  from = aws_internet_gateway.main
  to   = module.vpc.aws_internet_gateway.main[0]
}
```

#### Manual State Migration (Pre-1.1)
```bash
# Generate state migration commands
terraform state mv aws_vpc.main module.vpc.aws_vpc.main
terraform state mv aws_subnet.public_1 'module.vpc.aws_subnet.public["us-east-1a"]'
terraform state mv aws_subnet.public_2 'module.vpc.aws_subnet.public["us-east-1b"]'
terraform state mv aws_internet_gateway.main 'module.vpc.aws_internet_gateway.main[0]'
```

### 5. Module Documentation

```markdown
# VPC Module

## Overview
Creates a VPC with configurable public and private subnets across multiple availability zones.

## Features
- Multi-AZ subnet deployment
- Optional NAT gateway configuration
- VPC Flow Logs integration
- Customizable CIDR allocation

## Usage

\`\`\`hcl
module "vpc" {
  source = "./modules/vpc"
  
  name               = "my-vpc"
  cidr_block         = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b"]
  
  create_public_subnets  = true
  create_private_subnets = true
  enable_nat_gateway     = true
  
  tags = {
    Environment = "production"
  }
}
\`\`\`

## Requirements

| Name | Version |
|------|---------|
| terraform | >= 1.5.0 |
| aws | ~> 5.0 |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| name | Name prefix for resources | `string` | n/a | yes |
| cidr_block | VPC CIDR block | `string` | n/a | yes |
| availability_zones | List of AZs | `list(string)` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| vpc_id | VPC identifier |
| public_subnet_ids | Map of public subnet IDs |
| private_subnet_ids | Map of private subnet IDs |

## Examples

See [examples/](./examples/) directory for complete usage examples.
```

### 6. Testing

Use skill terraform-test

**Test File**: A `.tftest.hcl` or `.tftest.json` file containing test configuration and run blocks that validate your Terraform configuration.

**Test Block**: Optional configuration block that defines test-wide settings (available since Terraform 1.6.0).

**Run Block**: Defines a single test scenario with optional variables, provider configurations, and assertions. Each test file requires at least one run block.

**Assert Block**: Contains conditions that must evaluate to true for the test to pass. Failed assertions cause the test to fail.

**Mock Provider**: Simulates provider behavior without creating real infrastructure (available since Terraform 1.7.0).

**Test Modes**: Tests run in apply mode (default, creates real infrastructure) or plan mode (validates logic without creating resources).

#### File Structure

Terraform 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):

```
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
    ├── unit_test.tftest.hcl      # Unit test (plan mode)
    └── integration_test.tftest.hcl  # Integration test (apply mode - creates real resources)
```

## Refactoring Patterns

### Pattern 1: Resource Grouping
Extract related resources into cohesive modules:
- Networking (VPC, Subnets, Route Tables)
- Compute (ASG, Launch Templates, Load Balancers)
- Data (RDS, ElastiCache, S3)

### Pattern 2: Configuration Layering
```hcl
# Base module with defaults
module "vpc_base" {
  source = "./modules/vpc-base"
  # Minimal required inputs
}

# Environment-specific wrapper
module "vpc_prod" {
  source = "./modules/vpc-production"
  # Inherits from base, adds prod-specific config
}
```

### Pattern 3: Composition
```hcl
# Small, focused modules
module "vpc" {
  source = "./modules/vpc"
}

module "security_groups" {
  source = "./modules/security-groups"
  vpc_id = module.vpc.vpc_id
}

module "application" {
  source     = "./modules/application"
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
  sg_ids     = module.security_groups.app_sg_ids
}
```

## Common Pitfalls

### 1. Over-Abstraction
```hcl
# ❌ Don't create overly generic modules
variable "resources" {
  type = map(map(any))  # Too flexible, hard to validate
}

# ✅ Do use specific, typed interfaces
variable "database_config" {
  type = object({
    engine         = string
    instance_class = string
  })
}
```

### 2. Tight Coupling
```hcl
# ❌ Don't couple modules through direct references
# module A
output "instance_id" { value = aws_instance.app.id }

# module B (in same config)
resource "aws_eip" "app" {
  instance = module.a.instance_id  # Tight coupling
}

# ✅ Do pass dependencies through root module
module "compute" {
  source = "./modules/compute"
}

resource "aws_eip" "app" {
  instance = module.compute.instance_id
}
```

### 3. State Migration Errors
Always test migration in non-production first:
```bash
# Create plan to verify no changes after migration
terraform plan -out=migration.tfplan

# Review carefully
terraform show migration.tfplan

# Apply only if plan shows no changes
terraform apply migration.tfplan
```

## Version Control Strategy

```hcl
# Use semantic versioning for modules
module "vpc" {
  source  = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"
  version = "~> 1.2"
}

# Pin to specific versions in production
# Use version ranges in development
```

## Success Criteria

- [ ] Module has single, well-defined responsibility
- [ ] All variables have descriptions and types
- [ ] Validation rules prevent invalid configurations
- [ ] Outputs provide sufficient information for consumers
- [ ] Documentation includes usage examples
- [ ] Tests verify module behavior
- [ ] State migration completed without resource recreation
- [ ] No plan differences after refactoring

## Related Skills
- [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
- [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

## Resources
- [Terraform Module Development](https://developer.hashicorp.com/terraform/language/modules/develop)
- [Module Best Practices](https://developer.hashicorp.com/terraform/cloud-docs/registry/design)

## Revision History

| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2025-11-07 | Initial skill definition |


================================================
FILE: terraform/module-generation/skills/terraform-stacks/SKILL.md
================================================
---
name: terraform-stacks
description: 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.
metadata:
  copyright: Copyright IBM Corp. 2026
  version: "0.0.1"
---

# Terraform Stacks

Terraform 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.

## Core Concepts

**Stack**: A complete unit of infrastructure composed of components and deployments that can be managed together.

**Component**: An abstraction around a Terraform module that defines infrastructure pieces. Each component specifies a source module, inputs, and providers.

**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.

**Stack Language**: A separate HCL-based language (not regular Terraform HCL) with distinct blocks and file extensions.

## File Structure

Terraform Stacks use specific file extensions:

- **Component configuration**: `.tfcomponent.hcl`
- **Deployment configuration**: `.tfdeploy.hcl`
- **Provider lock file**: `.terraform.lock.hcl` (generated by CLI)

All configuration files must be at the root level of the Stack repository. HCP Terraform processes all files in dependency order.

### Recommended File Organization

```
my-stack/
├── .terraform-version               # The required Terraform version for this Stack
├── variables.tfcomponent.hcl        # Variable declarations
├── providers.tfcomponent.hcl        # Provider configurations
├── components.tfcomponent.hcl       # Component definitions
├── outputs.tfcomponent.hcl          # Stack outputs
├── deployments.tfdeploy.hcl         # Deployment definitions
├── .terraform.lock.hcl              # Provider lock file (generated)
└── modules/                         # Local modules (optional - only if using local modules)
    ├── s3/
    └── compute/
```

**Note**: The `modules/` directory is only required when using local module sources. Components can reference modules from:
- Local file paths: `./modules/vpc`
- Public registry: `terraform-aws-modules/vpc/aws`
- Private registry: `app.terraform.io/<org-name>/vpc/aws`
- Git: `git::https://github.com/org/repo.git//path?ref=v1.0.0`

HCP Terraform processes all `.tfcomponent.hcl` and `.tfdeploy.hcl` files in dependency order.

## Required Terraform version (.terraform-version)

Use Terraform v1.13.x or later to access the Stacks CLI plugin and to run
terraform stacks CLI commands. Begin by adding a .terraform-version file to
your Stack's root directory to specify the Terraform version required for your
Stack. For example, the following file specifies Terraform v1.14.5:

```
1.14.5
```

## Component Configuration (.tfcomponent.hcl)

### Variable Block

Declare input variables for the Stack configuration. Variables must define a `type` field and do not support the `validation` argument.

```hcl
variable "aws_region" {
  type        = string
  description = "AWS region for deployments"
  default     = "us-west-1"
}

variable "identity_token" {
  type        = string
  description = "OIDC identity token"
  ephemeral   = true  # Does not persist to state file
}

variable "instance_count" {
  type     = number
  nullable = false
}
```

**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.

### Required Providers Block

```hcl
required_providers {
  aws = {
    source  = "hashicorp/aws"
    version = "~> 6.0"
  }
  random = {
    source  = "hashicorp/random"
    version = "~> 3.5.0"
  }
}
```

### Provider Block

Provider blocks differ from traditional Terraform:

1. Support `for_each` meta-argument
2. Define aliases in the block header (not as an argument)
3. Accept configuration through a `config` block

**Single Provider Configuration:**

```hcl
provider "aws" "this" {
  config {
    region = var.aws_region
    assume_role_with_web_identity {
      role_arn           = var.role_arn
      web_identity_token = var.identity_token
    }
  }
}
```

**Multiple Provider Configurations with for_each:**

```hcl
provider "aws" "configurations" {
  for_each = var.regions

  config {
    region = each.value
    assume_role_with_web_identity {
      role_arn           = var.role_arn
      web_identity_token = var.identity_token
    }
  }
}
```

**Authentication Best Practice**: Use **workload identity** (OIDC) as the preferred authentication method for Stacks. This approach:
- Avoids long-lived static credentials
- Provides temporary, scoped credentials per deployment run
- Integrates with cloud provider IAM (AWS IAM Roles, Azure Managed Identities, GCP Service Accounts)
- Eliminates need for platform-managed environment variables

Configure 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

### Component Block

Each 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.

```hcl
component "vpc" {
  source  = "app.terraform.io/my-org/vpc/aws"  # Local, registry, or Git URL
  version = "2.1.0"          # For registry modules

  inputs = {
    cidr_block  = var.vpc_cidr
    name_prefix = var.name_prefix
  }

  providers = {
    aws = provider.aws.this
  }
}
```

See `references/component-blocks.md` for examples of dependencies, for_each, public registry modules, Git sources, and more.

**Key Points:**
- Reference outputs: `component.<name>.<output>` or `component.<name>[key].<output>` for for_each
- Dependencies inferred automatically from component references
- Aggregate with for expressions: `[for x in component.s3 : x.bucket_name]`
- For components with `for_each`, reference specific instances: `component.<name>[each.value].<output>`
- Provider references are normal values: `provider.<type>.<alias>` or `provider.<type>.<alias>[each.value]`

### Output Block

Outputs require a `type` argument and do not support `preconditions`:

```hcl
output "vpc_id" {
  type        = string
  description = "VPC ID"
  value       = component.vpc.vpc_id
}

output "endpoint_urls" {
  type      = map(string)
  value     = {
    for region, comp in component.api : region => comp.endpoint_url
  }
  sensitive = false
}
```

### Locals Block

Locals blocks work the same in both `.tfcomponent.hcl` and `.tfdeploy.hcl` files:

```hcl
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform Stacks"
    Project     = var.project_name
  }

  region_config = {
    for region in var.regions : region => {
      name_suffix = "${var.environment}-${region}"
    }
  }
}
```

### Removed Block

Use to safely remove components from a Stack. HCP Terraform requires the component's providers to remove it.

```hcl
removed {
  from   = component.old_component
  source = "./modules/old-module"
  
  providers = {
    aws = provider.aws.this
  }
}
```

## Deployment Configuration (.tfdeploy.hcl)

### Identity Token Block

Generate JWT tokens for OIDC authentication with cloud providers:

```hcl
identity_token "aws" {
  audience = ["aws.workload.identity"]
}

identity_token "azure" {
  audience = ["api://AzureADTokenExchange"]
}
```

Reference tokens in deployments using `identity_token.<name>.jwt`

### Store Block

Access HCP Terraform variable sets within Stack deployments:

```hcl
store "varset" "aws_credentials" {
  id       = "varset-ABC123"  # Alternatively use: name = "varset_name"
  source   = "tfc-cloud-shared"
  category = "terraform"      # Alternatively use: category = "env" for environment variables
}

deployment "production" {
  inputs = {
    aws_access_key = store.varset.aws_credentials.AWS_ACCESS_KEY_ID
  }
}
```

Use to centralize credentials and share variables across Stacks. See `references/deployment-blocks.md` for details.

### Deployment Block

Define deployment instances (minimum 1, maximum 20 per Stack):

```hcl
deployment "production" {
  inputs = {
    aws_region     = "us-west-1"
    instance_count = 3
    role_arn       = local.role_arn
    identity_token = identity_token.aws.jwt
  }
}

# Create multiple deployments for different environments
deployment "development" {
  inputs = {
    aws_region     = "us-east-1"
    instance_count = 1
    name_suffix    = "dev"
    role_arn       = local.role_arn
    identity_token = identity_token.aws.jwt
  }
}
```

**To destroy a deployment**: Set `destroy = true`, upload configuration, approve destroy run, then remove the deployment block. See `references/deployment-blocks.md` for details.

### Deployment Group Block

Group deployments together for shared settings (HCP Terraform Premium tier feature). Free/standard tiers use default groups named `{deployment-name}_default`.

```hcl
deployment_group "canary" {
  auto_approve_checks = [deployment_auto_approve.safe_changes]
}

deployment "dev" {
  inputs = { /* ... */ }
  deployment_group = deployment_group.canary
}
```

Multiple deployments can reference the same group. See `references/deployment-blocks.md` for details.

### Deployment Auto-Approve Block

Define rules to automatically approve deployment plans (HCP Terraform Premium tier feature):

```hcl
deployment_auto_approve "safe_changes" {
  deployment_group = deployment_group.canary

  check {
    condition = context.plan.changes.remove == 0
    reason    = "Cannot auto-approve plans with resource deletions"
  }
}
```

**Available context variables**: `context.plan.applyable`, `context.plan.changes.add/change/remove/total`, `context.success`

**Note:** `orchestrate` blocks are deprecated. Use `deployment_group` and `deployment_auto_approve` instead.

See `references/deployment-blocks.md` for all context variables and patterns.

### Publish Output and Upstream Input Blocks

Link Stacks together by publishing outputs from one Stack and consuming them in another:

```hcl
# In network Stack - publish outputs
publish_output "vpc_id_network" {
  type  = string
  value = deployment.network.vpc_id
}

# In application Stack - consume outputs
upstream_input "network_stack" {
  type   = "stack"
  source = "app.terraform.io/my-org/my-project/networking-stack"
}

deployment "app" {
  inputs = {
    vpc_id = upstream_input.network_stack.vpc_id_network
  }
}
```

See `references/linked-stacks.md` for complete documentation and examples.

## Terraform Stacks CLI

**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.

### Initialize and Validate

```bash
terraform stacks init              # Download providers, modules, generate lock file
terraform stacks providers-lock    # Regenerate lock file (add platforms if needed)
terraform stacks validate          # Check syntax without uploading
```

### Deployment Workflow

**Important**: No `plan` or `apply` commands. Upload configuration triggers deployment runs automatically.

```bash
# 1. Upload configuration (triggers deployment runs)
terraform stacks configuration upload

# 2. Monitor deployments
terraform stacks deployment-run list                          # List runs (non-interactive)
terraform stacks deployment-group watch -deployment-group=... # Stream status updates

# 3. Approve deployments (if auto-approve not configured)
terraform stacks deployment-run approve-all-plans -deployment-run-id=...
terraform stacks deployment-group approve-all-plans -deployment-group=...
terraform stacks deployment-run cancel -deployment-run-id=...  # Cancel if needed
```

### Configuration Management

```bash
terraform stacks configuration list                    # List configuration versions
terraform stacks configuration fetch -configuration-id=...  # Download configuration
terraform stacks configuration watch                   # Monitor upload status
```

### Other Commands

```bash
terraform stacks create              # Create new Stack (interactive)
terraform stacks fmt                 # Format Stack files
terraform stacks list                # Show all Stacks
terraform stacks version             # Display version
terraform stacks deployment-group rerun -deployment-group=...  # Rerun deployment
```

## Monitoring Deployments with HCP Terraform API

For 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:

- Configuration status and validation
- Deployment group summaries
- Deployment run status
- Deployment step details (plan/apply)
- Error diagnostics with file locations and code snippets
- Stack outputs via artifacts endpoint

**Key points:**
- CLI watch commands stream indefinitely and don't work in automation
- Use artifacts endpoint to retrieve Stack outputs: `GET /api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description`
- Diagnostics endpoint requires `stack_deployment_step_id` query parameter
- Artifacts endpoint returns HTTP 307 redirect (use `curl -L`)

For complete API workflow, authentication, polling best practices, and example scripts, see `references/api-monitoring.md`.

## Common Patterns

**Component Dependencies**: Dependencies are automatically inferred when one component references another's output (e.g., `subnet_ids = component.vpc.private_subnet_ids`).

**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.

**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.).

For complete examples including multi-region deployments, component dependencies, deferred changes patterns, and linked Stacks, see `references/examples.md`.

## Best Practices

1. **Component Granularity**: Create components for logical infrastructure units that share a lifecycle
2. **Module Compatibility**:
   - Modules used with Stacks cannot include provider blocks (configure providers in Stack configuration)
   - **Test public registry modules** before using in production Stacks - some modules may have compatibility issues
   - Consider using raw resources for critical infrastructure if module compatibility is uncertain
   - Example: Some terraform-aws-modules versions have been found to have compatibility issues with Stacks (e.g., ALB and ECS modules)
3. **State Isolation**: Each deployment has its own isolated state
4. **Input Variables**: Use variables for values that differ across deployments; use locals for shared values
5. **Provider Lock Files**: Always generate and commit `.terraform.lock.hcl` to version control
6. **Naming Conventions**: Use descriptive names for components and deployments
7. **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
8. **Testing**: Test Stack configurations in dev/staging deployments before production

## Troubleshooting

**Circular Dependencies**: Refactor to break circular references or use intermediate components.

**Deployment Destruction**: Cannot destroy from UI. Set `destroy = true` in deployment block, upload configuration, and HCP Terraform creates a destroy run.

**Empty Diagnostics**: Add required `stack_deployment_step_id` query parameter to diagnostics API requests.

**Module Compatibility**: Test public registry modules before production use. Some modules may have compatibility issues with Stacks.

## References

For detailed documentation, see:
- `references/component-blocks.md` - Complete component block reference with all arguments and syntax
- `references/deployment-blocks.md` - Complete deployment block reference with all configuration options
- `references/linked-stacks.md` - Publish outputs and upstream inputs for linking Stacks together
- `references/examples.md` - Complete working examples for multi-region and component dependencies
- `references/api-monitoring.md` - Full API workflow for programmatic monitoring and automation
- `references/troubleshooting.md` - Detailed troubleshooting guide for common issues and solutions


================================================
FILE: terraform/module-generation/skills/terraform-stacks/references/api-monitoring.md
================================================
# API Monitoring Reference

Complete 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.

## Table of Contents

1. [When to Use the API](#when-to-use-the-api)
2. [Authentication](#authentication)
3. [API Monitoring Workflow](#api-monitoring-workflow)
4. [Detailed Endpoint Reference](#detailed-endpoint-reference)
5. [Notes for AI Agents and Automation](#notes-for-ai-agents-and-automation)

## When to Use the API

Use the HCP Terraform API instead of CLI commands when:
- Running in non-interactive environments (CI/CD, automation scripts)
- Building tools or integrations that need programmatic access
- Monitoring multiple Stacks simultaneously
- Implementing custom retry logic or error handling
- Working in environments where streaming CLI commands don't work

**CLI commands that don't work in automation:**
- `terraform stacks deployment-run watch` - Streams output, blocks indefinitely
- `terraform stacks deployment-group watch` - Streams output, blocks indefinitely
- `terraform stacks configuration watch` - Streams output, blocks indefinitely

## Authentication

### Extract API Token from Credentials File

```bash
TOKEN=$(jq -r '.credentials["app.terraform.io"].token' ~/.terraform.d/credentials.tfrc.json)
```

### Alternative: Use Environment Variable

```bash
export TFC_TOKEN="your-token-here"
TOKEN=$TFC_TOKEN
```

### API Request Headers

All API requests require these headers:

```bash
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/vnd.api+json"
```

## API Monitoring Workflow

After uploading a configuration with `terraform stacks configuration upload`, follow this sequence to monitor deployment progress:

### Step 1: Get Configuration Status

**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}`

**Purpose:** Verify configuration upload completed successfully and get the configuration details.

**Request:**

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/stack-configurations/{configuration-id}" | jq '.'
```

**Response Fields:**
- `attributes.status` - Configuration processing status (pending/completed)
- `attributes.sequence-number` - Version number of this configuration
- `attributes.components-detected` - Number of components found
- `attributes.deployments-detected` - Number of deployments found

**Example Response:**

```json
{
  "data": {
    "id": "stc-ABC123",
    "type": "stack-configurations",
    "attributes": {
      "status": "completed",
      "sequence-number": 5,
      "components-detected": 3,
      "deployments-detected": 2,
      "created-at": "2024-01-15T10:30:00.000Z",
      "updated-at": "2024-01-15T10:30:45.000Z"
    }
  }
}
```

### Step 2: Get Deployment Group Summaries

**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries`

**Purpose:** Get list of deployment groups, their IDs, and current status summary.

**Request:**

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries" | jq '.'
```

**Response Fields:**
- `id` - Deployment group ID (needed for next step)
- `attributes.name` - Deployment group name (e.g., `dev_default`)
- `attributes.status` - Overall status (running/succeeded/failed)
- `attributes.status-counts` - Breakdown of deployment statuses

**Example Response:**

```json
{
  "data": [
    {
      "id": "sdg-XYZ789",
      "type": "stack-deployment-group-summaries",
      "attributes": {
        "name": "dev_default",
        "status": "running",
        "status-counts": {
          "pending": 0,
          "running": 1,
          "succeeded": 1,
          "failed": 0
        }
      }
    }
  ]
}
```

### Step 3: Get Deployment Runs

**Endpoint:** `GET /api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs`

**Purpose:** Get list of deployment runs for a specific group with their current status.

**Request:**

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs" | jq '.'
```

**Response Fields:**
- `id` - Deployment run ID (needed for next step)
- `attributes.status` - Current status (planning/planned/applying/applied/failed)
- `attributes.created-at` - Run start time
- `attributes.updated-at` - Last update time

**Example Response:**

```json
{
  "data": [
    {
      "id": "sdr-123ABC",
      "type": "stack-deployment-runs",
      "attributes": {
        "status": "planning",
        "created-at": "2024-01-15T10:31:00.000Z",
        "updated-at": "2024-01-15T10:31:15.000Z"
      }
    }
  ]
}
```

### Step 4: Get Deployment Steps

**Endpoint:** `GET /api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps`

**Purpose:** Get detailed information about individual plan and apply steps.

**Request:**

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps" | jq '.'
```

**Response Fields:**
- `id` - Step ID (needed for diagnostics and outputs)
- `attributes.operation-type` - Type of operation (plan/apply)
- `attributes.status` - Step status (running/completed/failed)
- `attributes.component-name` - Which component is being processed

**Example Response:**

```json
{
  "data": [
    {
      "id": "sds-PlanStep123",
      "type": "stack-deployment-steps",
      "attributes": {
        "operation-type": "plan",
        "status": "completed",
        "component-name": "vpc",
        "created-at": "2024-01-15T10:31:05.000Z",
        "completed-at": "2024-01-15T10:31:30.000Z"
      }
    },
    {
      "id": "sds-ApplyStep456",
      "type": "stack-deployment-steps",
      "attributes": {
        "operation-type": "apply",
        "status": "running",
        "component-name": "vpc",
        "created-at": "2024-01-15T10:32:00.000Z"
      }
    }
  ]
}
```

### Step 5: Get Error Diagnostics (When Deployment Fails)

**Endpoint:** `GET /api/v2/stack-deployment-steps/{step-id}/stack-diagnostics`

**Purpose:** Retrieve detailed error messages when a deployment step fails.

**Critical:** The `stack_deployment_step_id` query parameter is **required**. Without it, the API returns empty results.

**Request:**

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}" | jq '.'
```

**Response Fields:**
- `attributes.severity` - Diagnostic level (error/warning)
- `attributes.summary` - Brief error description
- `attributes.detail` - Detailed error message
- `attributes.diags` - Array of diagnostic objects with file locations and code snippets

**Example Response (Error with Details):**

```json
{
  "data": [
    {
      "
Download .txt
gitextract_z9sttyor/

├── .claude-plugin/
│   └── marketplace.json
├── .copywrite.hcl
├── .github/
│   ├── .tessl/
│   │   └── skill-review-cache.json
│   └── workflows/
│       ├── tessl-skill-review-comment.yml
│       ├── tessl-skill-review.yml
│       └── validate.yml
├── AGENTS.md
├── CHANGELOG.md
├── CODEOWNERS
├── LICENSE
├── README.md
├── packer/
│   ├── README.md
│   ├── builders/
│   │   ├── .claude-plugin/
│   │   │   └── plugin.json
│   │   └── skills/
│   │       ├── aws-ami-builder/
│   │       │   └── SKILL.md
│   │       ├── azure-image-builder/
│   │       │   └── SKILL.md
│   │       └── windows-builder/
│   │           └── SKILL.md
│   └── hcp/
│       ├── .claude-plugin/
│       │   └── plugin.json
│       └── skills/
│           └── push-to-registry/
│               └── SKILL.md
├── scripts/
│   └── validate-structure.sh
└── terraform/
    ├── README.md
    ├── code-generation/
    │   ├── .claude-plugin/
    │   │   └── plugin.json
    │   └── skills/
    │       ├── azure-verified-modules/
    │       │   └── SKILL.md
    │       ├── terraform-search-import/
    │       │   ├── SKILL.md
    │       │   ├── references/
    │       │   │   └── MANUAL-IMPORT.md
    │       │   └── scripts/
    │       │       └── list_resources.sh
    │       ├── terraform-style-guide/
    │       │   ├── SECURITY.md
    │       │   └── SKILL.md
    │       └── terraform-test/
    │           ├── SKILL.md
    │           └── references/
    │               ├── CI_CD.md
    │               ├── EXAMPLES.md
    │               └── MOCK_PROVIDERS.md
    ├── module-generation/
    │   ├── .claude-plugin/
    │   │   └── plugin.json
    │   └── skills/
    │       ├── refactor-module/
    │       │   └── SKILL.md
    │       └── terraform-stacks/
    │           ├── SKILL.md
    │           └── references/
    │               ├── api-monitoring.md
    │               ├── component-blocks.md
    │               ├── deployment-blocks.md
    │               ├── examples.md
    │               ├── linked-stacks.md
    │               └── troubleshooting.md
    └── provider-development/
        ├── .claude-plugin/
        │   └── plugin.json
        └── skills/
            ├── new-terraform-provider/
            │   ├── SKILL.md
            │   └── assets/
            │       └── main.go
            ├── provider-actions/
            │   └── SKILL.md
            ├── provider-docs/
            │   ├── SKILL.md
            │   ├── agents/
            │   │   └── openai.yaml
            │   └── references/
            │       └── hashicorp-provider-docs.md
            ├── provider-resources/
            │   └── SKILL.md
            ├── provider-test-patterns/
            │   ├── SKILL.md
            │   └── references/
            │       ├── checks.md
            │       ├── ephemeral.md
            │       └── sweepers.md
            └── run-acceptance-tests/
                └── SKILL.md
Download .txt
SYMBOL INDEX (1 symbols across 1 files)

FILE: terraform/provider-development/skills/new-terraform-provider/assets/main.go
  function main (line 24) | func main() {
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (367K chars).
[
  {
    "path": ".claude-plugin/marketplace.json",
    "chars": 2499,
    "preview": "{\n  \"name\": \"hashicorp\",\n  \"owner\": {\n    \"name\": \"HashiCorp\"\n  },\n  \"metadata\": {\n    \"description\": \"Official HashiCor"
  },
  {
    "path": ".copywrite.hcl",
    "chars": 345,
    "preview": "schema_version = 1\n\nproject {\n  license        = \"MPL-2.0\"\n  copyright_year = 2025\n\n  # (OPTIONAL) A list of globs that "
  },
  {
    "path": ".github/.tessl/skill-review-cache.json",
    "chars": 79,
    "preview": "{\n  \"version\": \"1\",\n  \"last_updated\": \"2026-02-24T00:00:00Z\",\n  \"skills\": {}\n}\n"
  },
  {
    "path": ".github/workflows/tessl-skill-review-comment.yml",
    "chars": 2901,
    "preview": "# Companion workflow to tessl-skill-eval.yml.\n#\n# The main workflow (Tessl Skill Review) runs on pull_request events, wh"
  },
  {
    "path": ".github/workflows/tessl-skill-review.yml",
    "chars": 18461,
    "preview": "name: Tessl Skill Review\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - '**/SKILL.md'\n      - '**/skills/*"
  },
  {
    "path": ".github/workflows/validate.yml",
    "chars": 4550,
    "preview": "name: Validate Structure\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\npermissions:\n  contents"
  },
  {
    "path": "AGENTS.md",
    "chars": 9162,
    "preview": "# Agent Instructions\n\nThis repository contains agent skills and Claude Code plugins for HashiCorp products, including Te"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 643,
    "preview": "# Changelog\n\nAll notable changes to the HashiCorp Agent Skills.\n\n## Unreleased\n\n### Added\n- `terraform-search-import` sk"
  },
  {
    "path": "CODEOWNERS",
    "chars": 51,
    "preview": "# Default owner\n* @hashicorp/team-proj-mcp-servers\n"
  },
  {
    "path": "LICENSE",
    "chars": 16726,
    "preview": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\""
  },
  {
    "path": "README.md",
    "chars": 2053,
    "preview": "# HashiCorp Agent Skills\n\nA collection of Agent skills and Claude Code plugins for HashiCorp products.\n\n| Product | Use "
  },
  {
    "path": "packer/README.md",
    "chars": 1922,
    "preview": "# Packer Skills\n\nAgent skills for building machine images with Packer and HCP Packer.\n\n## Plugins\n\n### packer-builders\n\n"
  },
  {
    "path": "packer/builders/.claude-plugin/plugin.json",
    "chars": 447,
    "preview": "{\n  \"name\": \"packer-builders\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Packer builder skills for AWS, Azure, and Windows"
  },
  {
    "path": "packer/builders/skills/aws-ami-builder/SKILL.md",
    "chars": 3663,
    "preview": "---\nname: aws-ami-builder\ndescription: Build Amazon Machine Images (AMIs) with Packer using the amazon-ebs builder. Use "
  },
  {
    "path": "packer/builders/skills/azure-image-builder/SKILL.md",
    "chars": 4203,
    "preview": "---\nname: azure-image-builder\ndescription: Build Azure managed images and Azure Compute Gallery images with Packer. Use "
  },
  {
    "path": "packer/builders/skills/windows-builder/SKILL.md",
    "chars": 4635,
    "preview": "---\nname: windows-builder\ndescription: Build Windows images with Packer using WinRM communicator and PowerShell provisio"
  },
  {
    "path": "packer/hcp/.claude-plugin/plugin.json",
    "chars": 461,
    "preview": "{\n  \"name\": \"packer-hcp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"HCP Packer registry integration for tracking and manag"
  },
  {
    "path": "packer/hcp/skills/push-to-registry/SKILL.md",
    "chars": 4739,
    "preview": "---\nname: push-to-registry\ndescription: Push Packer build metadata to HCP Packer registry for tracking and managing imag"
  },
  {
    "path": "scripts/validate-structure.sh",
    "chars": 8541,
    "preview": "#!/bin/bash\n# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\n#\n# Validates the agent-skills reposito"
  },
  {
    "path": "terraform/README.md",
    "chars": 3970,
    "preview": "# Terraform Skills\n\nAgent skills for Terraform infrastructure-as-code development.\n\n## Plugins\n\n### terraform-code-gener"
  },
  {
    "path": "terraform/code-generation/.claude-plugin/plugin.json",
    "chars": 888,
    "preview": "{\n  \"name\": \"terraform-code-generation\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform code generation skills for Cl"
  },
  {
    "path": "terraform/code-generation/skills/azure-verified-modules/SKILL.md",
    "chars": 16261,
    "preview": "---\nname: azure-verified-modules\ndescription: Azure Verified Modules (AVM) requirements and best practices for developin"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/SKILL.md",
    "chars": 8924,
    "preview": "---\nname: terraform-search-import\ndescription: Discover existing cloud resources using Terraform Search queries and bulk"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/references/MANUAL-IMPORT.md",
    "chars": 2449,
    "preview": "# Manual Terraform Import Reference\n\nUse this workflow when your target resource type isn't supported by Terraform Searc"
  },
  {
    "path": "terraform/code-generation/skills/terraform-search-import/scripts/list_resources.sh",
    "chars": 1231,
    "preview": "#!/bin/bash\n# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\n# Extract list resources supported by T"
  },
  {
    "path": "terraform/code-generation/skills/terraform-style-guide/SECURITY.md",
    "chars": 4885,
    "preview": "---\nname: terraform-style-guide-security\ndescription: Generate Terraform HCL code following HashiCorp's security practic"
  },
  {
    "path": "terraform/code-generation/skills/terraform-style-guide/SKILL.md",
    "chars": 6926,
    "preview": "---\nname: terraform-style-guide\ndescription: Generate Terraform HCL code following HashiCorp's official style convention"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/SKILL.md",
    "chars": 11161,
    "preview": "---\nname: terraform-test\ndescription: Comprehensive guide for writing and running Terraform tests. Use when creating tes"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/CI_CD.md",
    "chars": 1927,
    "preview": "# CI/CD Integration\n\n## GitHub Actions\n\n```yaml\nname: Terraform Tests\n\non:\n  pull_request:\n    branches: [ main ]\n  push"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/EXAMPLES.md",
    "chars": 6607,
    "preview": "# Example Test Suite\n\nComplete example testing a VPC module with unit, integration, and mock tests.\n\n## Unit Tests (Plan"
  },
  {
    "path": "terraform/code-generation/skills/terraform-test/references/MOCK_PROVIDERS.md",
    "chars": 4517,
    "preview": "# Mock Providers\n\nMock providers simulate provider behavior without creating real infrastructure (Terraform 1.7.0+). Use"
  },
  {
    "path": "terraform/module-generation/.claude-plugin/plugin.json",
    "chars": 879,
    "preview": "{\n  \"name\": \"terraform-module-generation\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform module generation and refac"
  },
  {
    "path": "terraform/module-generation/skills/refactor-module/SKILL.md",
    "chars": 13626,
    "preview": "---\nname: refactor-module\ndescription: Transform monolithic Terraform configurations into reusable, maintainable modules"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/SKILL.md",
    "chars": 17189,
    "preview": "---\nname: terraform-stacks\ndescription: Comprehensive guide for working with HashiCorp Terraform Stacks. Use when creati"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/api-monitoring.md",
    "chars": 16608,
    "preview": "# API Monitoring Reference\n\nComplete guide for monitoring Terraform Stack deployments using the HCP Terraform API. Use t"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/component-blocks.md",
    "chars": 10165,
    "preview": "# Component Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack component conf"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/deployment-blocks.md",
    "chars": 8877,
    "preview": "# Deployment Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack deployment co"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/examples.md",
    "chars": 30765,
    "preview": "# Terraform Stacks Complete Examples\n\nComplete, working examples for common Terraform Stacks scenarios.\n\n## Table of Con"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/linked-stacks.md",
    "chars": 4341,
    "preview": "# Linked Stacks Reference\n\nComplete reference for linking Terraform Stacks together using published outputs and upstream"
  },
  {
    "path": "terraform/module-generation/skills/terraform-stacks/references/troubleshooting.md",
    "chars": 16675,
    "preview": "# Troubleshooting Reference\n\nCommon issues and solutions when working with Terraform Stacks.\n\n## Table of Contents\n\n1. ["
  },
  {
    "path": "terraform/provider-development/.claude-plugin/plugin.json",
    "chars": 544,
    "preview": "{\n  \"name\": \"terraform-provider-development\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Terraform provider development ski"
  },
  {
    "path": "terraform/provider-development/skills/new-terraform-provider/SKILL.md",
    "chars": 896,
    "preview": "---\nname: new-terraform-provider\ndescription: Use this when scaffolding a new Terraform provider.\nlicense: MPL-2.0\nmetad"
  },
  {
    "path": "terraform/provider-development/skills/new-terraform-provider/assets/main.go",
    "chars": 1141,
    "preview": "// 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"
  },
  {
    "path": "terraform/provider-development/skills/provider-actions/SKILL.md",
    "chars": 13942,
    "preview": "---\nname: provider-actions\ndescription: Implement Terraform Provider actions using the Plugin Framework. Use when develo"
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/SKILL.md",
    "chars": 3339,
    "preview": "---\nname: provider-docs\ndescription: Create, update, and review Terraform provider documentation for Terraform Registry "
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/agents/openai.yaml",
    "chars": 343,
    "preview": "# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\ninterface:\n  display_name: \"Terraform Provider Docs"
  },
  {
    "path": "terraform/provider-development/skills/provider-docs/references/hashicorp-provider-docs.md",
    "chars": 2075,
    "preview": "# HashiCorp Provider Documentation Reference\n\nSource of truth for this skill:\n- https://developer.hashicorp.com/terrafor"
  },
  {
    "path": "terraform/provider-development/skills/provider-resources/SKILL.md",
    "chars": 16650,
    "preview": "---\nname: provider-resources\ndescription: Implement Terraform Provider resources and data sources using the Plugin Frame"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/SKILL.md",
    "chars": 12917,
    "preview": "---\nname: provider-test-patterns\ndescription: >-\n  Terraform provider acceptance test patterns using terraform-plugin-te"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/checks.md",
    "chars": 6844,
    "preview": "# State Checks and Plan Checks Reference\n\nDetailed reference for `statecheck` and `plancheck` packages from\n`terraform-p"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/ephemeral.md",
    "chars": 5998,
    "preview": "# Ephemeral Resource Testing Reference\n\nTesting patterns for ephemeral resources using `terraform-plugin-testing`.\nEphem"
  },
  {
    "path": "terraform/provider-development/skills/provider-test-patterns/references/sweepers.md",
    "chars": 2401,
    "preview": "# Test Sweepers Reference\n\nSweepers clean up infrastructure resources that leak during acceptance tests —\nwhen test infr"
  },
  {
    "path": "terraform/provider-development/skills/run-acceptance-tests/SKILL.md",
    "chars": 1751,
    "preview": "---\nname: run-acceptance-tests\ndescription: Guide for running acceptance tests for a Terraform provider. Use this when a"
  }
]

About this extraction

This page contains the full source code of the hashicorp/agent-skills GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (335.7 KB), approximately 84.7k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!