agentbundle 0.8.0__tar.gz → 0.9.0__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.
- {agentbundle-0.8.0 → agentbundle-0.9.0}/PKG-INFO +2 -2
- {agentbundle-0.8.0 → agentbundle-0.9.0}/README.md +1 -1
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/adapter.toml +45 -14
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_copilot.py +8 -9
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_cursor.py +7 -7
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_gemini.py +7 -7
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +3 -3
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_contract.py +4 -4
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_contract_scope.py +8 -7
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_contract_v07.py +7 -4
- agentbundle-0.9.0/agentbundle/build/tests/test_shared_prefix_contract.py +111 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/cli.py +29 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/diff.py +34 -8
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/init_state.py +33 -64
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/install.py +284 -50
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/reconcile.py +1 -1
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/uninstall.py +87 -32
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/upgrade.py +58 -13
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/config.py +268 -155
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/safety.py +16 -12
- agentbundle-0.9.0/agentbundle/statelock.py +148 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/version.py +1 -1
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle.egg-info/PKG-INFO +2 -2
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle.egg-info/SOURCES.txt +2 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/pyproject.toml +1 -1
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/__main__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/adapter.schema.json +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/install-defaults.toml +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/install-marker.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/pack.schema.json +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/_data/profile.schema.json +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/__main__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapter_root_bins.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/claude_code.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/codex.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/copilot.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/cursor.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/gemini.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/kiro.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/contract.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/lint_packs.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/main.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/phase_order.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/direct_directory.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/hook_id.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/merge_json.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/projections/user_merge_json.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/scope_rails.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/self_host.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/shared_libs.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/target_resolver.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_pipeline.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_security.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_validate.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/user_libs.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/build/validate.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/catalogue.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/categories.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/__init__.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/_common.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/_drop_warning.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/adapt.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/config.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/list_packs.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/list_profiles.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/list_targets.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/profile.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/render.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/scaffold.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/commands/validate.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/render.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/scope.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/source_defaults.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle/user_config.py +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle.egg-info/dependency_links.txt +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle.egg-info/entry_points.txt +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/agentbundle.egg-info/top_level.txt +0 -0
- {agentbundle-0.8.0 → agentbundle-0.9.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentbundle
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: npm for your coding agent. Install packs of skills, subagents, and hooks into any repo, for every major agent.
|
|
5
5
|
Author-email: eugenelim <eugenelim@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0 OR MIT
|
|
@@ -46,7 +46,7 @@ agentbundle install --pack research --scope user
|
|
|
46
46
|
|
|
47
47
|
User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
|
|
48
48
|
|
|
49
|
-
The install auto-detects your agent (`--adapter` overrides). To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
|
|
49
|
+
The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install the same pack for each agent at the same scope — they coexist, and the agents that read `.agents/skills/` (codex, cursor, gemini, copilot) share one skill copy instead of fighting over it. To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
|
|
50
50
|
|
|
51
51
|
## More commands
|
|
52
52
|
|
|
@@ -28,7 +28,7 @@ agentbundle install --pack research --scope user
|
|
|
28
28
|
|
|
29
29
|
User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
|
|
30
30
|
|
|
31
|
-
The install auto-detects your agent (`--adapter` overrides). To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
|
|
31
|
+
The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install the same pack for each agent at the same scope — they coexist, and the agents that read `.agents/skills/` (codex, cursor, gemini, copilot) share one skill copy instead of fighting over it. To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
|
|
32
32
|
|
|
33
33
|
## More commands
|
|
34
34
|
|
|
@@ -84,7 +84,13 @@
|
|
|
84
84
|
# `_append_layout_section` step (append-if-exists / never-create / never-
|
|
85
85
|
# overwrite) into an adopter-owned `agentbundle-layout.toml`. Additive +
|
|
86
86
|
# optional — packs pinned below v0.16 build and validate unchanged.
|
|
87
|
-
|
|
87
|
+
# RFC-0052 / ADR-0039+0040 (v0.17): the `[contract.shared-prefixes]` registry
|
|
88
|
+
# (below) classifies each allowed-prefix as `shared` (a path more than one
|
|
89
|
+
# adapter reads) or — by absence — `private`, and a shared prefix declares its
|
|
90
|
+
# reader cohort. cursor/gemini/copilot route the `skill` primitive to the shared
|
|
91
|
+
# `.agents/skills/` home (joining codex) and gain it in `allowed-prefixes` at
|
|
92
|
+
# both scopes; their agent/hook/command projection stays native.
|
|
93
|
+
version = "0.17"
|
|
88
94
|
|
|
89
95
|
# Sibling schemas this contract references — pack metadata and the
|
|
90
96
|
# per-pack Claude-plugin manifest. Defined by spec AC #3 + #4.
|
|
@@ -92,6 +98,16 @@ version = "0.16"
|
|
|
92
98
|
pack = "pack.schema.json"
|
|
93
99
|
plugin-manifest = "plugin-manifest.schema.json"
|
|
94
100
|
|
|
101
|
+
# Shared-prefix registry (RFC-0052 / ADR-0039). Each key is a `shared` prefix —
|
|
102
|
+
# a path more than one shipped adapter reads — mapped to its reader cohort (the
|
|
103
|
+
# shipped adapters that read it). Every prefix NOT listed here is `private`
|
|
104
|
+
# (adapter-exclusive). The install-time cross-adapter disclosure names a shared
|
|
105
|
+
# prefix's *other* shipped cohort members; the footprint gate co-owns a shared
|
|
106
|
+
# path across same-pack rows. A future adapter joins a cohort by one line here.
|
|
107
|
+
[contract.shared-prefixes]
|
|
108
|
+
".agents/skills/" = ["codex", "cursor", "gemini", "copilot"]
|
|
109
|
+
".kiro/skills/" = ["kiro-ide", "kiro-cli"]
|
|
110
|
+
|
|
95
111
|
# ---------------------------------------------------------------------------
|
|
96
112
|
# Primitive types — source paths within a pack's .apm/ directory
|
|
97
113
|
# ---------------------------------------------------------------------------
|
|
@@ -477,7 +493,10 @@ allowed-prefixes.user = [".kiro/", ".agentbundle/"]
|
|
|
477
493
|
[[adapter.copilot.projection]]
|
|
478
494
|
primitive = "skill"
|
|
479
495
|
mode = "direct-directory"
|
|
480
|
-
|
|
496
|
+
# RFC-0052 / ADR-0040: cohort skill home. Copilot reads `.agents/skills/`
|
|
497
|
+
# (project) and `~/.agents/skills/` (personal); routing skills there lets the
|
|
498
|
+
# whole cohort (codex, cursor, gemini, copilot) share one co-owned copy.
|
|
499
|
+
target-path = ".agents/skills/"
|
|
481
500
|
on-conflict = "prompt-then-preserve"
|
|
482
501
|
|
|
483
502
|
[[adapter.copilot.projection]]
|
|
@@ -517,12 +536,14 @@ repo = "."
|
|
|
517
536
|
user = "~"
|
|
518
537
|
# RFC-0024 + copilot-skills-and-web: repo-scope skills at `.github/skills/`,
|
|
519
538
|
# agents at `.github/agents/`, hook wiring + bodies at `.github/hooks/`.
|
|
520
|
-
|
|
521
|
-
#
|
|
522
|
-
|
|
523
|
-
#
|
|
524
|
-
#
|
|
525
|
-
|
|
539
|
+
# RFC-0052: skills route to the shared `.agents/skills/` home; agents + hooks
|
|
540
|
+
# stay native under `.github/` (ADR-0040 moves only the skill primitive).
|
|
541
|
+
allowed-prefixes.repo = [".agents/skills/", ".github/skills/", ".github/agents/", ".github/hooks/"]
|
|
542
|
+
# User-scope home is `~/.copilot/` for agents/hooks (the prefix the install
|
|
543
|
+
# rewrite produces) and `~/.agents/skills/` for skills (scope-agnostic, no
|
|
544
|
+
# rewrite). `.agentbundle/` carries the install state file
|
|
545
|
+
# (`~/.agentbundle/state.toml`), same as every other user-scope-capable adapter.
|
|
546
|
+
allowed-prefixes.user = [".agents/skills/", ".copilot/skills/", ".copilot/agents/", ".copilot/hooks/", ".agentbundle/"]
|
|
526
547
|
|
|
527
548
|
# ---------------------------------------------------------------------------
|
|
528
549
|
# Codex adapter — 5 projections (one per primitive)
|
|
@@ -596,7 +617,10 @@ allowed-prefixes.user = [".agents/skills/", ".codex/", ".agentbundle/"]
|
|
|
596
617
|
[[adapter.cursor.projection]]
|
|
597
618
|
primitive = "skill"
|
|
598
619
|
mode = "direct-directory"
|
|
599
|
-
|
|
620
|
+
# RFC-0052 / ADR-0040: cohort skill home. Cursor reads `.agents/skills/` and
|
|
621
|
+
# `~/.agents/skills/` alongside `.cursor/skills/`; route skills there to share
|
|
622
|
+
# one co-owned copy across the cohort.
|
|
623
|
+
target-path = ".agents/skills/"
|
|
600
624
|
on-conflict = "prompt-then-preserve"
|
|
601
625
|
|
|
602
626
|
[[adapter.cursor.projection]]
|
|
@@ -650,8 +674,10 @@ mode = "dropped"
|
|
|
650
674
|
[adapter.cursor.scope]
|
|
651
675
|
repo = "."
|
|
652
676
|
user = "~"
|
|
653
|
-
|
|
654
|
-
|
|
677
|
+
# RFC-0052: skills route to the shared `.agents/skills/` home (both scopes);
|
|
678
|
+
# agents/hooks stay under `.cursor/` (ADR-0040 moves only the skill primitive).
|
|
679
|
+
allowed-prefixes.repo = [".agents/skills/", ".cursor/", ".agentbundle/"]
|
|
680
|
+
allowed-prefixes.user = [".agents/skills/", ".cursor/", ".agentbundle/"]
|
|
655
681
|
|
|
656
682
|
# ---------------------------------------------------------------------------
|
|
657
683
|
# Gemini adapter — full-parity native adapter (RFC-0027 / ADR-0016 /
|
|
@@ -685,7 +711,10 @@ allowed-prefixes.user = [".cursor/", ".agentbundle/"]
|
|
|
685
711
|
[[adapter.gemini.projection]]
|
|
686
712
|
primitive = "skill"
|
|
687
713
|
mode = "direct-directory"
|
|
688
|
-
|
|
714
|
+
# RFC-0052 / ADR-0040: cohort skill home. Gemini CLI reads `.agents/skills/`
|
|
715
|
+
# and `~/.agents/skills/`, and its docs now make the alias precedence-winning
|
|
716
|
+
# over `.gemini/skills/`; route skills there to share one co-owned copy.
|
|
717
|
+
target-path = ".agents/skills/"
|
|
689
718
|
on-conflict = "prompt-then-preserve"
|
|
690
719
|
|
|
691
720
|
[[adapter.gemini.projection]]
|
|
@@ -744,8 +773,10 @@ mode = "dropped"
|
|
|
744
773
|
[adapter.gemini.scope]
|
|
745
774
|
repo = "."
|
|
746
775
|
user = "~"
|
|
747
|
-
|
|
748
|
-
|
|
776
|
+
# RFC-0052: skills route to the shared `.agents/skills/` home (both scopes);
|
|
777
|
+
# agents/hooks stay under `.gemini/` (ADR-0040 moves only the skill primitive).
|
|
778
|
+
allowed-prefixes.repo = [".agents/skills/", ".gemini/", ".agentbundle/"]
|
|
779
|
+
allowed-prefixes.user = [".agents/skills/", ".gemini/", ".agentbundle/"]
|
|
749
780
|
|
|
750
781
|
# ---------------------------------------------------------------------------
|
|
751
782
|
# Frontmatter mappings — rewrite rules applied when projecting a primitive
|
|
@@ -42,11 +42,9 @@ class CopilotAdapterTests(unittest.TestCase):
|
|
|
42
42
|
cls.contract = load_contract(CONTRACT_PATH)
|
|
43
43
|
|
|
44
44
|
def test_skill_projects_as_first_class_skill_md(self) -> None:
|
|
45
|
-
#
|
|
46
|
-
# `
|
|
47
|
-
#
|
|
48
|
-
# SKILL.md verbatim, so the source tree is copied byte-for-byte (no
|
|
49
|
-
# `.instructions.md` wrapper, no injected `applyTo` frontmatter).
|
|
45
|
+
# RFC-0052 / ADR-0040: copilot `skill` routes to the shared cohort home
|
|
46
|
+
# `.agents/skills/<name>/SKILL.md` (joining codex, cursor, gemini).
|
|
47
|
+
# Agents/hooks/hook-wiring remain under `.github/`.
|
|
50
48
|
with tempfile.TemporaryDirectory() as tmp:
|
|
51
49
|
tmp_path = Path(tmp)
|
|
52
50
|
pack = _seed_pack(tmp_path)
|
|
@@ -55,7 +53,7 @@ class CopilotAdapterTests(unittest.TestCase):
|
|
|
55
53
|
source_body = (pack / ".apm" / "skills" / "foo" / "SKILL.md").read_text(
|
|
56
54
|
encoding="utf-8"
|
|
57
55
|
)
|
|
58
|
-
output_path = out / ".
|
|
56
|
+
output_path = out / ".agents" / "skills" / "foo" / "SKILL.md"
|
|
59
57
|
self.assertTrue(output_path.exists())
|
|
60
58
|
self.assertEqual(output_path.read_text(encoding="utf-8"), source_body)
|
|
61
59
|
# No legacy instruction-file output.
|
|
@@ -65,19 +63,20 @@ class CopilotAdapterTests(unittest.TestCase):
|
|
|
65
63
|
# AC4: re-projection sweeps a stale skill dir, and the sweep is bounded
|
|
66
64
|
# to the skill target — it never touches sibling `.github/agents/` or
|
|
67
65
|
# `.github/hooks/` content.
|
|
66
|
+
# RFC-0052: skill target is now `.agents/skills/` (shared cohort home).
|
|
68
67
|
with tempfile.TemporaryDirectory() as tmp:
|
|
69
68
|
tmp_path = Path(tmp)
|
|
70
69
|
pack = _seed_pack(tmp_path)
|
|
71
70
|
out = tmp_path / "out"
|
|
72
71
|
project(pack, self.contract, out)
|
|
73
|
-
self.assertTrue((out / ".
|
|
72
|
+
self.assertTrue((out / ".agents" / "skills" / "foo").exists())
|
|
74
73
|
agents_before = sorted((out / ".github" / "agents").iterdir())
|
|
75
74
|
hooks_before = sorted((out / ".github" / "hooks").iterdir())
|
|
76
75
|
# Rename the source skill; re-project. The old name is now orphaned.
|
|
77
76
|
(pack / ".apm" / "skills" / "foo").rename(pack / ".apm" / "skills" / "renamed")
|
|
78
77
|
project(pack, self.contract, out)
|
|
79
|
-
self.assertFalse((out / ".
|
|
80
|
-
self.assertTrue((out / ".
|
|
78
|
+
self.assertFalse((out / ".agents" / "skills" / "foo").exists())
|
|
79
|
+
self.assertTrue((out / ".agents" / "skills" / "renamed").exists())
|
|
81
80
|
# Sibling agent/hook trees are untouched by the skill sweep.
|
|
82
81
|
self.assertEqual(sorted((out / ".github" / "agents").iterdir()), agents_before)
|
|
83
82
|
self.assertEqual(sorted((out / ".github" / "hooks").iterdir()), hooks_before)
|
|
@@ -54,10 +54,10 @@ class CursorContractTests(unittest.TestCase):
|
|
|
54
54
|
"""AC1 — contract bumped to 0.11 by cursor-full-parity; subsequently
|
|
55
55
|
0.12 (copilot-skills-and-web), 0.13 (docs/specs/gemini-full-parity),
|
|
56
56
|
0.14 (docs/specs/enriched-pack-manifest), 0.15
|
|
57
|
-
(docs/specs/kiro-cli-agent-skill-resources), then 0.
|
|
57
|
+
(docs/specs/kiro-cli-agent-skill-resources), then 0.17
|
|
58
58
|
(docs/specs/consolidated-pack-layout).
|
|
59
59
|
Name preserved to keep the diff small."""
|
|
60
|
-
self.assertEqual(self.contract["contract"]["version"], "0.
|
|
60
|
+
self.assertEqual(self.contract["contract"]["version"], "0.17")
|
|
61
61
|
|
|
62
62
|
def test_cursor_block_projects_five_primitives(self) -> None:
|
|
63
63
|
"""AC2 — the five standard primitives are in the projection array."""
|
|
@@ -68,7 +68,7 @@ class CursorContractTests(unittest.TestCase):
|
|
|
68
68
|
{"skill", "agent", "hook-body", "hook-wiring", "command"},
|
|
69
69
|
)
|
|
70
70
|
self.assertEqual(prims["skill"]["mode"], "direct-directory")
|
|
71
|
-
self.assertEqual(prims["skill"]["target-path"], ".
|
|
71
|
+
self.assertEqual(prims["skill"]["target-path"], ".agents/skills/")
|
|
72
72
|
self.assertEqual(prims["agent"]["mode"], "direct-file")
|
|
73
73
|
self.assertEqual(prims["agent"]["target-path"], ".cursor/agents/")
|
|
74
74
|
self.assertEqual(
|
|
@@ -109,7 +109,7 @@ class CursorContractTests(unittest.TestCase):
|
|
|
109
109
|
scope = self.contract["adapter"]["cursor"]["scope"]
|
|
110
110
|
self.assertEqual(scope["repo"], ".")
|
|
111
111
|
self.assertEqual(scope["user"], "~")
|
|
112
|
-
self.assertEqual(scope["allowed-prefixes"]["repo"], [".cursor/", ".agentbundle/"])
|
|
112
|
+
self.assertEqual(scope["allowed-prefixes"]["repo"], [".agents/skills/", ".cursor/", ".agentbundle/"])
|
|
113
113
|
self.assertEqual(
|
|
114
114
|
scope["allowed-prefixes"]["repo"], scope["allowed-prefixes"]["user"]
|
|
115
115
|
)
|
|
@@ -147,7 +147,7 @@ class CursorProjectionTests(unittest.TestCase):
|
|
|
147
147
|
(pack / ".apm" / "hooks" / "on-start.py").write_text("#!py", encoding="utf-8")
|
|
148
148
|
out = tmp_path / "out"
|
|
149
149
|
cursor.project(pack, self.contract, out)
|
|
150
|
-
self.assertTrue((out / ".
|
|
150
|
+
self.assertTrue((out / ".agents" / "skills" / "my-skill" / "SKILL.md").exists())
|
|
151
151
|
self.assertTrue((out / ".cursor" / "commands" / "do-thing.md").exists())
|
|
152
152
|
self.assertTrue((out / ".cursor" / "hooks" / "on-start.py").exists())
|
|
153
153
|
|
|
@@ -332,10 +332,10 @@ class CursorProjectionTests(unittest.TestCase):
|
|
|
332
332
|
os.symlink(secret, skill / "leak.txt")
|
|
333
333
|
out = tmp_path / "out"
|
|
334
334
|
cursor.project(pack, self.contract, out)
|
|
335
|
-
leaked = out / ".
|
|
335
|
+
leaked = out / ".agents" / "skills" / "s" / "leak.txt"
|
|
336
336
|
self.assertFalse(leaked.is_symlink(), "nested symlink must not be reproduced")
|
|
337
337
|
self.assertFalse(leaked.exists(), "nested symlink target must not be copied")
|
|
338
|
-
self.assertTrue((out / ".
|
|
338
|
+
self.assertTrue((out / ".agents" / "skills" / "s" / "SKILL.md").exists())
|
|
339
339
|
|
|
340
340
|
|
|
341
341
|
class CursorInstallDispatchTests(unittest.TestCase):
|
|
@@ -151,9 +151,9 @@ class GeminiContractTests(unittest.TestCase):
|
|
|
151
151
|
cls.contract = load_contract(CONTRACT_PATH)
|
|
152
152
|
|
|
153
153
|
def test_contract_version_is_0_14(self) -> None:
|
|
154
|
-
"""Contract version is 0.
|
|
154
|
+
"""Contract version is 0.17 (docs/specs/consolidated-pack-layout
|
|
155
155
|
bumped it from kiro-cli-agent-skill-resources' 0.15). Name preserved."""
|
|
156
|
-
self.assertEqual(self.contract["contract"]["version"], "0.
|
|
156
|
+
self.assertEqual(self.contract["contract"]["version"], "0.17")
|
|
157
157
|
|
|
158
158
|
def test_gemini_block_projects_five_primitives(self) -> None:
|
|
159
159
|
"""AC2 — five standard primitives with their gemini targets."""
|
|
@@ -161,7 +161,7 @@ class GeminiContractTests(unittest.TestCase):
|
|
|
161
161
|
prims = {e["primitive"]: e for e in block["projection"]}
|
|
162
162
|
self.assertEqual(set(prims), {"skill", "agent", "hook-body", "hook-wiring", "command"})
|
|
163
163
|
self.assertEqual(prims["skill"]["mode"], "direct-directory")
|
|
164
|
-
self.assertEqual(prims["skill"]["target-path"], ".
|
|
164
|
+
self.assertEqual(prims["skill"]["target-path"], ".agents/skills/")
|
|
165
165
|
self.assertEqual(prims["agent"]["mode"], "direct-file")
|
|
166
166
|
self.assertEqual(prims["agent"]["target-path"], ".gemini/agents/")
|
|
167
167
|
self.assertEqual(prims["agent"]["frontmatter-mapping"], "gemini-agent-frontmatter")
|
|
@@ -210,7 +210,7 @@ class GeminiContractTests(unittest.TestCase):
|
|
|
210
210
|
scope = self.contract["adapter"]["gemini"]["scope"]
|
|
211
211
|
self.assertEqual(scope["repo"], ".")
|
|
212
212
|
self.assertEqual(scope["user"], "~")
|
|
213
|
-
self.assertEqual(scope["allowed-prefixes"]["repo"], [".gemini/", ".agentbundle/"])
|
|
213
|
+
self.assertEqual(scope["allowed-prefixes"]["repo"], [".agents/skills/", ".gemini/", ".agentbundle/"])
|
|
214
214
|
self.assertEqual(scope["allowed-prefixes"]["repo"], scope["allowed-prefixes"]["user"])
|
|
215
215
|
|
|
216
216
|
def test_frontmatter_mapping_keeps_tools_and_model(self) -> None:
|
|
@@ -254,7 +254,7 @@ class GeminiProjectionTests(unittest.TestCase):
|
|
|
254
254
|
(pack / ".apm" / "hooks" / "on-start.py").write_text("#!py", encoding="utf-8")
|
|
255
255
|
out = tmp_path / "out"
|
|
256
256
|
gemini.project(pack, self.contract, out)
|
|
257
|
-
self.assertTrue((out / ".
|
|
257
|
+
self.assertTrue((out / ".agents" / "skills" / "my-skill" / "SKILL.md").exists())
|
|
258
258
|
self.assertTrue((out / ".gemini" / "commands" / "do-thing.toml").exists())
|
|
259
259
|
self.assertTrue((out / ".gemini" / "hooks" / "on-start.py").exists())
|
|
260
260
|
|
|
@@ -361,9 +361,9 @@ class GeminiProjectionTests(unittest.TestCase):
|
|
|
361
361
|
os.symlink(secret, skill / "leak.txt")
|
|
362
362
|
out = tmp_path / "out"
|
|
363
363
|
gemini.project(pack, self.contract, out)
|
|
364
|
-
leaked = out / ".
|
|
364
|
+
leaked = out / ".agents" / "skills" / "s" / "leak.txt"
|
|
365
365
|
self.assertFalse(leaked.exists())
|
|
366
|
-
self.assertTrue((out / ".
|
|
366
|
+
self.assertTrue((out / ".agents" / "skills" / "s" / "SKILL.md").exists())
|
|
367
367
|
|
|
368
368
|
|
|
369
369
|
# ===========================================================================
|
|
@@ -172,13 +172,13 @@ class KiroIdeAdapterTests(unittest.TestCase):
|
|
|
172
172
|
self.assertIn(".kiro.hook", target_repo)
|
|
173
173
|
|
|
174
174
|
def test_contract_version_is_0_9(self) -> None:
|
|
175
|
-
"""Contract version is 0.
|
|
175
|
+
"""Contract version is 0.17 (docs/specs/consolidated-pack-layout,
|
|
176
176
|
atop kiro-cli-agent-skill-resources' 0.15 and enriched-pack-manifest's 0.14).
|
|
177
177
|
Name preserved to keep the diff small."""
|
|
178
178
|
self.assertEqual(
|
|
179
179
|
self.contract["contract"]["version"],
|
|
180
|
-
"0.
|
|
181
|
-
"adapter.toml [contract] version must be '0.
|
|
180
|
+
"0.17",
|
|
181
|
+
"adapter.toml [contract] version must be '0.17' after consolidated-pack-layout",
|
|
182
182
|
)
|
|
183
183
|
|
|
184
184
|
def test_kiro_ide_hook_projects_with_flat_prefix_path(self) -> None:
|
|
@@ -393,7 +393,7 @@ class FrontmatterTableTests(unittest.TestCase):
|
|
|
393
393
|
if p["primitive"] == "skill"
|
|
394
394
|
)
|
|
395
395
|
self.assertEqual(skill["mode"], "direct-directory")
|
|
396
|
-
self.assertEqual(skill["target-path"], ".
|
|
396
|
+
self.assertEqual(skill["target-path"], ".agents/skills/")
|
|
397
397
|
self.assertNotIn("frontmatter-default", skill)
|
|
398
398
|
|
|
399
399
|
def test_frontmatter_mapping_and_default_are_structurally_distinct(self) -> None:
|
|
@@ -452,7 +452,7 @@ class ContractV05Tests(unittest.TestCase):
|
|
|
452
452
|
self.schema = _load_schema()
|
|
453
453
|
|
|
454
454
|
def test_contract_version_is_v05(self) -> None:
|
|
455
|
-
"""tomllib.loads of adapter.toml returns contract.version == "0.
|
|
455
|
+
"""tomllib.loads of adapter.toml returns contract.version == "0.17"
|
|
456
456
|
(bumped from kiro-cli-agent-skill-resources' "0.15" by
|
|
457
457
|
docs/specs/consolidated-pack-layout: pack.toml gains an optional
|
|
458
458
|
scope-keyed [pack.layout] table). Class/method names preserved to
|
|
@@ -460,8 +460,8 @@ class ContractV05Tests(unittest.TestCase):
|
|
|
460
460
|
"""
|
|
461
461
|
self.assertEqual(
|
|
462
462
|
self.contract["contract"]["version"],
|
|
463
|
-
"0.
|
|
464
|
-
"adapter.toml [contract] version must be '0.
|
|
463
|
+
"0.17",
|
|
464
|
+
"adapter.toml [contract] version must be '0.17' after consolidated-pack-layout",
|
|
465
465
|
)
|
|
466
466
|
|
|
467
467
|
def test_claude_code_install_routes_includes_apm(self) -> None:
|
|
@@ -89,20 +89,21 @@ class OtherAdaptersOmitScopeTests(unittest.TestCase):
|
|
|
89
89
|
scope = contract["adapter"]["copilot"].get("scope")
|
|
90
90
|
self.assertIsNotNone(scope, "copilot [scope] block missing")
|
|
91
91
|
self.assertEqual(scope["repo"], ".")
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
92
|
+
# RFC-0052 / ADR-0040: copilot skill routes to the shared `.agents/skills/`
|
|
93
|
+
# cohort home (prepended to the list); agents + hooks stay under `.github/`.
|
|
94
|
+
# The `.github/skills/` prefix is retained to allow the path-jail to admit
|
|
95
|
+
# any existing `.github/skills/` files written by the previous contract.
|
|
96
96
|
self.assertEqual(
|
|
97
97
|
scope["allowed-prefixes"]["repo"],
|
|
98
|
-
[".github/skills/", ".github/agents/", ".github/hooks/"],
|
|
98
|
+
[".agents/skills/", ".github/skills/", ".github/agents/", ".github/hooks/"],
|
|
99
99
|
)
|
|
100
|
-
# User scope:
|
|
101
|
-
#
|
|
100
|
+
# User scope: RFC-0052 prepends `.agents/skills/` to the user list.
|
|
101
|
+
# `.copilot/skills/` is kept for path-jail compat; `.agentbundle/` stays.
|
|
102
102
|
self.assertEqual(scope["user"], "~")
|
|
103
103
|
self.assertEqual(
|
|
104
104
|
scope["allowed-prefixes"]["user"],
|
|
105
105
|
[
|
|
106
|
+
".agents/skills/",
|
|
106
107
|
".copilot/skills/",
|
|
107
108
|
".copilot/agents/",
|
|
108
109
|
".copilot/hooks/",
|
|
@@ -83,17 +83,20 @@ class TestContractV07(unittest.TestCase):
|
|
|
83
83
|
copilot_scope = self.contract["adapter"]["copilot"].get("scope")
|
|
84
84
|
self.assertIsNotNone(copilot_scope, "copilot scope table missing")
|
|
85
85
|
self.assertEqual(copilot_scope["repo"], ".")
|
|
86
|
-
#
|
|
87
|
-
# (
|
|
88
|
-
#
|
|
86
|
+
# RFC-0052 / ADR-0040: copilot skill now routes to `.agents/skills/`
|
|
87
|
+
# (shared cohort home, prepended); agents + hooks stay under `.github/`.
|
|
88
|
+
# `.github/skills/` is kept for path-jail compat with existing files.
|
|
89
89
|
self.assertEqual(
|
|
90
90
|
copilot_scope["allowed-prefixes"]["repo"],
|
|
91
|
-
[".github/skills/", ".github/agents/", ".github/hooks/"],
|
|
91
|
+
[".agents/skills/", ".github/skills/", ".github/agents/", ".github/hooks/"],
|
|
92
92
|
)
|
|
93
93
|
self.assertEqual(copilot_scope["user"], "~")
|
|
94
|
+
# RFC-0052: `.agents/skills/` prepended to user list too.
|
|
95
|
+
# `.copilot/skills/` retained for path-jail compat.
|
|
94
96
|
self.assertEqual(
|
|
95
97
|
copilot_scope["allowed-prefixes"]["user"],
|
|
96
98
|
[
|
|
99
|
+
".agents/skills/",
|
|
97
100
|
".copilot/skills/",
|
|
98
101
|
".copilot/agents/",
|
|
99
102
|
".copilot/hooks/",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""T5 (RFC-0052 / ADR-0039+0040): the `shared` prefix class + cohort routing.
|
|
2
|
+
|
|
3
|
+
The adapter contract gains a `[contract.shared-prefixes]` registry mapping each
|
|
4
|
+
shared prefix to its reader cohort (shipped adapters); every other prefix is
|
|
5
|
+
`private` by derivation. cursor/gemini/copilot route the `skill` primitive to
|
|
6
|
+
the shared `.agents/skills/` home (joining codex) and include it in
|
|
7
|
+
`allowed-prefixes` at both scopes. The two contract copies agree byte-for-byte.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import tomllib
|
|
13
|
+
import unittest
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_REPO_ROOT = Path(__file__).resolve().parents[5]
|
|
18
|
+
_DATA_COPY = (
|
|
19
|
+
_REPO_ROOT
|
|
20
|
+
/ "packages/agentbundle/agentbundle/_data/adapter.toml"
|
|
21
|
+
)
|
|
22
|
+
_DOCS_COPY = _REPO_ROOT / "docs/contracts/adapter.toml"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _contract() -> dict:
|
|
26
|
+
return tomllib.loads(_DATA_COPY.read_text(encoding="utf-8"))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SharedPrefixRegistryTests(unittest.TestCase):
|
|
30
|
+
def setUp(self) -> None:
|
|
31
|
+
self.contract = _contract()
|
|
32
|
+
self.shared = self.contract["contract"]["shared-prefixes"]
|
|
33
|
+
|
|
34
|
+
def test_contract_version_bumped(self) -> None:
|
|
35
|
+
self.assertEqual(self.contract["contract"]["version"], "0.17")
|
|
36
|
+
|
|
37
|
+
def test_agents_skills_shared_with_full_cohort(self) -> None:
|
|
38
|
+
self.assertEqual(
|
|
39
|
+
sorted(self.shared[".agents/skills/"]),
|
|
40
|
+
["codex", "copilot", "cursor", "gemini"],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def test_kiro_skills_shared_with_kiro_cohort(self) -> None:
|
|
44
|
+
self.assertEqual(
|
|
45
|
+
sorted(self.shared[".kiro/skills/"]),
|
|
46
|
+
["kiro-cli", "kiro-ide"],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def test_only_known_shared_prefixes_declared(self) -> None:
|
|
50
|
+
# Everything not in the registry is `private` by derivation; the
|
|
51
|
+
# registry itself holds exactly the two shared prefixes.
|
|
52
|
+
self.assertEqual(
|
|
53
|
+
set(self.shared), {".agents/skills/", ".kiro/skills/"}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def test_private_prefix_not_in_registry(self) -> None:
|
|
57
|
+
# A representative private prefix (claude-code's `.claude/`) is absent.
|
|
58
|
+
self.assertNotIn(".claude/", self.shared)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CohortSkillRoutingTests(unittest.TestCase):
|
|
62
|
+
def setUp(self) -> None:
|
|
63
|
+
self.contract = _contract()
|
|
64
|
+
|
|
65
|
+
def _skill_target(self, adapter: str) -> str:
|
|
66
|
+
for p in self.contract["adapter"][adapter]["projection"]:
|
|
67
|
+
if p.get("primitive") == "skill":
|
|
68
|
+
return p["target-path"]
|
|
69
|
+
raise AssertionError(f"no skill projection for {adapter}")
|
|
70
|
+
|
|
71
|
+
def test_cohort_skill_targets_shared_home(self) -> None:
|
|
72
|
+
for adapter in ("codex", "cursor", "gemini", "copilot"):
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
self._skill_target(adapter), ".agents/skills/",
|
|
75
|
+
f"{adapter} skill must route to the shared .agents/skills/ home",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def test_cohort_allowed_prefixes_include_shared_home_both_scopes(self) -> None:
|
|
79
|
+
for adapter in ("cursor", "gemini", "copilot"):
|
|
80
|
+
scope = self.contract["adapter"][adapter]["scope"]
|
|
81
|
+
for which in ("repo", "user"):
|
|
82
|
+
self.assertIn(
|
|
83
|
+
".agents/skills/", scope["allowed-prefixes"][which],
|
|
84
|
+
f"{adapter}.{which} must admit the shared skill prefix",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def test_cohort_agents_stay_native(self) -> None:
|
|
88
|
+
# ADR-0040 moves only the skill primitive; agents stay native.
|
|
89
|
+
for adapter, native in (
|
|
90
|
+
("cursor", ".cursor/agents/"),
|
|
91
|
+
("gemini", ".gemini/agents/"),
|
|
92
|
+
("copilot", ".github/agents/"),
|
|
93
|
+
):
|
|
94
|
+
agent = next(
|
|
95
|
+
p for p in self.contract["adapter"][adapter]["projection"]
|
|
96
|
+
if p.get("primitive") == "agent"
|
|
97
|
+
)
|
|
98
|
+
self.assertEqual(agent["target-path"], native)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ContractMirrorTests(unittest.TestCase):
|
|
102
|
+
def test_data_and_docs_agree_byte_for_byte(self) -> None:
|
|
103
|
+
self.assertEqual(
|
|
104
|
+
_DATA_COPY.read_bytes(),
|
|
105
|
+
_DOCS_COPY.read_bytes(),
|
|
106
|
+
"the _data/ and docs/contracts/ adapter.toml copies must be identical",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
unittest.main()
|
|
@@ -406,6 +406,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
406
406
|
sp.add_argument("pack_path", help="Path to the pack to diff against.")
|
|
407
407
|
sp.add_argument("--root", default=".")
|
|
408
408
|
sp.add_argument("--scope", choices=("repo", "user"))
|
|
409
|
+
sp.add_argument(
|
|
410
|
+
"--adapter",
|
|
411
|
+
choices=_shipped_adapters_choices(),
|
|
412
|
+
help=(
|
|
413
|
+
"Disambiguate when the pack is installed for multiple adapters at "
|
|
414
|
+
"the resolved scope (RFC-0052). Inferred when the pack has a single "
|
|
415
|
+
"adapter row; required when it has more than one."
|
|
416
|
+
),
|
|
417
|
+
)
|
|
409
418
|
sp.set_defaults(func=_lazy("diff"))
|
|
410
419
|
|
|
411
420
|
# --- upgrade --- (--scope disambiguator)
|
|
@@ -436,6 +445,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
436
445
|
)
|
|
437
446
|
sp.add_argument("--root", default=".")
|
|
438
447
|
sp.add_argument("--scope", choices=("repo", "user"))
|
|
448
|
+
sp.add_argument(
|
|
449
|
+
"--adapter",
|
|
450
|
+
choices=_shipped_adapters_choices(),
|
|
451
|
+
help=(
|
|
452
|
+
"Disambiguate when the pack is installed for multiple adapters at "
|
|
453
|
+
"the resolved scope (RFC-0052). Inferred when the pack has a single "
|
|
454
|
+
"adapter row; required when it has more than one."
|
|
455
|
+
),
|
|
456
|
+
)
|
|
439
457
|
sp.add_argument(
|
|
440
458
|
"--yes",
|
|
441
459
|
action="store_true",
|
|
@@ -464,6 +482,17 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
464
482
|
sp.add_argument("--pack", required=True)
|
|
465
483
|
sp.add_argument("--root", default=".")
|
|
466
484
|
sp.add_argument("--scope", choices=("repo", "user"))
|
|
485
|
+
sp.add_argument(
|
|
486
|
+
"--adapter",
|
|
487
|
+
choices=_shipped_adapters_choices(),
|
|
488
|
+
help=(
|
|
489
|
+
"Disambiguate when the pack is installed for multiple adapters at "
|
|
490
|
+
"the resolved scope (RFC-0052). Inferred when the pack has a single "
|
|
491
|
+
"adapter row; required when it has more than one. Only the named "
|
|
492
|
+
"adapter row is uninstalled; shared files survive while a sibling "
|
|
493
|
+
"row still owns them."
|
|
494
|
+
),
|
|
495
|
+
)
|
|
467
496
|
sp.add_argument(
|
|
468
497
|
"--yes",
|
|
469
498
|
action="store_true",
|