tool_result block,
and calls the API again. The loop continues until the model signals end_turn.
dev_agent.py, an example written in Python,
is shown running in Fig 2. Click on the figure body to expand, click on the title to contract.
./Test/RustDirNav. It then uses the /analyze command with a file
path in that project to explore features and bugs in the Rust library.
./Test/RustDirNav
directory. The top of the output lists the 7 commands and confirms the working path. The
user then runs /files to see which source files the agent found, followed by
/analyze ./src/lib.rs. The agent reads the file, bundles it with a structured
analysis prompt, and sends the combined message to the LLM. The model returns a markdown
report covering purpose, code quality, potential improvements, identified bugs, and a
recommended refactoring - all displayed inline in the terminal.
tools = [{
"name": "read_file",
"description": "Read a source file and return its contents as a string.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Relative path to the file"}
},
"required": ["path"]
}
}]
end_turn) or a tool call that the agent must execute before continuing.
The full message history - including tool results - travels with every request so the
model retains context across iterations.
import anthropic, pathlib
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Summarize the file main.py."}]
while True:
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=2048,
tools=tools, messages=messages
)
if resp.stop_reason == "end_turn":
print(resp.content[0].text)
break
for block in resp.content:
if block.type == "tool_use":
text = pathlib.Path(block.input["path"]).read_text()
messages += [
{"role": "assistant", "content": resp.content},
{"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": text
}]}
]
1 """
2 Software Development Agent using Anthropic API
3 A sophisticated agent for analyzing, improving, and maintaining code in a specified directory.
4 """
5
6 import anthropic
7 import os
8 import sys
9 from pathlib import Path
10 from typing import List, Dict, Optional
11 import json
12 import argparse
13
14 class SoftwareDevAgent:
15 def __init__(self, api_key: str, directory: str, model: str = "claude-sonnet-4-20250514"):
16 self.client = anthropic.Anthropic(api_key=api_key)
17 self.directory = Path(directory).resolve()
18 self.model = model
19 self.conversation_history = []
20
21 if not self.directory.exists():
22 raise ValueError(f"Directory does not exist: {self.directory}")
23
24 def get_file_tree(self, max_depth: int = 3) -> str:
25 lines = [f"?? {self.directory.name}/"]
26
27 def add_tree(path: Path, prefix: str = "", depth: int = 0):
28 if depth >= max_depth:
29 return
30 try:
31 items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
32 items = [item for item in items if not item.name.startswith('.')
33 and item.name not in ['__pycache__', 'node_modules', 'bin', 'obj']]
34 for i, item in enumerate(items):
35 is_last = i == len(items) - 1
36 connector = "?? " if is_last else "??? "
37 extension = " " if is_last else "? "
38 icon = "?? " if item.is_dir() else "?? "
39 lines.append(f"{prefix}{connector}{icon}{item.name}")
40 if item.is_dir():
41 add_tree(item, prefix + extension, depth + 1)
42 except PermissionError:
43 pass
44
45 add_tree(self.directory)
46 return "\n".join(lines)
47
48 def get_code_files(self) -> List[Path]:
49 code_extensions = {'.py', '.js', '.jsx', '.ts', '.tsx', '.java', '.cpp',
50 '.c', '.h', '.hpp', '.cs', '.rs', '.go', '.rb', '.php'}
51 files = []
52 exclude_dirs = {'__pycache__', 'node_modules', 'bin', 'obj', '.git'}
53 for path in self.directory.rglob('*'):
54 if path.is_file() and path.suffix in code_extensions:
55 if not any(part in exclude_dirs for part in path.parts):
56 files.append(path)
57 return files
58
59 def read_file(self, file_path: str) -> str:
60 path = self.directory / file_path
61 if not path.exists():
62 return f"File not found: {file_path}"
63 try:
64 return path.read_text(encoding='utf-8')
65 except Exception as e:
66 return f"Error reading file: {e}"
67
68 def format_file_list(self) -> str:
69 files = self.get_code_files()
70 if not files:
71 return "No code files found in directory."
72 relative_files = [str(f.relative_to(self.directory)) for f in files]
73 return f"Found {len(files)} code files:\n" + "\n".join(f" - {f}" for f in relative_files)
74
75 def build_context(self) -> str:
76 tree = self.get_file_tree()
77 files = self.get_code_files()
78 file_list = "\n".join(f" - {f.relative_to(self.directory)}" for f in files[:20])
79 return f"Project: {self.directory.name}\n\nDirectory Structure:\n{tree}\n\nCode Files:\n{file_list}"
80
81 def chat(self, user_message: str, include_context: bool = False) -> str:
82 if include_context:
83 context = self.build_context()
84 full_message = f"Project context:\n{context}\n\nUser question: {user_message}"
85 else:
86 full_message = user_message
87
88 self.conversation_history.append({"role": "user", "content": full_message})
89
90 response = self.client.messages.create(
91 model=self.model,
92 max_tokens=4096,
93 system="""You are an expert software development assistant. You help analyze code,
94 suggest improvements, identify bugs, and provide documentation. Be concise but thorough.
95 When analyzing code, focus on: correctness, performance, security, and maintainability.""",
96 messages=self.conversation_history
97 )
98
99 assistant_message = response.content[0].text
100 self.conversation_history.append({"role": "assistant", "content": assistant_message})
101 return assistant_message
102
103 def analyze_file(self, file_path: str) -> str:
104 content = self.read_file(file_path)
105 prompt = f"""Analyze this source file: {file_path}
106 File contents:
107 {content}
108 Please provide:
109 1. Purpose and functionality
110 2. Code quality assessment
111 3. Potential improvements
112 4. Bugs and issues
113 5. Documentation suggestions
114 6. Recommended refactoring if needed"""
115 return self.chat(prompt)
116
117 def suggest_improvements(self) -> str:
118 context = self.build_context()
119 prompt = f"""Based on this project:
120 {context}
121 Provide comprehensive improvement suggestions for:
122 1. Project organization
123 2. Code architecture
124 3. Testing strategy
125 4. Documentation strategy
126 5. Development workflow"""
127 return self.chat(prompt)
128
129 def generate_readme(self) -> str:
130 context = self.build_context()
131 prompt = f"Generate a comprehensive README.md for:\n{context}"
128 return self.chat(prompt)
129
130 def interactive_mode(self):
131 print("\n?? Software Development Agent")
132 print(f"?? Working Directory: {self.directory}\n")
133 print("Commands:")
134 print(" /analyze [file] - Analyze a specific file")
135 print(" /improve - Get improvement suggestions")
136 print(" /readme - Generate README")
137 print(" /tree - Show directory tree")
138 print(" /files - List code files")
139 print(" /clear - Clear conversation history")
140 print(" /quit - Exit\n")
141 print("Or just ask me anything about your code!\n")
142
143 while True:
144 try:
145 user_input = input("You: ").strip()
146 if not user_input:
147 continue
148 if user_input.lower() == '/quit':
149 print("\nGoodbye!")
150 break
151 elif user_input.lower() == '/clear':
152 self.conversation_history = []
153 print("Conversation history cleared.")
154 elif user_input.lower() == '/tree':
155 print(self.get_file_tree())
156 elif user_input.lower() == '/files':
157 print(self.format_file_list())
158 elif user_input.lower() == '/improve':
159 print("\n?? Generating suggestions...\n")
160 print(self.suggest_improvements())
161 elif user_input.lower() == '/readme':
162 print("\n?? Generating README...\n")
163 print(self.generate_readme())
164 elif user_input.lower().startswith('/analyze'):
165 parts = user_input.split(maxsplit=1)
166 if len(parts) > 1:
167 file_path = parts[1]
168 print(f"\n?? Analyzing...\n")
169 print(self.analyze_file(file_path))
170 else:
171 print("Usage: /analyze <file_path>")
172 else:
173 include_ctx = len(self.conversation_history) == 0
174 print("\nAgent: " + self.chat(user_input, include_context=include_ctx) + "\n")
175 except KeyboardInterrupt:
176 print("\n\nInterrupted. Type /quit to exit.")
177 except Exception as e:
178 print(f"Error: {e}")
179
180 def main():
181 parser = argparse.ArgumentParser(description='Software Development Agent using Anthropic API')
182 parser.add_argument('directory', nargs='?', default='.', help='Project directory path')
183 parser.add_argument('--api-key', help='Anthropic API key (or set ANTHROPIC_API_KEY env var)')
184 parser.add_argument('--model', default='claude-sonnet-4-20250514', help='Claude model to use')
185 parser.add_argument('--analyze', help='Analyze a specific file and exit')
186 parser.add_argument('--improve', action='store_true', help='Get improvement suggestions and exit')
187 parser.add_argument('--readme', action='store_true', help='Generate README and exit')
188 args = parser.parse_args()
189
190 api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY")
191 if not api_key:
192 print("\n? Error: Anthropic API key is required")
193 print("Set ANTHROPIC_API_KEY or pass --api-key")
194 sys.exit(1)
195
196 try:
197 agent = SoftwareDevAgent(api_key, args.directory, args.model)
198 except ValueError as e:
199 print(f"Error: {e}")
200 sys.exit(1)
201
202 if args.analyze:
203 print(agent.analyze_file(args.analyze))
204 elif args.improve:
205 print(agent.suggest_improvements())
206 elif args.readme:
207 print(agent.generate_readme())
208 else:
209 agent.interactive_mode()
210
211 if __name__ == "__main__":
212 main()
1 C:\github\JimFawcett\NewSite\Code\AI\DemoAgent-Claude2
2 > python dev_agent.py ./Test/RustDirNav
3
4 ?? Software Development Agent
5 ?? Working Directory: C:\github\JimFawcett\NewSite\Code\AI\DemoAgent-Claude2\Test\RustDirNav
6
7 Commands:
8 /analyze [file] - Analyze a specific file
9 /improve - Get improvement suggestions
10 /readme - Generate README
11 /tree - Show directory tree
12 /files - List code files
13 /clear - Clear conversation history
14 /quit - Exit
15
16 Or just ask me anything about your code!
17
/tree command displays the directory subtree rooted at the specified
project path. That helps to properly configure an /analyze command.
17 You: /files
18
19 Found 4 code files:
20 - examples\test1.rs
21 - src\lib.rs
22 - test_dir\test_file.rs
23 - test_dir\test_sub1_dir\test_file1.rs
24 You: /tree
25
26 ?? RustDirNav/
27 ├── ?? Pictures
28 │ ├── ?? RustDirNav.jpg
29 │ └── ?? RustDirNavOutput.JPG
30 ├── ?? examples
31 │ ├── ?? test1.rs
32 │ └── ?? test11.rs.html
33 ├── ?? src
34 │ ├── ?? lib.rs
35 │ └── ?? lib1.rs.html
36 ├── ?? test_dir
37 │ ├── ?? test_sub1_dir
38 │ │ ├── ?? test_file1.rs
39 │ │ └── ?? test_file2.exe
40 │ ├── ?? test_sub2_dir
41 │ │ └── ?? test_file3.txt
42 │ └── ?? test_file.rs
43 ├── ?? Cargo.lock
44 ├── ?? Cargo.toml
45 └── ?? README.md
1 /////////////////////////////////////////////////////////////
2 // rust_dir_nav::lib.rs //
3 // Jim Fawcett, https://JimFawcett.github.io, 12 Apr 2020 //
4 /////////////////////////////////////////////////////////////
5 /*
6 DirNav<App> is a directory navigator that uses the generic
7 parameter App to define how files and directories are handled.
8 - displays only paths that have file targets by default
9 - hide(false) will show all directories traversed
10 - recurses directory tree at specified root by default
11 - recurse(false) examines only specified path.
12 */
13 use std::fs::{self, DirEntry};
14 use std::io;
15 use std::io::{Error, ErrorKind};
16 use std::path::{Path, PathBuf};
17
18 pub trait DirEvent {
19 fn do_dir(&mut self, d: &str);
20 fn do_file(&mut self, f: &str);
21 }
22
23 pub struct DirNav<App: DirEvent + Default> {
24 pub pats: Vec<String>,
25 pub app: App,
26 pub num_files: usize,
27 pub num_dirs: usize,
28 pub recurse: bool,
29 pub hide: bool,
30 }
31
32 impl<App: DirEvent + Default> DirNav<App> {
33 pub fn new() -> Self {
34 DirNav {
35 pats: Vec::new(),
36 app: App::default(),
37 num_files: 0,
38 num_dirs: 0,
39 recurse: true,
40 hide: true,
41 }
42 }
43
44 pub fn add_pat<S: Into<String>>(&mut self, patt: S) -> &mut Self {
45 self.pats.push(patt.into());
46 self
47 }
48
49 pub fn in_patterns(&self, entry: &DirEntry) -> bool {
50 let filename = entry.file_name().into_string().unwrap_or_default();
51 let ext = filename.split('.').last().unwrap_or_default();
52 self.pats.iter().any(|p| p == ext)
53 }
54
55 pub fn visit(&mut self, dir: &Path) -> io::Result<()> {
56 if !dir.is_dir() {
57 return Err(Error::new(ErrorKind::InvalidInput, "not a directory"));
58 }
59 self.num_dirs += 1;
60 let mut files: Vec<PathBuf> = Vec::new();
61 let mut dirs: Vec<PathBuf> = Vec::new();
62
63 for entry in fs::read_dir(dir)? {
64 let entry = entry?;
65 if entry.path().is_dir() {
66 dirs.push(entry.path());
67 } else if self.in_patterns(&entry) | self.pats.is_empty() {
68 files.push(entry.path());
69 }
70 }
71
72 if !files.is_empty() || !self.hide {
73 self.app.do_dir(&dir.display().to_string());
74 }
75
76 for f in files {
77 self.num_files += 1;
78 self.app.do_file(&f.display().to_string());
79 }
80
81 if self.recurse {
82 for d in dirs {
83 self.visit(&d)?;
84 }
85 }
86 Ok(())
87 }
88 }
RustDirNav/src/lib.rs covers purpose, code quality,
potential improvements, bugs, documentation, and a recommended refactoring.
46 You: /analyze ./src/lib.rs
47
48 ?? Analyzing...
49
50 Agent: ## Analysis of `rust_dir_nav::lib.rs`
51
52 ### 1. Purpose and Functionality
53
54 This library provides a generic directory navigation system (`DirNav<App>`) that traverses
55 filesystem directories and processes files based on configurable patterns (file extensions).
56
57 Key features:
58 - **Generic Design**: Uses a trait (`DirEvent`) to allow custom file/directory handling
59 - **Pattern Matching**: Filters files by extensions
60 - **Configurable Traversal**: Options for recursive traversal and hiding empty directories
61 - **Cross-platform**: Handles Windows/Linux path separator differences
62
63 ### 2. Code Quality Assessment
64
65 **Strengths:**
66 - Good use of generics and traits for extensibility
67 - Comprehensive test coverage with proper setup
68 - Clear separation of concerns
69 - Proper error handling with `io::Result`
70
71 **Areas for Improvement:**
72 - Some inconsistent naming conventions
73 - Mixed coding styles
74 - Platform-specific code that could be better abstracted
75
76 ### 3. Potential Improvements
77
78 #### A. Consistent Naming
79
80 ```rust
81 pub trait DirectoryEventHandler { // More descriptive than DirEvent
82 fn handle_directory(&mut self, path: &str);
83 fn handle_file(&mut self, path: &str);
84 }
85 ```
86
87 #### B. Improved Error Handling
88
89 ```rust
90 use thiserror::Error;
91
92 #[derive(Error, Debug)]
93 pub enum DirNavError {
94 #[error("Path is not a directory: {path}")]
95 NotADirectory { path: String },
96 #[error("IO error: {0}")]
97 Io(#[from] std::io::Error),
98 }
99 ```
100
101 #### C. Simplified Pattern Matching
102
103 ```rust
104 fn matches_pattern(&self, entry: &DirEntry) -> bool {
105 if self.patterns.is_empty() { return true; }
106 entry.path()
107 .extension()
108 .and_then(|ext| ext.to_str())
109 .map(|ext| self.patterns.iter().any(|pat| pat == ext))
110 .unwrap_or(false)
111 }
112 ```
113
114 ### 4. Bugs and Issues
115
116 #### Critical: Logical vs. Bitwise OR at line 67
117
118 ```rust
119 // BUG: | evaluates both sides regardless of left result
120 if self.in_patterns(&entry) | self.pats.is_empty() {
121 // CORRECT:
122 if self.in_patterns(&entry) || self.pats.is_empty() {
123 ```
124
125 #### Design: Mutable Reference Return
126 `get_app()` returns a mutable reference, breaking encapsulation.
127
128 ### 5. Documentation Suggestions
129
130 Add module-level doc comments explaining the generic parameter contract and usage examples.
131
132 ### 6. Recommended Refactoring
133
134 The core logic can be made more idiomatic by separating configuration from traversal state,
135 using a builder pattern for construction, and passing `&Path` instead of `&str` to event
136 handler methods.
/improve command asks the agent to review the full project structure and
suggest improvements covering:
400 You: /improve
401
402 ?? Generating suggestions...
403
404 Agent: ## Comprehensive Project Improvement Suggestions
405
406 ### 1. Project Organization
407
408 Recommended structure:
409 ```
410 rust_dir_nav/
411 ├── src/
412 │ ├── lib.rs # Public API and re-exports
413 │ ├── navigator.rs # Core DirNav implementation
414 │ ├── traits.rs # DirEvent and other traits
415 │ ├── error.rs # Error types
416 │ └── builder.rs # Builder pattern
417 ├── examples/
418 │ ├── basic_usage.rs
419 │ └── custom_handler.rs
420 ├── tests/
421 │ └── integration_tests.rs
422 └── benches/
423 └── navigation_bench.rs
424 ```
425
426 ### 2. Code Architecture
427
428 Separate configuration from traversal state with a builder:
429
430 ```rust
431 impl<App: DirEvent + Default> DirNav<App> {
432 pub fn builder() -> DirNavBuilder<App> {
433 DirNavBuilder::new()
434 }
435
436 pub fn navigate(&mut self, root: &Path) -> Result<&NavigationStats> {
437 self.visit_directory(root)?;
438 Ok(&self.stats)
439 }
440 }
441 ```
442
443 ### 3. Testing Strategy
444
445 ```rust
446 #[tokio::test]
447 async fn test_basic_navigation() {
448 let temp_dir = common::create_test_structure();
449 let mut nav = DirectoryNavigator::new(FileCollector::new())
450 .with_extensions(&["rs", "toml"])
451 .recursive(true);
452 let stats = nav.navigate(temp_dir.path()).await.unwrap();
453 assert_eq!(stats.files_processed, 3);
454 }
455 ```
456
457 ### 4. Documentation Strategy
458
459 Add module-level documentation with runnable examples in `//!` doc comments.
460 Use `cargo doc --open` to verify rendered output.
461
462 ### 5. Development Workflow
463
464 Add a CI pipeline (`.github/workflows/ci.yml`) that runs `cargo test`, `cargo clippy`,
465 and `cargo fmt --check` on stable, beta, and nightly toolchains.
1. Install dependencies:
pip install -r requirements.txt
2. Set your API key:
# For current session only
$env:ANTHROPIC_API_KEY="your-api-key-here"
# For permanent (all future sessions)
[System.Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY','your-api-key-here','User')
export ANTHROPIC_API_KEY='your-api-key-here'
# To make permanent:
echo 'export ANTHROPIC_API_KEY="your-api-key-here"' >> ~/.bashrc
/analyze <file> - Analyze a specific file in detail/improve - Get suggestions for improving the codebase/readme - Generate a comprehensive README/tree - Display the directory structure/files - List all code files found/clear - Clear conversation history/quit - Exit the agentYou: Can you review the error handling in my Python files?
Agent: [Analyzes error handling patterns across the codebase]
You: I'm getting a NullPointerException in UserService.java
Agent: [Examines the file and suggests fixes]
You: Should I split my main.py file into multiple modules?
Agent: [Provides architectural advice based on the code structure]
/clear too often; context helps/tree to understand the structure/files to see what code files were detected/analyze for specific files that need attention/improve to get a roadmap of improvements/readme when ready to document the projectfor _ in range(MAX_STEPS) - raise an error if exceededdev_agent.py above keeps all file access inside the project directory
specified at startup, and only reads files - it never writes. Adding write capability
should be treated as a significant trust boundary and protected accordingly.
| Resource | Description |
|---|---|
| Tool Use Docs | Anthropic’s guide to defining and using tools with the Claude API. |
| Messages API | Anthropic Messages API reference - the transport used by all agents. |
| AIBites: Agent AI | Full agent demos with extended code-viewer dropdowns and execution screenshots. |
| AIBites: Agentic AI | Multi-agent orchestration and agentic frameworks. |
| AI Agents pdf | Slide deck covering agent architecture, tool use, and safety. |