diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f17d645..d8ff4d7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -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": [ @@ -24,7 +24,9 @@ "testing", "ci-cd", "security-scanning", - "modules" + "modules", + "lsp", + "terraform-ls" ], "version": "1.8.0" } diff --git a/CLAUDE.md b/CLAUDE.md index 83c713c..4b3f1e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 @@ -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) diff --git a/README.md b/README.md index cc72d4d..62bd388 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -61,7 +64,7 @@ npx skills add https://github.com/antonbabenko/terraform-skill Claude Code ```bash -/plugin marketplace add antonbabenko/terraform-skill +/plugin marketplace add antonbabenko/agent-plugins /plugin install terraform-skill@antonbabenko ``` @@ -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:** @@ -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. diff --git a/skills/terraform-skill/SKILL.md b/skills/terraform-skill/SKILL.md index 5f8e69b..adf2362 100644 --- a/skills/terraform-skill/SKILL.md +++ b/skills/terraform-skill/SKILL.md @@ -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 @@ -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 @@ -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: @@ -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 diff --git a/skills/terraform-skill/references/code-intelligence-lsp.md b/skills/terraform-skill/references/code-intelligence-lsp.md new file mode 100644 index 0000000..dd1a85c --- /dev/null +++ b/skills/terraform-skill/references/code-intelligence-lsp.md @@ -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: . +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. diff --git a/tests/baseline-scenarios.md b/tests/baseline-scenarios.md index 418722f..95695bc 100644 --- a/tests/baseline-scenarios.md +++ b/tests/baseline-scenarios.md @@ -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 @@ -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 @@ -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 diff --git a/tests/rationalization-table.md b/tests/rationalization-table.md index 5cd0825..951d3b9 100644 --- a/tests/rationalization-table.md +++ b/tests/rationalization-table.md @@ -56,13 +56,14 @@ This document has two parts: | 14 | OIDC trust policy with wildcarded `sub` or missing `aud` | §14 OIDC audience mismatch | `references/ci-cd-workflows.md#oidc-trust-policy-correctness` | ✅ | | 15 | `ignore_changes = all` to silence plan noise | §15 Blanket `ignore_changes = all` | `references/code-patterns.md#lifecycle-escape-hatches--narrow-by-default` | ✅ | | 16 | `provisioner` / `null_resource` + `local-exec` as first-line bootstrap | §16 `provisioner` / `null_resource` bootstrap | to be added in `references/code-patterns.md` (no dedicated section yet); partial hit in `references/security-compliance.md` LLM checklist | ❌ | +| 17 | Semantic navigation skipped; value-symbol rename done as blind text replace; unsupported terraform-ls op claimed | §17 Code Navigation and Safe Rename | `SKILL.md` Code Intelligence + `references/code-intelligence-lsp.md#terraform-ls-capability-matrix` | ✅ | ### Coverage Summary -- **Total surfaces tracked:** 16 -- **Covered (`✅`):** 15 +- **Total surfaces tracked:** 17 +- **Covered (`✅`):** 16 - **Partial (`◐`):** 0 -- **Open gaps (`❌`):** 1 (row 16 — provisioners) +- **Open gaps (`❌`):** 1 (row 16 - provisioners) ### Priority Gaps (❌ rows) @@ -462,7 +463,7 @@ Agents are creative. New rationalizations surface over time. Add them to the cov ### Overall progress -- **Surfaces tracked:** 16 -- **Scenarios exercising each:** 16 (one-to-one in `baseline-scenarios.md`) -- **Covered:** 15 -- **Open:** 1 (provisioners — row 16) +- **Surfaces tracked:** 17 +- **Scenarios exercising each:** 17 (one-to-one in `baseline-scenarios.md`) +- **Covered:** 16 +- **Open:** 1 (provisioners - row 16)