Skip to content

Refactor/yaml target manifests#9

Merged
PhilippTh merged 19 commits into
mainfrom
refactor/yaml-target-manifests
May 22, 2026
Merged

Refactor/yaml target manifests#9
PhilippTh merged 19 commits into
mainfrom
refactor/yaml-target-manifests

Conversation

@PhilippTh

Copy link
Copy Markdown
Owner

No description provided.

PhilippTh added 19 commits May 21, 2026 14:54
Lay the foundation for moving per-target conventions out of hardcoded
Python constants and into declarative YAML manifests. Nothing is wired
into the deployer or MCP encoder yet — this commit only adds the new
modules alongside the existing code.

- target_schema.py: TargetDef / ResourceLayout / McpSpec / TransportSpec
  dataclasses plus a strict YAML parser. Every per-tool MCP quirk
  (opencode command-as-array + "environment" rename, copilot explicit
  "type": "stdio", gemini "httpUrl", codex "http_headers", opt-in type
  field) is expressible declaratively.
- registry.py: loads built-in manifests via importlib.resources so they
  ship in the wheel.
- builtin_targets/*.yml: claude, opencode, codex, cursor, copilot,
  gemini, windsurf, antigravity. Bug fixes baked in vs the current
  hardcoded table — codex skills under .codex/, codex agents added,
  cursor agents removed (fictional), cursor commands added, gemini MCP
  drops type field and uses httpUrl for HTTP, antigravity gets its own
  .agents/ + .agent/ namespace, windsurf commands map to workflows.
- Wheel build verified to include all eight YAMLs.
Replace the hardcoded per-target constants (SKILL_DIRS, COMMAND_DIRS,
AGENT_DIRS, MCP_TARGETS) and the per-target if-branches in the MCP
encoder with declarative behavior driven by the YAML target manifests
introduced in the previous commit.

- mcp.py: single generic encoder reads TransportSpec for type_value,
  command_format (string vs array), and renamed keys (env→environment,
  url→httpUrl, headers→http_headers). The opencode and copilot
  special-cases collapse into manifest config. defaults: applied to the
  config root so opencode's $schema is now written.
- deployer.py: single generic copy helper reads ResourceLayout
  (directory vs file) instead of looking up target name in a dict.
  Public API (deploy_skill / deploy_command / deploy_agent /
  deploy_single_* / DeployResult) preserved.
- config.py: VALID_TARGETS frozenset replaced with registry.list_builtins(),
  so the validation list is sourced from the bundled YAMLs and will
  auto-update as we add or rename manifests.
- cli.py: resolves config.targets to a list[TargetDef] once via the
  registry, then passes that list everywhere. _sync_resource_type now
  takes target_defs instead of the full config.
- agpack/targets.py: deleted. No remaining references.
- tests: test_mcp.py and test_deployer.py keep their existing bodies
  but go through small adapter shims that resolve name strings to
  TargetDef. Three tests that called private _merge_*/_remove_from_*
  helpers are reframed against the public cleanup_mcp_server API
  (which still handles fuzzy keys when the target is unknown). Two
  "doesn't support" assertions repointed because cursor now supports
  commands and codex now supports agents.

Behavior change observable to users: the path/encoding bug fixes baked
into the built-in YAMLs (codex .codex/skills, cursor .cursor/commands,
gemini httpUrl for HTTP, antigravity .agents/, etc.) are now live. The
lockfile still records exact deployed paths, so cleanup of files in
old (buggy) locations happens naturally on first sync after upgrade.

All 313 tests pass; ruff + mypy strict clean; wheel builds.
Let users override built-in target manifests or define brand-new targets
inline in agpack.yml, with the same shape used by the bundled YAMLs.
Replace semantics — if a name appears in target_definitions, that entry
fully supplants the built-in (no deep merge).

Precedence chain (highest first):
  1. project agpack.yml target_definitions
  2. global ~/.config/agpack/agpack.yml target_definitions
  3. bundled built-in manifests
Project entries win by name during merge_configs; global entries with
fresh names are added.

