Skip to content

Commit bbb8b8d

Browse files
fix(install): honor saved targets across MCP and LSP (#2414)
* fix(install): share effective target across integrations Honor CLI, manifest, and saved target precedence once across package, MCP, and LSP phases. Fail required unresolved or unwritable service integrations instead of skipping with success. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(install): fold target review follow-ups Preserve diagnostics on required failures, clarify target help, type the canonical decision, and cache immutable projections. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold second review panel feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Repair target preflight test contracts Extract no-work handling to preserve the pipeline module budget and make phase-focused tests select a target explicitly. apm-spec-waiver: Restores existing effective-target precedence without extending the OpenAPM contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Extract MCP and LSP reconciliation Keep install.py within the enforced source budget while preserving one shared target decision and command-owned failure rendering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold final target review findings Align target usage exits, update diagnostics, architecture guards, agent guidance, and self-audit hashes after the final convergence pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix cowork target phase fixtures Model the newly typed target_decision field explicitly so cowork gating tests exercise the unresolved-decision path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Resolve target rebase semantics Preserve the certified effective-target owner, alias exclusions, provenance typing, and unified guide while retaining the latest main behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix MCP helper compatibility after rebase Keep fail-on-write strictness opt-in for direct helper callers while production integration continues to pass it explicitly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e849824 commit bbb8b8d

49 files changed

Lines changed: 2089 additions & 248 deletions

Some content is hidden

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

.apm/instructions/architecture.instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ semicolon-delimited, and specific to the file(s) that own the fact.
3737
| Decision / fact | Canonical owner | Owner path selectors |
3838
|---|---|---|
3939
| Accepted target vocabulary | core/target_catalog.py | `src/apm_cli/core/target_catalog.py` |
40+
| Effective install target selection | core/target_detection.py (EffectiveTargetDecision); MCP compatibility adapter consumes the manifest parser | `src/apm_cli/core/target_detection.py`; `src/apm_cli/integration/mcp_integrator_install.py` |
4041
| Effective package target authorization | install/target_filter.py (resolve_effective_package_targets) | `src/apm_cli/install/target_filter.py` |
4142
| MCP target-selection precedence | integration/mcp_integrator_install.py (_resolve_target_runtimes) | `src/apm_cli/integration/mcp_integrator_install.py` |
4243
| Behavioral test taxonomy classification | module-level pytestmark (taxonomy inventory verifies) | `tests/quality/taxonomy_inventory_plugin.py`; `tests/quality/test_test_taxonomy.py` |

.github/instructions/architecture.instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ semicolon-delimited, and specific to the file(s) that own the fact.
3737
| Decision / fact | Canonical owner | Owner path selectors |
3838
|---|---|---|
3939
| Accepted target vocabulary | core/target_catalog.py | `src/apm_cli/core/target_catalog.py` |
40+
| Effective install target selection | core/target_detection.py (EffectiveTargetDecision); MCP compatibility adapter consumes the manifest parser | `src/apm_cli/core/target_detection.py`; `src/apm_cli/integration/mcp_integrator_install.py` |
4041
| Effective package target authorization | install/target_filter.py (resolve_effective_package_targets) | `src/apm_cli/install/target_filter.py` |
4142
| MCP target-selection precedence | integration/mcp_integrator_install.py (_resolve_target_runtimes) | `src/apm_cli/integration/mcp_integrator_install.py` |
4243
| Behavioral test taxonomy classification | module-level pytestmark (taxonomy inventory verifies) | `tests/quality/taxonomy_inventory_plugin.py`; `tests/quality/test_test_taxonomy.py` |

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
`docs/src/content/docs/specs/openapm-v0.1.md`.
1717
(by @sergio-sisternes-epam, #2365)
1818
- Package-declared targets now restrict dependency primitive deployment without expanding project or consumer authorization, preventing Claude-only hooks from leaking into Cursor and repairing stale owned entries on update; the contract is cited in `docs/src/content/docs/specs/openapm-v0.1.md`. By @sergio-sisternes-epam (#2362)
19+
- Saved targets from `apm config set target` now drive package, MCP, and LSP
20+
phases in `apm install` and `apm update`; unresolved or failed required
21+
service writes exit non-zero with a next step instead of silently succeeding.
22+
(reported by @ryodocx, #2414, closes #2345)
23+
1924
- Copilot hook packages with JavaScript scripts no longer fail with "hooks: hooks must be an object"; APM keeps generated `package.json` and nested JSON bundle assets out of project `.github/hooks/scripts/` and user `~/.copilot/hooks/scripts/`, where Copilot's recursive hook-loader scan would reject them as descriptors; use `.mjs` for ES module scripts targeting Copilot or VS Code. (#2322)
2025
- `apm uninstall` now accepts the portable `_local/<name>` key printed by
2126
`apm deps list` and rejects missing or ambiguous batches with a nonzero status
@@ -103,6 +108,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103108
`apm install --target intellij`: APM writes the plugin-read config path,
104109
migrates only APM-owned entries from the obsolete data path, and preserves
105110
user-authored entries in both files. (closes #2344) (by @xalvarez, #2410)
111+
- Saved targets from `apm config set target` now drive package, MCP, and LSP
112+
phases in `apm install` and `apm update`; unresolved or failed required
113+
service writes exit non-zero instead of skipping with success.
114+
(by @ryodocx, closes #2345)
115+
106116
- `apm install --dry-run` no longer lists the project's own `includes: auto`
107117
self-managed files under "Files that would be removed"; the orphan preview
108118
now excludes the synthesized lockfile self-entry, matching the real install

apm.lock.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,7 +2783,7 @@ deployments:
27832783
owners:
27842784
- .
27852785
active_owner: .
2786-
content_hash: sha256:0cb91df5342afe57262b3a4b2e8376b66483d9951ebe33d712d56a791101cb1a
2786+
content_hash: sha256:c0acf69ef4de5c9de8373337f2164d3206d8d9c6534c6cdf4294f156d99e2762
27872787
- kind: project-relative
27882788
target: copilot
27892789
value: .github/instructions/changelog.instructions.md
@@ -3290,7 +3290,7 @@ local_deployed_file_hashes:
32903290
.github/agents/spec-tag-architect.agent.md: sha256:82907265c5e7cf1ac61ad96866fa7c5683b69c8f09b7a4c5f3cc241acc9568ca
32913291
.github/agents/supply-chain-security-expert.agent.md: sha256:8fb8cc426d6af17ba084a28b3f026c2b475b62e3ca63ed2f88b83bd823f877af
32923292
.github/agents/test-coverage-expert.agent.md: sha256:48c2172d1f18a394fa83ef9dc2be0b9b921a4e51e976498165250fed66369711
3293-
.github/instructions/architecture.instructions.md: sha256:0cb91df5342afe57262b3a4b2e8376b66483d9951ebe33d712d56a791101cb1a
3293+
.github/instructions/architecture.instructions.md: sha256:c0acf69ef4de5c9de8373337f2164d3206d8d9c6534c6cdf4294f156d99e2762
32943294
.github/instructions/changelog.instructions.md: sha256:1e51ec4c74e847967962bd279dc4c6e582c5d3578490b3c28d5f3acd3e05f73e
32953295
.github/instructions/cicd.instructions.md: sha256:08d87b7d635761cb41deb8fc71d5d83f54678de463db484afb16d2d4f8713ecb
32963296
.github/instructions/cli.instructions.md: sha256:8e39e8d5047ce88575cb02f87c2bcede584dfef258bd86f7466c7badf136541a

docs/src/content/docs/consumer/install-lsp-servers.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,11 @@ the absolute plugin path for legacy Claude Code plugin compatibility.
174174

175175
LSP integration writes configuration for supported runtimes and leaves
176176
the manifest schema runtime-neutral. Target selection follows the same
177-
runtime detection and `--target`/`targets:` mechanics as MCP installs.
177+
effective decision as package and MCP installation: `--target` >
178+
`apm.yml targets:` > `apm config set target ...` > auto-detect. If LSP
179+
work is declared but no effective target supports LSP, or a native config
180+
write fails, install exits non-zero with a next step instead of reporting
181+
success.
178182

179183
| Runtime | LSP support |
180184
|---|---|

docs/src/content/docs/consumer/install-mcp-servers.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,17 @@ MCP install resolves targets in this order:
135135
1. Explicit CLI selection: `--runtime` (legacy, one runtime) or `--target`
136136
(one or more targets).
137137
2. Canonical `targets:` / `target:` values declared in `apm.yml`.
138-
3. Machine discovery, only when the manifest does not restrict targets.
138+
3. The saved `apm config set target ...` default.
139+
4. Machine discovery, only when the manifest and saved config do not
140+
restrict targets.
139141

140142
`--exclude` narrows whichever set wins. Progress output names that
141143
post-exclusion selection; project, scope, and adapter gates can still skip a
142144
write.
143145

146+
The same effective decision drives package, MCP, and LSP phases; APM does
147+
not re-resolve each phase independently.
148+
144149
**Portability boundary:** A committed target list makes lockfile MCP ownership
145150
deterministic across machines with different installed harnesses. If `targets:`
146151
is omitted (or a legacy `all` declaration is folded to omission), machine
@@ -170,11 +175,15 @@ This single rule replaces two older ones that used to coexist:
170175

171176
A malformed `targets:` field (both `target:` and `targets:` set,
172177
`targets: []`, or an unknown target name) fails closed before machine discovery:
173-
no MCP files are written and an `[x]` error names the field to fix. A
174-
greenfield project with no `targets:`, no `--target` flag, AND no detected
178+
no MCP files are written and an `[x]` error names the field to fix. A greenfield
179+
project with no `targets:`, no `--target` flag, no saved target, AND no detected
175180
signals (`.github/copilot-instructions.md`, `.cursor/`, etc.) also fails closed
176181
with the same `[x]` voice -- consistent with how `apm install` treats the same
177-
input. Pin a target with `--target` or declare one in `apm.yml`. (#1335)
182+
input. The command exits non-zero before adding a direct `--mcp` entry to
183+
`apm.yml` or deploying package files. Pin a target with `--target`, declare one
184+
in `apm.yml`, or save one with `apm config set target <value>`. A native MCP
185+
config write failure also exits non-zero and names the target path or
186+
permissions to check. (#1335)
178187

179188
`apm install -g --mcp NAME` routes the write to each runtime's
180189
user-scope MCP config (for example, Copilot CLI to

docs/src/content/docs/reference/cli/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Supported u
6565
| Key | Type | Default | Description |
6666
| --- | --- | --- | --- |
6767
| `auto-integrate` | boolean | `true` | Auto-discover `.prompt.md` files under `.github/prompts/` and `.apm/prompts/` and merge them into compiled `AGENTS.md` output. |
68-
| `target` | target token | unset | Default target for installs when `--target` and `apm.yml target(s)` are absent. Uses the same parser as `apm install --target` (single or comma-separated). |
68+
| `target` | target token | unset | Default target for package, MCP, and LSP phases of `apm install` and `apm update` when `--target` and `apm.yml target(s)` are absent. Uses the same parser as `apm install --target` (single or comma-separated). |
6969
| `self-update.channel` | enum | `stable` | Default release channel for `apm self-update`: `stable` selects the latest stable release; `prerelease` selects the newest non-draft prerelease. Both pass the selected release to the installer as one normalized `VERSION`. `APM_SELF_UPDATE_CHANNEL` overrides config. |
7070
| `self-update.install-dir` | path | installer default | Default target directory passed to the self-update installer as `APM_INSTALL_DIR`. `APM_INSTALL_DIR` overrides config. |
7171
| `temp-dir` | path | system temp | Directory used for clone and download operations. Useful when the OS temp directory is locked down (for example, corporate Windows endpoints rejecting `%TEMP%` with `[WinError 5]`). |

docs/src/content/docs/reference/cli/install.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ With no arguments it installs everything from `apm.yml`. With one or more `PACKA
4848
|---|---|---|
4949
| `--target`, `-t VALUE` | auto-detect | Force deployment targets. Comma-separated for multiple (`-t claude,cursor`). Values: `copilot`, `claude`, `cursor`, `opencode`, `codex`, `gemini`, `antigravity`, `windsurf`, `kiro`, `intellij`, `vscode`, `agent-skills`, `all`; experimental `copilot-cowork` and `copilot-app` are also accepted when enabled. IntelliJ-specific integration is MCP-only and writes JetBrains Copilot's user-scope MCP config; package file primitives use the Copilot profile. `all` excludes `agent-skills`, `antigravity`, and `intellij`; combine them explicitly to add them, for example `all,intellij`. Explicit MCP target lists are exact: `intellij,claude` writes only those two client configs. See the precedence note below. With nothing to detect, install exits `2` with a teaching message. |
5050
| `--runtime VALUE` | unset | Legacy alias for `--target` (single value only). Still accepted; prefer `--target`. |
51-
| `--exclude VALUE` | unset | Skip one runtime from the resolved MCP target set (explicit selection, manifest, or auto-detection). |
51+
| `--exclude VALUE` | unset | Skip one runtime from the resolved MCP/LSP target set (explicit selection, manifest, saved config, or auto-detection). |
5252
| `--only apm\|mcp` | both | Install only APM packages or only MCP servers. |
5353
| `-g`, `--global` | off | Install to user scope (`~/.apm/`) instead of the current project. MCP servers deploy only to global-capable runtimes, such as Copilot CLI, Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, Kiro, Windsurf, and JetBrains Copilot. |
5454
| `--legacy-skill-paths` | off | Deploy skills to per-client paths (`.cursor/skills/`, `.github/skills/`, ...) instead of the converged `.agents/skills/`. Env: `APM_LEGACY_SKILL_PATHS=1`. |
5555

5656
File primitives resolve targets in this order: `--target`, manifest
5757
`targets:`, `apm config set target ...`, then auto-detection. MCP resolves
58-
`--runtime` / `--target`, then manifest targets, then machine discovery only
59-
when the manifest is unrestricted.
58+
`--runtime` / `--target`, then manifest targets, saved config, then
59+
auto-detection only when `apm.yml` declares no targets.
6060

6161
### Policy and trust
6262

@@ -112,6 +112,8 @@ in `apm.yml`, then run `apm install` again.
112112

113113
- **Auto-bootstrap.** `apm install <pkg>` with no `apm.yml` creates a minimal one. Bare `apm install` with no `apm.yml` exits with a hint to run `apm init` or `apm install <org/repo>`.
114114
- **Target persistence on bootstrap.** When `--target` maps to recognized manifest targets, those target(s) are persisted to the new manifest's `targets:` field so a later bare `apm update` redeploys to the same targets without re-specifying `--target`.
115+
- **One effective target.** Package primitives, MCP servers, and LSP servers consume one target decision per invocation: `--target` > `apm.yml targets:` > `apm config set target ...` > auto-detect. A saved target therefore applies to `apm install`, `apm install --mcp`, and later `apm update` runs without another flag.
116+
- **Required service writes fail loudly.** If MCP or LSP work is declared but no target can be resolved, install exits non-zero before changing the manifest, package deployment, or native service config. A native MCP/LSP config write failure also exits non-zero with the failed target and a permissions/path next step. A successful direct `--mcp` add never reports `Install interrupted`.
115117
- **Diff-aware.** Packages whose ref or version changed in `apm.yml` are re-downloaded automatically. MCP servers with matching config are skipped (`already configured`); changed config is re-applied (`updated`).
116118
- **MCP-only lock state.** A normal project install creates or updates `apm.lock.yaml` when `apm.yml` declares only MCP dependencies, records the resolved MCP configs and targets, and migrates a legacy `apm.lock` first. Repeating the same install leaves the lockfile and target configs byte-identical. If initial lock creation fails, install exits nonzero and warns with writable-directory and rerun guidance.
117119
- **Lockfile replay and Git ref freshness.** Plain and `--frozen` installs may trust `apm.lock.yaml` and the local Git cache, reusing the locked commit for unchanged Git dependencies across the full resolved graph. In contrast, `apm install --update`, `apm install --refresh`, [`apm update`](../update/) with or without `--force`, [`apm lock --update`](../lock/), and [`apm outdated`](../outdated/) establish mutable Git refs from upstream instead of accepting stale refs from a local bare Git cache. APM picks up upstream changes to a transitive package's `apm.yml` only when you regenerate the graph -- run `apm update` or `apm lock --update`. See the [lockfile specification](../../lockfile-spec/) for the replay contract.
@@ -213,7 +215,7 @@ apm install owner/skill-bundle --skill '*' # reset to all skills
213215
| Code | Meaning |
214216
|---|---|
215217
| `0` | Success. All requested dependencies and local content deployed. |
216-
| `1` | Install failure: security scan blocked a critical finding, auth error, manifest write error, dependency resolution error, `--frozen` with a missing lockfile or a direct dependency absent from `apm.lock.yaml`, any reported install error (the diagnostic summary closes with `Installation failed with N error(s)`), or unhandled exception. `--force` does **not** suppress general install errors. The diagnostic summary names the cause. |
218+
| `1` | Install failure: security scan blocked a critical finding, auth error, manifest or required MCP/LSP config write error, dependency resolution error, `--frozen` with a missing lockfile or a direct dependency absent from `apm.lock.yaml`, any reported install error (the diagnostic summary closes with `Installation failed with N error(s)`), or unhandled exception. `--force` does **not** suppress general install errors. The diagnostic summary names the cause. |
217219
| `2` | Usage error: no deployment target detectable (no `--target`, no `target(s):` in `apm.yml`, no default target configured via `apm config set target <value>`, and no harness signal in the project), `--ssh` and `--https` both passed, `--frozen` and `--update` both passed, `--root` combined with `--global`, or a Click flag conflict. |
218220

219221
## Notes

docs/src/content/docs/reference/cli/update.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ For a read-only install that pins to whatever is already in `apm.lock.yaml` -- t
4545
| `--global`, `-g` | off | Refresh user-scope dependencies under `~/.apm/` instead of the current project (mirrors `apm install -g`). |
4646
| `--force` | off | Overwrite locally-authored files and deploy despite critical security findings. It does not change ref freshness: update still requires current upstream refs. Use only after independent verification. |
4747
| `--parallel-downloads N` | `4` | Max concurrent package downloads. `0` disables parallelism. |
48-
| `--target TARGET`, `-t TARGET` | auto-detect | Agent harness(es) to update for. Accepts a single value (`claude`, `copilot`, `cursor`, `windsurf`, `kiro`, `codex`, `opencode`, `gemini`) or comma-separated list (`--target claude,cursor`). Overrides `apm.yml targets:` and auto-detection. |
48+
| `--target TARGET`, `-t TARGET` | resolution chain | Agent harness(es) to update for. Accepts the same target values and comma-separated lists as [`apm install --target`](../install/#target-selection). Resolution is `--target` > `apm.yml targets:` > `apm config set target ...` > auto-detect. |
4949

5050
## Examples
5151

@@ -102,6 +102,8 @@ apm update
102102
- **No partial consent.** A single prompt covers both revision-pin manifest rewrites and the normal update plan; declining leaves everything unchanged.
103103
- **`--dry-run` skips the prompt.** It computes and prints the plan, including revision-pin SHA/tag rewrites, but never writes and never asks.
104104
- **Target contraction is reconciled.** A successful update removes unchanged dependencies' deployed files, lockfile ownership, and merge-hook config/sidecar entries for targets no longer declared in `apm.yml`, even when no dependency ref changes -- same contract as [`apm install`'s target-contraction note](../install/#notes); see [Hooks and commands](../../../producer/author-primitives/hooks-and-commands/#hooks) for what the merge-hook config/sidecar files are.
105+
- **One target decision covers services.** The effective target used for package deployment is reused for MCP and LSP reconciliation. Saved config targets are not re-detected or dropped after package updates. Required native config failures exit non-zero.
106+
- **No-op and service-only repair.** An accepted update with no dependency ref changes still reconciles missing MCP/LSP config. A manifest with only MCP/LSP dependencies also uses `apm update` as a configuration repair pass; `--dry-run` previews this without writing.
105107
- **Empty caches are restored.** If the lockfile expects dependencies but `apm_modules/` has no materialized packages, an otherwise unchanged update restores the cache from the same refs and reports `Restored dependency cache without changing refs.` No confirmation is required because dependency refs do not move.
106108

107109
## Back-compat: `apm update` used to be the self-updater

0 commit comments

Comments
 (0)