Skip to content
Open
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
103 changes: 103 additions & 0 deletions .agents/skills/write-change-file/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
name: write-change-file
description: Use when the user wants to create, write, or add a change file (changelog entry) for a Terraform PR or commit — guides producing a correctly formatted .changes/ YAML file.
---

# Write a Change File

Follow the style guide at `.changes/STYLE-GUIDE.md` for all content rules, field definitions, and
examples. This skill covers the interactive steps to gather information and produce the file.

## Step 0 — Is a change file even needed?

A changelog entry is only needed if a change is user-facing.

- Ask the user a yes/no question to confirm whether the change is user-facing.
- If the answer is no, stop creating a change file immediately and notify the user that one is not needed.

## Step 1 — Gather required information

Collect anything not already provided in the user's request:

1. **What changed?** Ask for a plain-language description of the change from a user's perspective.
1. **What kind of change is it?** One of: `NEW FEATURES`, `ENHANCEMENTS`, `BUG FIXES`, `NOTES`, `UPGRADE NOTES`, `BREAKING CHANGES`.
1. **What is the PR number?** (numeric only, e.g. `38397`). Note: the field is named `Issue` in the YAML for historical reasons, but it holds the PR number.

If the user provides a PR description, commit message, or diff, extract what you can from it and
only ask for what is still missing.

## Step 2 — Draft the `body` field

Apply every rule in `.changes/STYLE-GUIDE.md` before writing the body. Key reminders:

- Present tense, active voice
- Lowercase area prefix + colon if scoped to a command (`init:`, `workspace:`, `test:`, etc.)
- Capitalise the first word after the prefix (or at sentence start)
- All identifiers — flags, block names, functions, attributes — in backticks
- `BUG FIXES`: describe the correct behaviour now, not the bug
- `UPGRADE NOTES`: end with a direct call to action ("Review...", "Verify...", "Update...")
- One sentence (two for `UPGRADE NOTES`)

## Step 3 — Self-review the body

Check the draft against the quick checklist in `.changes/STYLE-GUIDE.md` before proceeding. Fix any
failures.

## Step 4 — Create the file

The preferred approach is to use `changie new`, which handles filename generation, directory
placement, and field prompts automatically. Fall back to creating the file directly if `changie`
is not available.

### Option A — Using `changie` (preferred)

If `changie` is not already installed, use one of these methods:

