/ia-terraform
Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest, state migration, or IaC patterns.
Claude Code
$ npx -y skills add iliaal/whetstone --skill ia-terraform --agent claude-code
Codex
$ npx skills add iliaal/whetstone --skill ia-terraform -a codex -y
Cursor
$ npx skills add iliaal/whetstone --skill ia-terraform -a cursor -y
Gemini CLI
$ npx skills add iliaal/whetstone --skill ia-terraform -a gemini-cli -y
GitHub Copilot
$ npx skills add iliaal/whetstone --skill ia-terraform -a github-copilot -y
OpenCode
$ npx skills add iliaal/whetstone --skill ia-terraform -a opencode -y
Windsurf
$ npx skills add iliaal/whetstone --skill ia-terraform -a windsurf -y
SKILL.md
ia-terraform.SKILL.mdname: ia-terraform
class: language
description: >-
Terraform and OpenTofu configuration, modules, testing, state management, and
HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest,
state migration, or IaC patterns.
paths:
- "**/*.tf"
- "**/*.tfvars"
Terraform & OpenTofu
Working rules
- Preserve state and resource addresses during refactoring; inspect the plan for unintended replacement.
- Separate tests with verified local or mocked effects from tests that access real services or create billable infrastructure. Plan mode can still read real data sources; mocked apply can run without infrastructure creation.
File Organization & Naming
| File | Purpose | |------|---------| | `terraform.tf` | Terraform + provider version requirements | | `providers.tf` | Provider configurations | | `main.tf` | Primary resources and data sources | | `variables.tf` | Input variables (alphabetical) | | `outputs.tf` | Output values (alphabetical) | | `locals.tf` | Local values |
- Lowercase with underscores: `web_api`, not `webAPI` or `web-api`
- Descriptive nouns excluding resource type: `aws_instance.web_api` not `aws_instance.web_api_instance`
- Singular, not plural
- `this` for singleton resources (one of that type per module)
- Contextual variable prefixes: `vpc_cidr_block` not `cidr`
Block Ordering
**Resources:** `count`/`for_each` (blank line after) → arguments → nested blocks → `tags` → `depends_on` → `lifecycle` (last)
**Variables:** `description` → `type` → `default` → `validation` → `nullable`
Every variable needs `type` + `description`. Every output needs `description`. Mark secrets `sensitive = true`.
Module Structure
| Type | Scope | Example | |------|-------|---------| | Resource Module | Single logical group | VPC + subnets, SG + rules | | Infrastructure Module | Collection of resource modules | Networking + compute for one region | | Composition | Complete infrastructure | Spans regions/accounts |
module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│ ├── minimal/
│ └── complete/
└── tests/
└── defaults.tftest.hclKeep modules small (single responsibility). `examples/` double as documentation and integration test fixtures. Semantic versioning for all published modules.
count vs for_each
| Scenario | Use | |----------|-----| | Boolean toggle (create or skip) | `count = condition ? 1 : 0` | | Named/keyed items that may reorder | `for_each = toset(list)` or `map` | | Fixed identical replicas | `count = N` |
Default to `for_each`: removing a middle item from a `count` list recreates all subsequent resources. Use `count` only for boolean conditionals or truly identical replicas.
Version Pinning
| Component | Strategy | Example | |-----------|----------|---------| | Terraform | Pin minor | `required_version = "~> 1.9.0"` | | Providers | Pin major | `version = "~> 5.0"` | | Modules (prod) | Pin exact | `version = "5.1.2"` | | Modules (dev) | Allow patch | `version = "~> 5.1.0"` |
Key modern features: `moved` blocks (1.1+), `optional()` with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+). Stacks (HCP; check current release status): orchestrates multiple configs as a single deployment unit. Evaluate for multi-environment patterns.
State & Security
- Remote backend with locking: S3 with `use_lockfile = true` (1.10+), Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. DynamoDB-based S3 locking (`dynamodb_table`) is deprecated and slated for removal; prefer `use_lockfile`. Both may be set at once while migrating an existing table off.
- OpenTofu-only: `terraform { encryption { key_provider "pbkdf2" "k" {...} method "aes_gcm" "m" { keys = key_provider.pbkdf2.k } state { method = method.aes_gcm.m } plan { method = method.aes_gcm.m } } }` encrypts state and plan files client-side (or via `TF_ENCRYPTION`). Roll out with a `fallback { method = method.unencrypted.x }` so existing plaintext state still loads, and never rename a key provider or method without a `fallback` block. OpenTofu also accepts `var.*`/`local.*` in `backend {}` arguments and in module `source`/`version` (resolved at `init`; no state or provider-function references); the same HCL is a hard error in Terraform ("A backend block cannot refer to named values").
- Encrypt state at rest. Never commit `.tfstate`, `.terraform/`, or `*.tfplan`. Always commit `.terraform.lock.hcl`.
- `sensitive = true` redacts CLI output (plan, apply, `terraform output`) only; the value is still stored in plaintext state. Keep secrets out of state in this order: provider-managed secrets (for example `manage_master_user_password = true`), then an `ephemeral` resource (1.10+) feeding a write-only `*_wo` argument (1.11+). Write-only values never enter plan or state, so changing one produces no diff: increment its `*_wo_version` companion argument (a provider convention, not enforced by core) to push a rotation.
- `default_tags` on provider for consistent resource tagging.
- Encryption at rest on all storage. Private networking by default; public access is opt-in.
- Least-privilege security groups. No `0.0.0.0/0` ingress without explicit justification.
- Never hardcode credentials; use assume_role, OIDC, or secrets managers.
- Pre-commit: auto-format first (`terraform fmt -recursive` rewrites files), then verify (`terraform validate && tflint && trivy config .`)
- Use `moved` blocks with `from` and `to` addresses for refactoring resource names/modules without destroy-recreate. Retain historical moves for downstream upgrades; remove only after every affected state has migrated, or as an explicitly breaking module release.
- `lifecycle { ignore_changes = [attr] }` suppresses **updates only**, and it substitutes the prior state value at plan time, on the *first* plan after the config change, with no "first apply" exception. Two conseq
Read more
name: ia-terraform class: language description: >- Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest, state migration, or IaC patterns. paths: - "**/*.tf" - "**/*.tfvars"
Terraform & OpenTofu
Working rules
- Preserve state and resource addresses during refactoring; inspect the plan for unintended replacement.
- Separate tests with verified local or mocked effects from tests that access real services or create billable infrastructure. Plan mode can still read real data sources; mocked apply can run without infrastructure creation.
File Organization & Naming
| File | Purpose | |------|---------| | `terraform.tf` | Terraform + provider version requirements | | `providers.tf` | Provider configurations | | `main.tf` | Primary resources and data sources | | `variables.tf` | Input variables (alphabetical) | | `outputs.tf` | Output values (alphabetical) | | `locals.tf` | Local values |
- Lowercase with underscores: `web_api`, not `webAPI` or `web-api`
- Descriptive nouns excluding resource type: `aws_instance.web_api` not `aws_instance.web_api_instance`
- Singular, not plural
- `this` for singleton resources (one of that type per module)
- Contextual variable prefixes: `vpc_cidr_block` not `cidr`
Block Ordering
**Resources:** `count`/`for_each` (blank line after) → arguments → nested blocks → `tags` → `depends_on` → `lifecycle` (last)
**Variables:** `description` → `type` → `default` → `validation` → `nullable`
Every variable needs `type` + `description`. Every output needs `description`. Mark secrets `sensitive = true`.
Module Structure
| Type | Scope | Example | |------|-------|---------| | Resource Module | Single logical group | VPC + subnets, SG + rules | | Infrastructure Module | Collection of resource modules | Networking + compute for one region | | Composition | Complete infrastructure | Spans regions/accounts |
module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│ ├── minimal/
│ └── complete/
└── tests/
└── defaults.tftest.hclKeep modules small (single responsibility). `examples/` double as documentation and integration test fixtures. Semantic versioning for all published modules.
count vs for_each
| Scenario | Use | |----------|-----| | Boolean toggle (create or skip) | `count = condition ? 1 : 0` | | Named/keyed items that may reorder | `for_each = toset(list)` or `map` | | Fixed identical replicas | `count = N` |
Default to `for_each`: removing a middle item from a `count` list recreates all subsequent resources. Use `count` only for boolean conditionals or truly identical replicas.
Version Pinning
| Component | Strategy | Example | |-----------|----------|---------| | Terraform | Pin minor | `required_version = "~> 1.9.0"` | | Providers | Pin major | `version = "~> 5.0"` | | Modules (prod) | Pin exact | `version = "5.1.2"` | | Modules (dev) | Allow patch | `version = "~> 5.1.0"` |
Key modern features: `moved` blocks (1.1+), `optional()` with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+). Stacks (HCP; check current release status): orchestrates multiple configs as a single deployment unit. Evaluate for multi-environment patterns.
State & Security
- Remote backend with locking: S3 with `use_lockfile = true` (1.10+), Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. DynamoDB-based S3 locking (`dynamodb_table`) is deprecated and slated for removal; prefer `use_lockfile`. Both may be set at once while migrating an existing table off.
- OpenTofu-only: `terraform { encryption { key_provider "pbkdf2" "k" {...} method "aes_gcm" "m" { keys = key_provider.pbkdf2.k } state { method = method.aes_gcm.m } plan { method = method.aes_gcm.m } } }` encrypts state and plan files client-side (or via `TF_ENCRYPTION`). Roll out with a `fallback { method = method.unencrypted.x }` so existing plaintext state still loads, and never rename a key provider or method without a `fallback` block. OpenTofu also accepts `var.*`/`local.*` in `backend {}` arguments and in module `source`/`version` (resolved at `init`; no state or provider-function references); the same HCL is a hard error in Terraform ("A backend block cannot refer to named values").
- Encrypt state at rest. Never commit `.tfstate`, `.terraform/`, or `*.tfplan`. Always commit `.terraform.lock.hcl`.
- `sensitive = true` redacts CLI output (plan, apply, `terraform output`) only; the value is still stored in plaintext state. Keep secrets out of state in this order: provider-managed secrets (for example `manage_master_user_password = true`), then an `ephemeral` resource (1.10+) feeding a write-only `*_wo` argument (1.11+). Write-only values never enter plan or state, so changing one produces no diff: increment its `*_wo_version` companion argument (a provider convention, not enforced by core) to push a rotation.
- `default_tags` on provider for consistent resource tagging.
- Encryption at rest on all storage. Private networking by default; public access is opt-in.
- Least-privilege security groups. No `0.0.0.0/0` ingress without explicit justification.
- Never hardcode credentials; use assume_role, OIDC, or secrets managers.
- Pre-commit: auto-format first (`terraform fmt -recursive` rewrites files), then verify (`terraform validate && tflint && trivy config .`)
- Use `moved` blocks with `from` and `to` addresses for refactoring resource names/modules without destroy-recreate. Retain historical moves for downstream upgrades; remove only after every affected state has migrated, or as an explicitly breaking module release.
- `lifecycle { ignore_changes = [attr] }` suppresses **updates only**, and it substitutes the prior state value at plan time, on the *first* plan after the config change, with no "first apply" exception. Two conseq
A Claude Code plugin that makes AI coding agents follow engineering discipline. Plan before coding. Verify before claiming done. Find root cause before patching. Review before merge. Skills activate based on file type and task signals, not manual toggling.
Other skills on whetstone.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about ia-terraform.
- Which coding agents can use ia-terraform?
- ia-terraform installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

