Skip to content
Open
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
33 changes: 33 additions & 0 deletions docs/src/content/docs/producer/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,39 @@ you can omit `start_marker` and `end_marker` if you use those verbatim.
- Content outside the markers is preserved verbatim across every compile
run; only the block between the markers is replaced.

## Global compilation (-g)

By default, `apm compile` reads instructions from your workspace and
writes root context files to `.github/`, `.claude/`, etc. For distributing
instructions to all AI tools on your user's machine (not scoped to a project),
use the `--global` or `-g` flag:
Comment on lines +221 to +226

```bash
apm compile --global
apm compile -g --dry-run
```

This reads **global instructions** from `~/.apm/apm_modules/` (instructions
without an `apply_to:` field) and writes user-scope root context files:

- `~/.claude/CLAUDE.md` (or `$CLAUDE_CONFIG_DIR/CLAUDE.md`)
- `~/.codex/AGENTS.md`, `~/.copilot/AGENTS.md`, `~/.cursor/AGENTS.md`, etc.
- `~/.gemini/GEMINI.md`

### Overwrite protection

When a root file exists but contains no APM marker, it is treated as
hand-authored and never overwritten. Use `--dry-run` to preview what would
be written without modifying files.

### Constraints

- `--global` cannot be combined with `--watch` or `--root`.
- Skills-only packages (no global instructions) do not write root files.
- To integrate global compile into your install flow, use
`apm install -g` (see [Install packages](../consumer/install-packages/)), which
automatically runs compile after installing global packages.

## Pitfalls

- **Confusing compile's scope.** Compile only handles **instructions**
Expand Down
10 changes: 10 additions & 0 deletions docs/src/content/docs/reference/cli/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ use `apm install` or `apm deps update` when you want shared
| `--dry-run` | Show placement decisions without writing files. |
| `-v, --verbose` | Show source attribution and optimizer analysis. |

### Global compilation

| Flag | Description |
|------|-------------|
| `-g, --global` | Compile user-scope root context files from `~/.apm/apm_modules`. Reads globally installed packages and writes one root context file per active target (e.g. `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`). Not valid with `--watch` or `--root`. Exits non-zero if `~/.apm/apm_modules` does not exist. |

`apm install --global` automatically runs this step after installing packages.
Use `apm compile --global` to re-run it manually after adding or removing global packages.
Hand-authored files (files that do not carry the APM-generated marker) are never overwritten.

## Examples

Compile for whatever the project is set up for:
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 @@ -52,7 +52,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`), `--chatmode`, `--dry-run`, `--no-links`, `--watch`, `--validate`, `--single-agents`, `-v` verbose, `--local-only`, `--clean`, `--with-constitution/--no-constitution`, `--no-dedup` / `--force-instructions` (opt out of Claude 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), `--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 `--watch` or `--root`), `--chatmode`, `--dry-run`, `--no-links`, `--watch`, `--validate`, `--single-agents`, `-v` verbose, `--local-only`, `--clean`, `--with-constitution/--no-constitution`, `--no-dedup` / `--force-instructions` (opt out of Claude 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
81 changes: 81 additions & 0 deletions src/apm_cli/commands/compile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,59 @@ def _resolve_effective_target(
return detected_target, detection_reason, config_target


def _handle_global_flag(dry_run: bool) -> int:
"""Handle --global compilation of user-scope root context files.

Returns 0 on success, 1 on error (for sys.exit).
"""

from ...compilation import compile_user_root_contexts
from ...core.scope import InstallScope, get_apm_dir
from ...integration.targets import KNOWN_TARGETS
from ...utils.console import _rich_error, _rich_info, _rich_success

source_root = get_apm_dir(InstallScope.USER)
apm_modules = source_root / "apm_modules"
if not apm_modules.is_dir():
Comment on lines +333 to +340
_rich_error(
f"User-scope apm_modules not found: {apm_modules}. "
"Run 'apm install -g <package>' to install packages globally."
)
return 1

results = compile_user_root_contexts(
list(KNOWN_TARGETS.values()),
source_root,
dry_run=dry_run,
logger=None,
)

if not results:
_rich_info("No user-scope targets produced output (no global instructions found).")
return 0

has_error = False
for entry in results:
status = entry["status"]
tname = entry["target"]
path = entry.get("path")
if status == "written":
_rich_success(f"{tname}: wrote {path}", symbol="check")
elif status == "would-write":
_rich_info(f"{tname}: would write {path} (dry-run)", symbol="preview")
elif status == "unchanged":
_rich_info(f"{tname}: unchanged {path}", symbol="info")
elif status == "skipped-hand-authored":
_rich_info(f"{tname}: skipped (hand-authored) {path}", symbol="info")
elif status == "skipped-no-instructions":
_rich_info(f"{tname}: skipped (no global instructions)", symbol="info")
elif status.startswith("error:"):
_rich_error(f"{tname}: {status[6:]}", symbol="error")
has_error = True

return 1 if has_error else 0


def _validate_project(logger: CommandLogger, dry_run: bool, source_root: Path) -> None:
"""Check APM project exists and has content.

Expand Down Expand Up @@ -870,6 +923,17 @@ def _coerce_provenance_targets(value):
"for scratch-dir verification. Cannot be combined with --watch."
),
)
@click.option(
"--global",
"-g",
"global_",
is_flag=True,
default=False,
help=(
"Compile user-scope root context files (~/.claude/CLAUDE.md, etc.) "
"from ~/.apm/apm_modules. Cannot be combined with --watch or --root."
),
)
@click.pass_context
def compile( # noqa: PLR0913 -- Click handler
ctx,
Expand All @@ -889,6 +953,7 @@ def compile( # noqa: PLR0913 -- Click handler
compile_all,
no_dedup,
root,
global_,
):
"""Compile APM context into distributed AGENTS.md files.

Expand Down Expand Up @@ -926,6 +991,22 @@ def compile( # noqa: PLR0913 -- Click handler
# consumers running with -W default, which we have none of.
logger.warning("'--target all' is deprecated; use '--all' instead.")

# --global: compile user-scope root context files from ~/.apm/apm_modules.
# Must be checked before --watch / --root guards so we return early.
if global_:
from ...utils.console import _rich_error

if watch:
_rich_error("--global cannot be combined with --watch")
sys.exit(2)
if root:
_rich_error("--global cannot be combined with --root")
sys.exit(2)
rc = _handle_global_flag(dry_run=dry_run)
if rc != 0:
sys.exit(rc)
return

# --root + --watch is rejected: ``_watch_mode`` uses bare-relative
# paths (``Path(APM_DIR)``, ``AgentsCompiler(".")``) and the watch
# loop would scan the deploy root rather than the source tree. The
Expand Down
3 changes: 3 additions & 0 deletions src/apm_cli/compilation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
find_chatmode_by_name,
render_instructions_block,
)
from .user_root_context import compile_user_root_contexts

__all__ = [ # noqa: RUF022
# Main compilation interface
"AgentsCompiler",
"compile_agents_md",
"CompilationConfig",
"CompilationResult",
# User-scope root context compilation
"compile_user_root_contexts",
# Template building
"build_conditional_sections",
"render_instructions_block",
Expand Down
Loading
Loading