- config.py: AgpackConfig and GlobalConfig gain a target_definitions
  field. _parse_target_definitions injects the mapping key as the entry
  name (or errors if an explicit name disagrees), then delegates to the
  existing parse_target_def schema validator. The legacy
  "Unrecognised target" check at load time is dropped — at parse time
  we can't yet see global target_definitions, so resolution-time
  errors do the work instead.
- cli.py: _resolve_targets checks target_definitions before falling
  back to load_builtin; unknown names raise a ClickException that
  lists both pools and points users at target_definitions.
- tests: 6 new unit tests around parsing + merging semantics (replace,
  not deep-merge), and 3 new integration tests covering an override
  of a built-in, a brand-new custom target name end-to-end, and the
  unknown-target CLI error.
Surface the new manifest system to users via two inspection commands
and a richer scaffold:

- `agpack targets list` shows every available target (built-in and
  user-defined) with its source and the resource types it supports.
  User entries that shadow a built-in are flagged.
- `agpack targets show <name>` prints the resolved manifest as block
  YAML, suitable for pasting into `target_definitions:` as a starting
  point for customization. TransportSpec fields that match defaults
  are omitted so the output stays clean.
- `agpack init` now scaffolds a commented-out `target_definitions:`
  block showing both override and brand-new patterns, plus a pointer
  to `agpack targets show`.

Both subcommands accept --config and --no-global so they work in
project-aware mode (showing user definitions) or standalone (just
built-ins) without complaining when agpack.yml is absent.

The serializer lives in target_schema.target_def_to_dict so it
stays colocated with the parser; a roundtrip test guarantees every
shipped built-in survives serialize→parse unchanged.

10 new tests (4 serializer + 6 CLI/integration); 335 pass overall;
ruff + mypy strict clean.
Small post-refactor tidy-up — no behavior changes:

- tests/test_cli_parallel.py: _make_config returned an AgpackConfig
  that was passed where _sync_resource_type now expects
  list[TargetDef]. The mismatch was silent because the mocked
  deploy_item_fn never touched it. Renamed to _make_targets and
  resolved to actual TargetDef objects.
- tests/test_mcp.py: inlined the two-step _resolve_targets +
  _known_targets helper into one; dropped a stale comment that
  referenced the deleted MCP_TARGETS module.
- tests/test_target_schema.py, tests/test_integration.py: hoisted
  in-function `from agpack.* import …` statements up to module-level
  imports.
- agpack/cli.py: _load_user_target_definitions duplicated the
  global-config-loading logic across an if/elif fork. Collapsed it
  into a single linear flow that picks up the project's `use_global`
  setting when present.

All 335 tests pass; ruff + mypy strict clean. Net -8 lines.
README updates:
- Refresh the target mapping table with the bug-fixed paths (Codex
  agents, Cursor commands, Gemini httpUrl, Antigravity in .agents/,
  Windsurf workflows).
- Add a 'Customising targets' section walking through the
  target_definitions block (override + brand-new patterns) and the
  manifest schema.
- Document `agpack targets list` and `agpack targets show` in the
  Commands reference.
- Add an 'Upgrading from 0.3.x' note explaining that the bug fixes
  ship as bundled YAML edits and that first-sync cleanup of files in
  old locations happens automatically via the lockfile.

Bump __version__ to 0.4.0 to reflect the new target-customisation
feature surface and the path/encoding bug fixes.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.40927% with 89 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agpack/cli.py 85.82% 35 Missing ⚠️
agpack/config.py 89.67% 16 Missing ⚠️
agpack/deployer.py 77.08% 11 Missing ⚠️
agpack/kinds/edit_file.py 96.53% 7 Missing ⚠️
agpack/lockfile.py 85.36% 6 Missing ⚠️
agpack/patch.py 84.61% 4 Missing ⚠️
agpack/registry.py 89.28% 3 Missing ⚠️
agpack/kinds/_shared.py 96.66% 2 Missing ⚠️
agpack/target_schema.py 97.67% 2 Missing ⚠️
agpack/envsubst.py 96.00% 1 Missing ⚠️
... and 2 more

📢 Thoughts on this report? Let us know!

@PhilippTh PhilippTh merged commit 7654be1 into main May 22, 2026
6 checks passed
@PhilippTh PhilippTh deleted the refactor/yaml-target-manifests branch May 22, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants