Skip to content
Merged
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
2 changes: 2 additions & 0 deletions scripts/test-dependency-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ name: dependency-test-project
version: 1.0.0
description: Test project for dependency integration testing
author: CI Test
target: copilot

dependencies:
apm:
Expand Down Expand Up @@ -149,6 +150,7 @@ name: multi-dependency-test
version: 1.0.0
description: Test project for multi-dependency scenario
author: CI Test
target: copilot

dependencies:
apm:
Expand Down
1 change: 1 addition & 0 deletions scripts/test-release-validation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ test_ghaw_compat() {
cat > apm.yml <<'APMYML'
name: ghaw-compat-test
version: 1.0.0
target: copilot
dependencies:
apm:
- microsoft/apm-sample-package
Expand Down
2 changes: 2 additions & 0 deletions scripts/windows/test-dependency-integration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ name: dependency-test-project
version: 1.0.0
description: Test project for dependency integration testing
author: CI Test
target: copilot

dependencies:
apm:
Expand Down Expand Up @@ -156,6 +157,7 @@ name: multi-dependency-test
version: 1.0.0
description: Test project for multi-dependency scenario
author: CI Test
target: copilot

dependencies:
apm:
Expand Down
18 changes: 13 additions & 5 deletions src/apm_cli/install/phases/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ def run(ctx: InstallContext) -> None:

On return ``ctx.targets`` and ``ctx.integrators`` are populated.
"""
from dataclasses import replace as _replace

from apm_cli.core.scope import InstallScope
from apm_cli.core.target_detection import (
Expand Down Expand Up @@ -282,7 +281,13 @@ def run(ctx: InstallContext) -> None:
raise SystemExit(1) from None
if ctx.logger:
ctx.logger.verbose_detail(f"Created {_profile.root_dir}/ ({_tname} target)")
_profile = _replace(_profile, resolved_deploy_root=_target_dir)
# NOTE: do NOT set resolved_deploy_root on static targets.
# That field is reserved for dynamic-root targets (cowork)
# and is treated as the final deploy destination by
# skill_integrator and base_integrator. Static targets must
# follow the standard primitive-mapping path so that
# ``deploy_root`` (e.g. .agents) and ``subdir`` (e.g. skills)
# are honored.
_v2_targets.append(_profile)

# Replace legacy targets with v2 targets for project-scope.
Expand Down Expand Up @@ -379,7 +384,6 @@ def run_targets_phase(ctx) -> None:
This is the three-guard collapse: every resolved target always materializes
its deploy directory (auto_create=True unconditionally post-resolution).
"""
from dataclasses import replace
from pathlib import Path

from apm_cli.core.target_detection import resolve_targets
Expand Down Expand Up @@ -420,8 +424,12 @@ def run_targets_phase(ctx) -> None:
if not target_dir.exists():
target_dir.mkdir(parents=True, exist_ok=True)

# Set resolved_deploy_root so downstream code knows the exact path
profile = replace(profile, resolved_deploy_root=target_dir)
# NOTE: do NOT set resolved_deploy_root on static targets.
# That field is reserved for dynamic-root targets (cowork) and is
# treated as the final deploy destination by downstream integrators.
# Static targets must follow the standard primitive-mapping path so
# that ``deploy_root`` (e.g. .agents) and ``subdir`` (e.g. skills)
# are honored.
profiles.append(profile)

ctx.targets = profiles
11 changes: 7 additions & 4 deletions tests/integration/test_apm_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,11 @@ def test_authentication_token_handling(self):
"Downloader should have a GitHub token for modules access"
)
assert downloader.github_token is not None, "GitHub token should be available"
assert downloader.github_token.startswith("github_pat_"), (
"Token should be a valid GitHub PAT"
# Accept any valid GitHub token prefix: github_pat_ (fine-grained PAT),
# ghp_ (classic PAT), gho_ (OAuth via gh CLI), ghs_/ghu_ (app tokens).
valid_prefixes = ("github_pat_", "ghp_", "gho_", "ghs_", "ghu_")
assert downloader.github_token.startswith(valid_prefixes), (
f"Token should be a valid GitHub token (one of {valid_prefixes})"
)

# Verify that environment variables are properly set for Git operations
Expand All @@ -542,8 +545,8 @@ def test_authentication_token_handling(self):
assert token_for_modules is not None, (
"Token manager should provide a token for modules access"
)
assert token_for_modules.startswith("github_pat_"), (
"Modules token should be a valid GitHub PAT"
assert token_for_modules.startswith(valid_prefixes), (
f"Modules token should be a valid GitHub token (one of {valid_prefixes})"
)

# This validates the authentication setup works with real tokens
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_cache_lockfile_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def project_with_apm(tmp_path: Path) -> Path:
"""\
name: parity-test
version: 0.1.0
target: copilot
dependencies:
apm:
- microsoft/apm-sample-package
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/test_deps_update_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def _write_apm_yml(target_dir, packages):
config = {
"name": "deps-update-test",
"version": "1.0.0",
"target": "copilot",
"dependencies": {"apm": packages, "mcp": []},
}
(target_dir / "apm.yml").write_text(
Expand Down Expand Up @@ -244,6 +245,7 @@ def _write_user_manifest(ref):
{
"name": "global-deps-update-test",
"version": "1.0.0",
"target": "copilot",
"dependencies": {
"apm": [{"git": SAMPLE_GIT_URL, "ref": ref}],
"mcp": [],
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_drift_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _make_apm_project(
*,
name: str = "drift-fixture",
version: str = "1.0.0",
target: str | None = None,
target: str | None = "copilot",
files: Mapping[str, bytes] | None = None,
) -> Path:
"""Create a minimal APM project rooted under ``tmp_path``.
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/test_drift_check_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
def _make_project(tmp_path: Path, name: str = "drift-e2e") -> Path:
project = tmp_path / name
project.mkdir()
(project / "apm.yml").write_bytes(yaml.safe_dump({"name": name, "version": "1.0.0"}).encode())
(project / "apm.yml").write_bytes(
yaml.safe_dump({"name": name, "version": "1.0.0", "target": "copilot"}).encode()
)
inst_dir = project / ".apm" / "instructions"
inst_dir.mkdir(parents=True)
(inst_dir / "rules.instructions.md").write_bytes(_INSTRUCTION_BYTES)
Expand Down
11 changes: 8 additions & 3 deletions tests/integration/test_link_rewrite_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,14 @@ def _make_consumer(root: Path, *, targets: list[str] | None = None) -> Path:
consumer = root / "consumer"
consumer.mkdir()
(consumer / ".github").mkdir()
yml: dict = {"name": "consumer", "version": "1.0.0", "dependencies": {"apm": []}}
yml: dict = {
"name": "consumer",
"version": "1.0.0",
"target": "copilot",
"dependencies": {"apm": []},
}
if targets:
yml["targets"] = targets
yml["target"] = ",".join(targets) if len(targets) > 1 else targets[0]
_write_yaml(consumer / "apm.yml", yml)
return consumer

Expand Down Expand Up @@ -375,7 +380,7 @@ def workspace(self, tmp_path):
producer / ".apm" / "instructions" / "multi.instructions.md",
'---\napplyTo: "**/*.py"\n---\n# Multi\n\nSee [style](../../standards/style.md).\n',
)
consumer = _make_consumer(ws)
consumer = _make_consumer(ws, targets=["copilot", "claude"])
# Auto-detect needs both target dirs present. Copilot
# instructions deploy to .github/instructions/; Claude
# instructions deploy to .claude/rules/.
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_mixed_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def temp_project(tmp_path):
apm_yml.write_text("""name: mixed-deps-project
version: 1.0.0
description: Test project with mixed dependencies
target: copilot
dependencies:
apm: []
mcp: []
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/test_skill_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def temp_project(tmp_path):
apm_yml.write_text("""name: test-skill-project
version: 1.0.0
description: Test project for skill installation
target: copilot
dependencies:
apm: []
mcp: []
Expand Down Expand Up @@ -270,6 +271,7 @@ def test_skill_install_without_github_folder(self, tmp_path, apm_command):
apm_yml = project_dir / "apm.yml"
apm_yml.write_text("""name: no-vscode-project
version: 1.0.0
target: copilot
dependencies:
apm: []
""")
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_skill_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def temp_project(tmp_path):
apm_yml.write_text("""name: skill-compile-project
version: 1.0.0
description: Test project for skill compilation
target: copilot
dependencies:
apm: []
mcp: []
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/test_transitive_chain_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def chain_workspace(tmp_path):
{
"name": "consumer-project",
"version": "1.0.0",
"target": "copilot",
"dependencies": {"apm": []},
}
)
Expand Down Expand Up @@ -203,6 +204,7 @@ def test_asymmetric_layout_anchors_on_declaring_pkg(tmp_path, apm_command):
{
"name": "consumer",
"version": "1.0.0",
"target": "copilot",
"dependencies": {"apm": ["./packages/specialized"]},
}
)
Expand Down
15 changes: 12 additions & 3 deletions tests/unit/install/phases/test_targets_phase_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,22 @@ def _make_ctx(
return ctx


def _resolved_dirs(ctx) -> list[Path]:
"""Collect resolved_deploy_root from every TargetProfile on ctx.targets."""
def _target_root_dirs(ctx, project_root: Path) -> list[Path]:
"""Collect on-disk deploy directories for every TargetProfile in ctx.targets.

Static targets resolve to ``project_root / target.root_dir`` (e.g.
``.claude``). Dynamic targets (cowork) carry an explicit
``resolved_deploy_root`` -- if present, prefer it.
"""
out: list[Path] = []
for t in ctx.targets:
root = getattr(t, "resolved_deploy_root", None)
if root is not None:
out.append(Path(root))
continue
root_dir = getattr(t, "root_dir", None)
if root_dir:
out.append(project_root / root_dir)
return out


Expand All @@ -68,7 +77,7 @@ def test_three_guard_collapse_no_skip(tmp_path):
run_targets_phase(ctx)

assert ctx.targets, "run_targets_phase produced no targets"
dirs = _resolved_dirs(ctx)
dirs = _target_root_dirs(ctx, project)
assert any(d.name == ".claude" for d in dirs), f"No .claude/ deploy dir resolved; got {dirs}"


Expand Down
Loading