Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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

# Dockerfile linting (hadolint-rs)
nom = "7" # Parser combinators for Dockerfile parsing

[dev-dependencies]
assert_cmd = "2"
predicates = "3"
Expand Down
695 changes: 674 additions & 21 deletions LICENSE

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<p align="center">
<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>
<a href="https://crates.io/crates/syncable-cli"><img src="https://img.shields.io/crates/d/syncable-cli?style=flat-square" alt="Downloads"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg?style=flat-square" alt="License"></a>
<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>
<a href="https://www.rust-lang.org/"><img src="https://img.shields.io/badge/Built%20with-Rust-orange?style=flat-square" alt="Rust"></a>
</p>

Expand Down Expand Up @@ -241,7 +241,16 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.

## 📄 License

MIT License — see [LICENSE](LICENSE) for details.
This project is licensed under the **GNU General Public License v3.0** (GPL-3.0).

See [LICENSE](LICENSE) for the full license text.

### Third-Party Attributions

The Dockerfile linting functionality (`src/analyzer/hadolint/`) is a Rust translation
of [Hadolint](https://github.com/hadolint/hadolint), originally written in Haskell by
Lukas Martinelli and contributors. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
for full attribution details.

---

Expand Down
75 changes: 75 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Third Party Notices

This file contains attributions and license information for third-party software
incorporated into Syncable-CLI.

---

## Hadolint

The Dockerfile linting functionality in `src/analyzer/hadolint/` is a Rust
translation of the original Hadolint project.

**Original Project:** [Hadolint](https://github.com/hadolint/hadolint)

**Original Authors:**
- Lukas Martinelli (lukasmartinelli)
- Lorenzo Bolla (lbolla)
- And all contributors to the Hadolint project

**Original License:** GNU General Public License v3.0 (GPL-3.0)

**Original Copyright:**
```
Copyright (c) 2016-2024 Lukas Martinelli and contributors
```

**What was translated:**
- Dockerfile parsing logic (originally in Haskell)
- Lint rule definitions (DL3xxx, DL4xxx series)
- Pragma/ignore directive handling
- Configuration file format
- Rule severity and messaging

**Modifications made:**
- Complete rewrite from Haskell to Rust
- Integration with Syncable-CLI's agent and tool system
- Native async support for streaming output
- Adaptation to Rust error handling patterns
- Additional rules and improvements specific to Syncable's use cases

**License Notice:**
This derivative work is licensed under GPL-3.0, as required by the original
Hadolint license. See the LICENSE file in the root of this repository.

The full text of the GPL-3.0 license can be found at:
https://www.gnu.org/licenses/gpl-3.0.en.html

---

## ShellCheck (Rule Concepts)

Some shell-related lint rules are inspired by ShellCheck.

**Original Project:** [ShellCheck](https://github.com/koalaman/shellcheck)

**Original Author:** Vidar Holen (koalaman)

**Original License:** GNU General Public License v3.0 (GPL-3.0)

**Note:** Syncable-CLI does not include ShellCheck code directly. The shell
analysis rules are original implementations inspired by ShellCheck's rule
concepts and documentation.

---

## Acknowledgments

We are grateful to the open source community and the authors of Hadolint for
creating and maintaining excellent Dockerfile linting tools. This translation
to Rust allows native integration with Syncable-CLI while preserving the
valuable rule definitions and linting logic developed by the original authors.

If you are the author of any software mentioned here and believe the attribution
is incorrect or incomplete, please open an issue at:
https://github.com/syncable-dev/syncable-cli/issues
10 changes: 9 additions & 1 deletion src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ pub type AgentResult<T> = Result<T, AgentError>;

/// Get the system prompt for the agent based on query type
fn get_system_prompt(project_path: &Path, query: Option<&str>) -> String {
// If query suggests generation (Docker, Terraform, Helm), use DevOps prompt
if let Some(q) = query {
// First check if it's a code development task (highest priority)
if prompts::is_code_development_query(q) {
return prompts::get_code_development_prompt(project_path);
}
// Then check if it's DevOps generation (Docker, Terraform, Helm)
if prompts::is_generation_query(q) {
return prompts::get_devops_prompt(project_path);
}
Expand Down Expand Up @@ -264,6 +268,7 @@ pub async fn run_interactive(
.tool(AnalyzeTool::new(project_path_buf.clone()))
.tool(SecurityScanTool::new(project_path_buf.clone()))
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
.tool(HadolintTool::new(project_path_buf.clone()))
.tool(ReadFileTool::new(project_path_buf.clone()))
.tool(ListDirectoryTool::new(project_path_buf.clone()));

Expand Down Expand Up @@ -312,6 +317,7 @@ pub async fn run_interactive(
.tool(AnalyzeTool::new(project_path_buf.clone()))
.tool(SecurityScanTool::new(project_path_buf.clone()))
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
.tool(HadolintTool::new(project_path_buf.clone()))
.tool(ReadFileTool::new(project_path_buf.clone()))
.tool(ListDirectoryTool::new(project_path_buf.clone()));

Expand Down Expand Up @@ -777,6 +783,7 @@ pub async fn run_query(
.tool(AnalyzeTool::new(project_path_buf.clone()))
.tool(SecurityScanTool::new(project_path_buf.clone()))
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
.tool(HadolintTool::new(project_path_buf.clone()))
.tool(ReadFileTool::new(project_path_buf.clone()))
.tool(ListDirectoryTool::new(project_path_buf.clone()));

Expand Down Expand Up @@ -811,6 +818,7 @@ pub async fn run_query(
.tool(AnalyzeTool::new(project_path_buf.clone()))
.tool(SecurityScanTool::new(project_path_buf.clone()))
.tool(VulnerabilitiesTool::new(project_path_buf.clone()))
.tool(HadolintTool::new(project_path_buf.clone()))
.tool(ReadFileTool::new(project_path_buf.clone()))
.tool(ListDirectoryTool::new(project_path_buf.clone()));

Expand Down
135 changes: 124 additions & 11 deletions src/agent/prompts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ You have access to tools to help analyze and understand the project:
1. **analyze_project** - Analyze the project to detect languages, frameworks, dependencies, and architecture
2. **security_scan** - Perform security analysis to find potential vulnerabilities and secrets
3. **check_vulnerabilities** - Check dependencies for known security vulnerabilities
4. **read_file** - Read the contents of a file in the project
5. **list_directory** - List files and directories in a path
4. **hadolint** - Lint Dockerfiles for best practices (use this instead of shell hadolint)
5. **read_file** - Read the contents of a file in the project
6. **list_directory** - List files and directories in a path

## Guidelines
- Use the available tools to gather information before answering questions about the project
Expand All @@ -35,6 +36,77 @@ You have access to tools to help analyze and understand the project:
)
}

/// Get the code development prompt for implementing features, translating code, etc.
pub fn get_code_development_prompt(project_path: &std::path::Path) -> String {
format!(
r#"You are an expert software engineer helping to develop, implement, and improve code in this project.

## Project Context
You are working with a project located at: {}

## Your Capabilities
You have access to the following tools:

### Analysis Tools
1. **analyze_project** - Analyze the project structure, languages, and dependencies
2. **read_file** - Read file contents
3. **list_directory** - List files and directories

### Development Tools
4. **write_file** - Write or update a single file
5. **write_files** - Write multiple files at once
6. **shell** - Run shell commands (build, test, lint)

## CRITICAL RULES - READ CAREFULLY

### Rule 1: DO NOT RE-READ FILES
- Once you read a file, DO NOT read it again in the same conversation
- Keep track of what you've read - the content is in your context
- If you need to reference a file you already read, use your memory

### Rule 2: BIAS TOWARDS ACTION
- After reading 3-5 key files, START WRITING CODE
- Don't endlessly analyze - make progress by writing
- It's better to write code and iterate than to analyze forever
- If unsure, write a minimal first version and improve it

### Rule 3: WRITE IN CHUNKS
- For large implementations, write one file at a time
- Don't try to write everything in one response
- Complete one module, test it, then move to the next

### Rule 4: PLAN BRIEFLY, EXECUTE QUICKLY
- State your plan in 2-3 sentences
- Then immediately start executing
- Don't write long planning documents before coding

## Work Protocol

1. **Quick Analysis** (1-3 tool calls max):
- Read the most relevant existing files
- Understand the project structure

2. **Plan** (2-3 sentences):
- Briefly state what you'll create
- Identify the files you'll write

3. **Implement** (start writing immediately):
- Create the files using write_file or write_files
- Write real, working code - not pseudocode

4. **Validate**:
- Run build/test commands with shell
- Fix any errors

## Code Quality Standards
- Follow the existing code style in the project
- Add appropriate error handling
- Include basic documentation/comments for complex logic
- Write idiomatic code for the language being used"#,
project_path.display()
)
}

/// Get the DevOps generation prompt (Docker, Terraform, Helm, K8s)
pub fn get_devops_prompt(project_path: &std::path::Path) -> String {
format!(
Expand All @@ -50,15 +122,16 @@ You have access to the following tools:
1. **analyze_project** - Analyze the project to detect languages, frameworks, dependencies, build commands, and architecture
2. **security_scan** - Perform security analysis to find potential vulnerabilities
3. **check_vulnerabilities** - Check dependencies for known security vulnerabilities
4. **read_file** - Read the contents of a file in the project
5. **list_directory** - List files and directories in a path
4. **hadolint** - Native Dockerfile linter (use this instead of shell hadolint command)
5. **read_file** - Read the contents of a file in the project
6. **list_directory** - List files and directories in a path

### Generation Tools
6. **write_file** - Write a single file (Dockerfile, terraform config, helm values, etc.)
7. **write_files** - Write multiple files at once (Terraform modules, Helm charts)
7. **write_file** - Write a single file (Dockerfile, terraform config, helm values, etc.)
8. **write_files** - Write multiple files at once (Terraform modules, Helm charts)

### Validation Tools
8. **shell** - Execute validation commands (docker build, terraform validate, helm lint, hadolint, etc.)
9. **shell** - Execute validation commands (docker build, terraform validate, helm lint, etc.)

## Production-Ready Standards

Expand Down Expand Up @@ -97,12 +170,20 @@ You have access to the following tools:
1. **Analyze First**: Always use `analyze_project` to understand the project before generating anything
2. **Plan**: Think through what files need to be created
3. **Generate**: Use `write_file` or `write_files` to create the artifacts
4. **Validate**: Use `shell` to validate with appropriate tools:
- Docker: `hadolint Dockerfile && docker build -t test .`
- Terraform: `terraform init && terraform validate`
- Helm: `helm lint ./chart`
4. **Validate**: Use appropriate validation tools:
- Docker: Use `hadolint` tool (native, no shell needed), then `shell` for `docker build -t test .`
- Terraform: `shell` for `terraform init && terraform validate`
- Helm: `shell` for `helm lint ./chart`
5. **Self-Correct**: If validation fails, read the error, fix the files, and re-validate

**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.

**CRITICAL**: If `hadolint` finds ANY errors or warnings:
1. STOP and report ALL the issues to the user FIRST
2. DO NOT proceed to `docker build` until the user acknowledges the issues
3. Show each violation with its line number, rule code, and message
4. Ask if the user wants you to fix the issues before building

## Error Handling
- If any validation command fails, analyze the error output
- Use `write_file` to fix the artifacts
Expand Down Expand Up @@ -179,7 +260,39 @@ pub fn is_generation_query(query: &str) -> bool {
"terraform", "helm", "kubernetes", "k8s",
"manifest", "chart", "module", "infrastructure",
"containerize", "containerise", "deploy", "ci/cd", "pipeline",
// Code development keywords
"implement", "translate", "port", "convert", "refactor",
"add feature", "new feature", "develop", "code",
];

generation_keywords.iter().any(|kw| query_lower.contains(kw))
}

/// Detect if a query is specifically about code development (not DevOps)
pub fn is_code_development_query(query: &str) -> bool {
let query_lower = query.to_lowercase();

// DevOps-specific terms - if these appear, it's DevOps not code dev
let devops_keywords = [
"dockerfile", "docker-compose", "docker compose",
"terraform", "helm", "kubernetes", "k8s",
"manifest", "chart", "infrastructure",
"containerize", "containerise", "deploy", "ci/cd", "pipeline",
];

// If it's clearly DevOps, return false
if devops_keywords.iter().any(|kw| query_lower.contains(kw)) {
return false;
}

// Code development keywords
let code_keywords = [
"implement", "translate", "port", "convert", "refactor",
"add feature", "new feature", "develop", "module", "library",
"crate", "function", "class", "struct", "trait",
"rust", "python", "javascript", "typescript", "haskell",
"code", "rewrite", "build a", "create a",
];

code_keywords.iter().any(|kw| query_lower.contains(kw))
}
Loading