Skip to content

Commit 9f7f13b

Browse files
danielmeppielDaniel MeppielCopilot
authored
fix(targets): explicit, auditable target resolution (closes #1154) (#1165)
* feat(targets): wire v2 resolution into install pipeline (Phase A+B) Three-guard collapse: v2 signal-based resolution replaces legacy auto-detect for project-scope installs while preserving backward compatibility. - core/errors.py: 5 error classes + 4 ASCII render functions - core/apm_yml.py: parse_targets_field() with schema validation - core/target_detection.py: Signal, ResolvedTargets, detect_signals(), resolve_targets(), expand_all_targets(), format_provenance() - install/phases/targets.py: hybrid run() -- legacy first, then v2 overlay for project-scope. Cowork gates preserved. Non-canonical targets (copilot-cowork) skip v2 entirely. - run_targets_phase() standalone v2 entry point 28 new unit tests (all passing), 25 E2E fixture scenarios prepared. 7717 total unit tests pass, lint clean. Refs: #1154 * feat(cli): add 'apm targets' command and compile --all flag (Phase C) - commands/targets.py: Click group with --json and --all flags, shows resolved targets for current project - cli.py: register targets command - compile/cli.py: add --all flag (equivalent to --target all), deprecation warning for --target all 7717 unit tests pass, lint clean. Refs: #1154 * fix(targets): complete E2E test suite and error propagation (Phase E) - Fix ConflictingTargetsError propagation: catch click.UsageError in targets phase and convert to SystemExit(2) before pipeline's generic except-Exception handler wraps it as RuntimeError (exit code 1→2) - Fix UnknownTargetError rendering: TargetParamType.convert() now raises UnknownTargetError with 3-section format instead of click.BadParameter - Fix 'apm targets' command for ambiguous projects: catch AmbiguousHarnessError and show all detected signals (users run 'apm targets' to see what's there) - Default table view shows all canonical targets with active/inactive status - Add instruction files to s07 fixture for compile command - Update unit tests for new UnknownTargetError type - All 7717 unit tests pass, all 32 E2E tests pass (29+2xfail+1xpass) - Lint and format clean * fix(targets): apply convergence items 6 + 13 - needs <path>, exclude agent-skills Make 'apm targets' table self-documenting on inactive rows by showing 'needs <signal-path>' (e.g. 'needs CLAUDE.md') instead of the dead-end 'no signal' label. Source columns now answer the user's next question ('what do I create?') without a docs lookup. Exclude the agent-skills meta-target from the default table; surface it only via 'apm targets --all --json' with meta_target=true so it's visible to scripts but doesn't pollute the human-facing default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(targets): apply panel feedback - convergence drift + doc accuracy Bundle of multi-panelist findings (cli-logging-expert, devx-ux-expert, test-coverage-expert, python-architect, doc-writer): Code: - install/phases/targets.py: route provenance line through _rich_info (convergence item 1; was bare click.echo, missed rich color/symbol) - commands/compile/cli.py: surface --target all deprecation through logger.warning so users actually see it (convergence item 9; was warnings.warn which is silenced by default in CLI output) - commands/targets.py: hoist detect_signals() out of per-row loop (was N filesystem scans for an N-row table); rewrite --all help text to describe the actual semantics (agent-skills meta-target in --json); route empty-state hint through _rich_info - core/target_detection.py: CANONICAL_SIGNAL[cursor] is .cursor/ not .cursorrules (legacy form; .cursor/ is the modern primary signal) - install/phases/targets.py: mark run_targets_phase as @internal/test-only Docs: - cli-commands.md: replace stale 'apm targets' table sample (3-col, 'no signal') and stale --json envelope (single-object) with the actual shipping shape (4-col, 'needs <path>', JSON array of objects); rewrite --all option description - first-package.md: remove orphan claim that 'Copilot is the default fallback' -- the very behavior #1165 removes; replace with the new resolution-chain prose pointing at apm targets and explicit --target - packages/apm-guide/.apm/skills/apm-usage/commands.md: scope the resolution-chain prose explicitly to apm install (apm compile still uses legacy detect_target with vscode/minimal fallback; bringing it onto strict resolution is a follow-up) Tests: - Add S08b (apm targets --json shape contract: array, per-target objects, --all surfaces agent-skills meta-target) - Add S07b (--target all deprecation must be visible in CLI output) - Remove S15 xfail marker (was xpassing -- behavior is satisfied) Meta: - CHANGELOG.md: replace (#PR_NUMBER) placeholder with (#1165) Verified: lint silent, format silent, 7717 unit tests pass, target_resolution E2E 32 passed / 2 xfailed (S13 + S16b unchanged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(lint): split third-party and first-party imports in test_error_renderer CI lint (ruff 0.15.x) flagged I001 because pytest (third-party) and apm_cli.core.errors (first-party) sat in the same import block. Inserted the required blank line between them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(init): prompt for targets so users land in apm.yml Tier 2 by default Adds an interactive target-selection step to `apm init` so users exit the wizard with `target:` declared in apm.yml (Tier 2 of the strict resolution chain landed earlier in this PR), instead of relying implicitly on Tier 3 auto-detect -- the fragility this PR was created to fix. Changes: - New `--target` flag on `apm init` (CSV; skips prompt; validates via existing TargetParamType). - New numbered-toggle prompt (Rich-formatted [x]/[ ] checkboxes; no new dependencies; matches existing _interactive_project_setup pattern). - Pre-checks seeded from existing `target:` field on re-init, otherwise from `detect_signals()`. - Empty selection emits commented-out skeleton in apm.yml so users can opt-in later without re-reading docs. - Non-TTY contexts auto-skip the prompt with one-line provenance log. - 10 new tests cover S1-S7 scenarios from /tmp/apm-1154-plan/init-prompt-scope.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(init): emit canonical 'targets:' plural list and harden non-TTY guard - Switch apm.yml emission from singular 'target:' CSV to plural 'targets:' list form (canonical per core/apm_yml.py); singular CSV stays readable as backwards-compat sugar for legacy projects. - Restore explicit sys.stdin.isatty() guard via patchable _stdin_is_tty() helper; non-TTY environments (Windows pipes, CI without --yes) auto- detect with a provenance log instead of hanging on the prompt. - _read_existing_targets now reads plural first, falls back to singular. - Update 10 prompt tests for plural list assertions; add legacy-singular re-init test and isatty=False auto-detect test (12 prompt tests total). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ux(init): drop verbose target descriptions from prompt list The '-- <desc>' suffix on each target choice was too verbose and the contents (deploy paths, file lists) will drift as integrators evolve. Keep just the target name and any '(detected ...)' provenance hint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ux(init): batch toggle input + reorder confirm to include targets - Parse range/CSV toggle inputs ('1-3', '1,3,5', '1,3-5,7', 'all', 'none') in addition to single numbers. Empty input (Enter) confirms. - Fix misleading prompt header (no longer claims 'space to toggle'). - Move target picker BEFORE the 'About to create' confirmation panel and embed selected targets in that panel so the user reviews the full config (including targets) in one place. - Add TestToggleInputParser unit tests for the new helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ux(init): restore 'select the tools your team uses' educational tip Split the picker tip into two lines: [i] Tip: select the tools your team uses. You can change this later with 'apm targets set <target,...>' or edit apm.yml directly. [i] Type a number to toggle, ranges like '1-3' or '1,3,5' for multiple, 'all' / 'none' to flip every entry, or press Enter to confirm. The educational framing was lost in the previous refactor; mechanics alone left first-time users without context for the choice. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ux(compile): align provenance line wording with install Compile now emits the canonical `[i] Targets: <list> (source: <src>)` line BEFORE the existing "Compiling for ..." output, matching the format `apm install` already uses (see core/target_detection.py format_provenance, double-space before `(source:` is intentional). Also fixes a parity gap where compile only honored the singular `target:` key in apm.yml; it now also reads the canonical plural `targets:` via parse_targets_field, the same parser install uses. Without this, `targets: [copilot, claude]` silently fell through to auto-detect on `apm compile`. Provenance line uses user-facing target names (`copilot`, not the expanded compiler families `agents, vscode`) so the schema users wrote matches the schema we report. Closes #1154 (compile parity follow-up) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Daniel Meppiel <copilot-rework@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b5ec548 commit 9f7f13b

113 files changed

Lines changed: 2897 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- **Explicit, auditable target resolution.** `apm install` and `apm compile` now resolve harness targets in a strict priority chain (`--target` flag > `apm.yml` `targets:` > auto-detect from filesystem signals) and print a one-line `[i] Targets: ... (source: ...)` provenance summary so the chosen path is never silently inferred. Empty repositories with no signal now exit 2 with a teaching message instead of silently defaulting to `copilot`. Adds `apm targets` discovery command and `apm compile --all` flag (deprecates `--target all`). (#1165, closes #1154, closes #1122, closes #1130, closes #518, closes #888, closes #891, closes #650, closes #1056)
13+
- **`apm init` target-selection prompt.** Interactive init now presents a numbered-toggle checklist pre-seeded from filesystem signals (or the existing `target:` on re-init) so users land in Tier 2 (`apm.yml target:`) by default. New `--target` flag for scripted use. (#1165)
14+
1015
### Fixed
1116

1217
- **`apm audit --ci` no longer silently skips when no org policy is resolved** -- `no_git_remote` / `absent` / `empty` auto-discovery outcomes now emit a `[!]` warning to stderr by default and honour `policy.fetch_failure_default: block` to fail closed (exit 1); JSON/SARIF on stdout stays clean. (#1159)

docs/src/content/docs/getting-started/first-package.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,18 @@ team-skills/
206206
+-- apm.lock.yaml
207207
```
208208

209-
`apm install` auto-detects which runtimes you have. The example above shows
210-
`.github/` because Copilot is the default fallback. If `.claude/`, `.cursor/`,
211-
`.opencode/`, or `.gemini/` exists in the project, they get populated too. To target
212-
explicitly, see the [Compilation guide](/apm/guides/compilation/).
209+
`apm install` resolves which harness directories to populate using a strict
210+
priority chain: `--target` flag > `apm.yml` `targets:` > auto-detect from
211+
filesystem signals (`.claude/`, `CLAUDE.md`, `.cursor/`, `.github/copilot-instructions.md`,
212+
`.codex/`, `.gemini/`, `GEMINI.md`, `.opencode/`, `.windsurf/`). The example layout
213+
above shows `.github/` because `.github/copilot-instructions.md` exists in the
214+
project; if you also have `.claude/`, `.cursor/`, `.opencode/`, or `.gemini/`, those
215+
directories get populated too. With no signal at all, `apm install` exits with
216+
code 2 and a teaching message instead of silently picking a target -- declare an
217+
intent explicitly via `--target copilot` (or another harness), or by adding
218+
`targets: [copilot]` to `apm.yml`. Run `apm targets` to inspect what APM detects
219+
in the current directory. To target explicitly, see the
220+
[Compilation guide](/apm/guides/compilation/).
213221

214222
> **What about `apm compile`?** Compile is a different concern: it
215223
> generates merged `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` files for tools

docs/src/content/docs/guides/dependencies.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ apm install --dry-run
239239

240240
`apm install` also deploys the project's own `.apm/` content (instructions, prompts, agents, skills, hooks, commands) to target directories alongside dependency content. Local content takes priority over dependencies on collision. This works even with zero dependencies -- just `apm.yml` and a `.apm/` directory is enough. See the [CLI reference](../../reference/cli-commands/#apm-install---install-dependencies-and-deploy-local-content) for details and exceptions.
241241

242+
:::caution[Migrating from auto-copilot fallback]
243+
Older APM versions silently deployed to `.github/` (Copilot) when no harness signal was present in the project. Starting with the target-resolution overhaul, that silent fallback is gone: an empty repo with no `targets:` in `apm.yml` and no harness marker (`.claude/`, `.cursor/`, `.github/copilot-instructions.md`, `.codex/`, `.gemini/`, `.opencode/`, `.windsurf/`, `CLAUDE.md`, `GEMINI.md`, `.cursorrules`) now exits 2 with a teaching message.
244+
245+
Pick one of the explicit fixes:
246+
247+
- `apm install --target copilot` -- one-shot deploy to `.github/`.
248+
- Add `targets: [copilot]` (or any other harness) to `apm.yml` -- persists across runs.
249+
- Create the harness marker (e.g. `touch .github/copilot-instructions.md`) -- auto-detect picks it up.
250+
251+
Run `apm targets` first to see what APM detects (or doesn't) in the current directory.
252+
:::
253+
242254
### 3. Verify Installation
243255

244256
```bash

docs/src/content/docs/reference/cli-commands.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ apm install [PACKAGES...] [OPTIONS]
9191
- `--runtime TEXT` - Target specific runtime only (copilot, codex, vscode, cursor, opencode, gemini, claude,windsurf)
9292
- `--exclude TEXT` - Exclude specific runtime from installation
9393
- `--only [apm|mcp]` - Install only specific dependency type
94-
- `--target [copilot|claude|cursor|codex|opencode|gemini|windsurf|agent-skills|copilot-cowork|all]` - Force deployment to specific target(s). Accepts comma-separated values for multiple targets (e.g., `-t claude,copilot`). Overrides auto-detection. `agent-skills` deploys to `.agents/skills/` (cross-client). `all` = copilot+claude+cursor+opencode+codex+gemini+windsurf (excludes agent-skills); combine with `agent-skills` for both.
94+
- `--target, -t [copilot|claude|cursor|codex|opencode|gemini|windsurf|agent-skills|copilot-cowork|all]` - Force deployment to specific target(s). Highest-priority entry in the resolution chain (`--target` > `apm.yml` `targets:` > auto-detect). Accepts comma-separated values for multiple targets (e.g., `--target claude,cursor`). `agent-skills` deploys to `.agents/skills/` (cross-client). `all` = copilot+claude+cursor+opencode+codex+gemini+windsurf (excludes agent-skills); combine with `agent-skills` for both. With no flag, no `targets:` in `apm.yml`, and no harness signal in the project (e.g. `.claude/`, `.cursor/`, `.github/copilot-instructions.md`), `apm install` exits 2 with a teaching message instead of silently defaulting to `copilot`. Run `apm targets` to see what APM detects in the current directory.
9595
- `windsurf` - Windsurf/Cascade (`.windsurf/rules/`, `.windsurf/skills/`, `.windsurf/workflows/`, `.windsurf/hooks.json`)
9696
- `copilot-cowork` - Microsoft 365 Copilot Cowork skills (user scope only, requires `copilot-cowork` experimental flag)
9797
- `vscode`, `agents` - Deprecated aliases for `copilot` (`.github/`). Still accepted by the parser; prefer `copilot` for GitHub Copilot deployment, or `agent-skills` for cross-client `.agents/skills/` deployment. Removal in v1.0.
@@ -349,6 +349,60 @@ Skills are copied directly to target directories:
349349

350350
This makes all package primitives available in VSCode, Cursor, OpenCode, Claude Code, and compatible editors for immediate use with your coding agents.
351351

352+
### `apm targets` - Show resolved deployment targets
353+
354+
Inspect which harness targets `apm install` and `apm compile` will deploy to from the current directory, and why. This is the discovery surface for the resolution chain (`--target` flag > `apm.yml` `targets:` > auto-detect from filesystem signals).
355+
356+
`apm targets` works with or without `apm.yml`: it reads filesystem signals (`.claude/`, `CLAUDE.md`, `.cursor/`, `.cursorrules`, `.github/copilot-instructions.md`, `.codex/`, `.gemini/`, `GEMINI.md`, `.opencode/`, `.windsurf/`) and reports each canonical target as `active` or inactive. Implemented as a Click *group* so future sub-commands can attach without breaking the bare `apm targets` invocation.
357+
358+
```bash
359+
apm targets [OPTIONS]
360+
```
361+
362+
**Options:**
363+
- `--all` - Also include the `agent-skills` meta-target (only meaningful with `--json`; the default table already lists every canonical harness target).
364+
- `--json` - Emit machine-readable JSON instead of the table.
365+
366+
**Sample table output:**
367+
```
368+
TARGET STATUS SOURCE DEPLOY DIR
369+
------------ ---------- ---------------------------------------- ----------
370+
claude active CLAUDE.md .claude/
371+
copilot inactive needs .github/copilot-instructions.md .github/
372+
cursor active .cursor/ .cursor/
373+
codex inactive needs .codex/ .codex/
374+
gemini inactive needs GEMINI.md .gemini/
375+
opencode inactive needs .opencode/ .opencode/
376+
windsurf inactive needs .windsurf/ .windsurf/
377+
```
378+
379+
The `STATUS` column is `active` when APM detects a signal for that harness, otherwise `inactive`. The `SOURCE` column shows the detected signal path for active rows, and `needs <path>` for inactive rows so the recovery path is self-documenting. The `agent-skills` meta-target is intentionally excluded from the table and only surfaces in `--all --json`.
380+
381+
**Sample `--json` output:**
382+
```json
383+
[
384+
{"target": "claude", "status": "active", "source": "CLAUDE.md", "deploy_dir": ".claude/", "needs": null},
385+
{"target": "copilot", "status": "inactive", "source": null, "deploy_dir": ".github/", "needs": ".github/copilot-instructions.md"},
386+
{"target": "cursor", "status": "active", "source": ".cursor/", "deploy_dir": ".cursor/", "needs": null}
387+
]
388+
```
389+
390+
Output is a JSON array of per-target objects ordered by canonical target order (claude, copilot, cursor, codex, gemini, opencode, windsurf), not alphabetical. Each object exposes `target`, `status`, `source`, `deploy_dir`, and `needs`. With `--all --json`, an additional row `{"target": "agent-skills", ..., "meta_target": true}` is appended.
391+
392+
**Use cases:**
393+
- **Discovery** - "What will `apm install` deploy to in this directory?" before running it.
394+
- **Scripting** - parse `--json` in CI to assert the expected target set or detect unexpected drift.
395+
- **Debugging** - diagnose why `apm install` chose a specific target (e.g. an upstream package shipped a stray `CLAUDE.md` that APM picked up as a Claude Code signal).
396+
397+
If APM detects a target you don't intend (a documentation `CLAUDE.md` or `GEMINI.md` is the most common false positive), pin your targets explicitly in `apm.yml`:
398+
399+
```yaml
400+
targets:
401+
- copilot
402+
```
403+
404+
See [`apm install`](#apm-install---install-dependencies-and-deploy-local-content) and [`apm compile`](#apm-compile---compile-apm-context-into-distributed-agentsmd-files) for how the resolved targets feed into deployment.
405+
352406
### `apm uninstall` - Remove APM packages
353407

354408
Remove installed APM packages and their integrated files.
@@ -1694,7 +1748,8 @@ apm compile [OPTIONS]
16941748

16951749
**Options:**
16961750
- `-o, --output TEXT` - Output file path (for single-file mode)
1697-
- `-t, --target [copilot|claude|cursor|codex|opencode|gemini|windsurf|agent-skills|all]` - Target agent format. Accepts comma-separated values for multiple targets (e.g., `-t claude,copilot`). `vscode` and `agents` are accepted as deprecated aliases for `copilot` (removal in v1.0). `agent-skills` is a no-op for compile (skills-only target). Auto-detects if not specified.
1751+
- `-t, --target [copilot|claude|cursor|codex|opencode|gemini|windsurf|agent-skills|all]` - Target agent format. Highest-priority entry in the resolution chain (`--target` > `apm.yml` `targets:` > auto-detect). Accepts comma-separated values for multiple targets (e.g., `-t claude,copilot`). `vscode` and `agents` are accepted as deprecated aliases for `copilot` (removal in v1.0). `agent-skills` is a no-op for compile (skills-only target). Auto-detects if not specified. Run [`apm targets`](#apm-targets---show-resolved-deployment-targets) to preview what auto-detect resolves to.
1752+
- `--all` - Compile for all canonical targets. Equivalent to `--target all` but does not need to be combined with target-name parsing. Mutually exclusive with `--target`. Prefer `--all` over `--target all`; `--target all` is deprecated and emits a one-line warning.
16981753
- `--chatmode TEXT` - Chatmode to prepend to the AGENTS.md file
16991754
- `--dry-run` - Preview compilation without writing files (shows placement decisions)
17001755
- `--no-links` - Skip markdown link resolution
@@ -1774,7 +1829,7 @@ apm compile --watch --dry-run
17741829
apm compile --target vscode # AGENTS.md + .github/ (incl. copilot-instructions.md)
17751830
apm compile --target claude # CLAUDE.md + .claude/ only
17761831
apm compile --target opencode # AGENTS.md + .opencode/ only
1777-
apm compile --target all # All formats (default)
1832+
apm compile --all # All canonical targets (preferred over --target all)
17781833
17791834
# Multiple targets (comma-separated)
17801835
apm compile -t claude,copilot # CLAUDE.md + AGENTS.md + .github/copilot-instructions.md

packages/apm-guide/.apm/skills/apm-usage/commands.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
| Command | Purpose | Key flags |
1212
|---------|---------|-----------|
13-
| `apm install [PKGS...]` | Install APM and MCP dependencies (supports APM packages, Claude skills (SKILL.md), and plugin collections (plugin.json)) | `--update` refresh refs, `--force` overwrite, `--dry-run`, `--verbose`, `--only [apm\|mcp]`, `--target` (comma-separated; use `copilot-cowork` with `--global` after `apm experimental enable copilot-cowork`), `--dev`, `-g` global, `--trust-transitive-mcp`, `--parallel-downloads N`, `--allow-insecure`, `--allow-insecure-host HOSTNAME`, `--skill NAME` install named skill(s) from SKILL_BUNDLE (repeatable; persisted in apm.yml; `'*'` resets to all), `--legacy-skill-paths` restore per-client skill dirs, `--mcp NAME` add MCP entry, `--transport`, `--url`, `--env KEY=VAL`, `--header KEY=VAL`, `--mcp-version`, `--registry URL` custom MCP registry |
13+
| `apm install [PKGS...]` | Install APM and MCP dependencies (supports APM packages, Claude skills (SKILL.md), and plugin collections (plugin.json)) | `--update` refresh refs, `--force` overwrite, `--dry-run`, `--verbose`, `--only [apm\|mcp]`, `--target` (comma-separated, e.g. `--target claude,cursor`; highest-priority entry in the resolution chain `--target` > apm.yml `targets:` > auto-detect; `--target all` deprecated, see `apm compile --all`; use `copilot-cowork` with `--global` after `apm experimental enable copilot-cowork`), `--dev`, `-g` global, `--trust-transitive-mcp`, `--parallel-downloads N`, `--allow-insecure`, `--allow-insecure-host HOSTNAME`, `--skill NAME` install named skill(s) from SKILL_BUNDLE (repeatable; persisted in apm.yml; `'*'` resets to all), `--legacy-skill-paths` restore per-client skill dirs, `--mcp NAME` add MCP entry, `--transport`, `--url`, `--env KEY=VAL`, `--header KEY=VAL`, `--mcp-version`, `--registry URL` custom MCP registry |
14+
| `apm targets` | Show resolved deployment targets for the current project (Click group; reads filesystem signals; works with or without `apm.yml`) | `--all` also include the `agent-skills` meta-target (only meaningful with `--json`), `--json` machine-readable output. No provenance line is printed (the table is the provenance). |
1415
| `apm uninstall PKGS...` | Remove packages | `--dry-run`, `-g` global |
1516
| `apm prune` | Remove orphaned packages | `--dry-run` |
1617
| `apm deps list` | List installed packages | `-g` global, `--all` both scopes, `--insecure` |
@@ -25,11 +26,31 @@
2526

2627
`apm install` validates subdirectory packages (`owner/repo/path#ref`) before writing to `apm.yml` using the same credential chain as the actual install. See [Authentication > Install validation chain](../authentication/) for the full probe sequence and troubleshooting.
2728

29+
### Target resolution chain
30+
31+
`apm install` resolves harness targets in strict priority order:
32+
33+
1. `--target` flag (highest; CSV form: `--target claude,cursor`).
34+
2. `apm.yml` `targets:` list (or singular `target:` sugar).
35+
3. Auto-detect from filesystem signals (`.claude/` or `CLAUDE.md` -> claude, `.cursor/` -> cursor, `.github/copilot-instructions.md` -> copilot, `.codex/` -> codex, `.gemini/` or `GEMINI.md` -> gemini, `.opencode/` -> opencode, `.windsurf/` -> windsurf).
36+
37+
`apm install` prints a one-line provenance summary before any mutation:
38+
39+
```
40+
[i] Targets: claude, copilot (source: auto-detect from CLAUDE.md, .github/copilot-instructions.md)
41+
```
42+
43+
Suppress with `--quiet`. Add `--verbose` to also print a `[>] Scanned: ...` line listing every signal probed.
44+
45+
If no `--target`, no `targets:` in `apm.yml`, and no harness signal is present, `apm install` exits 2 with a teaching message instead of silently defaulting to copilot. Run `apm targets` to inspect what APM detects in the current directory; use it for discovery, scripting (`--json`), and debugging unexpected detection.
46+
47+
`apm compile` continues to use legacy auto-detection with a `vscode`/`minimal` fallback for unsignalled projects -- bringing it onto the strict resolution chain is tracked as a follow-up.
48+
2849
## Compilation
2950

3051
| Command | Purpose | Key flags |
3152
|---------|---------|-----------|
32-
| `apm compile` | Compile agent context | `-o` output, `-t` target (comma-separated), `--chatmode`, `--dry-run`, `--no-links`, `--watch`, `--validate`, `--single-agents`, `-v` verbose, `--local-only`, `--clean`, `--with-constitution/--no-constitution` |
53+
| `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` |
3354

3455
## Scripts
3556

0 commit comments

Comments
 (0)