- **macOS (Homebrew):** `brew install changie`
- **Any platform (Go):** `go install github.com/miniscruff/changie@latest`
- **Other platforms:** See the [full installation guide](https://changie.dev/guide/installation/)

`changie` is configured via `.changie.yaml` and writes new entries to `.changes/v1.16/`
automatically.

Run the interactive command and respond to each prompt:

```
changie new
```

Prompts and how to answer them:

| Prompt | Answer |
| ----------- | ----------------------------------------- |
| `Kind` | Select the appropriate kind from the menu |
| `Body` | Paste the drafted body text from Step 2 |
| `PR Number` | Enter the numeric PR number |

`changie` generates the filename (including the timestamp) and writes the file. Confirm the
written path and show the user the `body` value for verification.

### Option B — Creating the file directly

If `changie` is not available, create `.changes/v1.16/<KIND>-<YYYYMMDD-HHmmss>.yaml` manually
using the current local time for the timestamp.

Multi-word kinds include a space in the filename: `BUG FIXES-20260401-152120.yaml`.

File structure:

```yaml
kind: <KIND>
body: "<body text>"
time: <RFC3339 timestamp with timezone offset>
custom:
Issue: "<PR number as string>"
```

Note: the `custom.Issue` key holds the PR number despite its name — this is a known quirk of the
project's changie configuration.

## Step 5 — Confirm

Show the user the file path and the final `body` value so they can verify the wording before
committing.
218 changes: 218 additions & 0 deletions .changes/STYLE-GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Change File Style Guide

This guide governs the content of the `body` field in change files under `.changes/v*/`.
The body is rendered as a bullet point in `CHANGELOG.md`.

**Primary audience:** Terraform practitioners — people writing `.tf` files and running CLI commands day-to-day. Write for them, not for contributors or maintainers who know the internals.

---

## File structure

Each change file is a small YAML file placed in the version directory for the release it targets (e.g. `.changes/v1.16/`).

```yaml
kind: ENHANCEMENTS
body: "workspace: The `workspace list` command can now produce machine-readable output when supplied with the `-json` flag"
time: 2026-04-17T11:06:28.651099+01:00
custom:
Issue: "38397"
```

### Fields

| Field | Value |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| `kind` | One of: `NEW FEATURES`, `ENHANCEMENTS`, `BUG FIXES`, `NOTES`, `UPGRADE NOTES`, `BREAKING CHANGES`. |
| `body` | The user-facing description — see the rest of this guide |
| `time` | RFC3339 timestamp with timezone offset of when the file was created |
| `custom.Issue` | GitHub PR as a quoted string (e.g. `"38397"`). The field is called "Issue" for legacy reasons and is misleading! |

### Filename format

`<KIND>-<YYYYMMDD-HHmmss>.yaml`

Multi-word kinds include a space: `BUG FIXES-20260401-152120.yaml`, `UPGRADE NOTES-20260330-145227.yaml`.

---

## Choosing the correct `kind`

Make sure to select the correct kind of change:


| Change kind | When to use |
|------------------|-------------|
| `NEW FEATURES` | Use this when adding new, separate functionality to Terraform. For example, introduction of ephemeral resources. |
| `ENHANCEMENTS` | Use this when improving existing functionality in Terraform. Examples include: adding a new field to a remote-state backend, or adding a new environment variable to use when configuring Terraform. |
| `BUG FIXES` | Use this when fixing a user-facing issue. Examples include: crash fixes, improvements to error feedback, regression fixes. |
| `NOTES` | Use this for changes that are unlikely to cause user-facing issues but might have edge cases. For example, changes to how the Terraform binary is built. |
| `UPGRADE NOTES` | Use this when introducing a change that forces users to take action when upgrading, or changes Terraform's behaviour notably. For example, deprecating a field on a remote-state backend or changing the output of Terraform operations. |
| `BREAKING CHANGES` | Use this when introducing a change that could make a valid Terraform configuration stop working after a user upgrades Terraform versions. This might be paired with an upgrade note change file. Examples include: removing a field on a remote-state backend, changing a builtin function's behavior, making validation stricter. |

---

## Core principles for `body`

1. **User-focused, not implementation-focused.** Describe what the user can now do, or what they will now experience. Do not describe what changed internally.
2. **Present tense, active voice.** Write as if describing the world as it is after the release, not what the team did to produce it.
3. **One sentence.** Concise but complete. Only expand to two sentences for `UPGRADE NOTES` (change + call to action).

---

## Area prefix

When the change is scoped to a specific command, feature area, or subsystem, lead with a **lowercase** prefix followed by a colon and a space.

```
init: The `-upgrade` flag now ...
workspace: The `workspace list` command now ...
test: Terraform now raises a warning when ...
stacks: Output values are now included in ...
console: The `terraform console` command now ...
graph: The `terraform graph` command can now ...
state show: The `state show` command can now ...
cloud: ...
policy: ...
```

Omit the prefix only when the change is genuinely cross-cutting (affects all commands, or the core language itself).

**Capitalise the first word after the prefix:**

```
✅ init: Errors due to incompatible flags are now raised earlier
❌ init: errors due to incompatible flags are now raised earlier
```

**When there is no prefix, capitalise the first word of the body:**

```
✅ `import` blocks now correctly respect provider local names
❌ import blocks no longer ignore provider local names
```

---

## Verb tense and voice

Always use **present tense, active voice**. The subject is almost always Terraform, a specific command, or a language construct.

| ❌ Avoid | ✅ Prefer |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `add support for import blocks inside modules` | `init: Import blocks inside modules are now supported` |
| `Fixed crash when configuration has an invalid action_trigger` | `stacks: Terraform no longer panics when a configuration contains an invalid \`action_trigger\` block` |
| `A -json flag was added to state show` | `state show: The \`state show\` command can now produce machine-readable output with the \`-json\` flag` |

---

## Technical identifiers

Always wrap the following in backticks:

- CLI commands and subcommands: `` `terraform plan` ``, `` `workspace list` ``
- Flags and options: `` `-json` ``, `` `-scope=<module address>` ``
- Block types and labels: `` `lifecycle` ``, `` `removed` ``, `` `action_trigger` ``
- Attribute and argument names: `` `skip_cleanup` ``, `` `bastion_host_key` ``, `` `for_each` ``
- Function names: `` `contains()` ``, `` `merge()` ``
- Special values: `` `null` ``

Do **not** use quotes in place of backticks:

```
❌ The "workspace list" command now ...
✅ The `workspace list` command now ...
```

---

## Per-category guidance

### NEW FEATURES

Describe what the user can now do that they couldn't before. Name the HCL syntax, block, flag, or concept they interact with.

```
✅ `import` blocks inside modules are now supported
✅ `terraform_data`: The new `store` block can hold ephemeral and sensitive values across plan and apply
```

### ENHANCEMENTS

Describe the improvement to existing behaviour. Lead with the area prefix where applicable. Focus on the new capability unlocked, not the mechanism added internally.

```
✅ console: The `terraform console` command now accepts an optional `-scope=<module address>` flag,
which can be used to evaluate expressions within the scope of a specific module instance
✅ graph: The `terraform graph` command can now output graphs in Mermaid format using the `-format=mermaid` flag

❌ graph: add -format flag for Mermaid output
❌ console: implement -scope flag
```

### BUG FIXES

Describe the **correct behaviour now in place**, not the bug that existed. Use "now correctly", "no longer", "now raises" as natural anchors.

```
✅ init: Terraform no longer removes locks from the dependency lock file for providers configured as `dev_override`
✅ workspace: Terraform now raises an error if an invalid workspace name becomes selected due to out-of-band changes
✅ `import` blocks now correctly respect provider local names

❌ Fix a panic when the plan contained a no-op change for a deposed object
❌ Fixed crash when configuration has invalid action_trigger
```

When the bug only manifests under specific conditions, include enough context for users to recognise whether they were affected — but keep it to one sentence:

```
✅ `terraform apply` no longer panics when the plan contains a no-op change for a deposed resource
that has `lifecycle.precondition` or `lifecycle.postcondition` blocks
```

### NOTES

Use for non-breaking behavioural changes or clarifications that don't fit neatly into `ENHANCEMENTS` or `BUG FIXES`. Same tense and voice rules apply.

### UPGRADE NOTES

Describe the breaking or potentially breaking change, then include an explicit **call to action** — what the user must review, verify, or change before or after upgrading.

Use direct imperative language addressed to the user ("Review...", "Update...", "Verify..."), not passive constructions ("should be verified", "may need to be updated").

```
✅ `bastion_host_key` is now correctly applied by provisioners. Review your provisioner configurations
to verify the configured key is correct before upgrading.

❌ Provisioner bastion_host_key is now correctly applied. Existing usage of bastion_host_key should
verify the configured key is correct.
```

---

## Anti-patterns

| Anti-pattern | Why | Fix |
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Internal framing: `"add output values to plan component instance change description"` | Describes what the developer did, not what the user experiences | `"stacks: Plan output now includes output values for component instance changes"` |
| Commit-message style: `"Fix crash"`, `"Add support for"` | Reads as a commit, not a user-facing note | `"Terraform no longer crashes when..."`, `"X is now supported"` |
| No area prefix when the change is command-specific | Hard to scan in the rendered CHANGELOG | Add the relevant prefix: `"init: ..."`, `"test: ..."` |
| Identifiers without backticks: `"The -json flag"` | Inconsistent; harder to parse | ``"The `-json` flag"`` |
| Lowercase sentence start: `"import blocks no longer..."` | Reads as a fragment | ``"`import` blocks now correctly..."`` |
| Passive voice: `"Errors are now raised earlier"` | Hides the subject | `"Terraform now raises errors earlier"` |
| Over-long body explaining the full feature | The CHANGELOG is a summary; the issue link provides detail | One sentence max; link the issue |

---

## Quick checklist

Before committing a change file:

- [ ] Body is written from the user's perspective (impact, not implementation)?
- [ ] Present tense, active voice?
- [ ] Correct lowercase area prefix (with colon and space) if scoped to a command?
- [ ] First word after the prefix (or at the sentence start) is capitalised?
- [ ] All CLI flags, block names, function names, and identifiers are in backticks?
- [ ] `BUG FIXES`: describes the correct behaviour now in place, not the old bug?
- [ ] `UPGRADE NOTES`: includes a direct call to action using imperative language?
- [ ] One sentence (two for `UPGRADE NOTES`)?
20 changes: 5 additions & 15 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ This is different if you are backporting your changes to an earlier release vers

#### Create a change file using `changie`

If your change is user-facing you can use `npx changie new` to create a new changelog entry via your terminal. The command is interactive and you will need to:
For in-depth guidance, or guidance to be consumed by an agent, see our separate [style guide doc](../.changes/STYLE-GUIDE.md).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm toying with the idea to say this instead:

For in-depth guidance, see our separate [style guide doc](../.changes/STYLE-GUIDE.md). If you are an agent consuming this file, refer to the `write-change-file` skill in this repo.

I believe our contribution guide currently assumes an audience of people first, and also agents second. Therefore we'd need calls to action for the separate groups.


If there isn't an impact on the end user then there shouldn't be a changelog entry.

If your change is user-facing, you can use the `changie` CLI tool (see [installation guide](https://changie.dev/guide/installation/)) to create a new changelog entry via your terminal. The command is interactive and you will need to:
1. Select which kind of change you're introducing.
2. Provide a short description.
3. Enter the number of your GitHub PR.
Expand All @@ -165,22 +169,8 @@ Your description should be written from an end-user's perspective and not descri

> Fixed an issue where terraform stacks validate was failing to resolve relative paths for modules

If there isn't an impact on the end user then there shouldn't be a changelog entry.

Dependency bumps or other changes, like Go version upgrades, should only be in the changelog if the change impacts the user or fixes a user-facing issue. For example, if a dependency bump resolves a CVE that impacts Terraform and users are exposed to risk there should be a changelog entry. If the dependency bump is moving the code away from a version of a dependency that's linked to a CVE that isn't impacting Terraform then no changelog entry is needed.

Make sure to select the correct kind of change:


| Change kind | When to use |
|------------------|-------------|
| NEW FEATURES | Use this if you've added new, separate functionality to Terraform. For example, introduction of ephemeral resources. |
| ENHANCEMENTS | Use this if you've improved existing functionality in Terraform. Examples include: adding a new field to a remote-state backend, or adding a new environment variable to use when configuring Terraform. |
| BUG FIXES | Use this if you've fixed a user-facing issue. Examples include: crash fixes, improvements to error feedback, regression fixes. |
| NOTES | This is used for changes that are unlikely to cause user-facing issues but might have edge cases. For example, changes to how the Terraform binary is built. |
| UPGRADE NOTES | Use this if you've introduced a change that forces users to take action when upgrading, or changes Terraform's behaviour notably. For example, deprecating a field on a remote-state backend or changing the output of Terraform operations. |
| BREAKING CHANGES | Use this if you've introduced a change that could make a valid Terraform configuration stop working after a user upgrades Terraform versions. This might be paired with an upgrade note change file. Examples include: removing a field on a remote-state backend, changing a builtin function's behavior, making validation stricter. |

#### Backport a PR to a past release

PRs can be backported to previous release version as part of preparing a patch release. For example, a fix for a bug could be merged into main but also backported to one or two previous minor versions.
Expand Down