Skip to content
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3f1c769
chore: bump version to 2.7.7
vitorfgomes Feb 28, 2026
edda06f
feat: Add Customer flow - Phase 1
vitorfgomes Feb 28, 2026
f540922
chore: bump version to 2.7.8
vitorfgomes Feb 28, 2026
0e5c7e6
fix: customer init skips git check, creates .auto-claude directly
vitorfgomes Feb 28, 2026
308eb74
fix: pass updated project with autoBuildPath to GitHubSetupModal
vitorfgomes Feb 28, 2026
1ec12ad
feat: add initializeCustomerProject IPC for git-free .auto-claude setup
vitorfgomes Mar 1, 2026
98727e2
feat: multi-repo GitHub Issues for Customer projects
vitorfgomes Mar 1, 2026
2648cb8
chore: bump version to 2.7.9
vitorfgomes Mar 1, 2026
dda7566
fix: show GitHub nav for Customer projects and their child repos
vitorfgomes Mar 1, 2026
4e9e570
chore: bump version to 2.7.10
vitorfgomes Mar 1, 2026
cc064df
fix: keep GitHub nav visible when child repo selected via Customer dr…
vitorfgomes Mar 1, 2026
06bb177
fix: detect child repo GitHub config from git remote origin
vitorfgomes Mar 1, 2026
bce3a24
feat: multi-repo GitHub PRs for Customer projects
vitorfgomes Mar 1, 2026
cd7747b
chore: bump version to 2.7.11
vitorfgomes Mar 1, 2026
6f713f1
feat: display Claude Code global MCPs in MCP Server Overview
vitorfgomes Mar 1, 2026
33f60bf
chore: bump version to 2.7.12
vitorfgomes Mar 1, 2026
b136a99
feat: read global MCPs from ~/.claude.json and display custom agents
vitorfgomes Mar 1, 2026
1994439
chore: bump version to 2.7.13
vitorfgomes Mar 1, 2026
db3b5fc
feat: enable investigation, auto-fix, and PR review for customer mult…
vitorfgomes Mar 1, 2026
b629cba
chore: bump version to 2.7.14
vitorfgomes Mar 1, 2026
f2b5acf
feat: fix project indexing for customer projects + add .NET, docs & m…
vitorfgomes Mar 1, 2026
69184e1
Merge pull request #1 from vitorafgomes/feat/customer-project-indexing
vitorafgomes Mar 1, 2026
3a37fcf
fix: remove unused import and handle XML namespaces in .csproj parser
vitorfgomes Mar 1, 2026
95ffde7
fix: address PR review feedback — DRY, specific exceptions, ES imports
vitorfgomes Mar 1, 2026
6057cba
fix: address CodeRabbit PR review findings — security, cross-platform…
vitorfgomes Mar 1, 2026
20d982d
fix: ruff format + fix test assertions for addProject type parameter
vitorfgomes Mar 1, 2026
a22302b
fix: resolve remaining PR review findings — MCP validation, i18n, asy…
vitorfgomes Mar 1, 2026
ab5714e
fix: resolve 50+ PR review items — multi-repo identity, i18n, path co…
vitorfgomes Mar 1, 2026
51abab3
fix: revert @shared aliases in main/preload — not supported by electr…
vitorfgomes Mar 1, 2026
bfe5756
fix: resolve remaining backend review items — .NET solution, routes, …
vitorfgomes Mar 1, 2026
cac0374
fix: resolve remaining PR #1908 review items — security, i18n, access…
vitorfgomes Mar 5, 2026
16fcc91
feat(memory): improve Graphiti memory system with filtering, TTL, sco…
vitorfgomes Mar 5, 2026
65a862f
feat: integrate custom agents from ~/.claude/agents/ into build pipeline
vitorfgomes Mar 5, 2026
4bec08b
feat(memory): add frontend embedding dimension helpers and IPC improv…
vitorfgomes Mar 5, 2026
d154ff9
refactor: make all custom agents automatically available instead of m…
vitorfgomes Mar 5, 2026
aa0c15a
feat: add health status and pipeline phase assignment for Global MCPs
vitorfgomes Mar 5, 2026
bcaba0a
fix: use dedicated health check for global MCPs without command allow…
vitorfgomes Mar 5, 2026
145fba7
fix: resolve PR review comments + load global MCPs from ~/.claude.json
vitorfgomes Mar 6, 2026
6e67376
fix: resolve remaining PR review comments (round 2)
vitorfgomes Mar 6, 2026
d2a8a8a
fix: remaining frontend review comments (AgentTools, settings)
vitorfgomes Mar 6, 2026
d8d7b71
chore: bump version to 2.7.15
vitorfgomes Mar 6, 2026
bcd091b
fix: final frontend review comments (i18n, path aliases, cleanup)
vitorfgomes Mar 6, 2026
3e9ab01
fix: pass env vars to MCP servers and filter disabled MCPs
vitorfgomes Mar 6, 2026
8104c46
fix: resolve PR #1920 review comments (critical + actionable items)
vitorfgomes Mar 8, 2026
8469f45
fix: isolate CLAUDE_CONFIG_DIR in auth/client tests
vitorfgomes Mar 8, 2026
ef2c4ac
fix: eliminate TOCTOU race in MCP plugin cache reader
vitorfgomes Mar 8, 2026
43a1a3d
Merge branch 'develop' into feature/consolidated-memory-and-agents
vitorafgomes Mar 8, 2026
9fe6eeb
fix: respect CLAUDE_CONFIG_DIR in fast_mode and export getUserConfigDir
vitorfgomes Mar 8, 2026
8eaa165
chore: bump version to 2.7.16
vitorfgomes Mar 8, 2026
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: 1 addition & 1 deletion apps/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
See README.md for full documentation.
"""

__version__ = "2.7.6"
__version__ = "2.7.14"
__author__ = "Auto Claude Team"
6 changes: 6 additions & 0 deletions apps/backend/agents/coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,94 @@
Main autonomous agent loop that runs the coder agent to implement subtasks.
"""

