Contributing

Thank you for your interest in contributing! This guide covers development workflow, coding standards, and our AI-powered development process.

Development Workflow

Branch Strategy

  • Main branch (main) - Always in a releasable state
  • Feature branches - Created from main for new features or fixes

Creating a Feature Branch

Use descriptive branch names with the following prefixes. Features and fixes use an issue number (for example, feat/025-semantic-diff, fix/026-template-escape), while other types use descriptive names:

PrefixPurposeExample
feat/New featuresfeat/semantic-diff-firewall-rules
fix/Bug fixesfix/template-rendering-escape
docs/Documentation changesdocs/update-installation-guide
refactor/Code refactoringrefactor/parser-immutable-models
chore/Maintenance taskschore/update-dependencies
workflow/Agent/workflow changesworkflow/add-uat-skill
website/Website changeswebsite/add-feature-page

Example workflow:

git checkout main
git pull origin main
git checkout -b feat/your-feature-name

Testing Requirements

All changes must include appropriate tests. The project uses a comprehensive testing strategy:

Test Types

  • Unit Tests - Test individual components in isolation
  • Integration Tests - Test end-to-end workflows, including Docker-based tests
  • Invariant Tests - Property-based tests that verify markdown invariants
  • Snapshot Tests - Golden file tests that detect unexpected output changes
  • Template Isolation Tests - Test each template independently
  • Fuzz Tests - Test with edge-case inputs (special characters, Unicode)
  • Markdownlint Integration - Docker-based linting with markdownlint-cli2

Running Tests

# Run all tests
dotnet test

# Run specific test categories
dotnet test --filter "FullyQualifiedName~MarkdownInvariantTests"
dotnet test --filter "FullyQualifiedName~MarkdownLintIntegrationTests"

Markdown Quality Requirements

All generated markdown must:

  • Pass markdownlint validation (MD012 and other rules)
  • Parse correctly with Markdig
  • Render correctly on GitHub and Azure DevOps
  • Have proper table structure (no blank lines between rows)
  • Have proper heading spacing (blank lines before/after)
  • Have balanced HTML tags (
    , )

Commit Messages

This project uses Conventional Commits to automate versioning and changelog generation.

Format

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Commit Types

TypeDescriptionVersion Bump
featA new featureMinor (0.x.0)
fixA bug fixPatch (0.0.x)
docsDocumentation onlyNone
styleCode style changesNone
refactorCode refactoringNone
testAdding or modifying testsNone
choreOther maintenanceNone

Breaking Changes

For breaking changes, add BREAKING CHANGE: in the commit footer or use ! after the type:

feat(parser)!: change output format to JSON

BREAKING CHANGE: The output format has changed from plain text to JSON.

Commit Cohesion

Each commit should focus on a single topic or change. If you find yourself using "and" repeatedly in a commit message, consider splitting it into multiple commits.

Examples

# Feature
git commit -m "feat(cli): add --output flag for custom output path"

# Bug fix
git commit -m "fix(parser): handle empty resource changes array"

# Documentation
git commit -m "docs: update installation instructions"

# Breaking change
git commit -m "feat(api)!: rename TerraformPlan to PlanResult"

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes following the coding guidelines
  3. Ensure all checks pass:
    dotnet format --verify-no-changes
    dotnet build
    dotnet test
  4. Push your branch and create a Pull Request
  5. Wait for review - PR validation will run automatically
  6. Merge using "Rebase and merge" - This project requires a linear history

PR Requirements

  • All CI checks must pass (build, test, format, vulnerability scan)
  • Code follows the project's style guidelines (enforced by .editorconfig)
  • Commit messages follow Conventional Commits format

Project Directory Structure

Understanding the project layout:

  • src/ - Core library and CLI entry point
  • tests/ - All test projects (unit, integration, snapshot tests)
  • tools/ - Helper tools (HtmlRenderer, ScreenshotGenerator, TerraformShowRenderer)
  • docs/ - Feature specifications, ADRs, architecture documentation
  • examples/ - Demo Terraform plans and output samples
  • website/ - GitHub Pages website (HTML/CSS)
  • scripts/ - Build, test, and workflow automation scripts
  • .github/ - GitHub Actions workflows and agent skill definitions

Merge Strategy

This project uses rebase and merge to maintain a linear Git history:

  • When merging a PR, use the "Rebase and merge" button
  • If conflicts occur, rebase onto main: git pull --rebase origin main
  • Force-push your branch: git push --force-with-lease
  • Do NOT use "Squash and merge" or "Create a merge commit"

Coding Standards

Access Modifiers

tfplan2md is a standalone CLI tool, not a class library. Use the most restrictive access modifier that works:

  • private - Default for class members
  • internal - For cross-assembly visibility within the solution
  • ⚠️ public - Only for main entry points or when absolutely necessary

Never use public just for testing. Instead, use InternalsVisibleTo to expose internal members to test projects.

Code Comments

All code must be thoroughly documented with XML doc comments:

  • All members (public, internal, private) require XML doc comments
  • Comments must explain "why" not just "what"
  • Use standard XML tags: <summary>, <param>, <returns>, <remarks>
  • Reference related features/specifications for traceability

Example

/// <summary>
/// Parses Terraform plan JSON and extracts resource changes.
/// </summary>
/// <param name="planFilePath">Absolute path to the plan JSON file.</param>
/// <returns>Collection of resource changes found in the plan.</returns>
/// <remarks>
/// Uses streaming deserialization for memory efficiency on large files.
/// Related feature: docs/features/008-comprehensive-demo/
/// </remarks>
internal async Task<IReadOnlyList<ResourceChange>> ParseAsync(string planFilePath)
{
    // Implementation
}

Local Development Setup

Prerequisites

Getting Started

# Clone the repository
git clone https://github.com/oocx/tfplan2md.git
cd tfplan2md

# Restore tools (including Husky for git hooks)
dotnet tool restore

# Install git hooks
dotnet husky install

# Build and test
dotnet build
dotnet test

Pre-commit Hooks

This project uses Husky.Net for git hooks:

  • pre-commit: Runs dotnet format --verify-no-changes and dotnet build
  • commit-msg: Validates commit message follows Conventional Commits format

If your commit is rejected:

  1. Format issues: Run dotnet format to fix formatting
  2. Build errors: Fix the build errors before committing
  3. Commit message: Ensure your message follows the format type: description

AI-Powered Development Workflow

This project was built 100% using GitHub Copilot with a multi-agent AI workflow.

Learn About the AI Workflow

Release Process

Releases are automated via GitHub Actions:

  1. When commits are pushed to main, the CI workflow runs Versionize
  2. Versionize only runs when Docker-relevant files changed (runtime code, examples, build config)
  3. If there are feat:, fix:, or BREAKING CHANGE commits, Versionize:
    • Bumps the version in .csproj
    • Updates CHANGELOG.md
    • Creates a git tag (for example, v0.2.0)
  4. The tag push triggers the Release workflow which:
    • Creates a GitHub Release with changelog notes
    • Builds and pushes the Docker image to Docker Hub

Note: Changes to tests, documentation, website, scripts, or GitHub workflows do not trigger releases, as they do not affect the published Docker image.

Questions?

If you have questions, feel free to open an issue for discussion.