with one click
port-hadolint-rule
Port a Hadolint rule from Haskell to Go implementation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Port a Hadolint rule from Haskell to Go implementation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Add auto-fix support to an existing BuildKit linter rule
Implement a new custom `tally/*` Dockerfile lint rule end-to-end (rule code, overlap research, fix coordination, realistic tests/fixtures, snapshots, and docs). Use when a user describes desired behavior for a new tally-specific rule.
Port a ShellCheck SC rule to native Go in tally while preserving shellcheck/SC#### compatibility (config, reporting, fixes, and docs ownership).
Implement a new custom `tally/*` Dockerfile lint rule end-to-end (rule code, overlap research, fix coordination, realistic tests/fixtures, snapshots, and docs). Use when a user describes desired behavior for a new tally-specific rule.
Create a new custom Go analysis linter (analyzer) in _tools/customlint/ for the tally project. Use this skill whenever the user wants to add a new lint check, custom analyzer, static analysis rule, or code quality check to the customlint plugin. Also use when the user says things like "add a linter for X", "catch bad pattern Y at CI time", "flag hardcoded Z values", or "enforce convention W in code".
Build and maintain documentation sites with Mintlify. Use when creating docs pages, configuring navigation, adding components, or setting up API references.
| name | port-hadolint-rule |
| description | Port a Hadolint rule from Haskell to Go implementation |
| argument-hint | rule-code (e.g. DL3022) |
| disable-model-invocation | true |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash(go *), Bash(git status), mcp__github__get_file_contents, mcp__github__search_code |
You are porting a Hadolint rule from Haskell to Go for the tally project.
Use the GitHub MCP tools to fetch the original Haskell implementation:
First, try to get the rule implementation file directly:
mcp__github__get_file_contents with:
owner: "hadolint"repo: "hadolint"path: "src/Hadolint/Rule/$ARGUMENTS.hs"branch: "master"If that fails, use mcp__github__search_code to find the rule:
repo:hadolint/hadolint for the rule codeCarefully analyze the Haskell implementation to understand:
Use GitHub MCP tools to get the test specification:
Try to get the test file directly:
mcp__github__get_file_contents with:
owner: "hadolint"repo: "hadolint"path: "test/Hadolint/Rule/$ARGUMENTSSpec.hs"branch: "master"If that fails, search for the test file:
mcp__github__search_code in repo:hadolint/hadolint for "$ARGUMENTSSpec"Extract ALL test cases from the spec file - both passing and failing cases
ruleCatches indicates the rule SHOULD trigger (expect violation)ruleCatchesNot indicates the rule should NOT trigger (expect no violation)Before implementing, read these files to understand the patterns:
internal/rules/hadolint/dl3004.go - a standard rule implementationinternal/rules/hadolint/dl3012.go - a pointer file for semantic-based rulesinternal/shell/shell.go - shell parsing utilitiesinternal/shell/packages.go - package manager parsinginternal/semantic/semantic.go - semantic modelinternal/semantic/builder.go - semantic model builderinternal/rules/rule.go - Rule interface and LintInputDecide where to implement based on rule nature:
internal/rules/hadolint/$ARGUMENTS.go documenting the semantic implementationNEVER parse shell commands using regex or string operations.
Always use the internal/shell package:
import "github.com/wharflab/tally/internal/shell"
// To check if a command contains a specific command name:
if shell.ContainsCommandWithVariant(cmdStr, "sudo", shellVariant) {
// violation
}
// To get all command names:
commands := shell.CommandNamesWithVariant(cmdStr, shellVariant)
// To extract package installations:
installs := shell.ExtractPackageInstalls(cmdStr, shellVariant)
Always leverage the semantic model (internal/semantic/):
// Get semantic model from input
sem, ok := input.Semantic.(*semantic.Model)
if !ok {
sem = nil
}
// Use for shell variant detection
if sem != nil {
if info := sem.StageInfo(stageIdx); info != nil {
shellVariant = info.ShellSetting.Variant
// Skip non-POSIX shells if rule is shell-specific
if shellVariant.IsNonPOSIX() {
continue
}
}
}
// Use for stage information
for info := range sem.ExternalImageStages() {
// Check external image references
}
If the rule requires semantic information not yet tracked:
StageInfo in internal/semantic/stage_info.gointernal/semantic/builder.gopackage hadolint
import (
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/wharflab/tally/internal/rules"
"github.com/wharflab/tally/internal/semantic"
"github.com/wharflab/tally/internal/shell"
)
// $ARGUMENTSRule implements the $ARGUMENTS linting rule.
type $ARGUMENTSRule struct{}
// New$ARGUMENTSRule creates a new $ARGUMENTS rule instance.
func New$ARGUMENTSRule() *$ARGUMENTSRule {
return &$ARGUMENTSRule{}
}
// Metadata returns the rule metadata.
func (r *$ARGUMENTSRule) Metadata() rules.RuleMetadata {
return rules.RuleMetadata{
Code: rules.HadolintRulePrefix + "$ARGUMENTS",
Name: "...", // Short name from Hadolint wiki
Description: "...", // Description from Hadolint wiki
DocURL: rules.HadolintDocURL("$ARGUMENTS"),
DefaultSeverity: rules.SeverityWarning, // or SeverityError based on original
Category: "...", // security, performance, style, etc.
IsExperimental: false,
}
}
// Check runs the $ARGUMENTS rule.
func (r *$ARGUMENTSRule) Check(input rules.LintInput) []rules.Violation {
var violations []rules.Violation
meta := r.Metadata()
// Get semantic model
sem, ok := input.Semantic.(*semantic.Model)
if !ok {
sem = nil
}
// Implementation...
return violations
}
// init registers the rule with the default registry.
func init() {
rules.Register(New$ARGUMENTSRule())
}
Create test file internal/rules/hadolint/$ARGUMENTS_test.go:
internal/rules/hadolint/dl3004_test.gotestutil.MakeLintInput to create test inputsfunc Test$ARGUMENTSRule_Check(t *testing.T) {
tests := []struct {
name string
dockerfile string
wantCount int
}{
// Add ALL cases from original Hadolint spec
// ruleCatches cases -> wantCount: 1 (or more)
// ruleCatchesNot cases -> wantCount: 0
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := testutil.MakeLintInput(t, "Dockerfile", tt.dockerfile)
r := New$ARGUMENTSRule()
violations := r.Check(input)
if len(violations) != tt.wantCount {
t.Errorf("got %d violations, want %d", len(violations), tt.wantCount)
}
})
}
}
Run the tests to verify:
go test ./internal/rules/hadolint/... -run $ARGUMENTS -v
Ensure ALL original Hadolint test cases pass.
Add a directory fixture for the new rule:
internal/integration/fixtures/lint/$ARGUMENTS/Dockerfile.internal/integration/fixtures/lint/$ARGUMENTS/.tally.toml selecting the Hadolint rule and any overlap rules needed by the case.internal/integration/fixtures/fix/$ARGUMENTS/Dockerfile and optional .tally.toml.UPDATE_SNAPS=true go test ./internal/integration -run 'Test(Lint|Fix)Fixtures' -count=1.UPDATE_SNAPS.The fixture harness writes go-snaps files next to the Dockerfile: result_1.snap.json for lint, fixed_1.snap.Dockerfile for fix output, and
optional report_1.snap.md for fix reports. Use explicit Go integration tests only for behavior that needs CLI/config/discovery/stdin/multi-file
coverage outside the directory harness.
Evaluate whether the rule can provide automatic fixes:
For fixes that can be computed immediately:
func (r *MyRule) Check(input rules.LintInput) []rules.Violation {
// Use helper for RUN commands
return ScanRunCommandsWithPOSIXShell(input, func(run *instructions.RunCommand, shellVariant shell.Variant, file string) []rules.Violation {
// ... detection logic ...
fix := &rules.SuggestedFix{
Description: "Replace X with Y",
Safety: rules.FixSafe, // or FixSuggestion, FixUnsafe
Edits: []rules.TextEdit{{
Location: rules.NewRangeLocation(file, startLine, startCol, endLine, endCol),
NewText: "replacement text",
}},
}
return []rules.Violation{
rules.NewViolation(loc, meta.Code, msg, meta.DefaultSeverity).
WithSuggestedFix(fix),
}
})
}
For fixes requiring network I/O (image digests, checksums):
fix := &rules.SuggestedFix{
Description: "Add image digest",
Safety: rules.FixSafe,
NeedsResolve: true,
ResolverID: "image-digest",
ResolverData: map[string]string{"image": "alpine", "tag": "3.18"},
}
FixSafe: Always correct, won't change behaviorFixSuggestion: Likely correct but may need reviewFixUnsafe: May change behavior significantlyFor precise command replacement within RUN instructions, use shell.FindCommandOccurrences() to get exact byte offsets within shell scripts.
After implementation is complete, update the tracking files:
Update hadolint-status.json:
Add an entry to internal/rules/hadolint-status.json:
"$ARGUMENTS": {
"status": "implemented",
"tally_rule": "hadolint/$ARGUMENTS"
}
Place it in alphabetical order among the other rules.
Regenerate documentation:
./scripts/generate-hadolint-table.sh --update
This updates the Hadolint compatibility table in the documentation.
Update integration test snapshots (if needed):
UPDATE_SNAPS=true go test ./internal/integration/...
Create _docs/rules/hadolint/$ARGUMENTS.mdx following the pattern in _docs/rules/hadolint/DL3004.mdx:
# hadolint/$ARGUMENTS## Auto-fix section with before/after example (if auto-fix is supported)## Reference section with named link: - [hadolint/$ARGUMENTS](https://github.com/hadolint/hadolint/wiki/$ARGUMENTS)Add the rule to _docs/rules/hadolint/overview.mdx in the main table.
internal/shell for command parsing (no regex)init() function registers the rulego test ./internal/rules/hadolint/... -run $ARGUMENTS -vinternal/integration/fixtures/lint/ and, when fix-capable, internal/integration/fixtures/fix/hadolint-status.json updated with new rulegenerate-hadolint-table.sh --update_docs/rules/hadolint/$ARGUMENTS.mdx created and added to overview