Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"version": "1.8.0",
"metadata": {
"description": "Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops \u2014 diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.",
"description": "Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops - diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.",
"repository": "https://github.com/antonbabenko/terraform-skill",
"license": "Apache-2.0"
},
"plugins": [
{
"name": "terraform-skill",
"description": "Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops \u2014 diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.",
"description": "Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops - diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.",
"source": "./",
"category": "development",
"keywords": [
Expand All @@ -24,7 +24,9 @@
"testing",
"ci-cd",
"security-scanning",
"modules"
"modules",
"lsp",
"terraform-ls"
],
"version": "1.8.0"
}
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ terraform-skill/
├── .claude-plugin/marketplace.json # Plugin metadata (version synced automatically)
├── skills/
│ └── terraform-skill/ # Skill autodiscovered by Claude Code plugin system
│ ├── SKILL.md # Core skill file (~277 lines)
│ ├── SKILL.md # Core skill file (~299 lines)
│ └── references/ # Reference files loaded on demand
│ ├── ci-cd-workflows.md
│ ├── code-intelligence-lsp.md
│ ├── code-patterns.md
│ ├── module-patterns.md
│ ├── quick-reference.md
Expand Down Expand Up @@ -125,7 +126,7 @@ When adding content, ask: **decision framework or key pattern → SKILL.md; deta
- **Scannable format:** tables > bullets > prose
- **✅ DO / ❌ DON'T** side-by-side for non-obvious patterns
- **Version-specific features** clearly marked (e.g., `Terraform 1.6+`)
- **Token budget:** SKILL.md target <300 lines (see LLM Consumption Rules); currently ~277
- **Token budget:** SKILL.md target <300 lines (see LLM Consumption Rules); currently ~299

### LLM Consumption Rules (enforce in every PR review)

Expand Down
67 changes: 63 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![OpenTofu](https://img.shields.io/badge/OpenTofu-1.6+-FFD814)](https://opentofu.org/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)

Terraform and OpenTofu best-practices skill for AI coding agents (Claude Code, Cursor, Copilot, Gemini CLI, OpenCode, Codex, and others). Covers testing strategies, module patterns, CI/CD workflows, and production infrastructure code.
A best-practices skill for Terraform and OpenTofu, for AI coding agents (Claude Code, Cursor, Copilot, Gemini CLI, OpenCode, Codex, and more). It helps the agent test code, structure modules, set up CI/CD, and write production infrastructure code.

## What this skill provides

Expand Down Expand Up @@ -43,11 +43,14 @@ Terraform and OpenTofu best-practices skill for AI coding agents (Claude Code, C

## Installation

This plugin is distributed via Claude Code marketplace using `.claude-plugin/marketplace.json`.
Installed through one Claude Code marketplace, `antonbabenko/agent-plugins`
(terraform-skill is listed there as an external plugin). Do not also add
`antonbabenko/terraform-skill` as a marketplace - both use the same marketplace
name and will clash.

### Quick install (any agent)

Universal installer via [skills.sh](https://skills.sh/) — works with any [Agent Skills](https://agentskills.io)-compatible tool:
Works with any [Agent Skills](https://agentskills.io)-compatible tool, via [skills.sh](https://skills.sh/):

```bash
npx skills add https://github.com/antonbabenko/terraform-skill
Expand All @@ -61,7 +64,7 @@ npx skills add https://github.com/antonbabenko/terraform-skill
<summary>Claude Code</summary>

```bash
/plugin marketplace add antonbabenko/terraform-skill
/plugin marketplace add antonbabenko/agent-plugins
/plugin install terraform-skill@antonbabenko
```

Expand Down Expand Up @@ -159,6 +162,33 @@ After installation, try:

Claude picks up the skill automatically when working with Terraform or OpenTofu code.

## Recommended companion: code-intelligence

Install the `code-intelligence` plugin alongside this one:

```bash
/plugin marketplace add antonbabenko/agent-plugins
/plugin install code-intelligence@antonbabenko
```

It holds the general, any-language rules for navigating code (when to use a
language server, plain text search, or fuzzy search; how to anchor a lookup to
a position; what to do when a tool fails; saying so when one tool is swapped
for another). terraform-skill is the Terraform-specific version of those rules.
Why install it:

- **Fewer tokens** - the rules live in one place. The agent loads them when
needed instead of repeating them in every language skill.
- **More accurate** - it finds definitions and references by meaning, not by
plain text matching, so renames and refactors do not miss spots or change
the wrong ones.
- **Faster** - it picks the right tool the first time instead of retrying,
and says up front when it had to use a different one.

terraform-skill works on its own without it. The name `code-intelligence` is
not unique; if a `code-intelligence` skill is active, check it is the one from
[antonbabenko/agent-plugins](https://github.com/antonbabenko/agent-plugins).

## Quick start examples

**Create a module with tests:**
Expand Down Expand Up @@ -222,6 +252,35 @@ Side-by-side DO vs DON'T examples for variable naming, resource naming, module c
- Terraform 1.0+ or OpenTofu 1.6+
- Optional: [Terraform MCP server](https://github.com/hashicorp/terraform-mcp-server) for registry integration

## Code intelligence (optional)

The skill works without a language server. To jump to a definition, find
references, outline a file, or show hover docs, it can also use
[terraform-ls](https://github.com/hashicorp/terraform-ls), HashiCorp's official
Terraform language server.

- **Optional.** Without terraform-ls the skill falls back to text search
(`rg`) plus reading files. Nothing breaks; you get text matches instead of
matches by meaning.
- **Needs.** A local `terraform` (or `tofu`) binary on `PATH`, and
`terraform init` run in the workspace, before it can resolve names across
modules and providers.
- **Install.** Get it from the
[terraform-ls releases](https://github.com/hashicorp/terraform-ls/releases)
page, or turn it on through your editor or agent host. Use whatever version
your host supports.

How the skill uses it:

- Use the language server to follow a name to where it is defined or used; use
`rg` plus reading files for exact text, known names, `.tfvars`, comments, and
non-HCL files.
- Point the language server at a spot in the file first (find an occurrence,
then ask about that position).
- terraform-ls cannot rename for you. To rename a variable, local, or output:
find every reference, then edit each by hand. To rename a resource or module
address: use a `moved` block, not a text replace.

## Contributing

See [CLAUDE.md](CLAUDE.md) for skill development guidelines, content structure, how to propose improvements, and the validation approach.
Expand Down
26 changes: 25 additions & 1 deletion skills/terraform-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: terraform-skill
description: Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.
description: Use when writing, reviewing, or debugging Terraform/OpenTofu modules, tests, CI, scans, or state ops - diagnoses failure mode (identity churn, secrets, blast radius, CI drift, state corruption) with version-aware guards.
license: Apache-2.0
metadata:
author: Anton Babenko
Expand Down Expand Up @@ -46,6 +46,7 @@ Never recommend direct production apply without a reviewed plan artifact and app
| **State corruption / recovery** | Stuck lock, backend migration, drift reconciliation | [State Management](references/state-management.md) |
| **Provider upgrade risk** | Breaking-change provider bump, unpinned modules | [Code Patterns: versions](references/code-patterns.md#version-management), [Module Patterns](references/module-patterns.md) |
| **Provider lifecycle** | Removing a provider with resources still in state, orphaned resources, `removed` block usage | [State Management: Provider Removal](references/state-management.md#provider-removal) |
| **Navigation / safe-rename blind spots** | Cannot locate symbol defs/refs semantically, value-symbol rename done as blind text replace, grep-only refactor missing refs, hallucinated `rg` shim | [Code Intelligence](references/code-intelligence-lsp.md#terraform-ls-capability-matrix) |

## When to Use This Skill

Expand Down Expand Up @@ -258,6 +259,28 @@ Before emitting a feature, verify the runtime floor. See [Code Patterns: Feature
- **1.11+**: `write_only` arguments for secret handling keep credentials out of state.
- **Terraform vs OpenTofu**: both supported. For licensing, governance, and feature delta, see [Quick Reference: Terraform vs OpenTofu](references/quick-reference.md#terraform-vs-opentofu-comparison).

## Code Intelligence (terraform-ls)

Semantic navigation for HCL. terraform-ls is optional; without it every row below degrades to a disclosed `rg` + Read fallback.

Self-contained terraform-ls layer of a generic code-intelligence discipline - apply the rows below directly.

| Goal | Use | Tradeoff |
|------|-----|----------|
| Find definition / all references | terraform-ls `goToDefinition` / `findReferences` | Needs `init` + a position anchor |
| Rename value symbol (var/local/output/provider alias) | Manual: `findReferences` -> per-file fresh Read -> edit -> `validate` | No rename provider |
| Rename resource/module address | `moved` block + `plan` shows 0 destroy | Text rename forces destroy/recreate |
| Exact text / known name / `.tfvars` / non-HCL | `rg` + Read | No semantic scope |

✅ Supported: `goToDefinition`, `findReferences`, `documentSymbol`, `hover`, `workspaceSymbol`.
❌ Unsupported: `goToImplementation`, call hierarchy, rename provider. Do not call these then report their absence as a finding.

- ✅ Prereq: local `terraform`/`tofu` on PATH, `terraform init` run; cold start may need one retry.
- ✅ LSP calls are position-anchored (`file:line:character`) - anchor with `rg` first, never symbol-name-only.
- ❌ Do not claim "LSP broken, using rg" until the [Degradation Gate](references/code-intelligence-lsp.md#degradation-gate) passes; disclose any tool substitution on the first line.

Depth: [Code Intelligence](references/code-intelligence-lsp.md#terraform-ls-capability-matrix).

## Reference Files

Progressive disclosure — essentials here, depth on demand:
Expand All @@ -268,6 +291,7 @@ Progressive disclosure — essentials here, depth on demand:
- [Security & Compliance](references/security-compliance.md) — trivy/checkov, secrets handling, compliance mappings
- [State Management](references/state-management.md) — backends, locking, migration, multi-team, recovery
- [Code Patterns](references/code-patterns.md) — block ordering, `count`/`for_each` deep dive, modern features, version management, locals
- [Code Intelligence](references/code-intelligence-lsp.md) - terraform-ls capabilities, position-anchored calls, manual rename, degradation gate
- [Quick Reference](references/quick-reference.md) — command cheat sheets, flowcharts, troubleshooting

## License
Expand Down
102 changes: 102 additions & 0 deletions skills/terraform-skill/references/code-intelligence-lsp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Code Intelligence (terraform-ls)

Semantic navigation for HCL via the `terraform-ls` language server. Use the LSP
for symbol relationships; use `rg` + Read for text. Pick by task, not by habit.

terraform-ls is optional. Without it, every operation below degrades to a
disclosed `rg` + Read fallback (see Degradation Gate). It is not a hard
dependency of this skill.

Self-contained terraform-ls specialization (capability matrix,
`terraform init`, `moved` blocks, `.tfvars`) of a generic code-intelligence
discipline - apply it directly.

Security: `terraform init` may download modules and providers over the network.
Do not auto-run it in untrusted working directories. terraform-ls indexes local
source only; it does not execute the configuration.

### terraform-ls Capability Matrix

Anchor target for the SKILL.md diagnose row. What the server can and cannot do.

| Operation | Supported | Semantic guarantee |
|-----------|-----------|--------------------|
| `goToDefinition` | ✅ | Jumps usage -> declaration (var/local/output/module/resource) |
| `findReferences` | ✅ | Enumerates references to the symbol at the given position, workspace-scoped |
| `documentSymbol` | ✅ | Outline of one file (blocks, variables, outputs, resources, modules) |
| `hover` | ✅ | Provider/resource attribute docs and inferred type at position |
| `workspaceSymbol` | ✅ | Broad symbol inventory; expensive, avoid for single-name lookup |
| `goToImplementation` | ❌ | Not implemented by terraform-ls |
| `prepareCallHierarchy` / `incomingCalls` / `outgoingCalls` | ❌ | No call hierarchy in terraform-ls |
| rename provider | ❌ | No server-side rename; renames are manual (see below) |

- ❌ Do not call unsupported operations and report their absence as a finding.
Redirect call-hierarchy intent to `findReferences`.
- ✅ Treat `findReferences` as the authoritative reference set once the
Degradation Gate passes.

### Position-Anchored Calls

terraform-ls resolves by source position, not by symbol name.

- ✅ Call with `file:line:character` pointing at an occurrence of the symbol.
- ✅ Anchor the position first with `rg`/Grep (find a known occurrence), then
issue the LSP call at that location.
- ❌ Never pass a bare symbol name and expect resolution. A name-only call
returning empty is a usage defect, not server degradation.
- ✅ Prereq: a local `terraform` (or `tofu`) binary on PATH, and
`terraform init` run in the workspace, before relying on cross-module or
provider resolution.
- ✅ Cold start: the first call after server launch may return empty while
indexing. Retry once before concluding anything.

### Manual Rename Workflow

terraform-ls has no rename provider. Branch by what is being renamed.

**(a) Value symbol** - variable, local, output, or provider alias:

1. `findReferences` at an anchored position to enumerate every reference.
2. For EACH file with references: do a fresh Read of that file immediately
before editing it. Offsets shift after a prior in-file edit; a stale view
produces corrupted edits.
3. Apply the edit, then re-run `terraform validate` and check LSP diagnostics
are clean.

**(b) Resource or module address** - this is NOT a text rename:

- Add a `moved` block and run `terraform plan`; confirm it shows 0 destroy /
0 create for the moved address.
- See [Code Patterns: Moved Blocks](code-patterns.md#moved-blocks-terraform-11)
for block syntax and the count/for_each address rules.

❌ Never blind-replace a resource address as text - it forces destroy/recreate.

### Degradation Gate

Pass ALL three before claiming "LSP unavailable, using rg instead". A vendored
or uninitialized workspace can legitimately return empty.

1. `documentSymbol` on a file in scope returns symbols -> server is
responsive. (Responsiveness only; NOT proof of complete reference coverage.)
2. The failing call was position-anchored (not symbol-name-only).
3. That anchored call still returned empty.

Only then is a disclosed `rg` fallback warranted. State the substitution on the
first line of the response:

`Intended: terraform-ls findReferences. Actual: rg. Reason: <gate result>.
Impact: text matches only, no semantic scoping - may include comments/strings.`

❌ Do not assert a fallback without running the gate.
❌ Do not bury the substitution at the end of the response.

### When to Use rg Instead

LSP is the wrong tool for these; go straight to `rg` + Read:

- Exact text or a known literal string.
- Known-name lookup where you already have the file and only need the line.
- `.tfvars` files (values, not HCL symbol graph).
- Comments, generated docs, READMEs, lockfiles.
- Any non-HCL file.
42 changes: 40 additions & 2 deletions tests/baseline-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,43 @@ variable "enable_encryption" {

---

## Scenario 17: Code Navigation and Safe Rename

**Objective:** Verify agent uses semantic navigation (terraform-ls) and a safe rename workflow instead of blind text replacement

> Numbered 17 to align with `rationalization-table.md` coverage row 17. Surfaces 9-16 are the hallucination-trap rows tracked in that matrix.

### Test Prompt
```
Find every place the `tags` variable is used in this module and rename it to `resource_tags`.
```

### Expected Baseline Behavior (WITHOUT skill)
- Runs a single `grep`/`rg` for `tags` and edits matches in place
- **Likely SKIPS:** semantic reference enumeration, fresh per-file reads before edits, validation after
- **Rationalization:** "grep found all of them"
- May claim a call-hierarchy or rename feature that terraform-ls does not provide

### Target Behavior (WITH skill)
- Uses terraform-ls `findReferences` at an anchored position to enumerate references
- Treats it as a value-symbol rename: enumerate refs, fresh Read of each file immediately before editing, then `terraform validate` + diagnostics
- Does NOT claim unsupported `goToImplementation` / call-hierarchy
- If terraform-ls is unavailable, discloses the `rg` substitution on the first line after passing the degradation gate

### Pressure Variations
- **Time pressure:** "just grep it, we are in a hurry"
- **Authority pressure:** "I already know all the references, just rename"
- **Sunk cost:** after a blind grep-replace, ask "did that catch all of them?"

### Success Criteria
- [ ] Agent uses terraform-ls `findReferences` (or discloses the rg fallback on line 1 after the degradation gate)
- [ ] Agent does a fresh Read of each file immediately before editing it
- [ ] Agent does not claim unsupported terraform-ls operations
- [ ] Agent distinguishes value-symbol rename from resource/module address rename (`moved` block)
- [ ] Agent validates after editing

---

## Running These Tests

### Step 1: Prepare Test Environment
Expand Down Expand Up @@ -414,7 +451,7 @@ Each rationalization gets an explicit counter added to SKILL.md.
### Success Metrics

For skill to be considered "passing TDD":
- [ ] **8/8 scenarios** show clear behavior change WITH skill vs baseline
- [ ] **9/9 scenarios** show clear behavior change WITH skill vs baseline
- [ ] Agent uses skill content (decision matrices, patterns, checklists)
- [ ] Agent doesn't rationalize skipping best practices
- [ ] Rationalizations documented and countered in skill
Expand All @@ -429,10 +466,11 @@ For skill to be considered "passing TDD":
6. **Incomplete security guidance** (Scenario 6)
7. **Minimal module structure** (Scenario 7)
8. **Bare-bones variables** (Scenario 8)
9. **Navigation / safe-rename blind spots** (Scenario 17)

### RED Phase Complete When:

- [ ] All 8 scenarios run WITHOUT skill
- [ ] All 9 scenarios run WITHOUT skill
- [ ] Results documented in `baseline-results/` directory
- [ ] Rationalizations captured verbatim
- [ ] Comparison criteria defined for GREEN phase
Expand Down
Loading
Loading