{"id":41868938,"url":"https://github.com/oocx/tfplan2md","last_synced_at":"2026-04-25T22:02:54.784Z","repository":{"id":334461881,"uuid":"1116451552","full_name":"oocx/tfplan2md","owner":"oocx","description":"Convert terraform plans (json) into human readable markdown for easier review of changes in pull requests.","archived":false,"fork":false,"pushed_at":"2026-04-20T11:03:27.000Z","size":89687,"stargazers_count":163,"open_issues_count":35,"forks_count":10,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-04-20T12:37:33.623Z","etag":null,"topics":["ai-generated","azure-devops","cicd","code-review","devops","github","infrastructure-as-code","markdown","pull-requests","terraform"],"latest_commit_sha":null,"homepage":"https://oocx.github.io/tfplan2md/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oocx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":".github/SUPPORT.md","governance":null,"roadmap":"docs/roadmap-workflow-improvements-2025-q4.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"docs/agents.md","dco":null,"cla":null}},"created_at":"2025-12-14T22:00:36.000Z","updated_at":"2026-04-18T14:48:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oocx/tfplan2md","commit_stats":null,"previous_names":["oocx/tfplan2md"],"tags_count":183,"template":false,"template_full_name":null,"purl":"pkg:github/oocx/tfplan2md","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oocx%2Ftfplan2md","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oocx%2Ftfplan2md/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oocx%2Ftfplan2md/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oocx%2Ftfplan2md/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oocx","download_url":"https://codeload.github.com/oocx/tfplan2md/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oocx%2Ftfplan2md/sbom","scorecard":{"id":1244960,"data":{"date":"2026-03-17T21:26:52Z","repo":{"name":"github.com/oocx/tfplan2md","commit":"292b544fce0d377e46163dae03791cf362948616"},"scorecard":{"version":"v5.3.0","commit":"c22063e786c11f9dd714d777a687ff7c4599b600"},"score":6.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/2 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#code-review"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dependency-update-tool"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 30 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#maintained"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#security-policy"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:31","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:29","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/coverage-data.yml:20","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:31","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:238","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:506","Info: jobLevel 'contents' permission set to 'read': .github/workflows/release.yml:552","Info: jobLevel 'contents' permission set to 'read': .github/workflows/release.yml:599","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:23","Info: jobLevel 'actions' permission set to 'read': .github/workflows/scorecard.yml:24","Warn: topLevel 'contents' permission set to 'write': .github/workflows/ci.yml:22","Info: topLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:22","Info: topLevel 'contents' permission set to 'read': .github/workflows/copilot-setup-steps.yml:17","Info: topLevel 'contents' permission set to 'read': .github/workflows/coverage-data.yml:13","Info: topLevel 'contents' permission set to 'read': .github/workflows/deploy-website.yml:8","Warn: topLevel 'checks' permission set to 'write': .github/workflows/pr-validation.yml:14","Info: topLevel 'contents' permission set to 'read': .github/workflows/pr-validation.yml:12","Info: topLevel 'contents' permission set to 'read': .github/workflows/release.yml:24","Info: topLevel permissions set to 'read-all': .github/workflows/scorecard.yml:11","Info: topLevel 'contents' permission set to 'read': .github/workflows/uat-validate.yml:8"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#token-permissions"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#license"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: examples/azuredevops/.terraform/providers/registry.terraform.io/microsoft/azuredevops/1.12.1/linux_amd64/terraform-provider-azuredevops_v1.12.1:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#vulnerabilities"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:593"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":7,"reason":"dependency not pinned by hash detected -- score normalized to 7","details":["Warn: containerImage not pinned by hash: src/Dockerfile:2: pin your Docker image by updating mcr.microsoft.com/dotnet/sdk:10.0-alpine to mcr.microsoft.com/dotnet/sdk:10.0-alpine@sha256:2b0e46d490f5b53a8dc07fbf636cdf5b90796878a256e1ce5b441e8d9675c5f4","Warn: nugetCommand not pinned by hash: src/Dockerfile:12: pin your dependecies by either enabling central package management (https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management) or using a lockfile (https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies)","Warn: downloadThenRun not pinned by hash: .github/workflows/copilot-setup-steps.yml:44","Warn: nugetCommand not pinned by hash: .github/workflows/coverage-data.yml:35: pin your dependecies by either enabling central package management (https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management) or using a lockfile (https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies)","Warn: nugetCommand not pinned by hash: .github/workflows/pr-validation.yml:140: pin your dependecies by either enabling central package management (https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management) or using a lockfile (https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies)","Info:  40 out of  40 GitHub-owned GitHubAction dependencies pinned","Info:   8 out of   8 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   3 nugetCommand dependencies pinned","Info:   4 out of   4 npmCommand dependencies pinned","Info:   0 out of   1 downloadThenRun dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: SAST configuration detected: CodeQL","Info: all commits (28) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#sast"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v1.39.0 not signed: https://api.github.com/repos/oocx/tfplan2md/releases/297869184","Warn: release artifact v1.38.0 not signed: https://api.github.com/repos/oocx/tfplan2md/releases/297699601","Warn: release artifact v1.37.4 not signed: https://api.github.com/repos/oocx/tfplan2md/releases/297669457","Warn: release artifact v1.37.3 not signed: https://api.github.com/repos/oocx/tfplan2md/releases/297220452","Warn: release artifact v1.37.2 not signed: https://api.github.com/repos/oocx/tfplan2md/releases/296428701","Warn: release artifact v1.39.0 does not have provenance: https://api.github.com/repos/oocx/tfplan2md/releases/297869184","Warn: release artifact v1.38.0 does not have provenance: https://api.github.com/repos/oocx/tfplan2md/releases/297699601","Warn: release artifact v1.37.4 does not have provenance: https://api.github.com/repos/oocx/tfplan2md/releases/297669457","Warn: release artifact v1.37.3 does not have provenance: https://api.github.com/repos/oocx/tfplan2md/releases/297220452","Warn: release artifact v1.37.2 does not have provenance: https://api.github.com/repos/oocx/tfplan2md/releases/296428701"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":4,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'main'","Warn: 'stale review dismissal' is disabled on branch 'main'","Warn: branch 'main' does not require approvers","Warn: codeowners review is not required on branch 'main'","Warn: 'last push approval' is disabled on branch 'main'","Info: 'up-to-date branches' is required to merge on branch 'main'","Info: status check found to merge onto on branch 'main'","Info: PRs are required in order to make changes on branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#branch-protection"}},{"name":"Contributors","score":3,"reason":"project has 1 contributing companies or organizations -- score normalized to 3","details":["Info: found contributions from: diamant software gmbh"],"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#contributors"}},{"name":"CI-Tests","score":10,"reason":"8 out of 8 merged PRs checked by a CI test -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#ci-tests"}}]},"last_synced_at":"2026-03-18T01:47:32.514Z","repository_id":334461881,"created_at":"2026-03-18T01:47:32.522Z","updated_at":"2026-03-18T01:47:32.522Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32278249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-generated","azure-devops","cicd","code-review","devops","github","infrastructure-as-code","markdown","pull-requests","terraform"],"created_at":"2026-01-25T12:03:14.604Z","updated_at":"2026-04-25T22:02:54.751Z","avatar_url":"https://github.com/oocx.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tfplan2md\n\n![tfplan2md](website/assets/images/logo-full.svg)\n\n[![CI](https://github.com/oocx/tfplan2md/workflows/CI/badge.svg)](https://github.com/oocx/tfplan2md/actions/workflows/ci.yml) [![Release](https://github.com/oocx/tfplan2md/workflows/Release/badge.svg)](https://github.com/oocx/tfplan2md/actions/workflows/release.yml) [![Coverage](https://raw.githubusercontent.com/oocx/tfplan2md/coverage-data/assets/coverage-badge.svg)](https://raw.githubusercontent.com/oocx/tfplan2md/coverage-data/docs/coverage/history.json) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker Pulls](https://img.shields.io/docker/pulls/oocx/tfplan2md)](https://hub.docker.com/r/oocx/tfplan2md) [![.NET](https://img.shields.io/badge/.NET-10.0-512BD4?logo=dotnet)](https://dotnet.microsoft.com/) [![Docker](https://img.shields.io/badge/docker-recommended-2496ED?logo=docker\u0026logoColor=white)](https://hub.docker.com/r/oocx/tfplan2md) [![Terraform](https://img.shields.io/badge/Terraform-1.0+-844FBA?logo=terraform)](https://www.terraform.io/) [![GitHub Copilot](https://img.shields.io/badge/GitHub%20Copilot-100%25-blue?logo=github)](https://github.com/features/copilot) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\n**📘 [Official Website](https://oocx.github.io/tfplan2md/)**\n\nConvert Terraform plan JSON files into human-readable Markdown reports.\n\n**NOTE:** This project was developed 100% with GitHub Copilot to explore how far AI-assisted development can go. All code and specifications were generated with AI support.\n\n## Use Cases\n\n### Pull Request Reviews\n\nTerraform plans are notoriously difficult to review in pull requests:\n\n- **Wall of text output** - Raw `terraform plan` output is verbose and hard to scan\n- **No structure** - Changes aren't grouped logically, making it difficult to understand impact\n- **Cryptic JSON** - `terraform show -json` provides complete data but is unreadable for humans\n- **Index-based diffs** - Changes to lists show as confusing index modifications (e.g., `firewall_rule[2]` deleted, `firewall_rule[1]` modified)\n- **Lost context** - Hard to see the big picture: \"What's actually changing and why?\"\n\n**tfplan2md solves this** by generating clean, readable Markdown reports that:\n\n- ✅ **Structure changes logically** - Group by module, summarize by action type\n- ✅ **Show semantic diffs** - See which firewall rules or NSG rules were added/removed, not index changes\n- ✅ **Highlight key changes** - One-line summaries show what changed in each resource at a glance\n- ✅ **Format for readability** - Collapsible sections, tables, and syntax highlighting make review efficient\n- ✅ **Render natively** - GitHub and Azure DevOps display reports beautifully in PR comments\n\n**Result:** Reviewers can quickly understand infrastructure changes, catch potential issues, and approve confidently.\n\n### Other Use Cases\n\n- **Release documentation** - Attach plan reports to release notes for audit trails\n- **Compliance audits** - Generate human-readable change documentation for compliance reviews\n- **Team communication** - Share infrastructure changes with stakeholders who don't read Terraform code\n- **CI/CD integration** - Automatically post plan summaries to PRs, Slack, or Teams\n\n## Features\n\n- 📄 **Convert Terraform plans to Markdown** - Generate clean, readable reports from `terraform show -json` output\n- 🔍 **Static analysis integration** - Display security and quality findings from Checkov, Trivy, TFLint, and Semgrep (SARIF 2.1.0 format) directly in reports\n- ✅ **Validated markdown output** - Comprehensive testing ensures GitHub/Azure DevOps compatibility\n- 🔒 **Sensitive value masking** - Sensitive values are masked by default for security\n- 📝 **Customizable templates** - Use Scriban templates for custom report formats\n- 🐳 **Minimal Docker image** - 14.7MB AOT-compiled native binary for fast deployments and minimal attack surface\n- 📁 **Module grouping** - Resource changes are grouped by module and rendered as module sections\n- 🆔 **Readable Azure Resource IDs** - Long Azure IDs are automatically formatted as readable scopes with values in code (e.g., Key Vault `kv` in resource group `rg`)\n- 🎨 **Semantic icons** - Visual icons for values: 🌐 for IPs, 🔌 for ports, 📨/🔗 for protocols, ✅/❌ for booleans, 👤/👥/💻 for principals, 🛡️ for roles, 🆔 for identifiers, 📧 for emails\n- 📝 **Resource summaries** - Each resource change shows a concise one-line summary for quick scanning\n- 🔄 **Replacement reasons** - Resources being replaced show which attributes forced the replacement\n- 🔧 **Specialized templates** - Custom rendering for complex resources (Azure Firewall rules, NSG rules, Azure DevOps build definitions and variable groups, Azure AD resources, and inline parent-child tables for memberships and Azure network resources)\n- 📚 **Azure API documentation links** - Reliable links to Microsoft Learn REST API documentation for 92 Azure resource types (AzAPI provider)\n\n## Installation\n\ntfplan2md is distributed in multiple ways to suit different environments:\n\n### Option 1: Homebrew (macOS and Linux)\n\nInstall tfplan2md using Homebrew:\n\n```bash\nbrew tap oocx/tfplan2md\nbrew install tfplan2md\n```\n\nTo upgrade to the latest version:\n\n```bash\nbrew upgrade tfplan2md\n```\n\n**Supported Platforms:**\n- macOS ARM64 (Apple Silicon - M1/M2/M3)\n- Linux x64 (including WSL)\n\n**Recommended for:**\n- macOS and Linux users who prefer package manager installation\n- Development environments where Homebrew is already installed\n- Users who want automatic update notifications via `brew outdated`\n\n### Option 2: Docker Image\n\n```bash\ndocker pull oocx/tfplan2md:latest\n```\n\nThe Docker image is a **14.7MB** AOT-compiled native binary built from scratch for optimal security and performance. It includes a comprehensive demo at `/examples/comprehensive-demo/` showcasing all features.\n\n**Recommended for:**\n- Containerized environments\n- CI/CD pipelines with Docker support\n- Users who prefer isolated, reproducible builds\n- Alpine Linux or musl-based systems\n\n### Option 3: Pre-built Binaries\n\n**Available starting with the next release**\n\nDownload pre-built binaries for your platform from [GitHub Releases](https://github.com/oocx/tfplan2md/releases):\n\n#### Available Platforms\n\n| Platform | Architecture | Archive | Notes |\n|----------|-------------|---------|-------|\n| **Linux** | x64 | `tfplan2md_VERSION_linux-x64.tar.gz` | Ubuntu 24.04+, Debian 13+, RHEL 10+ (glibc 2.39) |\n| **Linux** | ARM64 | `tfplan2md_VERSION_linux-arm64.tar.gz` | Ubuntu 24.04+, Debian 13+, RHEL 10+ (glibc 2.39) |\n| **Windows** | x64 | `tfplan2md_VERSION_windows-x64.zip` | Windows 10+ (x64) |\n| **macOS** | Apple Silicon | `tfplan2md_VERSION_macos-arm64.tar.gz` | macOS 11+ (M1/M2/M3) |\n\n#### Installation Steps\n\n1. **Download the binary for your platform:**\n   ```bash\n   VERSION=\"1.x.x\"  # Replace with desired version\n   PLATFORM=\"linux-x64\"  # Choose: linux-x64, linux-arm64, windows-x64, macos-arm64\n   \n   # Linux/macOS (tar.gz)\n   wget https://github.com/oocx/tfplan2md/releases/download/v${VERSION}/tfplan2md_${VERSION}_${PLATFORM}.tar.gz\n   \n   # Windows (zip) - use PowerShell\n   # Invoke-WebRequest -Uri \"https://github.com/oocx/tfplan2md/releases/download/v${VERSION}/tfplan2md_${VERSION}_windows-x64.zip\" -OutFile \"tfplan2md_${VERSION}_windows-x64.zip\"\n   ```\n\n2. **Download and verify checksums:**\n   ```bash\n   # Download consolidated checksums\n   wget https://github.com/oocx/tfplan2md/releases/download/v${VERSION}/SHA256SUMS\n   \n   # Verify (Linux/macOS)\n   sha256sum -c SHA256SUMS --ignore-missing\n   \n   # Verify (Windows PowerShell)\n   # $expectedHash = (Get-Content SHA256SUMS | Select-String \"windows-x64\").Line.Split()[0]\n   # $actualHash = (Get-FileHash tfplan2md_${VERSION}_windows-x64.zip).Hash.ToLower()\n   # if ($expectedHash -eq $actualHash) { \"OK\" } else { \"FAILED\" }\n   ```\n   Expected output: `tfplan2md_VERSION_PLATFORM.tar.gz: OK`\n\n3. **Extract and run:**\n   ```bash\n   # Linux/macOS\n   tar -xzf tfplan2md_${VERSION}_${PLATFORM}.tar.gz\n   ./tfplan2md --help\n   terraform show -json plan.tfplan | ./tfplan2md \u003e plan.md\n   \n   # Windows (PowerShell)\n   # Expand-Archive tfplan2md_${VERSION}_windows-x64.zip\n   # .\\tfplan2md.exe --help\n   ```\n\n#### Platform Requirements\n\n**Linux:**\n- glibc 2.39 or newer (binaries built on Ubuntu 24.04)\n- No .NET runtime required (self-contained NativeAOT)\n- Supported distributions:\n  - Ubuntu 24.04 LTS or newer\n  - Debian 13 (Trixie) or newer\n  - RHEL 10 or newer\n  - Other glibc-based distributions with glibc 2.39+\n- **Note:** For Alpine Linux or musl-based systems, use the Docker image\n\n**Windows:**\n- Windows 10 version 1607 or newer (x64)\n- No .NET runtime required (self-contained NativeAOT)\n- **Note:** Windows ARM64 builds are not currently available. Use x64 binary (runs via emulation) or Docker image.\n\n**macOS:**\n- macOS 11 (Big Sur) or newer for Apple Silicon builds\n- No .NET runtime required (self-contained NativeAOT)\n- **Note:** Intel (x64) builds are not currently available. Use Docker image or build from source.\n\n#### Use Cases\n\n**Recommended for:**\n- Closed/air-gapped systems where Docker images cannot be pulled\n- High compliance or regulatory environments\n- Environments without container runtime or Homebrew\n- Local development and testing without Docker overhead\n- Windows users (Homebrew not available on native Windows)\n\n### Option 4: Build from Source\n\nRequires .NET 10 SDK.\n\n```bash\ngit clone https://github.com/oocx/tfplan2md.git\ncd tfplan2md\ndotnet build\n```\n\n## Usage\n\n### From stdin (pipe from Terraform)\n\n```bash\nterraform show -json plan.tfplan | docker run -i oocx/tfplan2md\n```\n\n### From file\n\n```bash\n# Using Docker with mounted volume\ndocker run -v $(pwd):/data oocx/tfplan2md /data/plan.json\n\n# Or with .NET\ndotnet run --project src/Oocx.TfPlan2Md -- plan.json\n```\n\n### With output file\n\n```bash\nterraform show -json plan.tfplan | docker run -i -v $(pwd):/data oocx/tfplan2md --output /data/plan.md\n```\n\n### Summary-only output\n\n```bash\nterraform show -json plan.tfplan | docker run -i oocx/tfplan2md --template summary\n```\n\n### CLI Options\n\n| Option | Description |\n|--------|-------------|\n| `--output`, `-o \u003cfile\u003e` | Write output to a file instead of stdout |\n| `--template`, `-t \u003cname\\|file\u003e` | Use a built-in template by name (default, summary) or a custom Scriban template file |\n| `--report-title \u003ctext\u003e` | Override the level-1 heading in the generated report |\n| `--render-target \u003cgithub\\|azuredevops\u003e` | Target platform for rendering: `github` (simple diff) or `azuredevops` (inline diff, default) |\n| `--details \u003cauto\\|open\\|closed\u003e` | Control resource details display: `auto` (expand resources with findings, default), `open` (expand all), `closed` (collapse all) |\n| `--principal-mapping`, `--principals`, `-p \u003cfile\u003e` | Map Azure principal IDs to names using a JSON file |\n| `--code-analysis-results \u003cpattern\u003e` | SARIF file pattern for static analysis findings (can be specified multiple times) |\n| `--code-analysis-minimum-level \u003clevel\u003e` | Minimum severity to display (critical, high, medium, low, informational) |\n| `--fail-on-static-code-analysis-errors \u003clevel\u003e` | Exit with code 10 when findings at or above this level exist |\n| `--show-unchanged-values` | Include unchanged attribute values in tables (hidden by default) |\n| `--show-sensitive` | Show sensitive values unmasked |\n| `--hide-metadata` | Suppress tfplan2md version and generation timestamp from report header |\n| `--debug` | Append diagnostic information to the report for troubleshooting |\n| `--help`, `-h` | Display help information |\n| `--version`, `-v` | Display version information |\n\n#### Render Target Selection\n\nThe `--render-target` flag controls platform-specific rendering behavior. Attributes with newlines or over 100 characters are automatically moved to a collapsible `\u003cdetails\u003e` section below the main attribute table:\n\n- **`azuredevops`** (default, alias: `azdo`): Styled HTML with line-by-line and character-level diff highlighting. Optimized for Azure DevOps PR comments (GitHub strips styles but content remains readable).\n- **`github`**: Traditional diff format with `+`/`-` markers. Fully portable and works on both GitHub and Azure DevOps.\n\nExample:\n```bash\nterraform show -json plan.tfplan | tfplan2md --render-target github\n```\n\n**Migration note:** The `--large-value-format` flag has been deprecated and replaced by `--render-target`. Use `--render-target azuredevops` for `inline-diff` behavior or `--render-target github` for `simple-diff` behavior.\n\n#### Debug Output\n\nWhen troubleshooting issues or verifying tfplan2md's behavior, enable debug mode to append diagnostic information to the report:\n\n```bash\n# Enable debug output\nterraform show -json plan.tfplan | tfplan2md --debug\n\n# With principal mapping\ntfplan2md --debug --principal-mapping principals.json plan.json -o report.md\n```\n\nDebug information is added as a collapsible \"Debug Information\" section at the end of the report (collapsed by default to reduce visual clutter) and includes:\n\n- **Principal mapping diagnostics**: Load status, principal type counts, and failed ID resolutions with context showing which resource referenced each missing ID\n- **Enhanced error diagnostics** (when principal mapping fails):\n  - File and directory existence checks\n  - Specific error type (FileNotFound, JsonParseError, DirectoryNotFound, AccessDenied)\n  - Line and column numbers for JSON syntax errors\n  - Docker-specific troubleshooting guidance\n  - Actionable solutions based on the error type\n- **Template resolution**: Which templates (custom, built-in, or default) were used for each resource type\n\nThis helps diagnose principal mapping failures, Docker volume mount issues, and understand template selection behavior.\n\n#### Resource Details Display Control\n\nThe `--details` option controls whether resource details blocks are initially expanded or collapsed in the generated markdown report. This is particularly useful for large plans or when focusing on resources with code analysis findings:\n\n```bash\n# Collapse all resources by default (clean, scannable view)\nterraform show -json plan.tfplan | tfplan2md --details closed\n\n# Expand all resources by default\nterraform show -json plan.tfplan | tfplan2md --details open\n\n# Auto mode: expand only resources with code analysis findings (default)\nterraform show -json plan.tfplan | tfplan2md --details auto \\\n  --code-analysis-results checkov-results.sarif\n```\n\n**Display Modes:**\n\n- **`auto`** (default): Automatically expand resources that have code analysis findings attached, collapse others. This helps draw attention to security and quality issues while keeping the report clean.\n- **`open`**: Expand all resource details blocks. Useful for small plans or when you want to review everything in detail.\n- **`closed`**: Collapse all resource details blocks. Provides a clean overview where you can selectively expand resources of interest.\n\nUsers can always manually expand or collapse details blocks by clicking the summary line, regardless of the initial state. The debug information block (enabled with `--debug`) is always collapsed regardless of the `--details` setting.\n\n#### Principal Mapping File Format\n\nThe `--principal-mapping` file can include Azure AD principals, Azure metadata (subscriptions, management groups, tenants, roles), and Azure DevOps entities (users, groups, projects).\nThe new sections use an array-of-objects format; existing principal-only files remain supported.\n\n```json\n{\n  \"users\": [\n    { \"id\": \"user-guid\", \"displayName\": \"Jane Doe\" }\n  ],\n  \"groups\": [\n    { \"id\": \"group-guid\", \"displayName\": \"DevOps Team\" }\n  ],\n  \"servicePrincipals\": [\n    { \"id\": \"sp-guid\", \"displayName\": \"CI/CD Pipeline\" }\n  ],\n  \"subscriptions\": [\n    { \"id\": \"d1828a48-fced-4ea2-b2ec-4b9623f327fd\", \"displayName\": \"Production\" }\n  ],\n  \"managementGroups\": [\n    { \"id\": \"mg-production\", \"displayName\": \"Production Workloads\" }\n  ],\n  \"tenants\": [\n    { \"id\": \"tenant-guid\", \"displayName\": \"Contoso Corp\" }\n  ],\n  \"roles\": [\n    { \"id\": \"custom-role-guid\", \"displayName\": \"Custom Deployment Role\" }\n  ],\n  \"azdoUsers\": {\n    \"4a2c5e2b-3b4f-4e6f-8a9b-1c2d3e4f5a6b\": \"John Smith\",\n    \"7f8e9d0c-1b2a-3c4d-5e6f-7a8b9c0d1e2f\": \"Alice Johnson\"\n  },\n  \"azdoGroups\": {\n    \"vssgp.Uy0xLTktMTU1MTM...\": \"Platform Team\",\n    \"aadgp.Uy0.ReleaseManagers\": \"Release Managers\"\n  },\n  \"azdoProjects\": {\n    \"8f7e6d5c-4b3a-2c1d-0e9f-8a7b6c5d4e3f\": \"Infrastructure Project\",\n    \"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\": \"Application Platform\"\n  },\n  \"azdoRepositories\": {\n    \"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\": \"Infrastructure Repo\",\n    \"f9e8d7c6-b5a4-3210-fedc-ba9876543210\": \"Web Application Repo\"\n  }\n}\n```\n\n**Azure DevOps sections** (optional):\n- `azdoUsers`: Map Azure DevOps user GUIDs to display names\n- `azdoGroups`: Map Azure DevOps group descriptors to display names (supports long descriptors)\n- `azdoProjects`: Map Azure DevOps project GUIDs to display names\n- `azdoRepositories`: Map Azure DevOps repository GUIDs to display names\n\nAzure DevOps entities are automatically resolved in group memberships, team rosters, project references, and repository attributes. Repositories display as `🗃️ DisplayName [GUID]` when mapped. Branch/ref attributes (e.g., `default_branch`, `branch_name`) are shown with the ⎇ icon for improved visual scanning.\n\n#### Azure CLI Export Commands\n\nUse the Azure CLI to export the Azure AD and Azure infrastructure mapping sections (each command returns the array-of-objects format):\n\n```bash\n# Principals (Azure AD)\naz ad user list --all --query \"[].{id:id,displayName:displayName}\" -o json\naz ad group list --query \"[].{id:id,displayName:displayName}\" -o json\naz ad sp list --all --query \"[].{id:id,displayName:displayName}\" -o json\n\n# Subscriptions\naz account list --query \"[].{id:id,displayName:name}\" -o json\n\n# Management groups\naz account management-group list --query \"[].{id:name,displayName:displayName}\" -o json\n\n# Tenants\naz account tenant list --query \"[].{id:tenantId,displayName:displayName}\" -o json\n\n# Custom roles\naz role definition list --custom-role-only true --query \"[].{id:name,displayName:roleName}\" -o json\n```\n\n**Azure DevOps sections** must be created manually as the Azure DevOps CLI does not provide direct export commands for this format. Collect user GUIDs, group descriptors, project GUIDs, and repository GUIDs from your Azure DevOps organization and add them to the `azdoUsers`, `azdoGroups`, `azdoProjects`, and `azdoRepositories` sections in the JSON file.\n\nUse `scripts/validate-azure-cli-commands.sh` to validate the Azure CLI commands in your environment.\n\n#### Principal Mapping with Docker\n\nWhen using Docker, you need to mount the `principals.json` file into the container:\n\n```bash\n# Mount from current directory\ndocker run -v $(pwd):/data oocx/tfplan2md \\\n  --principal-mapping /data/principals.json \\\n  /data/plan.json --output /data/plan.md\n\n# Mount as read-only to specific path\ndocker run \\\n  -v $(pwd)/plan.json:/data/plan.json:ro \\\n  -v $(pwd)/principals.json:/app/principals.json:ro \\\n  oocx/tfplan2md --principal-mapping /app/principals.json /data/plan.json\n\n# With debug output\ndocker run -v $(pwd):/data oocx/tfplan2md --debug \\\n  --principal-mapping /data/principals.json \\\n  /data/plan.json --output /data/plan.md\n```\n\n### HTML renderer (development tool)\n\nRender existing tfplan2md reports to HTML with GitHub- or Azure-DevOps-like output using the standalone tool in [src/tools/Oocx.TfPlan2Md.HtmlRenderer](src/tools/Oocx.TfPlan2Md.HtmlRenderer):\n\n```bash\ndotnet run --project src/tools/Oocx.TfPlan2Md.HtmlRenderer -- \\\n  --input artifacts/comprehensive-demo.md \\\n  --flavor github\n\ndotnet run --project src/tools/Oocx.TfPlan2Md.HtmlRenderer -- \\\n  --input artifacts/comprehensive-demo.md \\\n  --flavor azdo \\\n  --template src/tools/Oocx.TfPlan2Md.HtmlRenderer/templates/azdo-wrapper.html \\\n  --output artifacts/comprehensive-demo.azdo.html\n```\n\n### Screenshot generator (development tool)\n\nGenerate PNG or JPEG screenshots from HTML using Playwright in [src/tools/Oocx.TfPlan2Md.ScreenshotGenerator](src/tools/Oocx.TfPlan2Md.ScreenshotGenerator). Install the browser once after build:\n\n```bash\npwsh src/tools/Oocx.TfPlan2Md.ScreenshotGenerator/bin/Debug/net10.0/playwright.ps1 install chromium --with-deps\n```\n\n**Automated screenshot generation (recommended for website):**\n\n### Screenshot Workflows\n\n#### For Release Notes (Simplified)\n\nUse `scripts/generate-release-screenshots.sh` for release notes - it generates a single 580×400 screenshot optimized for release documentation:\n\n```bash\nscripts/generate-release-screenshots.sh \\\n  --plan examples/firewall-with-static-analysis/plan.json \\\n  --output-prefix feature-065-icons \\\n  --output-dir docs/features/043-tenant-display-mapping \\\n  --selector \"details:has(summary:has-text('azurerm_role_assignment'))\" \\\n  --render-target github\n```\n\nThis generates a single PNG file at the specified size (default 580×400) in light mode.\n\n#### For Website (Full Control)\n\nUse `scripts/generate-screenshot.sh` to automate the full workflow (plan → markdown → HTML → screenshots with all variants):\n\n```bash\nscripts/generate-screenshot.sh \\\n  --plan examples/firewall-with-static-analysis/plan.json \\\n  --output-prefix firewall-example \\\n  --selector \"details:has(summary:has-text('azurerm_firewall'))\" \\\n  --thumbnail-width 580 --thumbnail-height 400 \\\n  --lightbox-width 1200 --lightbox-height 900 \\\n  --render-target azdo \\\n  --open-details-selector \"details\"\n```\n\nThis generates 12 screenshot files (thumbnail/lightbox × light/dark × 1x/2x DPI).\n\n**Manual usage examples** (formats: png default, jpeg; WebP deferred):\n\n```bash\n# Default viewport (1920x1080), output derived from input name\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html\n\n# Custom viewport\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html \\\n  --output artifacts/screenshot-1280x720.png \\\n  --width 1280 \\\n  --height 720\n\n# Full-page capture\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html \\\n  --output artifacts/full-report.png \\\n  --full-page\n\n# JPEG with quality\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html \\\n  --output artifacts/screenshot.jpg \\\n  --quality 85\n\n# Capture specific resource by Terraform address\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html \\\n  --output artifacts/resource.png \\\n  --target-terraform-resource-id \"azurerm_storage_account.example\"\n\n# Capture by selector with expanded details\ndotnet run --project src/tools/Oocx.TfPlan2Md.ScreenshotGenerator -- \\\n  --input artifacts/comprehensive-demo.github.html \\\n  --output artifacts/firewall.png \\\n  --target-selector \"details:has(summary:has-text('azurerm_firewall'))\" \\\n  --open-details \"details\"\n```\n\n### Terraform show renderer (development tool)\n\nGenerate terminal-style output that mirrors `terraform show` for creating \"before tfplan2md\" examples. The default output includes ANSI color; add `--no-color` for plain text.\n\n```bash\n# Colored output\ndotnet run --project src/tools/Oocx.TfPlan2Md.TerraformShowRenderer -- \\\n  --input src/tests/Oocx.TfPlan2Md.Tests/TestData/TerraformShow/plan1.json \\\n  --output artifacts/terraform-show-plan1.txt\n\n# Plain text (no ANSI)\ndotnet run --project src/tools/Oocx.TfPlan2Md.TerraformShowRenderer -- \\\n  --input src/tests/Oocx.TfPlan2Md.Tests/TestData/TerraformShow/plan1.json \\\n  --no-color \\\n  --output artifacts/terraform-show-plan1.nocolor.txt\n```\n\n## Example Output\n\nAll generated markdown is automatically validated and linted for correct formatting. Special characters in resource names and attribute values are properly escaped to ensure tables and headings render correctly on GitHub and Azure DevOps.\n\n```markdown\n# Terraform Plan Report\n\nGenerated by tfplan2md 0.30.0 (a1b2c3d) on 2026-01-03 14:23:15 UTC | Terraform 1.14.0\n\n## Summary\n\n| Action | Count | Resource Types |\n|--------|-------|----------------|\n| ➕ Add | 3 | 1 azurerm_resource_group\u003cbr/\u003e2 azurerm_storage_account |\n| 🔄 Change | 1 | 1 azurerm_key_vault |\n| ♻️ Replace | 1 | 1 azuredevops_git_repository |\n| ❌ Destroy | 1 | 1 azurerm_virtual_network |\n| **Total** | **6** | |\n\n## Resource Changes\n\n### Module: root\n\n#### ➕ azurerm_resource_group.main\n\n**Summary:** `example-rg` (`westeurope`)\n\n\u003cdetails\u003e\n\n| Attribute | Value |\n|-----------|-------|\n| location | `westeurope` |\n| name | 🆔 `example-rg` |\n\n\u003c/details\u003e\n\n#### 🔄 azurerm_storage_account.logs\n\n**Summary:** `stlogs` | Changed: custom_data, tags.environment\n\n\u003cdetails\u003e\n\n| Attribute | Before | After |\n|-----------|--------|-------|\n| tags.environment | `dev` | `production` |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eLarge values: custom_data (5 lines, 2 changed)\u003c/summary\u003e\n\n##### **custom_data:**\n\n\u003cpre style=\"font-family: monospace; line-height: 1.5;\"\u003e\u003ccode\u003e#!/bin/bash\n\u003cspan style=\"background-color: #fff5f5; border-left: 3px solid #d73a49; color: #24292e; display: block; padding-left: 8px; margin-left: -4px;\"\u003eecho \"Installing\u003cspan style=\"background-color: #ffc0c0; color: #24292e;\"\u003e v1.0\u003c/span\u003e\"\u003c/span\u003e\n\u003cspan style=\"background-color: #f0fff4; border-left: 3px solid #28a745; color: #24292e; display: block; padding-left: 8px; margin-left: -4px;\"\u003eecho \"Installing\u003cspan style=\"background-color: #acf2bd; color: #24292e;\"\u003e v2.0\u003c/span\u003e\"\u003c/span\u003e\napt-get update\napt-get install -y nginx\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003c/details\u003e\n```\n\n## Examples\n\nA comprehensive demo is available in the Docker image and the repository:\n\n```bash\n# View the demo report (Docker)\ndocker run --rm oocx/tfplan2md /examples/comprehensive-demo/plan.json \\\n  --principals /examples/comprehensive-demo/demo-principals.json\n\n# View the demo locally\ndotnet run --project src/Oocx.TfPlan2Md/Oocx.TfPlan2Md.csproj -- \\\n  examples/comprehensive-demo/plan.json \\\n  --principals examples/comprehensive-demo/demo-principals.json\n```\n\nThe demo includes:\n- Module grouping (root, module.network, module.security, nested modules)\n- All action types (create, update, replace, delete, no-op, forget)\n- Firewall rule semantic diffing\n- Network security group rule semantic diffing\n- Role assignments with principal mapping\n- Sensitive value handling\n- Complex nested attributes\n\nSee [examples/comprehensive-demo/README.md](examples/comprehensive-demo/README.md) for details.\n\n## Custom Templates\n\nCreate custom Scriban templates for your own report format. Templates focus on layout and presentation, with all value formatting handled by C# helpers for consistency.\n\n```bash\ndocker run -i -v $(pwd):/data oocx/tfplan2md --template /data/my-template.sbn \u003c plan.json\n```\n\nBuilt-in templates:\n- `default` (implicit when not specified): Full report with resource changes\n- `summary`: Compact summary with Terraform version, plan timestamp, and action counts only\n\nSee [Scriban documentation](https://github.com/scriban/scriban) for template syntax and [docs/features.md](docs/features.md) for available helper functions.\n\n### Resource-Specific Templates\n\nFor complex resources like firewall rule collections, tfplan2md provides resource-specific templates that show semantic diffs instead of confusing index-based changes. The default renderer (used by the CLI) applies resource-specific templates automatically when a matching template is available; the global default template is used as a fallback.\n\n**Currently supported:**\n- `azapi_resource` - Flattens JSON body into dot-notation tables with before/after comparison for updates; includes reliable documentation links to Microsoft Learn for 92 Azure resource types across 37 services\n- `azapi_update_resource` - Applies intelligent attribute grouping and array rendering to partial Azure resource updates; includes Azure API documentation links\n- `azurerm_firewall_application_rule_collection` - Shows application firewall rules with FQDNs, protocols (HTTP/HTTPS/MSSQL), and source addresses\n- `azurerm_firewall_network_rule_collection` - Shows network firewall rules with protocols, ports, and IP addresses\n- `azurerm_network_security_group` - Shows security rule changes with semantic diffing\n- `azurerm_role_assignment` - Displays human-readable role names, scopes, and principal information\n- `azuredevops_build_definition` - Shows pipeline variables, triggers, repository configuration, schedules, and jobs as structured tables with secret variable protection\n- `azuredevops_variable_group` - Shows all variables (regular and secret) with metadata, hiding only secret values\n\nExample output for a firewall rule update:\n\n```markdown\n### 🔄 azurerm_firewall_network_rule_collection.web_tier\n\n**Collection:** `web-tier-rules` | **Priority:** 100 | **Action:** Allow\n\n#### Rule Changes\n\n| | Rule Name | Protocols | Source Addresses | Destination Addresses | Destination Ports |\n|---|-----------|-----------|------------------|----------------------|-------------------|\n| ➕ | allow-dns | UDP | 10.0.1.0/24, 10.0.2.0/24 | 168.63.129.16 | 53 |\n| 🔄 | allow-http | TCP | 10.0.1.0/24, 10.0.3.0/24 | * | 80 |\n| ❌ | allow-ssh-old | TCP | 10.0.0.0/8 | 10.0.2.0/24 | 22 |\n| ⏺️ | allow-https | TCP | 10.0.1.0/24 | * | 443 |\n```\n\nSee [docs/features/001-resource-specific-templates/specification.md](docs/features/001-resource-specific-templates/specification.md) for creating custom resource templates.\n\n### Template Variables\n\nTemplates have access to:\n\n- `terraform_version` - Terraform version string\n- `format_version` - Plan format version\n- `timestamp` - Plan generation timestamp (RFC3339 format), if available\n- `summary` - Summary object with action details:\n  - `to_add`, `to_change`, `to_destroy`, `to_replace`, `no_op` - Each is an `ActionSummary` object containing:\n    - `count` - Number of resources for this action\n    - `breakdown` - Array of `ResourceTypeBreakdown` objects, each with `type` (resource type name) and `count` (number of that type)\n  - `total` - Total number of resources with changes\n- `changes` - List of resource changes with `address`, `type`, `action`, `action_symbol`, `attribute_changes`\n- `module_changes` - Resource changes grouped by module\n\n**Notes:** Attribute tables now vary depending on the resource change action:\n\n- **create** resources show a 2-column table (`Attribute | Value`) containing the *after* values.\n- **delete** resources show a 2-column table (`Attribute | Value`) containing the *before* values.\n- **update** and **replace** resources show a 3-column table (`Attribute | Before | After`).\n\nThis makes create/delete outputs more concise and avoids empty columns when a side is missing.\n\n## Development\n\n### Prerequisites\n\n- [.NET 10 SDK](https://dotnet.microsoft.com/download)\n- [Docker](https://www.docker.com/) (for container builds and integration tests)\n- [Git](https://git-scm.com/)\n\n### Getting Started\n\n```bash\n# Clone the repository\ngit clone https://github.com/oocx/tfplan2md.git\ncd tfplan2md\n\n# Restore tools (including Husky for git hooks)\ndotnet tool restore\n\n# Install git hooks\ndotnet husky install\n\n# Build and test\ndotnet build\ndotnet test\n\nTests use **TUnit** with **AwesomeAssertions** for fluent, readable assertions.\n\n### Coverage Helpers\n\nUse the helper scripts to summarize coverage from Cobertura output:\n\n```bash\n# Print overall line/branch coverage\nscripts/coverage-summary.sh\n\n# List lowest branch coverage classes (default 30, can pass a count)\nscripts/coverage-low-branches.sh 20\n```\n```\n\n### Pre-commit Hooks\n\nThis project uses [Husky.Net](https://github.com/alirezanet/Husky.Net) for git hooks:\n\n- **pre-commit**: Runs `dotnet format --verify-no-changes` and `dotnet build` (enforces code style and quality metrics)\n- **commit-msg**: Validates commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) format\n\n**Code quality checks:** The build enforces cyclomatic complexity (≤15), maintainability index (≥20), and line length (≤160 characters). See [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n### Docker Build\n\n```bash\ndocker build -t tfplan2md .\n```\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:\n\n- Branch naming conventions\n- Commit message format (Conventional Commits)\n- Pull request process\n- Code style requirements\n\n## CI/CD\n\nThis project uses GitHub Actions for continuous integration and deployment:\n\n| Workflow | Trigger | Purpose |\n|----------|---------|----------|\n| **PR Validation** | Pull requests to `main` | Format check, build, test, coverage enforcement, vulnerability scan |\n| **Coverage Data** | Push to `main` | Publish coverage badge + history to `coverage-data` branch |\n| **CI** | Push to `main` | Auto-version with [Versionize](https://github.com/versionize/versionize) when Docker-relevant files change |\n| **Release** | Version tags (`v*`) | Create GitHub Release, build and push Docker image |\n\n### Code Coverage\n\nCode coverage is automatically collected and enforced on every pull request:\n\n- **Coverage badge**: The [![Coverage](https://raw.githubusercontent.com/oocx/tfplan2md/coverage-data/assets/coverage-badge.svg)](https://raw.githubusercontent.com/oocx/tfplan2md/coverage-data/docs/coverage/history.json) badge in the README shows current line coverage percentage\n- **Coverage thresholds**: PRs must maintain or improve code coverage (currently 84.48% line coverage and 72.80% branch coverage)\n- **Coverage history**: Historical coverage data is published to the `coverage-data` branch at [docs/coverage/history.json](https://raw.githubusercontent.com/oocx/tfplan2md/coverage-data/docs/coverage/history.json)\n- **Coverage reports**: Detailed HTML coverage reports are available as workflow artifacts\n- **Maintainer override**: PRs can bypass coverage requirements using the `coverage-override` label when justified\n\n### Versioning\n\nVersioning is automated using [Conventional Commits](https://www.conventionalcommits.org/):\n\n- `feat:` commits bump the **minor** version\n- `fix:` commits bump the **patch** version\n- `BREAKING CHANGE` or `!` bumps the **major** version\n\n## About the Development Team\n\n### Mathias Raacke - Project Maintainer\n\n\u003cimg src=\"assets/profile.jpg\" alt=\"Mathias Raacke\" width=\"150\" height=\"150\" align=\"right\" style=\"border-radius: 50%; object-fit: cover; margin-left: 20px;\" /\u003e\n\nMathias Raacke develops software professionally since 2000 and uses .net and c# since 2003. He currently works at [Diamant Software](https://www.diamant-software.de) as part of the Platform-Team that provides Azure Landingzones for the Diamant Software SaaS solution. The Diamant Software Azure platform is developed with 100% IaC and Terraform. Before he moved to the Platform Team, he has been working as software-architect at Diamant since 2012. In the past, Mathias used to work as independent trainer and consultant for .NET development and software architecture, and he developed the WPF localization addin NLocalize for Visual Studio with his own former company Neovelop GmbH.\n\n[![LinkedIn](https://img.shields.io/badge/LinkedIn-mathiasraacke-0A66C2?logo=linkedin\u0026logoColor=white)](https://www.linkedin.com/in/mathiasraacke/) [![GitHub](https://img.shields.io/badge/GitHub-oocx-181717?logo=github\u0026logoColor=white)](https://github.com/oocx) [![YouTube](https://img.shields.io/badge/YouTube-Channel-FF0000?logo=youtube\u0026logoColor=white)](https://www.youtube.com/channel/UCksGVtTPuok5ub267_mgVPA) [![Bluesky](https://img.shields.io/badge/Bluesky-oocx-1185FE?logo=bluesky\u0026logoColor=white)](https://bsky.app/profile/oocx.bsky.social) [![Microsoft Certified](https://img.shields.io/badge/Microsoft-Certified-00A4EF?logo=microsoft\u0026logoColor=white)](https://learn.microsoft.com/en-us/users/mathiasraacke/transcript/drl3qhq482qr91p)\n\n### GitHub Copilot - AI Development Partner\n\n\u003cimg src=\"assets/github-copilot.png\" alt=\"GitHub Copilot\" width=\"150\" height=\"150\" align=\"right\" style=\"border-radius: 50%; object-fit: cover; margin-left: 20px; background: #d0d0d0\" /\u003e\n\nI'm GitHub Copilot, the AI pair programmer that helped write 100% of this project's code, tests, and documentation. I work as an intelligent coding assistant, providing context-aware suggestions, generating implementations from specifications, and helping maintain code quality throughout the development lifecycle.\n\nFor this project, we use a multi-model approach to leverage different AI strengths:\n\n- **Claude Sonnet 4.5** - Primary model for requirements engineering, code review, and technical writing\n- **GPT-5.2-Codex** - Latest Codex model for C# code generation, .NET patterns, and development tasks\n- **Claude Opus 4.5** - Reserved for difficult problems and edge cases where other models struggled\n- **GPT-5.2** - General-purpose reasoning, architectural decisions, and complex problem-solving\n- **Gemini 3 Flash** - Fast iteration for task planning, release management, and UAT testing\n\nThis hybrid approach combines the best capabilities of each model, selecting the right tool for each type of work while maintaining high code quality and development velocity.\n\n[![GitHub Copilot](https://img.shields.io/badge/GitHub%20Copilot-100%25-blue?logo=github)](https://github.com/features/copilot) [![Powered by AI](https://img.shields.io/badge/Powered%20by-Multi--Model%20AI-purple)](docs/ai-model-reference.md)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foocx%2Ftfplan2md","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foocx%2Ftfplan2md","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foocx%2Ftfplan2md/lists"}