agentbundle 0.3.0__tar.gz → 0.4.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.4.0/PKG-INFO +97 -0
- agentbundle-0.4.0/README.md +79 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/adapter.toml +9 -1
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/pack.schema.json +38 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/plugin-manifest.derived.schema.json +10 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/plugin-manifest.schema.json +11 -1
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/main.py +108 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/self_host.py +20 -1
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_cursor.py +3 -2
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_gemini.py +18 -4
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +4 -4
- agentbundle-0.4.0/agentbundle/build/tests/test_architect_design_reviewer_projection.py +88 -0
- agentbundle-0.4.0/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +74 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract.py +8 -8
- agentbundle-0.4.0/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +94 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema.py +150 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +69 -0
- agentbundle-0.4.0/agentbundle/build/tests/test_projectable_subset.py +188 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_self_host_check.py +39 -4
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/validate.py +10 -1
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/catalogue.py +3 -2
- agentbundle-0.4.0/agentbundle/categories.py +60 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/list_packs.py +17 -1
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/validate.py +20 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/config.py +1 -1
- agentbundle-0.4.0/agentbundle.egg-info/PKG-INFO +97 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/SOURCES.txt +5 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/pyproject.toml +3 -3
- agentbundle-0.3.0/PKG-INFO +0 -37
- agentbundle-0.3.0/README.md +0 -19
- agentbundle-0.3.0/agentbundle.egg-info/PKG-INFO +0 -37
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/__main__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/adapter.schema.json +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/install-marker.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/__main__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapter_root_bins.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/claude_code.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/codex.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/copilot.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/cursor.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/gemini.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/contract.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/lint_packs.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/phase_order.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/direct_directory.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/hook_id.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/merge_json.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/user_merge_json.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/scope_rails.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/shared_libs.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/target_resolver.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pipeline.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_security.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_validate.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/user_libs.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/cli.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/__init__.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/_common.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/_drop_warning.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/adapt.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/config.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/diff.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/init_state.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/install.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/list_targets.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/reconcile.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/render.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/scaffold.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/uninstall.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/upgrade.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/render.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/safety.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/scope.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/user_config.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/version.py +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/dependency_links.txt +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/entry_points.txt +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/top_level.txt +0 -0
- {agentbundle-0.3.0 → agentbundle-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentbundle
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: npm for your coding agent. Install packs of skills, subagents, and hooks into any repo, for every major agent.
|
|
5
|
+
Author-email: eugenelim <eugenelim@users.noreply.github.com>
|
|
6
|
+
License: Apache-2.0 OR MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/eugenelim/agent-ready-repo
|
|
8
|
+
Project-URL: Source, https://github.com/eugenelim/agent-ready-repo
|
|
9
|
+
Project-URL: Documentation, https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/install-agentbundle-from-clone.md
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# agentbundle
|
|
20
|
+
|
|
21
|
+
[](https://pypi.org/project/agentbundle/)
|
|
22
|
+
[](https://pypi.org/project/agentbundle/)
|
|
23
|
+
[](https://github.com/eugenelim/agent-ready-repo#license)
|
|
24
|
+
|
|
25
|
+
**The installer for [agent-ready-repo](https://github.com/eugenelim/agent-ready-repo).** Think npm, but for the skills, subagents, and hooks your coding agent runs on.
|
|
26
|
+
|
|
27
|
+
`agentbundle` installs packs of agent primitives into your repo or your home directory. It projects each primitive into the layout every agent expects. One pack. One command. Every major agent.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install agentbundle
|
|
31
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That lands `core`, the flagship pack and the loop itself, in your repo. Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro all read it.
|
|
35
|
+
|
|
36
|
+
## Common commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# See what's in a catalogue
|
|
40
|
+
agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
|
|
41
|
+
|
|
42
|
+
# Install the flagship loop into this repo
|
|
43
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
|
|
44
|
+
|
|
45
|
+
# Install a pack at user scope, so it follows you across every project
|
|
46
|
+
agentbundle install --pack research git+https://github.com/eugenelim/agent-ready-repo --scope user
|
|
47
|
+
|
|
48
|
+
# Preview an install without writing a file
|
|
49
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --dry-run
|
|
50
|
+
|
|
51
|
+
# Upgrade a pack; it reports the .upstream files you need to reconcile
|
|
52
|
+
agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override.
|
|
56
|
+
|
|
57
|
+
## Build your own catalogue
|
|
58
|
+
|
|
59
|
+
`agentbundle` isn't tied to the agent-ready-repo catalogue. Any repo that lays its packs out the same way can use it. A pack is a directory:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
my-pack/
|
|
63
|
+
pack.toml # name, version, adapter-contract, install scope,
|
|
64
|
+
# plus rich metadata (license, maintainers, links,
|
|
65
|
+
# categories, keywords) and a README pointer
|
|
66
|
+
.claude-plugin/
|
|
67
|
+
plugin.json # Claude Code plugin manifest (hand-authored)
|
|
68
|
+
README.md # the pack's portable doc — projected with the pack
|
|
69
|
+
.apm/ # primitives — projected by the build pipeline
|
|
70
|
+
skills/<name>/
|
|
71
|
+
SKILL.md # the skill body; one folder per skill
|
|
72
|
+
references/ # progressive-disclosure docs, loaded on demand
|
|
73
|
+
assets/ # templates the skill copies into the repo
|
|
74
|
+
agents/<name>.md # subagents
|
|
75
|
+
hooks/<name>.py # lifecycle hooks
|
|
76
|
+
seeds/ # files scaffolded into the adopter repo
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`pack.toml` is the **single source of truth** for a pack's metadata. Declare
|
|
80
|
+
`license`, `[[pack.maintainers]]`, `[pack.links]`, `categories`, and
|
|
81
|
+
`keywords` once; the build projects the cleanly-mappable subset — plus the
|
|
82
|
+
pack's `README.md` — into each distribution route's manifest (the `plugin.json`
|
|
83
|
+
/ `marketplace.json` entry), so the catalogue describes each pack richly rather
|
|
84
|
+
than with a single sentence. Extra fields stay in `pack.toml`; the projection
|
|
85
|
+
is deliberately lossy per tool.
|
|
86
|
+
|
|
87
|
+
Point a catalogue URI (a git URL or a local path) at the repo that holds your packs. Then `validate` a pack against the adapter contract, `render` it to preview the projection, and `install` it into a target repo. `scaffold` drops a pack's seeds into a fresh directory to start from. The build pipeline (`agentbundle.build`) is the same engine `make build` runs.
|
|
88
|
+
|
|
89
|
+
See the [pack layout reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/architecture/pack-layout.md) and [authoring a skill](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/author-a-skill.md).
|
|
90
|
+
|
|
91
|
+
## Credentials
|
|
92
|
+
|
|
93
|
+
`agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](https://pypi.org/project/credbroker/), a standalone resolver that keeps cleartext out of the model's reach.
|
|
94
|
+
|
|
95
|
+
## Learn more
|
|
96
|
+
|
|
97
|
+
The full story — the loop, the reviewers, the pack catalogue — is in the [agent-ready-repo README](https://github.com/eugenelim/agent-ready-repo#readme).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# agentbundle
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/agentbundle/)
|
|
4
|
+
[](https://pypi.org/project/agentbundle/)
|
|
5
|
+
[](https://github.com/eugenelim/agent-ready-repo#license)
|
|
6
|
+
|
|
7
|
+
**The installer for [agent-ready-repo](https://github.com/eugenelim/agent-ready-repo).** Think npm, but for the skills, subagents, and hooks your coding agent runs on.
|
|
8
|
+
|
|
9
|
+
`agentbundle` installs packs of agent primitives into your repo or your home directory. It projects each primitive into the layout every agent expects. One pack. One command. Every major agent.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install agentbundle
|
|
13
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That lands `core`, the flagship pack and the loop itself, in your repo. Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro all read it.
|
|
17
|
+
|
|
18
|
+
## Common commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# See what's in a catalogue
|
|
22
|
+
agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
|
|
23
|
+
|
|
24
|
+
# Install the flagship loop into this repo
|
|
25
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
|
|
26
|
+
|
|
27
|
+
# Install a pack at user scope, so it follows you across every project
|
|
28
|
+
agentbundle install --pack research git+https://github.com/eugenelim/agent-ready-repo --scope user
|
|
29
|
+
|
|
30
|
+
# Preview an install without writing a file
|
|
31
|
+
agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --dry-run
|
|
32
|
+
|
|
33
|
+
# Upgrade a pack; it reports the .upstream files you need to reconcile
|
|
34
|
+
agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override.
|
|
38
|
+
|
|
39
|
+
## Build your own catalogue
|
|
40
|
+
|
|
41
|
+
`agentbundle` isn't tied to the agent-ready-repo catalogue. Any repo that lays its packs out the same way can use it. A pack is a directory:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
my-pack/
|
|
45
|
+
pack.toml # name, version, adapter-contract, install scope,
|
|
46
|
+
# plus rich metadata (license, maintainers, links,
|
|
47
|
+
# categories, keywords) and a README pointer
|
|
48
|
+
.claude-plugin/
|
|
49
|
+
plugin.json # Claude Code plugin manifest (hand-authored)
|
|
50
|
+
README.md # the pack's portable doc — projected with the pack
|
|
51
|
+
.apm/ # primitives — projected by the build pipeline
|
|
52
|
+
skills/<name>/
|
|
53
|
+
SKILL.md # the skill body; one folder per skill
|
|
54
|
+
references/ # progressive-disclosure docs, loaded on demand
|
|
55
|
+
assets/ # templates the skill copies into the repo
|
|
56
|
+
agents/<name>.md # subagents
|
|
57
|
+
hooks/<name>.py # lifecycle hooks
|
|
58
|
+
seeds/ # files scaffolded into the adopter repo
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`pack.toml` is the **single source of truth** for a pack's metadata. Declare
|
|
62
|
+
`license`, `[[pack.maintainers]]`, `[pack.links]`, `categories`, and
|
|
63
|
+
`keywords` once; the build projects the cleanly-mappable subset — plus the
|
|
64
|
+
pack's `README.md` — into each distribution route's manifest (the `plugin.json`
|
|
65
|
+
/ `marketplace.json` entry), so the catalogue describes each pack richly rather
|
|
66
|
+
than with a single sentence. Extra fields stay in `pack.toml`; the projection
|
|
67
|
+
is deliberately lossy per tool.
|
|
68
|
+
|
|
69
|
+
Point a catalogue URI (a git URL or a local path) at the repo that holds your packs. Then `validate` a pack against the adapter contract, `render` it to preview the projection, and `install` it into a target repo. `scaffold` drops a pack's seeds into a fresh directory to start from. The build pipeline (`agentbundle.build`) is the same engine `make build` runs.
|
|
70
|
+
|
|
71
|
+
See the [pack layout reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/architecture/pack-layout.md) and [authoring a skill](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/author-a-skill.md).
|
|
72
|
+
|
|
73
|
+
## Credentials
|
|
74
|
+
|
|
75
|
+
`agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](https://pypi.org/project/credbroker/), a standalone resolver that keeps cleartext out of the model's reach.
|
|
76
|
+
|
|
77
|
+
## Learn more
|
|
78
|
+
|
|
79
|
+
The full story — the loop, the reviewers, the pack catalogue — is in the [agent-ready-repo README](https://github.com/eugenelim/agent-ready-repo#readme).
|
|
@@ -63,7 +63,15 @@
|
|
|
63
63
|
# kept and name-mapped; tier-preserving model map. hook-body lands under
|
|
64
64
|
# `.gemini/hooks/` (the cursor model — single prefix at both scopes). kiro-ide-hook
|
|
65
65
|
# dropped. Distribution-only (not in SELF_HOST_ADAPTERS).
|
|
66
|
-
|
|
66
|
+
# RFC-0031 / ADR-0021 / docs/specs/enriched-pack-manifest (v0.14): pack.toml
|
|
67
|
+
# becomes the rich metadata source of truth (optional readme / display_name /
|
|
68
|
+
# license / maintainers / links / categories / keywords / [pack.metadata.<tool>]
|
|
69
|
+
# / [pack].catalogue). The build projects the cleanly-projectable subset into
|
|
70
|
+
# the claude-plugins + apm routes (plugin.json / marketplace.json entry) plus
|
|
71
|
+
# each pack's README. All new fields are optional — packs pinned below v0.14
|
|
72
|
+
# build and validate unchanged. Declaration + projection only (no @catalogue/pack
|
|
73
|
+
# resolution, no search, no persisted index).
|
|
74
|
+
version = "0.14"
|
|
67
75
|
|
|
68
76
|
# Sibling schemas this contract references — pack metadata and the
|
|
69
77
|
# per-pack Claude-plugin manifest. Defined by spec AC #3 + #4.
|
|
@@ -9,6 +9,44 @@
|
|
|
9
9
|
"name": {"type": "string"},
|
|
10
10
|
"version": {"type": "string"},
|
|
11
11
|
"description": {"type": "string"},
|
|
12
|
+
"readme": {"type": "string"},
|
|
13
|
+
"display_name": {"type": "string"},
|
|
14
|
+
"license": {"type": "string"},
|
|
15
|
+
"catalogue": {"type": "string"},
|
|
16
|
+
"categories": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": {"type": "string"},
|
|
19
|
+
"maxItems": 5
|
|
20
|
+
},
|
|
21
|
+
"keywords": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {"type": "string"},
|
|
24
|
+
"maxItems": 5
|
|
25
|
+
},
|
|
26
|
+
"maintainers": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"required": ["name"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"name": {"type": "string"},
|
|
33
|
+
"email": {"type": "string"},
|
|
34
|
+
"url": {"type": "string"}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"links": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"homepage": {"type": "string"},
|
|
42
|
+
"repository": {"type": "string"},
|
|
43
|
+
"documentation": {"type": "string"},
|
|
44
|
+
"changelog": {"type": "string"},
|
|
45
|
+
"issues": {"type": "string"},
|
|
46
|
+
"icon": {"type": "string"}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"metadata": {"type": "object"},
|
|
12
50
|
"adapter-contract": {
|
|
13
51
|
"type": "object",
|
|
14
52
|
"properties": {
|
{agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/plugin-manifest.derived.schema.json
RENAMED
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
"type": "array",
|
|
15
15
|
"items": {"type": "string"}
|
|
16
16
|
},
|
|
17
|
+
"author": {"type": "string"},
|
|
18
|
+
"license": {"type": "string"},
|
|
19
|
+
"homepage": {"type": "string"},
|
|
20
|
+
"repository": {"type": "string"},
|
|
21
|
+
"keywords": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {"type": "string"}
|
|
24
|
+
},
|
|
25
|
+
"category": {"type": "string"},
|
|
26
|
+
"displayName": {"type": "string"},
|
|
17
27
|
"hooks": {
|
|
18
28
|
"type": "object",
|
|
19
29
|
"properties": {
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
"agents": {
|
|
14
14
|
"type": "array",
|
|
15
15
|
"items": {"type": "string"}
|
|
16
|
-
}
|
|
16
|
+
},
|
|
17
|
+
"author": {"type": "string"},
|
|
18
|
+
"license": {"type": "string"},
|
|
19
|
+
"homepage": {"type": "string"},
|
|
20
|
+
"repository": {"type": "string"},
|
|
21
|
+
"keywords": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {"type": "string"}
|
|
24
|
+
},
|
|
25
|
+
"category": {"type": "string"},
|
|
26
|
+
"displayName": {"type": "string"}
|
|
17
27
|
}
|
|
18
28
|
}
|
|
@@ -137,6 +137,23 @@ def _read_install_marker_template() -> bytes:
|
|
|
137
137
|
return (REPO_ROOT / "packages" / "agentbundle" / "templates" / "install-marker.py").read_bytes()
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
def _project_pack_readme(pack_path: Path, per_pack_output: Path) -> None:
|
|
141
|
+
"""Copy a pack's ``README.md`` into its per-pack dist route, if present.
|
|
142
|
+
|
|
143
|
+
enriched-pack-manifest T5: the README is the sole portable per-pack doc,
|
|
144
|
+
and the manifest's ``readme = "README.md"`` pointer resolves relative to
|
|
145
|
+
the route directory. A pack without a README projects none and does not
|
|
146
|
+
error (the ``readme`` field is then simply absent / unresolved — never a
|
|
147
|
+
build failure). ``follow_symlinks=False`` mirrors the pack.toml copy so a
|
|
148
|
+
symlinked README is not dereferenced into ``dist/`` at build time.
|
|
149
|
+
"""
|
|
150
|
+
readme_src = pack_path / "README.md"
|
|
151
|
+
if readme_src.is_file():
|
|
152
|
+
shutil.copy2(
|
|
153
|
+
readme_src, per_pack_output / "README.md", follow_symlinks=False
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
140
157
|
def validate_derived_plugin_manifest_dict(manifest: dict, label: str = "<derived>") -> None:
|
|
141
158
|
"""Validate an in-memory derived plugin manifest dict against the derived schema.
|
|
142
159
|
|
|
@@ -161,6 +178,78 @@ def validate_derived_plugin_manifest(plugin_json_path: Path) -> None:
|
|
|
161
178
|
manifest = json.loads(plugin_json_path.read_text(encoding="utf-8"))
|
|
162
179
|
validate_derived_plugin_manifest_dict(manifest, label=str(plugin_json_path))
|
|
163
180
|
|
|
181
|
+
|
|
182
|
+
def derive_projectable_subset(pack_toml: dict) -> dict:
|
|
183
|
+
"""Map a parsed ``pack.toml`` to the projectable plugin-manifest subset.
|
|
184
|
+
|
|
185
|
+
enriched-pack-manifest (RFC-0031 / ADR-0021): ``pack.toml`` is the rich
|
|
186
|
+
metadata source of truth; the build projects a *lossy*, schema-compliant
|
|
187
|
+
subset into the claude-plugins + apm routes (the ``plugin.json`` /
|
|
188
|
+
``marketplace.json`` entry). Fixed mapping:
|
|
189
|
+
|
|
190
|
+
- ``author`` ← first ``[[pack.maintainers]]``, rendered
|
|
191
|
+
``"Name <email>"`` (name alone when no email).
|
|
192
|
+
- ``license`` ← ``[pack].license`` (verbatim).
|
|
193
|
+
- ``homepage`` ← ``[pack.links].homepage`` (verbatim).
|
|
194
|
+
- ``repository`` ← ``[pack.links].repository`` (verbatim).
|
|
195
|
+
- ``keywords`` ← ``[pack].keywords`` (string entries, verbatim).
|
|
196
|
+
- ``category`` ← ``categories[0]``.
|
|
197
|
+
- ``displayName`` ← ``[pack].display_name``.
|
|
198
|
+
|
|
199
|
+
**Emit-only-when-present** is the load-bearing invariant: a key appears in
|
|
200
|
+
the output only when its source field is present and non-empty, so a
|
|
201
|
+
legacy ``pack.toml`` declaring none of the enriched fields yields ``{}``
|
|
202
|
+
and the projected manifest is byte-identical to the pre-enrichment output
|
|
203
|
+
(legacy-invariance AC). This is a pure function — no I/O, no schema read.
|
|
204
|
+
"""
|
|
205
|
+
pack = pack_toml.get("pack", {})
|
|
206
|
+
if not isinstance(pack, dict):
|
|
207
|
+
return {}
|
|
208
|
+
out: dict = {}
|
|
209
|
+
|
|
210
|
+
maintainers = pack.get("maintainers")
|
|
211
|
+
if isinstance(maintainers, list) and maintainers:
|
|
212
|
+
first = maintainers[0]
|
|
213
|
+
if isinstance(first, dict):
|
|
214
|
+
name = first.get("name")
|
|
215
|
+
email = first.get("email")
|
|
216
|
+
if isinstance(name, str) and name:
|
|
217
|
+
if isinstance(email, str) and email:
|
|
218
|
+
out["author"] = f"{name} <{email}>"
|
|
219
|
+
else:
|
|
220
|
+
out["author"] = name
|
|
221
|
+
|
|
222
|
+
license_ = pack.get("license")
|
|
223
|
+
if isinstance(license_, str) and license_:
|
|
224
|
+
out["license"] = license_
|
|
225
|
+
|
|
226
|
+
links = pack.get("links")
|
|
227
|
+
if isinstance(links, dict):
|
|
228
|
+
homepage = links.get("homepage")
|
|
229
|
+
if isinstance(homepage, str) and homepage:
|
|
230
|
+
out["homepage"] = homepage
|
|
231
|
+
repository = links.get("repository")
|
|
232
|
+
if isinstance(repository, str) and repository:
|
|
233
|
+
out["repository"] = repository
|
|
234
|
+
|
|
235
|
+
keywords = pack.get("keywords")
|
|
236
|
+
if isinstance(keywords, list):
|
|
237
|
+
kws = [k for k in keywords if isinstance(k, str) and k]
|
|
238
|
+
if kws:
|
|
239
|
+
out["keywords"] = kws
|
|
240
|
+
|
|
241
|
+
categories = pack.get("categories")
|
|
242
|
+
if isinstance(categories, list) and categories:
|
|
243
|
+
first_cat = categories[0]
|
|
244
|
+
if isinstance(first_cat, str) and first_cat:
|
|
245
|
+
out["category"] = first_cat
|
|
246
|
+
|
|
247
|
+
display_name = pack.get("display_name")
|
|
248
|
+
if isinstance(display_name, str) and display_name:
|
|
249
|
+
out["displayName"] = display_name
|
|
250
|
+
|
|
251
|
+
return out
|
|
252
|
+
|
|
164
253
|
# The three RFC-0001 recipes that plain `make build` invokes.
|
|
165
254
|
# RFC-0002 recipes (per-pack-overlay, composite-agents-md,
|
|
166
255
|
# composite-marketplace) fire only under --self.
|
|
@@ -392,6 +481,15 @@ def _run_per_pack_single(
|
|
|
392
481
|
derived["hooks"] = {
|
|
393
482
|
"SessionStart": [{"command": _SESSION_START_COMMAND}]
|
|
394
483
|
}
|
|
484
|
+
# enriched-pack-manifest: merge the projectable metadata subset derived
|
|
485
|
+
# from this pack's pack.toml (emit-only-when-present, so a legacy pack
|
|
486
|
+
# adds no keys and the output stays byte-identical).
|
|
487
|
+
pack_toml_for_subset = pack.path / "pack.toml"
|
|
488
|
+
if pack_toml_for_subset.exists():
|
|
489
|
+
pack_meta = tomllib.loads(
|
|
490
|
+
pack_toml_for_subset.read_text(encoding="utf-8")
|
|
491
|
+
)
|
|
492
|
+
derived.update(derive_projectable_subset(pack_meta))
|
|
395
493
|
# Validate the derived manifest IN MEMORY before writing to disk
|
|
396
494
|
# (Blocker-3: pre-write validation so a synthesis bug never lands
|
|
397
495
|
# a malformed plugin.json in dist/).
|
|
@@ -411,6 +509,12 @@ def _run_per_pack_single(
|
|
|
411
509
|
if pack_toml_src.exists():
|
|
412
510
|
shutil.copy2(pack_toml_src, per_pack_output / "pack.toml", follow_symlinks=False)
|
|
413
511
|
|
|
512
|
+
# enriched-pack-manifest T5: project the pack's README.md into the route so
|
|
513
|
+
# the manifest's `readme = "README.md"` pointer resolves. The README is the
|
|
514
|
+
# sole portable per-pack doc. follow_symlinks=False mirrors the pack.toml
|
|
515
|
+
# copy's posture (a symlinked README is not dereferenced into dist/).
|
|
516
|
+
_project_pack_readme(pack.path, per_pack_output)
|
|
517
|
+
|
|
414
518
|
# Project the canonical install-marker.py writer into scripts/.
|
|
415
519
|
scripts_dir = per_pack_output / ".claude-plugin" / "scripts"
|
|
416
520
|
scripts_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -478,6 +582,10 @@ def _run_per_pack_apm(recipe: Recipe, packs: list[Pack], output_dir: Path) -> di
|
|
|
478
582
|
follow_symlinks=False,
|
|
479
583
|
)
|
|
480
584
|
|
|
585
|
+
# enriched-pack-manifest T5: project the pack's README into the APM
|
|
586
|
+
# route too (the sole portable per-pack doc; same posture as above).
|
|
587
|
+
_project_pack_readme(pack.path, per_pack_output)
|
|
588
|
+
|
|
481
589
|
# Issue #190 / RFC-0001 §595: ship the pack's seeds/ inside the APM
|
|
482
590
|
# package so the governance content travels with the pack on the APM
|
|
483
591
|
# route. symlinks=True preserves a seed symlink as a symlink rather
|
|
@@ -39,6 +39,7 @@ import shutil
|
|
|
39
39
|
import stat
|
|
40
40
|
import subprocess
|
|
41
41
|
import sys
|
|
42
|
+
import tomllib
|
|
42
43
|
import tempfile
|
|
43
44
|
from pathlib import Path
|
|
44
45
|
|
|
@@ -47,6 +48,7 @@ from agentbundle.build.contract import load as load_contract
|
|
|
47
48
|
from agentbundle.build.main import (
|
|
48
49
|
CONTRACT_PATH,
|
|
49
50
|
REPO_ROOT,
|
|
51
|
+
derive_projectable_subset,
|
|
50
52
|
discover_packs,
|
|
51
53
|
validate_pack_uniqueness,
|
|
52
54
|
)
|
|
@@ -480,6 +482,14 @@ def _project_seeds(packs_dir: Path, output_root: Path) -> dict[Path, Path]:
|
|
|
480
482
|
# For re-install / self-host against this repo, Manual targets
|
|
481
483
|
# exist and are preserved.
|
|
482
484
|
for relative, src in seen.items():
|
|
485
|
+
# Guides are repo-owned and reach adopters via `deliver_seeds` at
|
|
486
|
+
# install, not via self-host projection. Never scaffold the
|
|
487
|
+
# by-quadrant guide tree here: the seed stays by-quadrant for
|
|
488
|
+
# adopters, but writing it during self-host litters a repo that
|
|
489
|
+
# owns its guides (e.g. organized by pack) with untracked
|
|
490
|
+
# `docs/guides/<quadrant>/README.md` on every build-self run.
|
|
491
|
+
if relative.as_posix().startswith("docs/guides/"):
|
|
492
|
+
continue
|
|
483
493
|
if _is_excluded(relative) and (output_root / relative).exists():
|
|
484
494
|
# Manual file on disk — leave it alone. The seed is
|
|
485
495
|
# placeholder; the on-disk file is the adopter's
|
|
@@ -507,7 +517,16 @@ def _aggregate_marketplace(
|
|
|
507
517
|
continue
|
|
508
518
|
manifest = pack_path / ".claude-plugin" / "plugin.json"
|
|
509
519
|
if manifest.exists():
|
|
510
|
-
|
|
520
|
+
entry = json.loads(manifest.read_text(encoding="utf-8"))
|
|
521
|
+
# enriched-pack-manifest: surface the projectable metadata subset
|
|
522
|
+
# (author / license / links / keywords / category / displayName)
|
|
523
|
+
# derived from pack.toml, so the catalogue entry is described
|
|
524
|
+
# richly. Emit-only-when-present keeps legacy entries byte-identical.
|
|
525
|
+
pack_meta = tomllib.loads(
|
|
526
|
+
(pack_path / "pack.toml").read_text(encoding="utf-8")
|
|
527
|
+
)
|
|
528
|
+
entry.update(derive_projectable_subset(pack_meta))
|
|
529
|
+
entries.append(entry)
|
|
511
530
|
target = output_root / ".claude-plugin" / "marketplace.json"
|
|
512
531
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
513
532
|
payload = {
|
|
@@ -52,9 +52,10 @@ class CursorContractTests(unittest.TestCase):
|
|
|
52
52
|
|
|
53
53
|
def test_contract_version_is_0_11(self) -> None:
|
|
54
54
|
"""AC1 — contract bumped to 0.11 by cursor-full-parity; subsequently
|
|
55
|
-
0.12 (copilot-skills-and-web)
|
|
55
|
+
0.12 (copilot-skills-and-web), 0.13 (docs/specs/gemini-full-parity),
|
|
56
|
+
then 0.14 (docs/specs/enriched-pack-manifest).
|
|
56
57
|
Name preserved to keep the diff small."""
|
|
57
|
-
self.assertEqual(self.contract["contract"]["version"], "0.
|
|
58
|
+
self.assertEqual(self.contract["contract"]["version"], "0.14")
|
|
58
59
|
|
|
59
60
|
def test_cursor_block_projects_five_primitives(self) -> None:
|
|
60
61
|
"""AC2 — the five standard primitives are in the projection array."""
|
|
@@ -150,9 +150,10 @@ class GeminiContractTests(unittest.TestCase):
|
|
|
150
150
|
def setUpClass(cls) -> None:
|
|
151
151
|
cls.contract = load_contract(CONTRACT_PATH)
|
|
152
152
|
|
|
153
|
-
def
|
|
154
|
-
"""
|
|
155
|
-
|
|
153
|
+
def test_contract_version_is_0_14(self) -> None:
|
|
154
|
+
"""Contract version is 0.14 (docs/specs/enriched-pack-manifest bumped it
|
|
155
|
+
from gemini-full-parity's 0.13). Renamed from the 0_13 method."""
|
|
156
|
+
self.assertEqual(self.contract["contract"]["version"], "0.14")
|
|
156
157
|
|
|
157
158
|
def test_gemini_block_projects_five_primitives(self) -> None:
|
|
158
159
|
"""AC2 — five standard primitives with their gemini targets."""
|
|
@@ -591,9 +592,18 @@ class GeminiAllPacksAdmissibleTests(unittest.TestCase):
|
|
|
591
592
|
from agentbundle.commands.install import _resolve_target_adapter
|
|
592
593
|
|
|
593
594
|
pack_dirs = sorted(p for p in self.PACKS_DIR.iterdir() if (p / "pack.toml").exists())
|
|
594
|
-
self.
|
|
595
|
+
self.assertTrue(pack_dirs, "no packs discovered under packs/ — pack lookup is broken")
|
|
596
|
+
# Count-independent by design: don't pin the number of packs (every new
|
|
597
|
+
# pack would break an unrelated adapter test). Assert gemini resolution
|
|
598
|
+
# for the packs that *support* gemini — a pack with no allowed-adapters
|
|
599
|
+
# list admits any shipped adapter; a pack with a list must name gemini.
|
|
600
|
+
# Packs that constrain to a list without gemini are skipped, not failed.
|
|
601
|
+
# `checked` is the non-vacuity guard the old hard count used to be.
|
|
602
|
+
checked = 0
|
|
595
603
|
for pack_dir in pack_dirs:
|
|
596
604
|
allowed = self._allowed_adapters(pack_dir)
|
|
605
|
+
if allowed is not None and "gemini" not in allowed:
|
|
606
|
+
continue
|
|
597
607
|
for scope in ("repo", "user"):
|
|
598
608
|
resolved = _resolve_target_adapter(
|
|
599
609
|
pack_dir,
|
|
@@ -607,6 +617,10 @@ class GeminiAllPacksAdmissibleTests(unittest.TestCase):
|
|
|
607
617
|
resolved, "gemini",
|
|
608
618
|
f"{pack_dir.name} @ {scope}: --adapter gemini was not admitted",
|
|
609
619
|
)
|
|
620
|
+
checked += 1
|
|
621
|
+
self.assertTrue(
|
|
622
|
+
checked, "no pack exercised gemini admission — the check ran vacuously"
|
|
623
|
+
)
|
|
610
624
|
|
|
611
625
|
|
|
612
626
|
if __name__ == "__main__":
|
|
@@ -134,13 +134,13 @@ class KiroIdeAdapterTests(unittest.TestCase):
|
|
|
134
134
|
self.assertIn(".kiro.hook", target_repo)
|
|
135
135
|
|
|
136
136
|
def test_contract_version_is_0_9(self) -> None:
|
|
137
|
-
"""Contract version is 0.
|
|
138
|
-
|
|
137
|
+
"""Contract version is 0.14 (docs/specs/enriched-pack-manifest, atop
|
|
138
|
+
gemini-full-parity's 0.13 and copilot-skills-and-web's 0.12).
|
|
139
139
|
Name preserved to keep the diff small."""
|
|
140
140
|
self.assertEqual(
|
|
141
141
|
self.contract["contract"]["version"],
|
|
142
|
-
"0.
|
|
143
|
-
"adapter.toml [contract] version must be '0.
|
|
142
|
+
"0.14",
|
|
143
|
+
"adapter.toml [contract] version must be '0.14' after enriched-pack-manifest",
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
def test_kiro_ide_hook_projects_with_flat_prefix_path(self) -> None:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Durable projection check for architect's `design-reviewer` subagent (RFC-0032).
|
|
2
|
+
|
|
3
|
+
architect-design-reviewer spec AC10: the agent must project across **all seven**
|
|
4
|
+
adapters architect declares in `allowed-adapters`. The per-adapter projection
|
|
5
|
+
*mechanism* is covered elsewhere with synthetic agents; this test pins the
|
|
6
|
+
*real* architect agent across every route so a future adapter change that drops
|
|
7
|
+
it fails here. It also lands RFC-0032's deferred `kiro-cli` confirmation
|
|
8
|
+
(`kiro-cli` is the one adapter architect ships that the research pack does not).
|
|
9
|
+
|
|
10
|
+
Naming varies per adapter (codex emits `.toml`, copilot `.agent.md`, kiro remaps
|
|
11
|
+
frontmatter, …), so the assertion globs `design-reviewer*` under the projected
|
|
12
|
+
output rather than hard-coding one extension.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import tempfile
|
|
18
|
+
import tomllib
|
|
19
|
+
import unittest
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from agentbundle.build.adapters import ADAPTERS
|
|
23
|
+
from agentbundle.build.contract import load as load_contract
|
|
24
|
+
|
|
25
|
+
REPO_ROOT = Path(__file__).resolve().parents[5]
|
|
26
|
+
CONTRACT_PATH = REPO_ROOT / "docs" / "contracts" / "adapter.toml"
|
|
27
|
+
ARCHITECT_PACK = REPO_ROOT / "packs" / "architect"
|
|
28
|
+
AGENT_NAME = "design-reviewer"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _architect_allowed_adapters() -> list[str]:
|
|
32
|
+
data = tomllib.loads((ARCHITECT_PACK / "pack.toml").read_text(encoding="utf-8"))
|
|
33
|
+
return list(data["pack"]["install"]["allowed-adapters"])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ArchitectDesignReviewerProjectionTests(unittest.TestCase):
|
|
37
|
+
@classmethod
|
|
38
|
+
def setUpClass(cls) -> None:
|
|
39
|
+
cls.contract = load_contract(CONTRACT_PATH)
|
|
40
|
+
cls.adapters = _architect_allowed_adapters()
|
|
41
|
+
|
|
42
|
+
def test_source_agent_present(self) -> None:
|
|
43
|
+
self.assertTrue(
|
|
44
|
+
(ARCHITECT_PACK / ".apm" / "agents" / f"{AGENT_NAME}.md").exists(),
|
|
45
|
+
"architect must ship .apm/agents/design-reviewer.md",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def test_allowed_adapters_are_the_expected_seven(self) -> None:
|
|
49
|
+
# Pin the seven so this test (and AC10's "all seven") stays honest if the
|
|
50
|
+
# list ever changes — update deliberately, with the projection re-checked.
|
|
51
|
+
self.assertEqual(
|
|
52
|
+
set(self.adapters),
|
|
53
|
+
{"claude-code", "codex", "copilot", "kiro-ide", "kiro-cli", "cursor", "gemini"},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def test_design_reviewer_projects_for_every_allowed_adapter(self) -> None:
|
|
57
|
+
for adapter in self.adapters:
|
|
58
|
+
with self.subTest(adapter=adapter):
|
|
59
|
+
self.assertIn(adapter, ADAPTERS, f"no projector registered for {adapter!r}")
|
|
60
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
61
|
+
out = Path(tmp) / "out"
|
|
62
|
+
ADAPTERS[adapter](ARCHITECT_PACK, self.contract, out)
|
|
63
|
+
hits = list(out.rglob(f"{AGENT_NAME}*"))
|
|
64
|
+
self.assertTrue(
|
|
65
|
+
hits,
|
|
66
|
+
f"{adapter}: design-reviewer agent did not project under {out}",
|
|
67
|
+
)
|
|
68
|
+
self.assertTrue(
|
|
69
|
+
any("agents" in h.parts for h in hits),
|
|
70
|
+
f"{adapter}: design-reviewer projected but not under an agents/ route: {hits}",
|
|
71
|
+
)
|
|
72
|
+
if adapter == "cursor":
|
|
73
|
+
# cursor encodes the read-only contract as a `readonly`
|
|
74
|
+
# frontmatter flag (it drops the source `tools:` allowlist
|
|
75
|
+
# and derives the flag for a non-mutating agent). Assert it
|
|
76
|
+
# survives projection so AC5's read-only guarantee holds at
|
|
77
|
+
# the one target that represents it as a flag rather than a
|
|
78
|
+
# tools list — the design-reviewer flags, never rewrites.
|
|
79
|
+
agent_hit = next(h for h in hits if "agents" in h.parts)
|
|
80
|
+
self.assertIn(
|
|
81
|
+
"readonly",
|
|
82
|
+
agent_hit.read_text(encoding="utf-8"),
|
|
83
|
+
"cursor: design-reviewer must project with the readonly flag",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
unittest.main()
|