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
17 changes: 7 additions & 10 deletions src/apm_cli/commands/compile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,23 +380,20 @@ def compile(

# Handle distributed vs single-file compilation
if config.strategy == "distributed" and not single_agents:
# Show target-aware message with detection reason
# Show target-aware message with detection reason. Use
# get_target_description() so any future target added to
# target_detection shows up here automatically.
if detected_target == "minimal":
logger.progress(f"Compiling for AGENTS.md only ({detection_reason})")
logger.progress(
" Create .github/ or .claude/ folder for full integration",
" Create .github/, .claude/, .codex/, .opencode/ or .cursor/ folder for full integration",
symbol="light_bulb",
)
elif detected_target == "vscode" or detected_target == "agents":
logger.progress(
f"Compiling for AGENTS.md (VSCode/Copilot) - {detection_reason}"
)
elif detected_target == "claude":
else:
description = get_target_description(detected_target)
logger.progress(
f"Compiling for CLAUDE.md (Claude Code) - {detection_reason}"
f"Compiling for {description} - {detection_reason}"
)
else: # "all"
logger.progress(f"Compiling for AGENTS.md + CLAUDE.md - {detection_reason}")

if dry_run:
logger.dry_run_notice(
Expand Down
60 changes: 52 additions & 8 deletions src/apm_cli/compilation/agents_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
)
from .link_resolver import resolve_markdown_links, validate_link_targets
from ..utils.paths import portable_relpath
from ..core.target_detection import should_compile_agents_md, should_compile_claude_md


# User-facing target aliases that map to the canonical "vscode" target.
# Kept in sync with target_detection.detect_target().
_VSCODE_TARGET_ALIASES = ("copilot", "agents")
_KNOWN_TARGETS = (
"vscode", "claude", "cursor", "opencode", "codex", "all", "minimal",
) + _VSCODE_TARGET_ALIASES


@dataclass
Expand Down Expand Up @@ -199,17 +208,52 @@ def compile(self, config: CompilationConfig, primitives: Optional[PrimitiveColle
exclude_patterns=config.exclude,
)

# Route to targets based on config.target
# Route to targets based on config.target.
# Use target_detection helpers as the single source of truth so
# new targets (codex, opencode, cursor, minimal, ...) route
# correctly without touching this method again.
routing_target = (
"vscode" if config.target in _VSCODE_TARGET_ALIASES else config.target
)

if routing_target not in _KNOWN_TARGETS and config.target not in _KNOWN_TARGETS:
self.errors.append(
f"Unknown compilation target: {config.target!r}. "
f"Expected one of: {', '.join(sorted(set(_KNOWN_TARGETS)))}"
)
return CompilationResult(
success=False,
output_path="",
content="",
warnings=self.warnings.copy(),
errors=self.errors.copy(),
stats={},
)

results: List[CompilationResult] = []

# AGENTS.md target (vscode/agents)
if config.target in ("vscode", "agents", "all"):

if should_compile_agents_md(routing_target):
results.append(self._compile_agents_md(config, primitives))

# CLAUDE.md target
if config.target in ("claude", "all"):

if should_compile_claude_md(routing_target):
results.append(self._compile_claude_md(config, primitives))


# Defensive: should never happen for a known target, but guards
# against future target_detection drift silently producing no-ops.
if not results:
self.errors.append(
f"Target {config.target!r} did not route to any compiler. "
"This is an internal bug in target routing."
)
return CompilationResult(
success=False,
output_path="",
content="",
warnings=self.warnings.copy(),
errors=self.errors.copy(),
stats={},
)

# Merge results from all targets
return self._merge_results(results)

Expand Down
61 changes: 59 additions & 2 deletions tests/unit/compilation/test_compile_target_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,71 @@ def test_target_all_generates_both(self, temp_project, sample_primitives):
dry_run=True,
single_agents=True # Use single-file for AGENTS.md
)

compiler = AgentsCompiler(str(temp_project))
result = compiler.compile(config, sample_primitives)

assert result.success
# Output path should mention both targets
assert "AGENTS.md" in result.output_path or "CLAUDE" in result.output_path

def test_target_codex_generates_agents_md(self, temp_project, sample_primitives):
"""Regression for issue #766: --target codex must produce AGENTS.md, not a silent no-op."""
config = CompilationConfig(
target="codex",
dry_run=True,
single_agents=True,
)

compiler = AgentsCompiler(str(temp_project))
result = compiler.compile(config, sample_primitives)

assert result.success
assert result.output_path, "codex target must route to a compiler, not return empty"
assert "AGENTS.md" in result.output_path

def test_target_opencode_generates_agents_md(self, temp_project, sample_primitives):
"""target='opencode' must route to AGENTS.md (same universal format as codex)."""
config = CompilationConfig(
target="opencode",
dry_run=True,
single_agents=True,
)

compiler = AgentsCompiler(str(temp_project))
result = compiler.compile(config, sample_primitives)

assert result.success
assert "AGENTS.md" in result.output_path

def test_target_minimal_generates_agents_md(self, temp_project, sample_primitives):
"""target='minimal' must route to AGENTS.md-only."""
config = CompilationConfig(
target="minimal",
dry_run=True,
single_agents=True,
)

compiler = AgentsCompiler(str(temp_project))
result = compiler.compile(config, sample_primitives)

assert result.success
assert "AGENTS.md" in result.output_path

def test_unknown_target_returns_failure(self, temp_project, sample_primitives):
"""Unknown target must fail explicitly instead of silently succeeding."""
config = CompilationConfig(
target="not-a-real-target",
dry_run=True,
single_agents=True,
)

compiler = AgentsCompiler(str(temp_project))
result = compiler.compile(config, sample_primitives)

assert result.success is False
assert any("Unknown compilation target" in e for e in result.errors)


class TestMergeResults:
"""Tests for _merge_results() method."""
Expand Down
Loading