Skip to content
Closed
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
15 changes: 15 additions & 0 deletions src/deepwork/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ def _inject_deepwork_policy(jobs_dir: Path, project_path: Path) -> None:
_inject_standard_job("deepwork_policy", jobs_dir, project_path)


def _inject_env_investigate(jobs_dir: Path, project_path: Path) -> None:
"""
Inject the env_investigate job definition into the project.

Args:
jobs_dir: Path to .deepwork/jobs directory
project_path: Path to project root (for relative path display)

Raises:
InstallError: If injection fails
"""
_inject_standard_job("env_investigate", jobs_dir, project_path)


def _create_deepwork_gitignore(deepwork_dir: Path) -> None:
"""
Create .gitignore file in .deepwork/ directory.
Expand Down Expand Up @@ -272,6 +286,7 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
console.print("[yellow]→[/yellow] Installing core job definitions...")
_inject_deepwork_jobs(jobs_dir, project_path)
_inject_deepwork_policy(jobs_dir, project_path)
_inject_env_investigate(jobs_dir, project_path)

# Step 3c: Create .gitignore for temporary files
_create_deepwork_gitignore(deepwork_dir)
Expand Down
18 changes: 17 additions & 1 deletion src/deepwork/cli/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from rich.table import Table

from deepwork.core.adapters import AgentAdapter
from deepwork.core.agent_generator import AgentGenerator
from deepwork.core.generator import CommandGenerator
from deepwork.core.hooks_syncer import collect_job_hooks, sync_hooks_to_platform
from deepwork.core.parser import parse_job_definition
Expand Down Expand Up @@ -116,7 +117,8 @@ def sync_commands(project_path: Path) -> None:

# Sync each platform
generator = CommandGenerator()
stats = {"platforms": 0, "commands": 0, "hooks": 0}
agent_generator = AgentGenerator()
stats = {"platforms": 0, "commands": 0, "hooks": 0, "agents": 0}
synced_adapters: list[AgentAdapter] = []

for platform_name in platforms:
Expand Down Expand Up @@ -146,6 +148,18 @@ def sync_commands(project_path: Path) -> None:
except Exception as e:
console.print(f" [red]✗[/red] Failed for {job.name}: {e}")

# Generate agent files for platforms that support them
console.print(" [dim]•[/dim] Generating agent files...")
try:
agent_paths = agent_generator.generate_agents(adapter, project_path)
if agent_paths:
stats["agents"] += len(agent_paths)
console.print(f" [green]✓[/green] Generated {len(agent_paths)} agent file(s)")
else:
console.print(" [dim]•[/dim] No agent templates for this platform")
except Exception as e:
console.print(f" [red]✗[/red] Failed to generate agents: {e}")

# Sync hooks to platform settings
if job_hooks_list:
console.print(" [dim]•[/dim] Syncing hooks...")
Expand All @@ -171,6 +185,8 @@ def sync_commands(project_path: Path) -> None:

table.add_row("Platforms synced", str(stats["platforms"]))
table.add_row("Total commands", str(stats["commands"]))
if stats["agents"] > 0:
table.add_row("Agent files", str(stats["agents"]))
if stats["hooks"] > 0:
table.add_row("Hooks synced", str(stats["hooks"]))

Expand Down
128 changes: 128 additions & 0 deletions src/deepwork/core/agent_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Agent file generator for subagent definitions."""

from pathlib import Path

from jinja2 import Environment, FileSystemLoader

from deepwork.core.adapters import AgentAdapter
from deepwork.utils.fs import safe_write


class AgentGeneratorError(Exception):
"""Exception raised for agent generation errors."""

pass


class AgentGenerator:
"""Generates agent definition files for platforms that support them."""

def __init__(self, templates_dir: Path | str | None = None):
"""
Initialize agent generator.

Args:
templates_dir: Path to templates directory
(defaults to package templates directory)
"""
if templates_dir is None:
# Use package templates directory
templates_dir = Path(__file__).parent.parent / "templates"

self.templates_dir = Path(templates_dir)

if not self.templates_dir.exists():
raise AgentGeneratorError(f"Templates directory not found: {self.templates_dir}")

def _get_agent_templates(self, adapter: AgentAdapter) -> list[Path]:
"""
Get list of agent template files for an adapter.

Args:
adapter: Agent adapter

Returns:
List of agent template file paths (empty if none exist)
"""
platform_templates_dir = adapter.get_template_dir(self.templates_dir)
agents_dir = platform_templates_dir / "agents"

if not agents_dir.exists():
return []

# Find all .j2 template files in the agents directory
agent_templates = list(agents_dir.glob("*.j2"))
return agent_templates

def generate_agents(
self,
adapter: AgentAdapter,
project_path: Path | str,
) -> list[Path]:
"""
Generate agent definition files for a platform.

This creates agent files (e.g., .claude/agents/*.md) from templates
in the templates directory. Only platforms that support agents will
have agent templates.

Args:
adapter: Agent adapter for the target platform
project_path: Path to project root

Returns:
List of paths to generated agent files

Raises:
AgentGeneratorError: If generation fails
"""
project_path = Path(project_path)

# Get agent templates for this platform
agent_templates = self._get_agent_templates(adapter)

if not agent_templates:
# No agent templates for this platform - that's okay
return []

# Create agents directory in platform config
platform_dir = project_path / adapter.config_dir
agents_dir = platform_dir / "agents"
agents_dir.mkdir(parents=True, exist_ok=True)

# Setup Jinja environment
platform_templates_dir = adapter.get_template_dir(self.templates_dir)
agents_templates_dir = platform_templates_dir / "agents"

env = Environment(
loader=FileSystemLoader(agents_templates_dir),
trim_blocks=True,
lstrip_blocks=True,
)

generated_paths: list[Path] = []

# Process each agent template
for template_path in agent_templates:
template_name = template_path.name

# Remove .j2 extension for output file
output_filename = template_name[:-3] if template_name.endswith(".j2") else template_name

try:
# Load and render template
template = env.get_template(template_name)
rendered = template.render()

# Write agent file
agent_path = agents_dir / output_filename

safe_write(agent_path, rendered)
generated_paths.append(agent_path)

except Exception as e:
raise AgentGeneratorError(
f"Failed to generate agent {template_name}: {e}"
) from e

return generated_paths
Loading