import asyncio
import json
import logging
import os
import re
from datetime import datetime, timedelta
from pathlib import Path

from context.constants import SKIP_DIRS
from core.client import create_client
from core.file_utils import write_json_atomic
from linear_updater import (
LinearTaskState,
is_linear_enabled,
linear_build_complete,
linear_task_started,
linear_task_stuck,
)
from phase_config import (
get_fast_mode,
get_phase_client_thinking_kwargs,
get_phase_model,
get_phase_model_betas,
)
from phase_event import ExecutionPhase, emit_phase
from progress import (
count_subtasks,
count_subtasks_detailed,
get_current_phase,
get_next_subtask,
is_build_complete,
print_build_complete_banner,
print_progress_summary,
print_session_header,
)
from prompt_generator import (
format_context_for_prompt,
generate_planner_prompt,
generate_subtask_prompt,
load_subtask_context,
)
from prompts import is_first_run
from recovery import RecoveryManager
from security.constants import PROJECT_DIR_ENV_VAR
from task_logger import (
LogPhase,
get_task_logger,
)
from ui import (
BuildState,
Icons,
StatusManager,
bold,
box,
highlight,
icon,
muted,
print_key_value,
print_status,
)

from .base import (
AUTH_FAILURE_PAUSE_FILE,
AUTH_RESUME_CHECK_INTERVAL_SECONDS,
AUTH_RESUME_MAX_WAIT_SECONDS,
AUTO_CONTINUE_DELAY_SECONDS,
HUMAN_INTERVENTION_FILE,
INITIAL_RETRY_DELAY_SECONDS,
MAX_CONCURRENCY_RETRIES,
MAX_RATE_LIMIT_WAIT_SECONDS,
MAX_RETRY_DELAY_SECONDS,
MAX_SUBTASK_RETRIES,
RATE_LIMIT_CHECK_INTERVAL_SECONDS,
RATE_LIMIT_PAUSE_FILE,
RESUME_FILE,
sanitize_error_message,
)
from .memory_manager import debug_memory_system_status, get_graphiti_context
from .custom_agents import build_agents_catalog_prompt
from .session import post_session_processing, run_agent_session
from .utils import (
find_phase_for_subtask,
find_subtask_in_plan,
get_commit_count,
get_latest_commit,
load_implementation_plan,
sync_spec_to_source,
)

Check failure on line 95 in apps/backend/agents/coder.py

View workflow job for this annotation

GitHub Actions / Python (Ruff)

Ruff (I001)

apps/backend/agents/coder.py:8:1: I001 Import block is un-sorted or un-formatted

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -982,6 +983,9 @@
f"[Coder] [Fast Mode] {'ENABLED' if fast_mode else 'disabled'} for phase={current_phase}"
)

# Build catalog of available specialist agents (loaded once, cached)
agents_catalog = build_agents_catalog_prompt()

if first_run:
# Create client for planning phase
client = create_client(
Expand All @@ -991,6 +995,7 @@
agent_type="planner",
betas=phase_betas,
fast_mode=fast_mode,
agents_catalog_prompt=agents_catalog,
**thinking_kwargs,
)
prompt = generate_planner_prompt(spec_dir, project_dir)
Expand Down Expand Up @@ -1139,6 +1144,7 @@
agent_type="coder",
betas=phase_betas,
fast_mode=fast_mode,
agents_catalog_prompt=agents_catalog,
**thinking_kwargs,
)

