Skip to content

Commit 4a74096

Browse files
Alex Holmbergclaude
authored andcommitted
feat(hadolint): add native Rust Dockerfile linter with GPL-3.0 license
This commit introduces hadolint-rs, a complete Rust translation of the Hadolint Dockerfile linter, along with agent integration and proper GPL-3.0 licensing/attribution. ## Hadolint-RS (src/analyzer/hadolint/) Native Dockerfile linting without external binary dependency: - Complete Dockerfile parser with AST representation - 70+ lint rules (DL3xxx, DL4xxx series) - Pragma support for inline rule ignoring (# hadolint ignore=DL3008) - Configurable severity thresholds and rule ignoring - Shell command analysis for RUN instructions ### Formatters (src/analyzer/hadolint/formatter/) - JSON: Machine-readable for CI/CD pipelines - SARIF: GitHub Actions Code Scanning integration - TTY: Colored terminal output for humans - Checkstyle: XML format for Jenkins - CodeClimate: NDJSON for GitLab CI - GNU: Compiler-style output for editors ## Agent Integration (src/agent/tools/hadolint.rs) AI-optimized tool output with: - Priority rankings (critical/high/medium/low) - Category classification (security/best-practice/maintainability/performance) - Actionable fix recommendations for each rule - Quick fixes summary for most important issues - Decision context for rapid assessment - Documentation links to rule wikis ## Agent Display (src/agent/ui/) Docker-themed visual output: - Docker blue color scheme (#39 ANSI) - Priority-colored indicators (red/orange/yellow/green) - Category badges ([SEC], [BP], [DEP], [PERF]) - Collapsible issue preview with quick fix hints - New icons for Docker, lint priorities ## License & Attribution Changed license to GPL-3.0 due to Hadolint derivative work: - Updated LICENSE file to GPL-3.0 full text - Created THIRD_PARTY_NOTICES.md with full attribution - Added attribution header to hadolint/mod.rs - Updated README.md with GPL-3.0 badge and attribution section - Updated Cargo.toml license field ## Other Changes - Shell tool: Added async streaming output with tokio::process - Agent: Improved tool call handling and context management - Removed stray screenshot from analyzer directory 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5c3e2e0 commit 4a74096

104 files changed

Lines changed: 12795 additions & 61 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.16.0"
44
edition = "2024"
55
authors = ["Syncable Team"]
66
description = "A Rust-based CLI that analyzes code repositories and generates Infrastructure as Code configurations"
7-
license = "MIT OR Apache-2.0"
7+
license = "GPL-3.0"
88
repository = "https://github.com/syncable-dev/syncable-cli"
99
keywords = ["iac", "infrastructure", "docker", "terraform", "cli"]
1010
categories = ["command-line-utilities", "development-tools"]
@@ -48,7 +48,7 @@ term_size = "0.3"
4848
# Vulnerability checking dependencies
4949
rustsec = "0.30"
5050
reqwest = { version = "0.12", features = ["json", "blocking"] }
51-
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread", "sync"] }
51+
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread", "sync", "process", "io-util"] }
5252
textwrap = "0.16"
5353
tempfile = "3"
5454
dirs = "6"
@@ -77,6 +77,9 @@ rig-core = { version = "0.26", features = ["derive"] }
7777
# Diff rendering for file confirmation UI
7878
similar = "2.6"
7979

80+
# Dockerfile linting (hadolint-rs)
81+
nom = "7" # Parser combinators for Dockerfile parsing
82+
8083
[dev-dependencies]
8184
assert_cmd = "2"
8285
predicates = "3"

LICENSE

Lines changed: 674 additions & 21 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<p align="center">
1212
<a href="https://crates.io/crates/syncable-cli"><img src="https://img.shields.io/crates/v/syncable-cli?style=flat-square&color=blue" alt="Crates.io"></a>
1313
<a href="https://crates.io/crates/syncable-cli"><img src="https://img.shields.io/crates/d/syncable-cli?style=flat-square" alt="Downloads"></a>
14-
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg?style=flat-square" alt="License"></a>
14+
<a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square" alt="License"></a>
1515
<a href="https://www.rust-lang.org/"><img src="https://img.shields.io/badge/Built%20with-Rust-orange?style=flat-square" alt="Rust"></a>
1616
</p>
1717

