Is your feature request related to a problem? Please describe.
There is no way to make apm install / apm compile fail when targets are not declared explicitly. When apm.yml has no targets: field and no --target flag is passed, both commands fall back to autodetecting harness targets by scanning the working tree for marker directories and files (.claude/CLAUDE.md, .codex, .gemini/GEMINI.md, .cursor, .github/copilot-instructions.md, .opencode, .windsurf, .kiro). The resolution chain is --target flag > apm.yml target:/targets: > autodetect (resolve_targets in src/apm_cli/core/target_detection.py, signal list in SIGNAL_WHITELIST).
In CI this is the wrong default. I want a build to fail loudly when a contributor forgets to commit targets:, not to quietly pick targets from whatever directories happen to exist on the runner. The silent fallback defeats reproducibility: a check that runs apm install && apm compile && git diff --exit-code to assert that committed compiled files match apm.yml is not stable, because the resolved set depends on uncommitted, machine-local state rather than on apm.yml alone.
The existing guard does not cover this case. Autodetect only fails when it finds zero markers — resolve_targets raises NoHarnessError (exit 2) on an empty repo. With exactly one marker present (a committed .claude/, or a throwaway local .gemini/ from an unfinished prototype) it silently resolves to that target and proceeds. So the absence of a declared targets: field is never reported as an error as long as the working tree contains at least one harness directory. That is precisely when I most want it reported.
(For context: a non-empty targets: in apm.yml already short-circuits resolution before autodetect, so declared targets are honoured deterministically. The strict precedence chain landed in v0.12.3 (#1165); plural targets: lists are honoured in install since v0.16.1 (#1560). The gap is only the undeclared case, where the fallback is silent rather than fatal.)
Describe the solution you'd like
A way to require explicit targets, so that a missing declaration is a hard error rather than a trigger for autodetection.
When enabled, apm install and apm compile resolve targets from --target and apm.yml targets:/target: only. If neither is present, they exit non-zero with a clear message ("No targets declared: set targets: in apm.yml or pass --target") and never scan the working tree. The working-tree contents — committed or not — must not affect which targets are resolved.
Any of these shapes would work; I have no strong preference:
- A flag, e.g.
apm install --require-targets / --no-autodetect.
- An environment variable, e.g.
APM_REQUIRE_TARGETS=1, convenient for CI.
- An
apm.yml setting, e.g. autodetect: false.
- Making "explicit only" the default and turning autodetect into an opt-in request, e.g.
--target auto.
The goal: given a fixed apm.yml, the resolved targets depend only on apm.yml and explicit flags, and an undeclared project fails fast instead of guessing.
Describe alternatives you've considered
- Parse
apm.yml in CI and fail if targets: is absent. This works, but it is a workaround: it reimplements outside the tool a guarantee the tool should provide, and it can drift from APM's own parsing rules (singular vs plural form, CSV sugar, validation).
- Always pass
--target in CI. This duplicates the target list outside apm.yml, so the two can drift, and it does not protect local developer runs.
- Persist resolved targets to
apm.yml on first install. apm init already writes the resolved set into targets: (_resolve_init_targets -> config["targets"] in src/apm_cli/commands/init.py); apm install does not. Writing them back on first use would also make later runs deterministic. It is a softer fix than failing, but it does not give CI the "fail when undeclared" behaviour I want, so I see it as complementary rather than a substitute.
Additional context
- Signal list and resolution:
SIGNAL_WHITELIST and resolve_targets in src/apm_cli/core/target_detection.py; install calls it from _resolve_targets_by_scope in src/apm_cli/install/phases/targets.py.
- The existing zero-markers guard:
NoHarnessError (exit 2) is raised only when autodetect finds nothing, not when targets: is simply undeclared.
compile resolves targets through a separate legacy path (detect_target via _resolve_effective_target in src/apm_cli/commands/compile/cli.py), which in the autodetect branch returns "all" on two or more detected folders rather than failing. A --require-targets mode should apply to both commands and resolve this divergence so they behave identically.
- The codebase already moves toward explicitness: the "No harness detected" error notes that APM previously defaulted to
copilot and that the default is "now explicit". Requiring declared targets is the next step on that path.
Is your feature request related to a problem? Please describe.
There is no way to make
apm install/apm compilefail when targets are not declared explicitly. Whenapm.ymlhas notargets:field and no--targetflag is passed, both commands fall back to autodetecting harness targets by scanning the working tree for marker directories and files (.claude/CLAUDE.md,.codex,.gemini/GEMINI.md,.cursor,.github/copilot-instructions.md,.opencode,.windsurf,.kiro). The resolution chain is--targetflag >apm.ymltarget:/targets:> autodetect (resolve_targetsinsrc/apm_cli/core/target_detection.py, signal list inSIGNAL_WHITELIST).In CI this is the wrong default. I want a build to fail loudly when a contributor forgets to commit
targets:, not to quietly pick targets from whatever directories happen to exist on the runner. The silent fallback defeats reproducibility: a check that runsapm install && apm compile && git diff --exit-codeto assert that committed compiled files matchapm.ymlis not stable, because the resolved set depends on uncommitted, machine-local state rather than onapm.ymlalone.The existing guard does not cover this case. Autodetect only fails when it finds zero markers —
resolve_targetsraisesNoHarnessError(exit 2) on an empty repo. With exactly one marker present (a committed.claude/, or a throwaway local.gemini/from an unfinished prototype) it silently resolves to that target and proceeds. So the absence of a declaredtargets:field is never reported as an error as long as the working tree contains at least one harness directory. That is precisely when I most want it reported.(For context: a non-empty
targets:inapm.ymlalready short-circuits resolution before autodetect, so declared targets are honoured deterministically. The strict precedence chain landed in v0.12.3 (#1165); pluraltargets:lists are honoured ininstallsince v0.16.1 (#1560). The gap is only the undeclared case, where the fallback is silent rather than fatal.)Describe the solution you'd like
A way to require explicit targets, so that a missing declaration is a hard error rather than a trigger for autodetection.
When enabled,
apm installandapm compileresolve targets from--targetandapm.ymltargets:/target:only. If neither is present, they exit non-zero with a clear message ("No targets declared: settargets:in apm.yml or pass--target") and never scan the working tree. The working-tree contents — committed or not — must not affect which targets are resolved.Any of these shapes would work; I have no strong preference:
apm install --require-targets/--no-autodetect.APM_REQUIRE_TARGETS=1, convenient for CI.apm.ymlsetting, e.g.autodetect: false.--target auto.The goal: given a fixed
apm.yml, the resolved targets depend only onapm.ymland explicit flags, and an undeclared project fails fast instead of guessing.Describe alternatives you've considered
apm.ymlin CI and fail iftargets:is absent. This works, but it is a workaround: it reimplements outside the tool a guarantee the tool should provide, and it can drift from APM's own parsing rules (singular vs plural form, CSV sugar, validation).--targetin CI. This duplicates the target list outsideapm.yml, so the two can drift, and it does not protect local developer runs.apm.ymlon first install.apm initalready writes the resolved set intotargets:(_resolve_init_targets->config["targets"]insrc/apm_cli/commands/init.py);apm installdoes not. Writing them back on first use would also make later runs deterministic. It is a softer fix than failing, but it does not give CI the "fail when undeclared" behaviour I want, so I see it as complementary rather than a substitute.Additional context
SIGNAL_WHITELISTandresolve_targetsinsrc/apm_cli/core/target_detection.py;installcalls it from_resolve_targets_by_scopeinsrc/apm_cli/install/phases/targets.py.NoHarnessError(exit 2) is raised only when autodetect finds nothing, not whentargets:is simply undeclared.compileresolves targets through a separate legacy path (detect_targetvia_resolve_effective_targetinsrc/apm_cli/commands/compile/cli.py), which in the autodetect branch returns"all"on two or more detected folders rather than failing. A--require-targetsmode should apply to both commands and resolve this divergence so they behave identically.copilotand that the default is "now explicit". Requiring declared targets is the next step on that path.