apm-cli 0.8.4__tar.gz → 0.8.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apm_cli-0.8.4/src/apm_cli.egg-info → apm_cli-0.8.6}/PKG-INFO +1 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/pyproject.toml +2 -2
- apm_cli-0.8.6/src/apm_cli/bundle/lockfile_enrichment.py +154 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/bundle/packer.py +26 -9
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/bundle/plugin_exporter.py +2 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/bundle/unpacker.py +15 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/_helpers.py +4 -7
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/audit.py +245 -100
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/compile/cli.py +2 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/deps/_utils.py +23 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/deps/cli.py +24 -13
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/install.py +191 -67
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/mcp.py +1 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/pack.py +105 -9
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/runtime.py +2 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/uninstall/cli.py +3 -5
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/uninstall/engine.py +5 -5
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/agents_compiler.py +12 -42
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/claude_formatter.py +6 -5
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/context_optimizer.py +16 -16
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/distributed_compiler.py +12 -11
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/template_builder.py +7 -6
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/config.py +26 -9
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/auth.py +23 -18
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/script_runner.py +7 -8
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/target_detection.py +28 -5
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/aggregator.py +2 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/github_downloader.py +41 -52
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/lockfile.py +22 -3
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/plugin_parser.py +4 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/verifier.py +2 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/agent_integrator.py +7 -6
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/command_integrator.py +3 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/hook_integrator.py +2 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/instruction_integrator.py +3 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/mcp_integrator.py +2 -4
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/prompt_integrator.py +2 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/skill_integrator.py +107 -140
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/targets.py +40 -13
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/apm_package.py +2 -2
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/dependency/reference.py +328 -241
- apm_cli-0.8.6/src/apm_cli/policy/__init__.py +47 -0
- apm_cli-0.8.6/src/apm_cli/policy/ci_checks.py +322 -0
- apm_cli-0.8.6/src/apm_cli/policy/discovery.py +426 -0
- apm_cli-0.8.6/src/apm_cli/policy/inheritance.py +260 -0
- apm_cli-0.8.6/src/apm_cli/policy/matcher.py +84 -0
- apm_cli-0.8.6/src/apm_cli/policy/models.py +143 -0
- apm_cli-0.8.6/src/apm_cli/policy/parser.py +269 -0
- apm_cli-0.8.6/src/apm_cli/policy/policy_checks.py +788 -0
- apm_cli-0.8.6/src/apm_cli/policy/schema.py +110 -0
- apm_cli-0.8.6/src/apm_cli/security/file_scanner.py +85 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/security/gate.py +2 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/__init__.py +3 -0
- apm_cli-0.8.6/src/apm_cli/utils/file_ops.py +294 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/path_security.py +50 -6
- apm_cli-0.8.6/src/apm_cli/utils/paths.py +27 -0
- apm_cli-0.8.6/src/apm_cli/utils/yaml_io.py +55 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6/src/apm_cli.egg-info}/PKG-INFO +1 -1
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli.egg-info/SOURCES.txt +13 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_apm_package_models.py +463 -348
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_github_downloader.py +9 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_lockfile.py +68 -0
- apm_cli-0.8.4/src/apm_cli/bundle/lockfile_enrichment.py +0 -76
- {apm_cli-0.8.4 → apm_cli-0.8.6}/AUTHORS +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/LICENSE +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/README.md +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/setup.cfg +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/copilot.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/cli.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/commands/update.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/command_logger.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/core/token_manager.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/models/validation.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/diagnostics.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/version.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_console.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_token_manager.py +0 -0
- {apm_cli-0.8.4 → apm_cli-0.8.6}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apm-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.6"
|
|
8
8
|
description = "MCP configuration tool"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -71,7 +71,7 @@ warn_return_any = true
|
|
|
71
71
|
warn_unused_configs = true
|
|
72
72
|
|
|
73
73
|
[tool.pytest.ini_options]
|
|
74
|
-
addopts = "-m 'not benchmark'
|
|
74
|
+
addopts = "-m 'not benchmark'"
|
|
75
75
|
markers = [
|
|
76
76
|
"integration: marks tests as integration tests that may require network access",
|
|
77
77
|
"slow: marks tests as slow running tests",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Lockfile enrichment for pack-time metadata."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Dict, List, Tuple
|
|
5
|
+
|
|
6
|
+
from ..deps.lockfile import LockFile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Authoritative mapping of target names to deployed-file path prefixes.
|
|
10
|
+
_TARGET_PREFIXES = {
|
|
11
|
+
"copilot": [".github/"],
|
|
12
|
+
"vscode": [".github/"],
|
|
13
|
+
"claude": [".claude/"],
|
|
14
|
+
"cursor": [".cursor/"],
|
|
15
|
+
"opencode": [".opencode/"],
|
|
16
|
+
"all": [".github/", ".claude/", ".cursor/", ".opencode/"],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Cross-target path equivalences for skills/ and agents/ directories.
|
|
20
|
+
# Only these two directory types are semantically identical across targets;
|
|
21
|
+
# commands, instructions, hooks are target-specific and are NOT mapped.
|
|
22
|
+
#
|
|
23
|
+
# .github/ is the canonical interop prefix -- install always creates it, so
|
|
24
|
+
# all non-github targets map FROM .github/. The copilot target additionally
|
|
25
|
+
# maps FROM .claude/ for the common case of Claude-first projects packing
|
|
26
|
+
# for Copilot. Cursor/opencode sources are niche; if someone publishes
|
|
27
|
+
# skills exclusively under .cursor/, they must pack with --target cursor.
|
|
28
|
+
_CROSS_TARGET_MAPS: Dict[str, Dict[str, str]] = {
|
|
29
|
+
"claude": {
|
|
30
|
+
".github/skills/": ".claude/skills/",
|
|
31
|
+
".github/agents/": ".claude/agents/",
|
|
32
|
+
},
|
|
33
|
+
"vscode": {
|
|
34
|
+
".claude/skills/": ".github/skills/",
|
|
35
|
+
".claude/agents/": ".github/agents/",
|
|
36
|
+
},
|
|
37
|
+
"copilot": {
|
|
38
|
+
".claude/skills/": ".github/skills/",
|
|
39
|
+
".claude/agents/": ".github/agents/",
|
|
40
|
+
},
|
|
41
|
+
"cursor": {
|
|
42
|
+
".github/skills/": ".cursor/skills/",
|
|
43
|
+
".github/agents/": ".cursor/agents/",
|
|
44
|
+
},
|
|
45
|
+
"opencode": {
|
|
46
|
+
".github/skills/": ".opencode/skills/",
|
|
47
|
+
".github/agents/": ".opencode/agents/",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _filter_files_by_target(
|
|
53
|
+
deployed_files: List[str], target: str
|
|
54
|
+
) -> Tuple[List[str], Dict[str, str]]:
|
|
55
|
+
"""Filter deployed file paths by target prefix, with cross-target mapping.
|
|
56
|
+
|
|
57
|
+
When files are deployed under one target prefix (e.g. ``.github/skills/``)
|
|
58
|
+
but the pack target is different (e.g. ``claude``), skills and agents are
|
|
59
|
+
remapped to the equivalent target path. Commands, instructions, and hooks
|
|
60
|
+
are NOT remapped -- they are target-specific.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A tuple of ``(filtered_files, path_mappings)`` where *path_mappings*
|
|
64
|
+
maps ``bundle_path -> disk_path`` for any file that was cross-target
|
|
65
|
+
remapped. Direct matches have no entry in the dict.
|
|
66
|
+
"""
|
|
67
|
+
prefixes = _TARGET_PREFIXES.get(target, _TARGET_PREFIXES["all"])
|
|
68
|
+
direct = [f for f in deployed_files if any(f.startswith(p) for p in prefixes)]
|
|
69
|
+
|
|
70
|
+
path_mappings: Dict[str, str] = {}
|
|
71
|
+
cross_map = _CROSS_TARGET_MAPS.get(target, {})
|
|
72
|
+
if cross_map:
|
|
73
|
+
direct_set = set(direct)
|
|
74
|
+
for f in deployed_files:
|
|
75
|
+
if f in direct_set:
|
|
76
|
+
continue
|
|
77
|
+
for src_prefix, dst_prefix in cross_map.items():
|
|
78
|
+
if f.startswith(src_prefix):
|
|
79
|
+
mapped = dst_prefix + f[len(src_prefix):]
|
|
80
|
+
if mapped not in direct_set:
|
|
81
|
+
direct.append(mapped)
|
|
82
|
+
direct_set.add(mapped)
|
|
83
|
+
path_mappings[mapped] = f
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
return direct, path_mappings
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def enrich_lockfile_for_pack(
|
|
90
|
+
lockfile: LockFile,
|
|
91
|
+
fmt: str,
|
|
92
|
+
target: str,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Create an enriched copy of the lockfile YAML with a ``pack:`` section.
|
|
95
|
+
|
|
96
|
+
Filters each dependency's ``deployed_files`` to only include paths
|
|
97
|
+
matching the pack *target*, so the bundle lockfile is consistent with
|
|
98
|
+
the files actually shipped in the bundle.
|
|
99
|
+
|
|
100
|
+
Does NOT mutate the original *lockfile* object -- serialises a copy and
|
|
101
|
+
prepends the pack metadata.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
lockfile: The resolved lockfile to enrich.
|
|
105
|
+
fmt: Bundle format (``"apm"`` or ``"plugin"``).
|
|
106
|
+
target: Effective target used for packing (e.g. ``"copilot"``, ``"claude"``,
|
|
107
|
+
``"all"``). The internal alias ``"vscode"`` is also accepted.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A YAML string with the ``pack:`` block followed by the original
|
|
111
|
+
lockfile content.
|
|
112
|
+
"""
|
|
113
|
+
import yaml
|
|
114
|
+
|
|
115
|
+
# Build a filtered lockfile YAML: each dep's deployed_files is narrowed
|
|
116
|
+
# to only the paths matching the pack target (with cross-target mapping).
|
|
117
|
+
all_mappings: Dict[str, str] = {}
|
|
118
|
+
data = yaml.safe_load(lockfile.to_yaml())
|
|
119
|
+
if data and "dependencies" in data:
|
|
120
|
+
for dep in data["dependencies"]:
|
|
121
|
+
if "deployed_files" in dep:
|
|
122
|
+
filtered, mappings = _filter_files_by_target(
|
|
123
|
+
dep["deployed_files"], target
|
|
124
|
+
)
|
|
125
|
+
dep["deployed_files"] = filtered
|
|
126
|
+
all_mappings.update(mappings)
|
|
127
|
+
|
|
128
|
+
# Build the pack: metadata section (after filtering so we know if mapping
|
|
129
|
+
# occurred).
|
|
130
|
+
pack_meta: Dict = {
|
|
131
|
+
"format": fmt,
|
|
132
|
+
"target": target,
|
|
133
|
+
"packed_at": datetime.now(timezone.utc).isoformat(),
|
|
134
|
+
}
|
|
135
|
+
if all_mappings:
|
|
136
|
+
# Record the source prefixes that were remapped so consumers know the
|
|
137
|
+
# bundle paths differ from the original lockfile. Use the canonical
|
|
138
|
+
# prefix keys from _CROSS_TARGET_MAPS rather than reverse-engineering
|
|
139
|
+
# them from file paths.
|
|
140
|
+
cross_map = _CROSS_TARGET_MAPS.get(target, {})
|
|
141
|
+
used_src_prefixes = set()
|
|
142
|
+
for original in all_mappings.values():
|
|
143
|
+
for src_prefix in cross_map:
|
|
144
|
+
if original.startswith(src_prefix):
|
|
145
|
+
used_src_prefixes.add(src_prefix)
|
|
146
|
+
break
|
|
147
|
+
pack_meta["mapped_from"] = sorted(used_src_prefixes)
|
|
148
|
+
|
|
149
|
+
from ..utils.yaml_io import yaml_to_str
|
|
150
|
+
|
|
151
|
+
pack_section = yaml_to_str({"pack": pack_meta})
|
|
152
|
+
|
|
153
|
+
lockfile_yaml = yaml_to_str(data)
|
|
154
|
+
return pack_section + lockfile_yaml
|
|
@@ -5,12 +5,12 @@ import shutil
|
|
|
5
5
|
import tarfile
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import List, Optional
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
9
|
|
|
10
10
|
from ..deps.lockfile import LockFile, get_lockfile_path, migrate_lockfile_if_needed
|
|
11
11
|
from ..models.apm_package import APMPackage
|
|
12
12
|
from ..core.target_detection import detect_target
|
|
13
|
-
from .lockfile_enrichment import enrich_lockfile_for_pack,
|
|
13
|
+
from .lockfile_enrichment import enrich_lockfile_for_pack, _filter_files_by_target
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass
|
|
@@ -20,6 +20,8 @@ class PackResult:
|
|
|
20
20
|
bundle_path: Path
|
|
21
21
|
files: List[str] = field(default_factory=list)
|
|
22
22
|
lockfile_enriched: bool = False
|
|
23
|
+
mapped_count: int = 0
|
|
24
|
+
path_mappings: Dict[str, str] = field(default_factory=dict)
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
def pack_bundle(
|
|
@@ -38,7 +40,7 @@ def pack_bundle(
|
|
|
38
40
|
project_root: Root of the project containing ``apm.lock.yaml`` and ``apm.yml``.
|
|
39
41
|
output_dir: Directory where the bundle will be created.
|
|
40
42
|
fmt: Bundle format -- ``"apm"`` (default) or ``"plugin"``.
|
|
41
|
-
target: Target filter -- ``"
|
|
43
|
+
target: Target filter -- ``"copilot"``, ``"claude"``, ``"all"``, or *None*
|
|
42
44
|
(auto-detect from apm.yml / project structure).
|
|
43
45
|
archive: If *True*, produce a ``.tar.gz`` and remove the directory.
|
|
44
46
|
dry_run: If *True*, resolve the file list but write nothing to disk.
|
|
@@ -114,7 +116,7 @@ def pack_bundle(
|
|
|
114
116
|
for dep in lockfile.get_all_dependencies():
|
|
115
117
|
all_deployed.extend(dep.deployed_files)
|
|
116
118
|
|
|
117
|
-
filtered_files = _filter_files_by_target(all_deployed, effective_target)
|
|
119
|
+
filtered_files, path_mappings = _filter_files_by_target(all_deployed, effective_target)
|
|
118
120
|
# Deduplicate while preserving order
|
|
119
121
|
seen = set()
|
|
120
122
|
unique_files: List[str] = []
|
|
@@ -133,14 +135,16 @@ def pack_bundle(
|
|
|
133
135
|
raise ValueError(
|
|
134
136
|
f"Refusing to pack unsafe path from lockfile: {rel_path!r}"
|
|
135
137
|
)
|
|
136
|
-
|
|
138
|
+
# For cross-target mapped files, verify the original (on-disk) path
|
|
139
|
+
disk_path = path_mappings.get(rel_path, rel_path)
|
|
140
|
+
abs_path = project_root / disk_path
|
|
137
141
|
if not abs_path.resolve().is_relative_to(project_root_resolved):
|
|
138
142
|
raise ValueError(
|
|
139
|
-
f"Refusing to pack path that escapes project root: {
|
|
143
|
+
f"Refusing to pack path that escapes project root: {disk_path!r}"
|
|
140
144
|
)
|
|
141
145
|
# deployed_files may reference directories (ending with /)
|
|
142
146
|
if not abs_path.exists():
|
|
143
|
-
missing.append(
|
|
147
|
+
missing.append(disk_path)
|
|
144
148
|
if missing:
|
|
145
149
|
raise ValueError(
|
|
146
150
|
f"The following deployed files are missing on disk -- "
|
|
@@ -155,6 +159,8 @@ def pack_bundle(
|
|
|
155
159
|
bundle_path=bundle_dir,
|
|
156
160
|
files=unique_files,
|
|
157
161
|
lockfile_enriched=True,
|
|
162
|
+
mapped_count=len(path_mappings),
|
|
163
|
+
path_mappings=path_mappings,
|
|
158
164
|
)
|
|
159
165
|
|
|
160
166
|
# 5b. Scan files for hidden characters before bundling.
|
|
@@ -168,7 +174,8 @@ def pack_bundle(
|
|
|
168
174
|
|
|
169
175
|
_scan_findings_total = 0
|
|
170
176
|
for rel_path in unique_files:
|
|
171
|
-
|
|
177
|
+
disk_path = path_mappings.get(rel_path, rel_path)
|
|
178
|
+
src = project_root / disk_path
|
|
172
179
|
if src.is_symlink():
|
|
173
180
|
continue
|
|
174
181
|
if src.is_dir():
|
|
@@ -193,13 +200,21 @@ def pack_bundle(
|
|
|
193
200
|
# 6. Build output directory
|
|
194
201
|
bundle_dir = output_dir / f"{pkg_name}-{pkg_version}"
|
|
195
202
|
bundle_dir.mkdir(parents=True, exist_ok=True)
|
|
203
|
+
bundle_dir_resolved = bundle_dir.resolve()
|
|
196
204
|
|
|
197
205
|
# 7. Copy files preserving directory structure
|
|
198
206
|
for rel_path in unique_files:
|
|
199
|
-
|
|
207
|
+
# For cross-target mapped files, read from the original disk path
|
|
208
|
+
disk_path = path_mappings.get(rel_path, rel_path)
|
|
209
|
+
src = project_root / disk_path
|
|
200
210
|
if src.is_symlink():
|
|
201
211
|
continue # Never bundle symlinks
|
|
202
212
|
dest = bundle_dir / rel_path
|
|
213
|
+
# Defense-in-depth: verify mapped destination stays inside the bundle
|
|
214
|
+
if not dest.resolve().is_relative_to(bundle_dir_resolved):
|
|
215
|
+
raise ValueError(
|
|
216
|
+
f"Refusing to write outside bundle directory: {rel_path!r}"
|
|
217
|
+
)
|
|
203
218
|
if src.is_dir():
|
|
204
219
|
from ..security.gate import ignore_symlinks
|
|
205
220
|
shutil.copytree(src, dest, dirs_exist_ok=True, ignore=ignore_symlinks)
|
|
@@ -215,6 +230,8 @@ def pack_bundle(
|
|
|
215
230
|
bundle_path=bundle_dir,
|
|
216
231
|
files=unique_files,
|
|
217
232
|
lockfile_enriched=True,
|
|
233
|
+
mapped_count=len(path_mappings),
|
|
234
|
+
path_mappings=path_mappings,
|
|
218
235
|
)
|
|
219
236
|
|
|
220
237
|
# 10. Archive if requested
|
|
@@ -285,7 +285,8 @@ def _get_dev_dependency_urls(apm_yml_path: Path) -> Set[Tuple[str, str]]:
|
|
|
285
285
|
``github/awesome-copilot``).
|
|
286
286
|
"""
|
|
287
287
|
try:
|
|
288
|
-
|
|
288
|
+
from ..utils.yaml_io import load_yaml
|
|
289
|
+
data = load_yaml(apm_yml_path)
|
|
289
290
|
except (yaml.YAMLError, OSError, ValueError):
|
|
290
291
|
return set()
|
|
291
292
|
if not isinstance(data, dict):
|
|
@@ -22,6 +22,7 @@ class UnpackResult:
|
|
|
22
22
|
skipped_count: int = 0
|
|
23
23
|
security_warnings: int = 0
|
|
24
24
|
security_critical: int = 0
|
|
25
|
+
pack_meta: Dict = field(default_factory=dict)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def unpack_bundle(
|
|
@@ -98,6 +99,18 @@ def unpack_bundle(
|
|
|
98
99
|
legacy_lockfile_path = source_dir / LEGACY_LOCKFILE_NAME
|
|
99
100
|
if legacy_lockfile_path.exists():
|
|
100
101
|
lockfile_path = legacy_lockfile_path
|
|
102
|
+
|
|
103
|
+
# Extract pack: metadata (written by apm pack) before structured parse
|
|
104
|
+
pack_meta: Dict = {}
|
|
105
|
+
try:
|
|
106
|
+
import yaml
|
|
107
|
+
raw = yaml.safe_load(lockfile_path.read_text(encoding="utf-8"))
|
|
108
|
+
if isinstance(raw, dict):
|
|
109
|
+
val = raw.get("pack", {})
|
|
110
|
+
pack_meta = val if isinstance(val, dict) else {}
|
|
111
|
+
except Exception:
|
|
112
|
+
pass # non-critical -- proceed without metadata
|
|
113
|
+
|
|
101
114
|
lockfile = LockFile.read(lockfile_path)
|
|
102
115
|
if lockfile is None:
|
|
103
116
|
if not lockfile_path.exists():
|
|
@@ -176,6 +189,7 @@ def unpack_bundle(
|
|
|
176
189
|
dependency_files=dep_file_map,
|
|
177
190
|
security_warnings=security_warnings,
|
|
178
191
|
security_critical=security_critical,
|
|
192
|
+
pack_meta=pack_meta,
|
|
179
193
|
)
|
|
180
194
|
|
|
181
195
|
# 4. Copy target files to output_dir (additive, no deletes)
|
|
@@ -219,6 +233,7 @@ def unpack_bundle(
|
|
|
219
233
|
skipped_count=skipped,
|
|
220
234
|
security_warnings=security_warnings,
|
|
221
235
|
security_critical=security_critical,
|
|
236
|
+
pack_meta=pack_meta,
|
|
222
237
|
)
|
|
223
238
|
finally:
|
|
224
239
|
# Clean up temp dir if we created one
|
|
@@ -331,9 +331,8 @@ def _update_gitignore_for_apm_modules(logger=None):
|
|
|
331
331
|
def _load_apm_config():
|
|
332
332
|
"""Load configuration from apm.yml."""
|
|
333
333
|
if Path(APM_YML_FILENAME).exists():
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return yaml.safe_load(f)
|
|
334
|
+
from ..utils.yaml_io import load_yaml
|
|
335
|
+
return load_yaml(APM_YML_FILENAME)
|
|
337
336
|
return None
|
|
338
337
|
|
|
339
338
|
|
|
@@ -440,8 +439,6 @@ def _create_minimal_apm_yml(config, plugin=False):
|
|
|
440
439
|
config: dict with name, version, description, author keys.
|
|
441
440
|
plugin: if True, include a devDependencies section.
|
|
442
441
|
"""
|
|
443
|
-
yaml = _lazy_yaml()
|
|
444
|
-
|
|
445
442
|
# Create minimal apm.yml structure
|
|
446
443
|
apm_yml_data = {
|
|
447
444
|
"name": config["name"],
|
|
@@ -457,5 +454,5 @@ def _create_minimal_apm_yml(config, plugin=False):
|
|
|
457
454
|
apm_yml_data["scripts"] = {}
|
|
458
455
|
|
|
459
456
|
# Write apm.yml
|
|
460
|
-
|
|
461
|
-
|
|
457
|
+
from ..utils.yaml_io import dump_yaml
|
|
458
|
+
dump_yaml(apm_yml_data, APM_YML_FILENAME)
|