Skip to content

Commit fb1083f

Browse files
committed
Use DSPy Code in CWD only move dspy_cache to CWD
1 parent e2d8684 commit fb1083f

File tree

13 files changed

+708
-472
lines changed

13 files changed

+708
-472
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ cython_debug/
154154
dspy_config.yaml
155155
*.log
156156

157+
# dspy-code internal directories (all data in CWD)
158+
.dspy_code/
159+
.dspy_cache/
160+
157161
# Environment variables and secrets
158162
.env
159163
.env.*
@@ -190,3 +194,7 @@ $RECYCLE.BIN/
190194

191195
# Linux
192196
.directory
197+
198+
# Docs
199+
# docs/
200+
# mkdocs.yml

README.md

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212

1313
*An interactive CLI tool for building, optimizing, and deploying DSPy applications*
1414

15-
[📖 Documentation](https://superagenticai.github.io/dspy-code/)[🚀 Quick Start](#-quick-start)[💬 GitHub Discussions](https://github.com/SuperagenticAI/dspy-code/discussions)
15+
[📖 Documentation](https://superagenticai.github.io/dspy-code/)
1616

1717
</div>
1818

1919
---
2020

2121
## ✨ What is DSPy Code?
2222

23-
DSPy Code is an **interactive development environment** that transforms how you learn and build with DSPy. Built as an intelligent CLI tool, it provides natural language interactions, code generation, optimization workflows, and comprehensive validationall designed specifically for DSPy development.
23+
DSPy Code is an **interactive development environment** that transforms how you learn and build with DSPy. Built as an intelligent CLI tool, it provides natural language interactions, code generation, optimization workflows, and comprehensive validation, all designed specifically for DSPy development.
2424

2525
**Learn as you build.** Whether you're a complete beginner or a DSPy expert, the CLI adapts to your level and guides you through every step.
2626

@@ -77,21 +77,77 @@ DSPy Code is a **purpose-built development environment** that embeds DSPy expert
7777

7878
### Installation
7979

80+
**⚠️ CRITICAL: Always create your virtual environment INSIDE your project directory!**
81+
8082
```bash
81-
# Install from PyPI
83+
# 1. Create a project directory
84+
mkdir my-dspy-project
85+
cd my-dspy-project
86+
87+
# 2. Create virtual environment IN this directory (not elsewhere!)
88+
python -m venv .venv
89+
90+
# 3. Activate it
91+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
92+
93+
# 4. Install dspy-code (installs into .venv/ in your project)
8294
pip install dspy-code
8395

84-
# DSPy is installed as a dependency
85-
# You can also install it separately: pip install dspy
96+
# 5. Run dspy-code (everything stays in this directory!)
97+
dspy-code
98+
```
99+
100+
**Why virtual environment IN your project directory?**
101+
- 🔒 **Security**: All file scanning stays within your project directory
102+
- 📦 **Isolation**: Your project dependencies are self-contained
103+
- 🚀 **Portability**: Share your project by zipping the entire directory
104+
- 🎯 **Simplicity**: Everything in one place - no scattered files
105+
- 🧹 **Clean**: Delete the project folder to remove everything
106+
107+
### Project Structure
108+
109+
When you follow this setup, your project will be fully self-contained:
110+
111+
```
112+
my-dspy-project/ # Your CWD
113+
├── .venv/ # Virtual environment (packages installed here)
114+
├── .dspy_cache/ # DSPy's LLM response cache
115+
├── .dspy_code/ # dspy-code's internal data
116+
│ ├── cache/ # RAG index cache
117+
│ ├── sessions/ # Session state
118+
│ ├── optimization/ # GEPA workflow data
119+
│ └── history.txt # Command history
120+
├── generated/ # Your generated code
121+
├── modules/ # Your modules
122+
├── signatures/ # Your signatures
123+
└── dspy_config.yaml # Your config
124+
```
125+
126+
**Everything in one directory!** Delete the folder, and it's all gone. Zip it, and share with others.
127+
128+
**Never run dspy-code from:**
129+
- ❌ Your home directory (`~/`)
130+
- ❌ Desktop, Documents, Downloads, or Pictures folders
131+
- ❌ System directories
132+
- ❌ With a virtual environment outside your project
133+
134+
**Never do this:**
135+
```bash
136+
# ❌ DON'T: Virtual env outside project
137+
cd ~/
138+
python -m venv my_global_venv
139+
140+
# ❌ DON'T: System-wide installation
141+
pip install dspy-code
86142
```
87143

88-
### Your First Program (3 minutes)
144+
### Your First Program (5 minutes)
89145

90146
```bash
91-
# Start the interactive CLI
147+
# From your project directory with activated venv:
92148
dspy-code
93149

94-
# Initialize your project
150+
# Initialize your project (creates config and scans your environment)
95151
/init
96152

97153
# Connect to a model (example with Ollama)
@@ -366,7 +422,7 @@ Special thanks to the DSPy community and all contributors!
366422

367423
<div align="center">
368424

369-
**[📖 Full Documentation](https://superagenticai.github.io/dspy-code/)****[🚀 Get Started](#-quick-start)****[💬 GitHub Discussions](https://github.com/SuperagenticAI/dspy-code/discussions)**
425+
**[📖 Full Documentation](https://superagenticai.github.io/dspy-code/)**
370426

371427
Made for the DSPy community
372428

dspy_code/core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class CodebaseRAGConfig:
4444

4545
enabled: bool = True
4646
codebases: list[str] = field(default_factory=lambda: ["dspy-cli", "dspy", "gepa"])
47-
cache_dir: str | None = None # Defaults to ~/.dspy_cli/cache/codebase_index
47+
cache_dir: str | None = None # Defaults to .dspy_code/cache/codebase_index in CWD
4848
max_cache_size_mb: int = 100
4949
index_refresh_days: int = 7
5050
use_tfidf: bool = True

dspy_code/export/handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def __init__(self, config_manager=None):
2828
config_manager: Optional configuration manager
2929
"""
3030
self.config_manager = config_manager
31-
self.export_dir = Path.home() / ".dspy_cli" / "exports"
31+
# Export directory in CWD for isolation and portability
32+
self.export_dir = Path.cwd() / ".dspy_code" / "exports"
3233
self.export_dir.mkdir(parents=True, exist_ok=True)
3334

3435
def export_code(self, code: str, format: str, output_path: Path) -> None:

dspy_code/main.py

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
All commands are now SLASH COMMANDS in interactive mode!
88
"""
99

10+
import os
1011
import sys
12+
from pathlib import Path
1113

1214
import click
1315
from rich.console import Console
16+
from rich.panel import Panel
1417
from rich.traceback import install
1518

1619
from .commands import interactive_command
@@ -23,10 +26,157 @@
2326
console = Console()
2427

2528

29+
def check_safe_working_directory() -> None:
30+
"""Check if current working directory is safe for dspy-code operations.
31+
32+
SECURITY: This prevents accidental scanning of user home directories,
33+
system directories, and other sensitive locations.
34+
"""
35+
cwd = Path.cwd().resolve()
36+
home = Path.home().resolve()
37+
38+
# Check if running from home directory itself
39+
if cwd == home:
40+
console.print()
41+
console.print(
42+
Panel.fit(
43+
"[bold red]🚨 SECURITY WARNING[/bold red]\n\n"
44+
"[yellow]You are running dspy-code from your home directory![/yellow]\n\n"
45+
"This is dangerous as it may attempt to scan ALL files in your home directory,\n"
46+
"including personal documents, photos, and sensitive data.\n\n"
47+
"[bold green]Recommended actions:[/bold green]\n"
48+
"1. Create a dedicated project directory: [cyan]mkdir ~/my-dspy-project[/cyan]\n"
49+
"2. Navigate to it: [cyan]cd ~/my-dspy-project[/cyan]\n"
50+
"3. Create a virtual environment: [cyan]python -m venv .venv[/cyan]\n"
51+
"4. Activate it: [cyan]source .venv/bin/activate[/cyan]\n"
52+
"5. Install dspy-code: [cyan]pip install dspy-code[/cyan]\n"
53+
"6. Then run: [cyan]dspy-code[/cyan]\n\n"
54+
"[dim]Press Ctrl+C to exit, or Enter to continue at your own risk...[/dim]",
55+
title="⚠️ Safety Check",
56+
border_style="red",
57+
)
58+
)
59+
try:
60+
input()
61+
except KeyboardInterrupt:
62+
console.print("\n[green]Good choice! Please run from a project directory.[/green]")
63+
sys.exit(0)
64+
console.print(
65+
"[yellow]⚠️ Proceeding with limited functionality to protect your files...[/yellow]\n"
66+
)
67+
return
68+
69+
# Check if running from immediate subdirectory of home (e.g., ~/Desktop, ~/Documents)
70+
try:
71+
relative = cwd.relative_to(home)
72+
parts = relative.parts
73+
74+
if len(parts) <= 1:
75+
dangerous_dirs = [
76+
"desktop",
77+
"documents",
78+
"downloads",
79+
"pictures",
80+
"photos",
81+
"movies",
82+
"music",
83+
"library",
84+
"icloud",
85+
"public",
86+
]
87+
88+
if parts and parts[0].lower() in dangerous_dirs:
89+
console.print()
90+
console.print(
91+
Panel.fit(
92+
f"[bold yellow]⚠️ WARNING[/bold yellow]\n\n"
93+
f"You are running dspy-code from: [cyan]{cwd}[/cyan]\n\n"
94+
"This directory may contain personal files. For safety,\n"
95+
"dspy-code will not scan files here.\n\n"
96+
"[bold green]Recommendation:[/bold green]\n"
97+
"Create a dedicated project directory for your DSPy work:\n"
98+
"[cyan]mkdir ~/projects/my-dspy-project && cd ~/projects/my-dspy-project[/cyan]",
99+
title="💡 Location Notice",
100+
border_style="yellow",
101+
)
102+
)
103+
except ValueError:
104+
# Not relative to home, which is fine
105+
pass
106+
107+
# FIRST: Check if we're in an allowed temp directory (exception to system dir rules)
108+
allowed_temp_paths = [
109+
Path("/tmp"),
110+
Path("/var/folders"),
111+
Path("/private/tmp"),
112+
Path("/private/var/folders"),
113+
]
114+
115+
is_in_temp = any(
116+
cwd.is_relative_to(temp_path) if temp_path.exists() else False
117+
for temp_path in allowed_temp_paths
118+
)
119+
120+
# If we're in a temp directory, skip system directory checks
121+
if is_in_temp:
122+
return
123+
124+
# Check if running from system directories
125+
system_dirs = [Path("/"), Path("/System"), Path("/Library"), Path("/usr")]
126+
for sys_dir in system_dirs:
127+
if cwd == sys_dir or cwd.is_relative_to(sys_dir):
128+
console.print()
129+
console.print(
130+
Panel.fit(
131+
"[bold red]🚨 CRITICAL ERROR[/bold red]\n\n"
132+
f"You cannot run dspy-code from system directory: [cyan]{cwd}[/cyan]\n\n"
133+
"This could damage your system. Please run from a user project directory.",
134+
title="❌ System Directory",
135+
border_style="red",
136+
)
137+
)
138+
sys.exit(1)
139+
140+
# Check /var and /private (temp dirs already handled above)
141+
var_path = Path("/var")
142+
private_path = Path("/private")
143+
144+
if cwd == var_path or cwd == private_path:
145+
console.print()
146+
console.print(
147+
Panel.fit(
148+
"[bold red]🚨 CRITICAL ERROR[/bold red]\n\n"
149+
f"You cannot run dspy-code from: [cyan]{cwd}[/cyan]\n\n"
150+
"Please run from a user project directory.",
151+
title="❌ System Directory",
152+
border_style="red",
153+
)
154+
)
155+
sys.exit(1)
156+
elif cwd.is_relative_to(private_path):
157+
console.print()
158+
console.print(
159+
Panel.fit(
160+
"[bold red]🚨 CRITICAL ERROR[/bold red]\n\n"
161+
f"You cannot run dspy-code from system directory: [cyan]{cwd}[/cyan]\n\n"
162+
"Please run from a user project directory.",
163+
title="❌ System Directory",
164+
border_style="red",
165+
)
166+
)
167+
sys.exit(1)
168+
169+
26170
@click.command()
27171
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
28172
@click.option("--debug", is_flag=True, help="Enable debug mode")
29-
def cli(verbose: bool, debug: bool):
173+
@click.option(
174+
"--skip-safety-check",
175+
is_flag=True,
176+
hidden=True,
177+
help="Skip directory safety check (not recommended)",
178+
)
179+
def cli(verbose: bool, debug: bool, skip_safety_check: bool):
30180
"""
31181
DSPy Code - Interactive DSPy Development Environment
32182
@@ -43,6 +193,10 @@ def cli(verbose: bool, debug: bool):
43193
DSPy Code adapts to YOUR installed DSPy version and gives you access
44194
to all DSPy features through natural language.
45195
"""
196+
# SECURITY: Check if running from safe directory
197+
if not skip_safety_check:
198+
check_safe_working_directory()
199+
46200
# Setup logging
47201
setup_logging(verbose=verbose, debug=debug)
48202

dspy_code/optimization/workflow_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ def __init__(self, code_generator=None, config_manager=None):
9292
self.current_workflow: OptimizationWorkflow | None = None
9393
self.data_collector = DataCollector()
9494

95-
# Workflow storage
96-
self.workflow_dir = Path.home() / ".dspy_cli" / "optimization"
95+
# Workflow storage in CWD for isolation and portability
96+
self.workflow_dir = Path.cwd() / ".dspy_code" / "optimization"
9797
self.workflow_dir.mkdir(parents=True, exist_ok=True)
9898

9999
def start_optimization(self, module_code: str, budget: str = "medium") -> OptimizationWorkflow:

dspy_code/project/scanner.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ def scan_directory(self, path: str = ".") -> ProjectState:
110110
)
111111

112112
def _get_all_files(self, path: Path) -> list[str]:
113-
"""Get all files in directory (excluding hidden and common ignore patterns)."""
113+
"""Get all files in directory (excluding hidden and common ignore patterns).
114+
115+
SECURITY: Only scans within the specified path with depth limit.
116+
"""
114117
files = []
115118
ignore_patterns = {
116119
"__pycache__",
@@ -125,12 +128,33 @@ def _get_all_files(self, path: Path) -> list[str]:
125128
"*.pyc",
126129
}
127130

131+
# SECURITY: Resolve path and enforce depth limit
132+
path = path.resolve()
133+
max_depth = 10 # Prevent deep recursion
134+
base_depth = len(path.parts)
135+
128136
try:
129137
for item in path.rglob("*"):
138+
# SECURITY: Check depth limit
139+
if len(item.parts) - base_depth > max_depth:
140+
continue
141+
142+
# SECURITY: Ensure item is actually within base path (prevent symlink attacks)
143+
try:
144+
item.resolve().relative_to(path)
145+
except ValueError:
146+
# Item is outside base path (e.g., symlink to another location)
147+
continue
148+
130149
if item.is_file():
131150
# Skip ignored patterns
132151
if any(pattern in str(item) for pattern in ignore_patterns):
133152
continue
153+
154+
# Check read permission
155+
if not os.access(item, os.R_OK):
156+
continue
157+
134158
files.append(str(item.relative_to(path)))
135159
except PermissionError:
136160
pass

dspy_code/rag/codebase_rag.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, config_manager: ConfigManager | None = None, cache_dir: Path
3030
3131
Args:
3232
config_manager: Configuration manager
33-
cache_dir: Cache directory (defaults to ~/.dspy_cli/cache/codebase_index)
33+
cache_dir: Cache directory (defaults to .dspy_code/cache/codebase_index in CWD)
3434
"""
3535
self.config_manager = config_manager
3636
self.enabled = self._is_enabled()

0 commit comments

Comments
 (0)