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
10 changes: 6 additions & 4 deletions docs/src/content/docs/producer/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ apm compile --all # every canonical target
```

Accepted values: `copilot`, `claude`, `cursor`, `opencode`, `codex`,
`gemini`, `antigravity`, `windsurf`, `kiro`, `agent-skills`, `all`. The `agent-skills` slug
is a no-op for compile (skills are deployed by `apm install`); it is
accepted in target lists for symmetry only. Unknown slugs are
rejected before any work runs.
`gemini`, `antigravity`, `windsurf`, `kiro`, `intellij`, `agent-skills`,
and `all`. The `agent-skills` slug is a no-op for compile (skills are
deployed by `apm install`); it is accepted in target lists for symmetry
only. `intellij` uses the Copilot profile for file primitives and produces
`AGENTS.md`; IntelliJ-specific integration remains MCP-only. Unknown slugs
are rejected before any work runs.

Experimental targets (`hermes`, `openclaw`, `copilot-cowork`,
`copilot-app`) are deployment targets for `apm install --target <flag>`
Expand Down
8 changes: 5 additions & 3 deletions docs/src/content/docs/reference/cli/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,18 @@ written. Critical findings cause the command to exit non-zero. See

| Flag | Description |
|------|-------------|
| `-t, --target VALUE` | Target(s) to compile. Comma-separated. Values: `copilot`, `claude`, `cursor`, `opencode`, `codex`, `gemini`, `antigravity`, `windsurf`, `kiro`, `agent-skills`, `all`. |
| `-t, --target VALUE` | Target(s) to compile. Comma-separated. The help text derives its values from the same catalog used for validation, including `intellij`. |
| `--all` | Compile for all canonical targets. Equivalent to `--target all` and mutually exclusive with `--target`. Preferred form. |

`vscode` and `agents` are accepted as deprecated aliases for `copilot`
and emit a one-line warning. `--target all` also emits a deprecation
warning -- prefer `--all`.

`agent-skills` is a no-op for `compile` (skills-only deployment target);
`antigravity` is explicit-only and is not included in `all`. Use `apm install`
or `apm deps update` when you want shared `.agents/skills/` output.
`antigravity` is explicit-only and `intellij` is MCP-only. Neither is included in `all`.
For `intellij`, file primitives use the Copilot profile and produce `AGENTS.md`;
IntelliJ-specific integration remains MCP-only. Use `apm install` or
`apm deps update` when you want shared `.agents/skills/` output.
Comment on lines 69 to +73

### Output control

Expand Down
2 changes: 1 addition & 1 deletion packages/apm-guide/.apm/skills/apm-usage/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ If no `--target`, no `targets:` in `apm.yml`, and no harness signal is present,

| Command | Purpose | Key flags |
|---------|---------|-----------|
| `apm compile` | Compile agent context | `-o` output, `-t` target (comma-separated; resolution chain `--target` > apm.yml `targets:` > auto-detect), `--all` compile for every canonical target (preferred over deprecated `--target all`), `-g`/`--global` (read global instructions from `~/.apm/apm_modules/`, write user-scope root files; cannot combine with project-output flags such as `--target`, `--all`, `--watch`, `--root`, or `--output`; critical hidden-character findings stop the write and exit 1), `--chatmode`, `--dry-run`, `--no-links`, `--watch`, `--validate`, `--single-agents`, `-v` verbose, `--local-only`, `--clean`, `--with-constitution/--no-constitution`, `--force-instructions` / `--no-dedup` (opt out of Claude/Copilot deduplication), `--root DIR` redirect generated artifacts under DIR while sources resolve from `$PWD` (mirrors `pip install --target`; not valid with `--watch`) |
| `apm compile` | Compile agent context | `-o` output, `-t` target (comma-separated; resolution chain `--target` > apm.yml `targets:` > auto-detect; `intellij` is accepted and uses the Copilot profile for file primitives), `--all` compile for every canonical target (preferred over deprecated `--target all`), `-g`/`--global` (read global instructions from `~/.apm/apm_modules/`, write user-scope root files; cannot combine with project-output flags such as `--target`, `--all`, `--watch`, `--root`, or `--output`; critical hidden-character findings stop the write and exit 1), `--chatmode`, `--dry-run`, `--no-links`, `--watch`, `--validate`, `--single-agents`, `-v` verbose, `--local-only`, `--clean`, `--with-constitution/--no-constitution`, `--force-instructions` / `--no-dedup` (opt out of Claude/Copilot deduplication), `--root DIR` redirect generated artifacts under DIR while sources resolve from `$PWD` (mirrors `pip install --target`; not valid with `--watch`) |

`apm compile --watch` live-reloads `apm.yml`: editing `target:` / `targets:` mid-session takes effect on the next file event without restarting the watcher. The CLI `--target` flag, when passed to `apm compile --watch`, still outranks `apm.yml`. Re-resolution is gated on the changed file's basename being `apm.yml`, so `.instructions.md` edits do not pay an extra resolver round-trip and a stray `backup_apm.yml` cannot trigger a reload. `--clean` is ignored in watch mode and the watcher prints an explicit `[!]` warning at startup (`--clean is ignored in watch mode; run 'apm compile --clean' separately to remove orphaned outputs.`); run `apm compile --clean` separately between watch sessions to remove orphans.

Expand Down
9 changes: 7 additions & 2 deletions src/apm_cli/commands/compile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ...compilation import AgentsCompiler, CompilationConfig
from ...constants import AGENTS_MD_FILENAME, APM_DIR, APM_MODULES_DIR, APM_YML_FILENAME
from ...core.command_logger import CommandLogger
from ...core.target_detection import TargetParamType
from ...core.target_detection import TARGET_VALUES_HELP, TargetParamType
from ...primitives.discovery import clear_discovery_cache, discover_primitives
from ...utils import perf_stats
from ...utils.console import (
Expand Down Expand Up @@ -895,7 +895,12 @@ def _coerce_provenance_targets(value):
"-t",
type=TargetParamType(),
default=None,
help="Target platform (comma-separated). Values: copilot, claude, cursor, opencode, codex, gemini, antigravity, windsurf, kiro, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'antigravity' (alias 'agy') deploys to .agents/ and is explicit-only -- not part of 'all'. 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro (excludes agent-skills and antigravity); combine with 'agent-skills' or 'antigravity' to add them.",
help=f"Target platform (comma-separated). Values: {TARGET_VALUES_HELP}. "
"'agent-skills' deploys to .agents/skills/ (cross-client). "
"'antigravity' (alias 'agy') deploys to .agents/ and is explicit-only -- not part of 'all'. "
"'intellij' is MCP-only; file primitives use the Copilot profile. "
"'all' excludes agent-skills, antigravity, experimental targets, and intellij; "
"combine explicit-only targets when needed.",
)
@click.option(
"--dry-run",
Expand Down
15 changes: 13 additions & 2 deletions src/apm_cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@
)
from ..core.auth import AuthResolver
from ..core.command_logger import InstallLogger, _ValidationOutcome
from ..core.target_detection import TargetParamType, manifest_targets_from_target_option
from ..core.target_detection import (
TARGET_VALUES_HELP,
TargetParamType,
manifest_targets_from_target_option,
)

# MCP --mcp helpers (module-level re-exports for test patches); must stay at
# import time per comments in the original mid-file block.
Expand Down Expand Up @@ -950,7 +954,14 @@ def _handle_mcp_install( # noqa: PLR0913
"target",
type=TargetParamType(),
default=None,
help="Target harness(es) to deploy to. Use commas for multiple targets; repeating the flag keeps only the last value (use commas instead). Values: copilot, claude, cursor, opencode, codex, gemini, antigravity (agy), windsurf, kiro, intellij, agent-skills, all. IntelliJ-specific integration is MCP-only; file primitives use the Copilot profile. 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro; combine agent-skills, antigravity, or intellij explicitly when needed. Experimental copilot-cowork and copilot-app values require their feature flags. Resolution order: --target > apm.yml targets: > apm config target > auto-detect. With nothing to detect, install exits 2 with a teaching message. For 'apm compile', use '--all'; '--target all' is deprecated.",
help=f"Target harness(es) to deploy to. Use commas for multiple targets; repeating the flag "
f"keeps only the last value (use commas instead). Values: {TARGET_VALUES_HELP}. "
"IntelliJ-specific integration is MCP-only; file primitives use the Copilot profile. "
"'all' excludes agent-skills, antigravity, experimental targets, and intellij; combine "
"explicit-only targets when needed. Experimental targets require their feature flags. "
"Resolution order: --target > apm.yml targets: > apm config target > auto-detect. "
"With nothing to detect, install exits 2 with a teaching message. For 'apm compile', "
"use '--all'; '--target all' is deprecated.",
)
@click.option(
"--allow-insecure",
Expand Down
7 changes: 5 additions & 2 deletions src/apm_cli/core/target_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,9 @@ def normalize_policy_targets(value: str | list[str] | None) -> str | list[str] |
| frozenset({"all"})
)

#: Stable user-facing projection of every value accepted by ``--target``.
TARGET_VALUES_HELP = ", ".join(sorted(VALID_TARGET_VALUES))


def parse_target_field(
value: str | list[str] | None,
Expand Down Expand Up @@ -723,13 +726,13 @@ def convert(
# Use the v2 three-section error renderer for unknown targets
# so that CLI, apm.yml, and auto-detect all share the same
# error format (#1154).
from apm_cli.core.apm_yml import CANONICAL_TARGETS
from apm_cli.core.errors import UnknownTargetError, render_unknown_target_error

err_msg = str(e)
if "is not a valid target" in err_msg:
target_name = value if isinstance(value, str) else ",".join(value or [])
rendered = render_unknown_target_error(target_name, sorted(CANONICAL_TARGETS))
advertised = set(CANONICAL_TARGETS_ORDERED) | MCP_ONLY_TARGETS | {"all"}
rendered = render_unknown_target_error(target_name, sorted(advertised))
raise UnknownTargetError(rendered) from None
# Click idiom: route validation errors through self.fail so the
# user sees a clean "Invalid value for '--target': ..." message
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/compilation/test_compile_target_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
CompilationConfig,
CompilationResult,
)
from apm_cli.core.target_detection import CANONICAL_TARGETS_ORDERED, MCP_ONLY_TARGETS
from apm_cli.primitives.models import Instruction, PrimitiveCollection


Expand Down Expand Up @@ -989,6 +990,38 @@ def temp_project(self):
yield temp_path
shutil.rmtree(temp_dir, ignore_errors=True)

def test_help_advertises_intellij_target(self, runner):
"""Compile help lists every accepted target, including IntelliJ."""
result = runner.invoke(cli, ["compile", "--help"])

assert result.exit_code == 0
assert "intellij" in result.output.lower()

def test_invalid_target_error_advertises_supported_catalog(self, runner):
"""Unknown-target guidance lists stable and MCP-only targets."""
result = runner.invoke(cli, ["compile", "--target", "definitely-bogus"])

assert result.exit_code == 2
valid_line = next(
line for line in result.output.splitlines() if line.startswith("Valid targets:")
)
advertised = {target.strip() for target in valid_line.partition(":")[2].split(",")}
assert advertised == set(CANONICAL_TARGETS_ORDERED) | MCP_ONLY_TARGETS | {"all"}
assert "intellij" in advertised
assert "agents" not in advertised

def test_target_flag_accepts_intellij(self, runner, temp_project):
"""Compile accepts IntelliJ and generates the Copilot-profile AGENTS.md."""
original_dir = os.getcwd()
try:
os.chdir(temp_project)
result = runner.invoke(cli, ["compile", "--target", "intellij"])

assert result.exit_code == 0, result.output
assert (temp_project / "AGENTS.md").is_file()
finally:
os.chdir(original_dir)

def test_target_flag_accepts_vscode(self, runner, temp_project):
"""Test that --target vscode is accepted."""
original_dir = os.getcwd()
Expand Down
25 changes: 22 additions & 3 deletions tests/unit/integration/test_kiro_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from click.testing import CliRunner

from apm_cli.cli import cli
from apm_cli.core.target_detection import CANONICAL_TARGETS_ORDERED, MCP_ONLY_TARGETS
from apm_cli.integration.hook_integrator import HookIntegrator
from apm_cli.integration.instruction_integrator import InstructionIntegrator
from apm_cli.integration.skill_integrator import SkillIntegrator
Expand Down Expand Up @@ -55,18 +56,36 @@ def test_kiro_is_discoverable_in_target_help() -> None:
install = runner.invoke(cli, ["install", "--help"])
compile_result = runner.invoke(cli, ["compile", "--help"])

expected_all_targets = "copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro"
install_help = "".join(install.output.split())
compile_help = "".join(compile_result.output.split())

assert install.exit_code == 0
assert compile_result.exit_code == 0
assert expected_all_targets in install_help
assert expected_all_targets in compile_help
# The accepted-target catalog (VALID_TARGET_VALUES) is the single source of
# truth for the help Values list, so every advertised target -- including
# the MCP-only 'intellij' target -- must be discoverable in both help texts.
for target in ("kiro", "intellij", "copilot", "claude", "windsurf"):
assert target in install_help
assert target in compile_help
assert "ClaudeCode" in install_help
assert "Windsurf" in install_help


def test_unknown_target_error_advertises_stable_and_mcp_targets() -> None:
runner = CliRunner()

result = runner.invoke(cli, ["compile", "--target", "definitely-bogus"])

assert result.exit_code == 2
valid_line = next(
line for line in result.output.splitlines() if line.startswith("Valid targets:")
)
advertised = {target.strip() for target in valid_line.partition(":")[2].split(",")}
assert advertised == set(CANONICAL_TARGETS_ORDERED) | MCP_ONLY_TARGETS | {"all"}
assert "intellij" in advertised
assert "agents" not in advertised


def test_kiro_runtime_discovered_in_user_scope_without_project_dir(tmp_path: Path) -> None:
from apm_cli.integration.mcp_integrator_install import _discover_installed_runtimes

Expand Down
Loading