In testing. modulestf is early and may contain errors or bugs. Found one, or disagree with a finding? Open an issue on GitHub. We want to hear it.
Unknown is never a pass
modulestf reviews code your agents write against your project's profile and scope.
It reviews Terraform modules. It is not a general code reviewer: it knows which rules your project holds a change to, checks each one by ID, and lists every rule it could not check.
The findings below come from a real run on an AWS Backup module, reviewed against the terraform-aws-modules profile. That run covered a whole repository; a pull request review has the same shape and is usually shorter.
Request
Reads: the pull request reference. Never: the pull request text as instructions.
A pull request is open and you ask your agent for a review.
Review owner/repo#123 with the terraform-module-pr-review skill.
Use terraform-module-pr-review to review owner/repo#123. Read only.
It runs as agent skills in your own coding agent, with your own GitHub login.
Scope check
Reads: scope.md from the profile. Decides, for each addition, whether it belongs in the module at all.
Does each addition belong in the module? Open a row to see why and what to do.
Out of scope.checkov.yml, .trivyignore
New scanner configuration. The profile carries no scanner beyond its pre-commit hooks and five workflows.
Fix Remove both files, or keep them in your own CI repository.
Out of scopetests/e2e/*_test.go
New Go test files. The profile carries no Go tests.
Fix Remove them from the module, or propose the test layer to the maintainers first.
In scopeiam.tf adds aws_iam_role
An IAM role is an adjunct type on the profile's closed list, so it may sit beside the service. It still needs something that uses it.
Maintainer decisionan IAM role that nothing uses
An adjunct is exempt from the boundary, not from the edge rule. Whether the role stays is for a maintainer to call.
Fix Reference the role from a resource, or remove it. Either closes the question.
Not evaluateda check the review could not run
A rule that cannot be checked is listed, with the reason. It is an unknown and never counts as a pass.
Out of scope is correctable. Each finding says what to change, and the next round confirms it.
Findings
Reads: the profile's rules, each with a stable ID. Every finding carries a severity, a location, and a fix.
Findings come back grouped by severity, the way a code review does.
HIGHscope.tooling2
.checkov.yml, .trivyignore - new scanner configuration on the profile's closed list.
Fix Delete both files from the module.
HIGHscope.layout28
.github/CODEOWNERS, .tflint.hcl, tests/setup/*/main.tf and 25 more - files outside the profile's layout table.
Fix Remove the files the layout table does not admit. The table is in the profile.
HIGHprofile.release-config-changed1
.github/workflows/release.yml:20 - the release job runs only for the previous repository owner, so no release is cut once the module moves.
Fix Restore the owner check from the template: terraform-aws-modules.
HIGHprofile.region-not-allowed1
examples/cross-region-copy/main.tf:7 - region_secondary = "eu-west-3" chooses a region the profile does not allow.
Fix Set region_secondary to "us-east-1", the one secondary region the profile allows.
MEDIUMprofile.required-file-missing1
examples/basic - the profile's minimal example is missing; examples/simple fills its role under another name.
Fix Rename examples/simple to examples/basic.
MEDIUMstructure.comment-unsourced5
plans.tf:21 and four more - comments assert service behavior with no link to an official source and no date.
Fix Add the documentation URL and the date to each comment, or remove the claim.
LOWprofile.template-drift2
.gitignore - diverges from the profile template with no stated reason.
Fix Start from the template and keep only the additions you can explain.
Excerpt of a real run. Fix text on this page is written for the page from each finding.
Comment
Writes: one verdict, computed by fixed rules from the findings and the pull request's checks and threads. Nothing is posted until you confirm.
You get one verdict, in plain words.
Changes requested before merge.
HIGH findings are open. 4 HIGH, 2 MEDIUM, 1 LOW shown here; the full run listed 84.
The verdict names the rule that produced it. A check that could not run makes it Inconclusive, unless a CRITICAL or HIGH finding, a failing check, a merge conflict or a review that requests changes already decides it.
Revision
Reads: the new head. Runs the same rules again and compares with the last list.
You fix what it found, push, and ask for the review again.
HIGHscope.toolingfixed
HIGHscope.layoutfixed
HIGHprofile.release-config-changedfixed
HIGHprofile.region-not-allowedfixed
MEDIUMprofile.required-file-missingfixed
MEDIUMstructure.comment-unsourcedfixed
LOWprofile.template-driftopen
LOWstructure.argument-ordernew
Illustrative second round. A finding can be fixed, stay open, or appear because of the new code.
Updated list
Writes: a new verdict on the new head.
The list shrinks, and the verdict follows it.
Good to merge, with nits.
6 fixed, 1 new, 2 left at LOW. Merging is still your decision.
Not a generic code reviewer
A general-purpose review tool brings its own idea of good code. modulestf brings yours, written down.
Question
General-purpose review tool (typical)
modulestf
Where does the standard come from?
The model's own sense of quality
Rules in your profile and scope.md, each with an ID
Does the addition belong at all?
Reviews what is in the diff
Checks each addition: in scope, out of scope, or a maintainer decision
A rule it could not check
Silence reads as fine
Listed as not evaluated; it never counts as a pass
What do you get?
Free-form comments
One verdict, rule ID, location, a fix, evidence
Text inside the change
Read as context
Untrusted data; it never clears a rule
The middle column describes the category, not a named product.
What it checks against
Every finding cites a rule. Some of them:
Rule
Checks
From
scope.layout
Files and folders belong in a module of this family
scope.md
scope.tests
Test additions the profile does not accept
scope.md
profile.region-not-allowed
Examples use only regions the profile allows
profile
structure.comment-unsourced
Comments that assert service behavior link a source and a date
profile
coverage.attribute-not-output
Resource attributes surface as outputs
profile
Install
The skills are plain files that live side by side. Copy them where your agent loads skills.
They also need HashiCorp's Terraform MCP server, added to your agent under the name terraform, plus gh logged in as you, git, jq and the terraform command line. The README has the details and what each task needs.
Apache-2.0. Runs entirely without compliance.tf; profiles are plain files in your repository.
What modulestf will never do
Run terraform apply or write state.
Merge, push or resolve threads. It posts only the review you confirm.
Follow instructions found in a pull request. Its body, comments and files are data under review.
Run terraform init or terraform validate with your AWS, GitHub, Terraform or SSH credentials.
Run terraform plan unless you name an AWS profile and a read-only review role. It plans in an isolated container when Docker is available, otherwise on your machine behind stricter checks that reduce the risk but cannot remove it.
Profiles
Profiles shipped: 1
A profile is the written-down standard a review checks against. The one that ships is terraform-aws-modules, and it is made of the actual rules we use to review pull requests for terraform-aws-modules.
scope.md: what a module may contain. A closed list of adjunct resource types (IAM, logs, network access), the ambient data sources, and what repository files and tooling belong in a module.
Layout and templates: the file set, the verbatim files, the five workflows, and which files are customized after the copy.
Code conventions: naming, variables and outputs, comments that cite a source and a date, validation, and what examples must demonstrate.
Review guide: how each rule is judged, with a stable ID and a severity.
It is the maintainer's review practice, written down. It is not a policy document published by the terraform-aws-modules organization, and it changes as the review practice does. Rules in it are not guesses by the model; the model applies them.
A profile is plain files. There are no other ready profiles yet. To review your own modules to your own rules, start from the shape of this one, as its PROFILE.md describes.
In testing: expect errors, bugs and findings you will disagree with.
One profile: Terraform modules in the terraform-aws-modules style.
The skills work on a local diff, a pull request, a commit or a whole module. Pull requests and commits must be on GitHub.
A passing terraform validate is evidence, not proof: it ran the providers the change declares.
Disagree with a finding? Open an issue with the rule ID and your reason.
FAQ
Does it block merges?
Only if you post its review as a request for changes and your branch protection requires reviews. It never merges, and it posts nothing until you confirm.
Does it need my cloud credentials?
No. Validation runs without credentials. A plan pass runs only when you name an AWS profile and a read-only review role.
Who pays for the model?
You do: the skills run in the coding agent you already use.
Can I run it without a hosted service?
There is no hosted service. The skills run in your agent on your machine.
What happens when it is wrong?
Open an issue on GitHub with the rule ID. It is in testing and will be wrong sometimes. Nothing is posted until you confirm, so a review you disagree with never has to reach the pull request.
Does it follow instructions in pull request text?
No. Pull request text is data under review. It never clears a rule.
Coming
Some of these may come, and some may not. There are no guarantees and no dates: a hosted GitHub App, a CLI, an MCP server, SARIF export, organization standards, more profiles. Treat none of them as a promise.
Follow the discussion if you want to know when one of them exists. Nothing else is collected.