Expand Down
270 changes: 270 additions & 0 deletions apps/backend/agents/custom_agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
"""
Custom Agents Integration
=========================

Parses custom agent .md files from ~/.claude/agents/ and provides
their configuration (system prompt, optional tool/MCP overrides)
for use in the Auto-Claude build pipeline.

Custom agent files are markdown files that may include YAML frontmatter
for tool and MCP server configuration:

---
tools: [Read, Write, Edit, Bash, Glob, Grep]
mcp_servers: [context7, graphiti]
thinking: high
---
You are a frontend specialist...

If no frontmatter is provided, only the system prompt (markdown body)
is used, and tool/MCP configuration comes from the base AGENT_CONFIGS.
"""

import logging
import os
import re
from dataclasses import dataclass, field
from pathlib import Path

logger = logging.getLogger(__name__)


@dataclass
class CustomAgentConfig:
"""Parsed configuration from a custom agent .md file."""

agent_id: str
system_prompt: str
tools: list[str] | None = None # Override base tools (None = use defaults)
mcp_servers: list[str] | None = None # Override MCP servers (None = use defaults)
thinking: str | None = None # Override thinking level (None = use default)
raw_frontmatter: dict = field(default_factory=dict)


def get_agents_dir() -> Path:
"""Get the custom agents directory (~/.claude/agents/)."""
config_dir = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.join(
os.path.expanduser("~"), ".claude"
)
return Path(config_dir) / "agents"


def parse_agent_file(file_path: Path) -> CustomAgentConfig | None:
"""
Parse a custom agent .md file.

Extracts optional YAML frontmatter (between --- delimiters) and the
markdown body as the system prompt.

Args:
file_path: Path to the .md agent file

Returns:
CustomAgentConfig if file is valid, None if file doesn't exist or is invalid
"""
if not file_path.exists() or not file_path.is_file():
logger.warning(f"Custom agent file not found: {file_path}")
return None

try:
content = file_path.read_text(encoding="utf-8")
except Exception as e:
logger.warning(f"Failed to read custom agent file {file_path}: {e}")
return None

agent_id = file_path.stem # filename without .md
frontmatter = {}
body = content

# Extract YAML frontmatter if present
fm_match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)", content, re.DOTALL)
if fm_match:
fm_text = fm_match.group(1)
body = fm_match.group(2).strip()

# Simple YAML-like parsing (avoid heavy yaml dependency)
frontmatter = _parse_simple_yaml(fm_text)

system_prompt = body.strip()
if not system_prompt:
logger.warning(f"Custom agent file has no content: {file_path}")
return None

# Extract optional overrides from frontmatter
tools = _parse_string_list(frontmatter.get("tools"))
mcp_servers = _parse_string_list(frontmatter.get("mcp_servers"))
thinking = frontmatter.get("thinking")

if thinking and thinking not in ("low", "medium", "high"):
logger.warning(
f"Invalid thinking level '{thinking}' in {file_path}, ignoring"
)
thinking = None

return CustomAgentConfig(
agent_id=agent_id,
system_prompt=system_prompt,
tools=tools,
mcp_servers=mcp_servers,
thinking=thinking,
raw_frontmatter=frontmatter,
)


def load_custom_agent(agent_id: str) -> CustomAgentConfig | None:
"""
Load a custom agent by ID.

Searches through category directories in ~/.claude/agents/ for
a matching agent file.

Args:
agent_id: Agent ID (filename without .md extension)

Returns:
CustomAgentConfig if found, None otherwise
"""
agents_dir = get_agents_dir()
if not agents_dir.exists():
return None

# Search in all category directories
for category_dir in sorted(agents_dir.iterdir()):
if not category_dir.is_dir():
continue
agent_file = category_dir / f"{agent_id}.md"
if agent_file.exists():
return parse_agent_file(agent_file)

# Also check root agents dir (no category)
root_file = agents_dir / f"{agent_id}.md"
if root_file.exists():
return parse_agent_file(root_file)

logger.debug(f"Custom agent '{agent_id}' not found in {agents_dir}")
return None


def load_all_agents() -> list[CustomAgentConfig]:
"""Load all custom agents from all categories in ~/.claude/agents/."""
agents_dir = get_agents_dir()
if not agents_dir.exists():
return []

agents = []
for category_dir in sorted(agents_dir.iterdir()):
if not category_dir.is_dir():
continue
for agent_file in sorted(category_dir.glob("*.md")):
if agent_file.name == "README.md":
continue
agent = parse_agent_file(agent_file)
if agent:
agents.append(agent)
return agents


def build_agents_catalog_prompt() -> str | None:
"""
Build a concise catalog of all available custom agents for system prompt injection.

Returns a formatted string listing all agents by category with their descriptions,
or None if no agents are available.
"""
agents_dir = get_agents_dir()
if not agents_dir.exists():
return None

categories: list[tuple[str, list[tuple[str, str]]]] = []

