Skill: iliaal/compound-eng-terraform (version 3.0.5)

--- 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 ## 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.hcl ``` Keep 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. ## Testing | Situation | Approach | |-----------|----------| | Quick validation | `terraform fmt -check && terraform validate` | | Pre-commit | + `tflint` + `trivy config .` / `checkov -d .` | | Logic validation (1.6+) | Native `terraform test` with `command = plan` | | Cost-free unit tests (1.7+) | Native tests + `mock_provider` | | Real infra validation | Native tests with `command = apply`, or Terratest (Go) | **Native test essentials** (`.tftest.hcl` in `tests/`): - `command = plan` for fast unit tests; `command = apply` for integration (default) - `assert { condition = expr; error_message = "..." }` -- multiple per run block - `expect_failures = [var.name]` for negative testing (validate rejection of bad input) - `mock_provider "aws" { mock_resource "..." { defaults = { ... } } }` -- plan-mode only, no credentials, fast CI - `variables {}` at file level (all runs) or within a `run` block (override) - Reference prior run outputs: `run.setup.vpc_id` - `parallel = true` on independent runs with separate state -- creates sync point at next sequential run - `state_key = "name"` required for `parallel = true` runs with independent state - File naming: `*_unit_test.tftest.hcl` (plan mode) vs `*_integration_test.tftest.hcl` (apply mode) ## Version Pinning | Component | Strategy | Example | |-----------|----------|---------| | Terraform | Pin minor | `required_version = "~> 1.9"` | | Providers | Pin major | `version = "~> 5.0"` | | Modules (prod) | Pin exact | `version = "5.1.2"` | | Modules (dev) | Allow patch | `version = "~> 5.1"` | 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, preview): orchestrates multiple configs as a single deployment unit -- evaluate for multi-environment patterns. ## State & Security - Remote backend with locking: S3+DynamoDB, Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. - Encrypt state at rest. Never commit `.tfstate`, `.terraform/`, or `*.tfplan`. Always commit `.terraform.lock.hcl`. - `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: `terraform fmt -recursive && terraform validate && trivy config .` - `moved { from = old; to = new }` for refactoring resource names/modules without destroy-recreate. Remove block after apply. ## Troubleshooting - State lock stuck: `terraform force-unlock <ID>` -- only after confirming no other operation running - Resource drift: `terraform plan -refresh-only` to detect, `terraform apply -refresh-only` to accept - Replace tainted: `terraform apply -replace=ADDR` (not deprecated `terraform taint`) - Import existing: `import` blocks (1.5+) for declarative import, or `terraform import ADDR ID` ## Dependency Management Use `locals` with `try()` to control deletion ordering without explicit `depends_on`: ```hcl locals { vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this.id, "") } ``` This forces Terraform to destroy subnets before CIDR associations -- prevents deletion errors. - `cidrsubnet(var.vpc_cidr, 8, count.index)` for calculated subnet CIDRs -- never hardcode subnets - Multi-region: `provider "aws" { alias = "eu_west_1" }` + `providers = { aws = aws.eu_west_1 }` in module blocks ## Verify Run before declaring done: ```bash terraform fmt -check && terraform validate && tflint && trivy config . ``` All commands must pass with zero errors.

path: SPEC.md
content: # ia-terraform Specification

## Intent

`ia-terraform` is a `language`-class skill (stack-specific patterns and idioms). 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.

## Scope

In scope:
- Behaviors described in `SKILL.md` and routed via the should_trigger phrasings in `distillery/tests/fixtures/triggers/ia-terraform.jsonl`.
- Updates to runtime behavior, structure, trigger precision, references, and validation.

Out of scope:
- Acting as the runtime instructions themselves (those live in `SKILL.md`).
- Trigger phrasings already covered by adjacent `ia-*` skills (`validate-plugin` flags >70% description overlap as DUPLICATE_TRIGGER).
- <!-- to fill in: domain-specific exclusions when the skill drifts -->

## Trigger Context

- Class: `language`
- Hook regex: `plugins/compound-engineering/hooks/skill-patterns.sh` -> `SKILL_PATTERNS[ia-terraform]`
- Common requests (from fixture should_trigger):
  - "write a terraform module for the VPC and subnets"
  - "review the infrastructure as code for the staging environment"
  - "write a Terraform module for the VPC"
- Should not trigger for (from fixture should_not_trigger):
  - "implement the shopping cart feature in React"
  - "add PHPUnit tests for the order service"
  - "write a Pulumi program for the same setup"

## Source And Evidence Model

Authoritative sources:

- `SKILL.md` -- runtime instructions and reference routing.
- `references/*.md` -- bundled supplementary content (0 file(s)).
- `distillery/tests/fixtures/triggers/ia-terraform.jsonl` -- positive and negative trigger phrasings under regression test.
- `plugins/compound-engineering/hooks/skill-patterns.sh` -- regex pattern that fires this skill.
- `distillery/.eval-data/ia-terraform/` -- harvested session examples (when present).

Data that must not be stored in this skill or its references:

- Secrets, credentials, tokens.
- Machine-specific filesystem paths (`/home/...`, `/Users/...`, `~/ai/...`). The validator (`MACHINE_PATH_LEAK`) flags these as HIGH.
- Private URLs, customer data, or unredacted personal information.

### Coverage matrix

| Dimension | Status | Evidence |
|---|---|---|
| Trigger fixtures | complete | distillery/tests/fixtures/triggers/ia-terraform.jsonl (>=5 should_trigger, >=5 should_not_trigger) |
| Hook regex pattern | complete | plugins/compound-engineering/hooks/skill-patterns.sh (`SKILL_PATTERNS[ia-terraform]`) |
| Reference architecture | n/a | no references; SKILL.md is self-contained |
| Real-usage signal | <!-- populated by harvest-sessions when sessions exist --> | distillery/.eval-data/ia-terraform/ (created by harvest-sessions) |

## Evaluation

Lightweight (run on every change):

```bash
python3 distillery/scripts/distiller.py validate-plugin --component ia-terraform
python3 distillery/scripts/distiller.py test-triggers --skill ia-terraform
```

Deeper (when behavior risk warrants):

```bash
python3 distillery/scripts/distiller.py dspy-eval ia-terraform
python3 distillery/scripts/distiller.py diagnose-negatives ia-terraform
```

Acceptance gates:
- `validate-plugin --component ia-terraform` returns 0 HIGH findings.
- `test-triggers --skill ia-terraform` returns F1 = 1.0 with floors of 5 should_trigger and 5 should_not_trigger.
- For dspy-eval, the composite score does not regress against the most recent saved baseline (see `distillery/.eval-data/ia-terraform/history.json`).

## Known Limitations

<!-- to fill in over time as drift surfaces. Default rule: any time diagnose-negatives
     surfaces a recurring failure pattern, document it here so future maintainers
     understand the trade-off the current implementation accepts. -->

## Maintenance Notes

- Update `SKILL.md` when the runtime workflow, branch conditions, or output contract changes.
- Update this `SPEC.md` when intent, scope, evidence model, evaluation gates, or maintenance expectations change.
- Update the trigger fixture when adding new positive phrasings, removing stale ones, or expanding scope (the 5/5 floor is a hard validator gate).
- Update the hook regex in `skill-patterns.sh` whenever fixture positives expose a missed phrasing; verify F1 = 1.0 with `eval-triggers` before committing.
- Run the full release pipeline via `/release` -- never bump versions or update CHANGELOG.md from a per-skill edit.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)