@@ -241,7 +241,16 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
241241
242242
## 📄 License
243243
244-
MIT License — see [LICENSE](LICENSE) for details.
244+
This project is licensed under the **GNU General Public License v3.0** (GPL-3.0).
245+
246+
See [LICENSE](LICENSE) for the full license text.
247+
248+
### Third-Party Attributions
249+
250+
The Dockerfile linting functionality (`src/analyzer/hadolint/`) is a Rust translation
251+
of [Hadolint](https://github.com/hadolint/hadolint), originally written in Haskell by
252+
Lukas Martinelli and contributors. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
253+
for full attribution details.
245254
246255
---
247256

THIRD_PARTY_NOTICES.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Third Party Notices
2+
3+
This file contains attributions and license information for third-party software
4+
incorporated into Syncable-CLI.
5+
6+
---
7+
8+
## Hadolint
9+
10+
The Dockerfile linting functionality in `src/analyzer/hadolint/` is a Rust
11+
translation of the original Hadolint project.
12+
13+
**Original Project:** [Hadolint](https://github.com/hadolint/hadolint)
14+
15+
**Original Authors:**
16+
- Lukas Martinelli (lukasmartinelli)
17+
- Lorenzo Bolla (lbolla)
18+
- And all contributors to the Hadolint project
19+
20+
**Original License:** GNU General Public License v3.0 (GPL-3.0)
21+
22+
**Original Copyright:**
23+
```
24+
Copyright (c) 2016-2024 Lukas Martinelli and contributors
25+
```
26+
27+
**What was translated:**
28+
- Dockerfile parsing logic (originally in Haskell)
29+
- Lint rule definitions (DL3xxx, DL4xxx series)
30+
- Pragma/ignore directive handling
31+
- Configuration file format
32+
- Rule severity and messaging
33+
34+
**Modifications made:**
35+
- Complete rewrite from Haskell to Rust
36+
- Integration with Syncable-CLI's agent and tool system
37+
- Native async support for streaming output
38+
- Adaptation to Rust error handling patterns
39+
- Additional rules and improvements specific to Syncable's use cases
40+
41+
**License Notice:**
42+
This derivative work is licensed under GPL-3.0, as required by the original
43+
Hadolint license. See the LICENSE file in the root of this repository.
44+
45+
The full text of the GPL-3.0 license can be found at:
46+
https://www.gnu.org/licenses/gpl-3.0.en.html
47+
48+
---
49+
50+
## ShellCheck (Rule Concepts)
51+
52+
Some shell-related lint rules are inspired by ShellCheck.
53+
54+
**Original Project:** [ShellCheck](https://github.com/koalaman/shellcheck)
55+
56+
**Original Author:** Vidar Holen (koalaman)
57+
58+
**Original License:** GNU General Public License v3.0 (GPL-3.0)
59+
60+
**Note:** Syncable-CLI does not include ShellCheck code directly. The shell
61+
analysis rules are original implementations inspired by ShellCheck's rule
62+
concepts and documentation.
63+
64+
---
65+
66+
## Acknowledgments
67+
68+
We are grateful to the open source community and the authors of Hadolint for
69+
creating and maintaining excellent Dockerfile linting tools. This translation
70+
to Rust allows native integration with Syncable-CLI while preserving the
71+
valuable rule definitions and linting logic developed by the original authors.
72+
73+
If you are the author of any software mentioned here and believe the attribution
74+
is incorrect or incomplete, please open an issue at:
75+
https://github.com/syncable-dev/syncable-cli/issues

src/agent/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,12 @@ pub type AgentResult<T> = Result<T, AgentError>;
9999

100100
/// Get the system prompt for the agent based on query type
101101
fn get_system_prompt(project_path: &Path, query: Option<&str>) -> String {
102-
// If query suggests generation (Docker, Terraform, Helm), use DevOps prompt
103102
if let Some(q) = query {
103+
// First check if it's a code development task (highest priority)
104+
if prompts::is_code_development_query(q) {
105+
return prompts::get_code_development_prompt(project_path);
106+
}
107+
// Then check if it's DevOps generation (Docker, Terraform, Helm)
104108
if prompts::is_generation_query(q) {
105109
return prompts::get_devops_prompt(project_path);
106110
}
@@ -264,6 +268,7 @@ pub async fn run_interactive(
264268
.tool(AnalyzeTool::new(project_path_buf.clone()))
265269
.tool(SecurityScanTool::new(project_path_buf.clone()))
266270
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
271+
.tool(HadolintTool::new(project_path_buf.clone()))
267272
.tool(ReadFileTool::new(project_path_buf.clone()))
268273
.tool(ListDirectoryTool::new(project_path_buf.clone()));
269274

@@ -312,6 +317,7 @@ pub async fn run_interactive(
312317
.tool(AnalyzeTool::new(project_path_buf.clone()))
313318
.tool(SecurityScanTool::new(project_path_buf.clone()))
314319
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
320+
.tool(HadolintTool::new(project_path_buf.clone()))
315321
.tool(ReadFileTool::new(project_path_buf.clone()))
316322
.tool(ListDirectoryTool::new(project_path_buf.clone()));
317323

@@ -777,6 +783,7 @@ pub async fn run_query(
777783
.tool(AnalyzeTool::new(project_path_buf.clone()))
778784
.tool(SecurityScanTool::new(project_path_buf.clone()))
779785
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
786+
.tool(HadolintTool::new(project_path_buf.clone()))
780787
.tool(ReadFileTool::new(project_path_buf.clone()))
781788
.tool(ListDirectoryTool::new(project_path_buf.clone()));
782789

@@ -811,6 +818,7 @@ pub async fn run_query(
811818
.tool(AnalyzeTool::new(project_path_buf.clone()))
812819
.tool(SecurityScanTool::new(project_path_buf.clone()))
813820
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
821+
.tool(HadolintTool::new(project_path_buf.clone()))
814822
.tool(ReadFileTool::new(project_path_buf.clone()))
815823
.tool(ListDirectoryTool::new(project_path_buf.clone()));
816824

src/agent/prompts/mod.rs

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ You have access to tools to help analyze and understand the project:
2323
1. **analyze_project** - Analyze the project to detect languages, frameworks, dependencies, and architecture
2424
2. **security_scan** - Perform security analysis to find potential vulnerabilities and secrets
2525
3. **check_vulnerabilities** - Check dependencies for known security vulnerabilities
26-
4. **read_file** - Read the contents of a file in the project
27-
5. **list_directory** - List files and directories in a path
26+
4. **hadolint** - Lint Dockerfiles for best practices (use this instead of shell hadolint)
27+
5. **read_file** - Read the contents of a file in the project
28+
6. **list_directory** - List files and directories in a path
2829
2930
## Guidelines
3031
- Use the available tools to gather information before answering questions about the project
@@ -35,6 +36,77 @@ You have access to tools to help analyze and understand the project:
3536
)
3637
}
3738

39+
/// Get the code development prompt for implementing features, translating code, etc.
40+
pub fn get_code_development_prompt(project_path: &std::path::Path) -> String {
41+
format!(
42+
r#"You are an expert software engineer helping to develop, implement, and improve code in this project.
43+
44+
## Project Context
45+
You are working with a project located at: {}
46+
47+
## Your Capabilities
48+
You have access to the following tools:
49+
50+
### Analysis Tools
51+
1. **analyze_project** - Analyze the project structure, languages, and dependencies
52+
2. **read_file** - Read file contents
53+
3. **list_directory** - List files and directories
54+
55+
### Development Tools
56+
4. **write_file** - Write or update a single file
57+
5. **write_files** - Write multiple files at once
58+
6. **shell** - Run shell commands (build, test, lint)
59+
60+
## CRITICAL RULES - READ CAREFULLY
61+
62+
### Rule 1: DO NOT RE-READ FILES
63+
- Once you read a file, DO NOT read it again in the same conversation
64+
- Keep track of what you've read - the content is in your context
65+
- If you need to reference a file you already read, use your memory
66+
67+
### Rule 2: BIAS TOWARDS ACTION
68+
- After reading 3-5 key files, START WRITING CODE
69+
- Don't endlessly analyze - make progress by writing
70+
- It's better to write code and iterate than to analyze forever
71+
- If unsure, write a minimal first version and improve it
72+
73+
### Rule 3: WRITE IN CHUNKS
74+
- For large implementations, write one file at a time
75+
- Don't try to write everything in one response
76+
- Complete one module, test it, then move to the next
77+
78+
### Rule 4: PLAN BRIEFLY, EXECUTE QUICKLY
79+
- State your plan in 2-3 sentences
80+
- Then immediately start executing
81+
- Don't write long planning documents before coding
82+
83+
## Work Protocol
84+
85+
1. **Quick Analysis** (1-3 tool calls max):
86+
- Read the most relevant existing files
87+
- Understand the project structure
88+
89+
2. **Plan** (2-3 sentences):
90+
- Briefly state what you'll create
91+
- Identify the files you'll write
92+
93+
3. **Implement** (start writing immediately):
94+
- Create the files using write_file or write_files
95+
- Write real, working code - not pseudocode
96+
97+
4. **Validate**:
98+
- Run build/test commands with shell
99+
- Fix any errors
100+
101+
## Code Quality Standards
102+
- Follow the existing code style in the project
103+
- Add appropriate error handling
104+
- Include basic documentation/comments for complex logic
105+
- Write idiomatic code for the language being used"#,
106+
project_path.display()
107+
)
108+
}
109+
38110
/// Get the DevOps generation prompt (Docker, Terraform, Helm, K8s)
39111
pub fn get_devops_prompt(project_path: &std::path::Path) -> String {
40112
format!(
@@ -50,15 +122,16 @@ You have access to the following tools:
50122
1. **analyze_project** - Analyze the project to detect languages, frameworks, dependencies, build commands, and architecture
51123
2. **security_scan** - Perform security analysis to find potential vulnerabilities
52124
3. **check_vulnerabilities** - Check dependencies for known security vulnerabilities
53-
4. **read_file** - Read the contents of a file in the project
54-
5. **list_directory** - List files and directories in a path
125+
4. **hadolint** - Native Dockerfile linter (use this instead of shell hadolint command)
126+
5. **read_file** - Read the contents of a file in the project
127+
6. **list_directory** - List files and directories in a path
55128
56129
### Generation Tools
57-
6. **write_file** - Write a single file (Dockerfile, terraform config, helm values, etc.)
58-
7. **write_files** - Write multiple files at once (Terraform modules, Helm charts)
130+
7. **write_file** - Write a single file (Dockerfile, terraform config, helm values, etc.)
131+
8. **write_files** - Write multiple files at once (Terraform modules, Helm charts)
59132
60133
### Validation Tools
61-
8. **shell** - Execute validation commands (docker build, terraform validate, helm lint, hadolint, etc.)
134+
9. **shell** - Execute validation commands (docker build, terraform validate, helm lint, etc.)
62135
63136
## Production-Ready Standards
64137
@@ -97,12 +170,20 @@ You have access to the following tools:
97170
1. **Analyze First**: Always use `analyze_project` to understand the project before generating anything
98171
2. **Plan**: Think through what files need to be created
99172
3. **Generate**: Use `write_file` or `write_files` to create the artifacts
100-
4. **Validate**: Use `shell` to validate with appropriate tools:
101-
- Docker: `hadolint Dockerfile && docker build -t test .`
102-
- Terraform: `terraform init && terraform validate`
103-
- Helm: `helm lint ./chart`
173+
4. **Validate**: Use appropriate validation tools:
174+
- Docker: Use `hadolint` tool (native, no shell needed), then `shell` for `docker build -t test .`
175+
- Terraform: `shell` for `terraform init && terraform validate`
176+
- Helm: `shell` for `helm lint ./chart`
104177
5. **Self-Correct**: If validation fails, read the error, fix the files, and re-validate
105178
179+
**IMPORTANT**: For Dockerfile linting, ALWAYS use the native `hadolint` tool, NOT `shell hadolint`. The native tool is faster and doesn't require the hadolint binary to be installed.
180+
181+
**CRITICAL**: If `hadolint` finds ANY errors or warnings:
182+
1. STOP and report ALL the issues to the user FIRST
183+
2. DO NOT proceed to `docker build` until the user acknowledges the issues
184+
3. Show each violation with its line number, rule code, and message
185+
4. Ask if the user wants you to fix the issues before building
186+
106187
## Error Handling
107188
- If any validation command fails, analyze the error output
108189
- Use `write_file` to fix the artifacts
@@ -179,7 +260,39 @@ pub fn is_generation_query(query: &str) -> bool {
179260
"terraform", "helm", "kubernetes", "k8s",
180261
"manifest", "chart", "module", "infrastructure",
181262
"containerize", "containerise", "deploy", "ci/cd", "pipeline",
263+
// Code development keywords
264+
"implement", "translate", "port", "convert", "refactor",
265+
"add feature", "new feature", "develop", "code",
182266
];
183267

184268
generation_keywords.iter().any(|kw| query_lower.contains(kw))
185269
}
270+
271+
/// Detect if a query is specifically about code development (not DevOps)
272+
pub fn is_code_development_query(query: &str) -> bool {
273+
let query_lower = query.to_lowercase();
274+
275+
// DevOps-specific terms - if these appear, it's DevOps not code dev
276+
let devops_keywords = [
277+
"dockerfile", "docker-compose", "docker compose",
278+
"terraform", "helm", "kubernetes", "k8s",
279+
"manifest", "chart", "infrastructure",
280+
"containerize", "containerise", "deploy", "ci/cd", "pipeline",
281+
];
282+
283+
// If it's clearly DevOps, return false
284+
if devops_keywords.iter().any(|kw| query_lower.contains(kw)) {
285+
return false;
286+
}
287+
288+
// Code development keywords
289+
let code_keywords = [
290+
"implement", "translate", "port", "convert", "refactor",
291+
"add feature", "new feature", "develop", "module", "library",
292+
"crate", "function", "class", "struct", "trait",
293+
"rust", "python", "javascript", "typescript", "haskell",
294+
"code", "rewrite", "build a", "create a",
295+
];
296+
297+
code_keywords.iter().any(|kw| query_lower.contains(kw))
298+
}

0 commit comments

Comments
 (0)