tfskel is a Go CLI tool for making terraform operations Simplified and predictable.
Use Conventional Commits: type(scope): description
Types: feat, fix, perf, docs, test, refactor, build, ci, chore, style
Scopes (pick one, or omit for cross-cutting changes):
| Scope | Covers |
|---|---|
init |
tfskel init command and supporting code |
scaffold |
tfskel scaffold command and supporting code |
validate |
tfskel validate command and internal/validate/ |
review |
tfskel review command and internal/plan/ |
config |
Configuration system (internal/config/) |
Cross-cutting changes (format, logger, build, CI) use the type alone — no scope needed.
Breaking changes: add ! after the type, e.g. feat(validate)!: redesign report output
make check # fmt, vet, lint, test
make build # build binary- Follow Effective Go
- See CONTRIBUTING.md for full guidelines
When a value can be derived from existing config, an API, the filesystem, or computed at runtime, derive it. Hardcoded literals drift from reality the moment something upstream changes.
Hardcode only when one of these applies:
- Externally fixed standard — a value defined by an outside spec/ecosystem with no programmatic source (e.g. HCL file extensions
.tf/.tfvars/.hcl, HTTP status codes, well-known port numbers). - Security boundary — allowlists, trusted roots, or policy decisions that must not be derived from untrusted input.
- No reasonable dynamic source exists — the value encodes a custom decision specific to this project that nothing else knows.
When you do hardcode, name it: lift the literal into a named const or var with a short comment explaining why it can't be derived. A reader should be able to tell at a glance whether a literal is a deliberate constant or a value that should have been looked up.
Trust invariants set by upstream code. Add runtime checks only at system boundaries (CLI flags, user input, file I/O, external APIs).
- Don't re-validate arguments passed between internal functions — the caller and the type system already guarantee them.
- Don't add silent fallbacks (
if x == "" { x = y }) for cases that only fire when upstream is broken. They hide bugs. - If an "impossible" case could theoretically occur, let it surface: explicit error, panic for programming bugs, or just ugly output. Visible failure beats silent recovery.
Rule of thumb: if deleting a check would only change behavior when something upstream is broken, delete the check.
A helper earns its keep by doing one of two things:
- Hiding real complexity — the inline version is hard to read or easy to get wrong.
- Centralizing a decision likely to change — so updating it is one edit instead of many.
If a helper does neither — if it only shortens call sites by forwarding the same arguments to another function — inline the code. Repetition of short, obvious code is cheaper than the cognitive cost of jumping to a helper to find out it does nothing.
Red flags that a helper is pulling its weight:
- Its body is a single call with the same args rearranged.
- Its name paraphrases what the underlying function already says (
newCmdLoggerwrappinglogger.NewWithOptions(...)). - Callers lose useful information by not seeing the inline code (e.g. a dry-run wrap is load-bearing behavior and benefits from being visible at the call site).
"Four sites → one helper" is not an automatic win. Four readable inline calls beat one helper that forces every reader to jump elsewhere to understand a single-purpose call site.
| Error Type | Shows Usage? | Reasoning |
|---|---|---|
| Unknown flag | No | User knows the command, just mistyped a flag |
| Missing required flag | No | User knows the command, just forgot a flag |
| Mutually exclusive flags | No | User knows both flags, just combined them wrong |
| Invalid flag value | No | User knows the flag, just got the value wrong |
| Wrong number of args | No | User knows the command, just forgot an argument |
| Runtime errors | No | Nothing to do with CLI syntax |
| No subcommand given | Yes | User needs to discover available subcommands |
Rule of thumb: Show usage when the user doesn't know what commands/flags exist. Suppress it when they clearly do but made a mistake. Users who need help will use --help.