Your monorepo just opened 8 Terraform plans on this one PR. Your reviewer has gone very quiet.
He's reviewing the networking stack.
tfstackplan turns the N plan.json files a monorepo spits out per PR into a
single comment a human can actually read — then, if you're brave, gates the
apply behind a button someone has to consciously press.
Terramate makes a Terraform monorepo runnable. tfstackplan makes it
reviewable, governable, and safe inside GitHub's PR workflow. It closes the
four gaps a raw terramate run in CI leaves wide open — and it has four
faces, one per gap:
| Face | The gap it closes | In one line |
|---|---|---|
render |
Review | N unreadable plan.json → one scannable PR comment |
serve |
Governance | no approval story → live gates + privilege-backed apply |
run |
Observability | CI logs are a black hole → live DAG UI, per-stack logs, check runs |
state |
Management | cross-stack refactors are scary → declarative state moves |
Terramate runs your stacks. GitHub shrugs and posts the logs.
terraform applydoes exactly what you told it to, which is the problem.tfstackplanis the adult in the room.
render is fully standalone — point it at a directory of plans and you get
a comment. You never have to touch run / serve / state. The other three
layer a control plane on top, while Terraform keeps executing in your CI under
your identities.
New here? Start with the guide — it reads in order, from why this exists to every flag there is.
A run over eight stacks with classification on, rendered the way GitHub shows it in a PR comment: a scannable summary table, then a per-stack drill-down. Each resource is its own row inside an indented blockquote bar, so it's always clear which stack and resource you're reading. Small changes show expanded; big ones collapse to a row you click to open.
| Stack | Add | Change | Destroy | Replace | Categories |
|---|---|---|---|---|---|
| platform/nonprod | 0 | 4 | 0 | 0 | 🔐 iam |
| service-projects/app-dev | 4 | 3 | 0 | 0 | ✅ safe |
| data/warehouse | 0 | 0 | 6 | 0 | 💣 destructive |
| networking/shared-vpc | 0 | 5 | 0 | 2 | 💣 destructive |
| observability/grafana | 5 | 6 | 0 | 0 | ✅ safe |
📁 platform/nonprod · 🔐 iam · 4 change
✏️ google_project_iam_member.data_engineers
1 changed~ role = "roles/viewer" → "roles/editor"
📁 data/warehouse · 💣 destructive · 6 destroy
➖ google_bigquery_dataset.legacy_events
2 attrs- location = "us-central1" - name = "legacy_events"
That whole block is live Markdown, not a screenshot — it renders natively on
GitHub and you can paste it anywhere. The first line of the real output is an
invisible HTML-comment marker (<!-- tfstackplan:nonprod -->) that CI uses to
upsert one comment per environment. The full anatomy — folding rules, aligned
diffs, structured-value rendering, state-op rows — lives in
the render chapter.
More worked outputs (big plans, the byte budget kicking in, state ops) are in
examples/.
render is offline and standalone — no terraform, no posting, no config
required. Give it a directory of plans laid out to mirror your stack tree.
# 1. install
go install github.com/Fluent-Health/terraform-stack-plan/cmd/tfstackplan@latest
# 2. for each stack, capture a plan as JSON next to where it lives
terraform plan -out plan.bin && terraform show -json plan.bin > out/<stack>/tfplan.json
# 3. render every stack under out/ into one comment
tfstackplan render --plans-dir out/ \
--title "Terraform plan — nonprod" \
--marker tfstackplan:nonprod \
--output report.mdreport.md is the comment you saw above. With no config, classification is off
and the tool degrades gracefully — add a .tfstackplan.hcl when you want
categories, links, and budgets. Then have your CI post report.md; render
writes Markdown, your CI does the posting.
The full path from here — classification, then the control plane — is laid out in the guide.
render — the review gap. A pure, offline
renderer: many plan.json files in, one reviewer-friendly Markdown comment out,
with optional classification. It never runs terraform and never posts. Use it
entirely on its own.
run — the observability gap. The CI driver. It
wraps your terramate script run, detects the changed stacks, runs
plan/apply/verify, renders + classifies in-process, and reports the execution
lifecycle to the control plane — so a CI run stops being a wall of interleaved
logs.
serve — the governance gap. The control plane: a
live dependency-DAG UI, approval gates, one GitHub check run per environment,
and SSE-tailed per-stack logs.
terraform applyacross 8 stacks, straight from CI, with no one watching, is a perfectly reasonable thing to do.serveis for the other 100% of cases.
state — the management gap. Declarative
cross-stack Terraform state moves, applied as part of the normal apply — so
refactoring resources between stacks stops being a hand-run, fingers-crossed
operation.
The docs read as a sequence — start at the index — but if you know what you want:
- Why does this exist? → The gaps · Mental model
- I just want a comment. → Quickstart ·
render· Classification - I want a control plane. →
run·serve· CI integration - I'm moving resources between stacks. →
state - Just give me every flag. → Reference · CLI · Configuration
- How is it built? → Architecture ·
DESIGN.md
go install github.com/Fluent-Health/terraform-stack-plan/cmd/tfstackplan@latest
# or
go build -o tfstackplan ./cmd/tfstackplanPrebuilt binaries (linux/darwin · amd64/arm64) are on the
Releases page,
which also ships a multi-arch, distroless Cloud Run container (entrypoint
serve). The binary is fully static — pure-Go SQLite, no cgo — and embeds its
assets, so the image needs no runtime files.
This repo is also its own asdf plugin (hook scripts in
bin/, no separate plugin repo):
asdf plugin add tfstackplan https://github.com/Fluent-Health/terraform-stack-plan.git
asdf install tfstackplan latest # or a pinned version
asdf set tfstackplan latest # writes .tool-versionsFull install detail — toolchain notes, container, deployment — is in Reference → Install & deploy.
tfstackplan publishes a comprehensive, cross-runtime Workspace Agent Skill directly in this repository under skills/tfstackplan/SKILL.md. This skill governs the entire lifecycle and CLI surface of tfstackplan (lint, plan, state refactoring, watch status, claims, and applies), instructing future coding agents on how to operate this codebase with maximum safety and TDD discipline.
We package tfstackplan as a standard Claude Code plugin. You can load this plugin directly from GitHub or subscribe to our marketplace feed:
- Install directly as a Claude Plugin:
/plugin add https://github.com/Fluent-Health/terraform-stack-plan
- Add as a Marketplace Catalog:
/plugin marketplace add https://github.com/Fluent-Health/terraform-stack-plan/main/.claude-plugin/marketplace.json
Claude Code will automatically detect .claude-plugin/plugin.json and load the configured ./skills on demand when your session context matches the skill's frontmatter description, executing these instructions natively via the Claude Skill tool.
You can install this repository's agent skill and extensions globally or link them locally during development:
- Install globally as a Gemini Extension:
gemini extensions install https://github.com/Fluent-Health/terraform-stack-plan
- Link the skill locally:
gemini skills link ./skills/tfstackplan
Once loaded, Gemini CLI will automatically trigger and follow this skill whenever you ask the model to validate, plan, or restructure Terraform state.
Both Claude Code and Gemini CLI support the open Agent Skills standard, enabling cross-runtime interoperability:
- Create the global skill directory:
mkdir -p ~/.agents/skills/tfstackplan - Symlink or copy the skill file:
ln -s "$(pwd)/skills/tfstackplan/SKILL.md" ~/.agents/skills/tfstackplan/SKILL.md
Honesty up front saves everyone a wasted afternoon:
- One root module, one plan per PR. If
terraform planalready produces a single readable diff, you don't need a multi-plan renderer. Reach fortfplan2mdorterraform-plan-summary. - You want the tool to run
terraformfor you.renderdoesn't; its inputs are pre-existingplan.jsonfiles. (rundoes drive Terraform via Terramate — that's a different face.) - You want it to post comments for you.
renderwrites Markdown; your CI posts it. (serveposts its own check runs, but that's the control plane, not the renderer.) - You need static-analysis rollups (Checkov / Trivy / SARIF). Not today; possibly later.
Yes, it's another Terraform tool. We're as surprised as you are. The difference is the multi-plan part — taking the many plans a monorepo emits per PR and making them one reviewable, gate-able, observable thing:
tfplan2md— single-plan Markdown renderer; the inspiration for the per-stack diff style.- Atlantis — PR-comment-driven Terraform CI; renders per-project, tied to its own workflow.
terraform-plan-summary— single-plan summary table; no multi-plan support.
Contributions welcome — see CONTRIBUTING.md. For security issues, follow SECURITY.md rather than opening a public issue.
Licensed under the Apache License, Version 2.0 — see LICENSE.
Built and maintained by Fluent Health.