for category_dir in sorted(agents_dir.iterdir()):
if not category_dir.is_dir():
continue
category_name = category_dir.name.split("-", 1)[-1].replace("-", " ").title() if "-" in category_dir.name else category_dir.name

agent_entries = []
for agent_file in sorted(category_dir.glob("*.md")):
if agent_file.name == "README.md":
continue
agent = parse_agent_file(agent_file)
if agent:
# Get description from frontmatter, or first line of prompt
description = agent.raw_frontmatter.get("description", "")
if not description:
# Use first sentence of system prompt as fallback
first_line = agent.system_prompt.split("\n")[0].strip()
description = first_line[:120]
elif len(description) > 150:
description = description[:147] + "..."
agent_entries.append((agent.agent_id, description))

if agent_entries:
categories.append((category_name, agent_entries))

if not categories:
return None

total = sum(len(entries) for _, entries in categories)
lines = [
f"# Available Specialist Agents ({total} agents)",
"",
"You have access to the following specialist agents organized by category.",
"Use them when the task requires specialized expertise — spawn them as subagents",
"via the Agent tool with the appropriate subagent_type.",
"",
]

for category_name, entries in categories:
lines.append(f"## {category_name}")
for agent_id, desc in entries:
lines.append(f"- **{agent_id}**: {desc}")
lines.append("")

return "\n".join(lines)


def _parse_simple_yaml(text: str) -> dict:
"""
Parse simple YAML-like frontmatter (key: value pairs).

Handles:
- key: value (strings)
- key: [item1, item2] (inline lists)
- key: (empty value)

Does NOT handle nested structures or multi-line values.
"""
result = {}
for line in text.split("\n"):
line = line.strip()
if not line or line.startswith("#"):
continue
if ":" not in line:
continue
key, _, value = line.partition(":")
key = key.strip()
value = value.strip()

# Parse inline list: [item1, item2]
if value.startswith("[") and value.endswith("]"):
items = value[1:-1].split(",")
result[key] = [item.strip().strip("\"'") for item in items if item.strip()]
elif value:
# Strip quotes
result[key] = value.strip("\"'")
else:
result[key] = ""

return result


def _parse_string_list(value) -> list[str] | None:
"""Parse a value as a list of strings, or None if empty/invalid."""
if value is None:
return None
if isinstance(value, list):
cleaned = [str(v).strip() for v in value if v]
return cleaned if cleaned else None
if isinstance(value, str) and value:
return [v.strip() for v in value.split(",") if v.strip()]
return None
6 changes: 6 additions & 0 deletions apps/backend/agents/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
print_status,
)

from .custom_agents import build_agents_catalog_prompt
from .session import run_agent_session

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -107,13 +108,18 @@ async def run_followup_planner(
logger.info(
f"[Planner] [Fast Mode] {'ENABLED' if fast_mode else 'disabled'} for follow-up planning"
)

# Build catalog of available specialist agents
agents_catalog = build_agents_catalog_prompt()

client = create_client(
project_dir,
spec_dir,
planning_model,
agent_type="planner",
betas=planning_betas,
fast_mode=fast_mode,
agents_catalog_prompt=agents_catalog,
**thinking_kwargs,
)

Expand Down
39 changes: 39 additions & 0 deletions apps/backend/analysis/analyzers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
"core",
"shared",
"common",
"docs",
"documentation",
"microservices",
}

# Files that indicate a service root
Expand All @@ -83,8 +86,44 @@
"build.gradle",
"Makefile",
"Dockerfile",
# Documentation tools
"mkdocs.yml",
"mkdocs.yaml",
"docusaurus.config.js",
"docusaurus.config.ts",
"conf.py",
"book.toml",
}

# Glob patterns that indicate a service root (for files with variable names)
SERVICE_ROOT_GLOBS = [
"*.csproj",
"*.fsproj",
"*.sln",
]

# Deeper glob patterns for projects that nest manifests in subdirectories (e.g. .NET repos with src/)
SERVICE_ROOT_DEEP_GLOBS = [
"src/**/*.csproj",
"src/**/*.fsproj",
"src/**/*.cs", # Fallback: .cs source files without .csproj (incomplete repos)
]


def has_service_root(dir_path: Path) -> bool:
"""Check if a directory has service root indicators (exact files or glob patterns)."""
if any((dir_path / f).exists() for f in SERVICE_ROOT_FILES):
return True
if any(
next(dir_path.glob(pattern), None) is not None for pattern in SERVICE_ROOT_GLOBS
):
return True
# Check deeper patterns for .NET repos that keep .csproj in src/ subdirectories
return any(
next(dir_path.glob(pattern), None) is not None
for pattern in SERVICE_ROOT_DEEP_GLOBS
)


class BaseAnalyzer:
"""Base class with common utilities for all analyzers."""
Expand Down
Loading
Loading