agentbundle 0.3.1__tar.gz → 0.5.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.
Files changed (118) hide show
  1. {agentbundle-0.3.1 → agentbundle-0.5.0}/PKG-INFO +32 -10
  2. {agentbundle-0.3.1 → agentbundle-0.5.0}/README.md +30 -8
  3. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/adapter.toml +9 -1
  4. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/pack.schema.json +38 -0
  5. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/plugin-manifest.derived.schema.json +10 -0
  6. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/plugin-manifest.schema.json +11 -1
  7. agentbundle-0.5.0/agentbundle/_data/profile.schema.json +21 -0
  8. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/main.py +108 -0
  9. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/scope_rails.py +12 -2
  10. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/self_host.py +20 -1
  11. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_cursor.py +3 -2
  12. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_gemini.py +18 -4
  13. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +4 -4
  14. agentbundle-0.5.0/agentbundle/build/tests/test_architect_design_reviewer_projection.py +88 -0
  15. agentbundle-0.5.0/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +74 -0
  16. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_contract.py +8 -8
  17. agentbundle-0.5.0/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +94 -0
  18. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_pack_schema.py +150 -0
  19. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +69 -0
  20. agentbundle-0.5.0/agentbundle/build/tests/test_projectable_subset.py +188 -0
  21. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_self_host_check.py +39 -4
  22. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/validate.py +10 -1
  23. agentbundle-0.5.0/agentbundle/categories.py +60 -0
  24. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/cli.py +30 -4
  25. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/install.py +410 -25
  26. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/list_packs.py +17 -1
  27. agentbundle-0.5.0/agentbundle/commands/list_profiles.py +63 -0
  28. agentbundle-0.5.0/agentbundle/commands/profile.py +155 -0
  29. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/uninstall.py +4 -1
  30. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/upgrade.py +21 -6
  31. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/validate.py +36 -5
  32. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/version.py +1 -1
  33. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle.egg-info/PKG-INFO +32 -10
  34. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle.egg-info/SOURCES.txt +8 -0
  35. {agentbundle-0.3.1 → agentbundle-0.5.0}/pyproject.toml +2 -2
  36. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/__init__.py +0 -0
  37. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/__main__.py +0 -0
  38. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/adapter.schema.json +0 -0
  39. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/_data/install-marker.py +0 -0
  40. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/__init__.py +0 -0
  41. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/__main__.py +0 -0
  42. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapter_root_bins.py +0 -0
  43. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/__init__.py +0 -0
  44. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/claude_code.py +0 -0
  45. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/codex.py +0 -0
  46. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/copilot.py +0 -0
  47. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/cursor.py +0 -0
  48. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/gemini.py +0 -0
  49. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/kiro.py +0 -0
  50. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  51. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  52. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/contract.py +0 -0
  53. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/lint_packs.py +0 -0
  54. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/phase_order.py +0 -0
  55. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/__init__.py +0 -0
  56. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  57. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  58. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  59. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/direct_directory.py +0 -0
  60. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  61. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/hook_id.py +0 -0
  62. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  63. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  64. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/merge_json.py +0 -0
  65. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  66. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/shared_libs.py +0 -0
  67. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/target_resolver.py +0 -0
  68. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/__init__.py +0 -0
  69. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  70. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  71. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  72. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  73. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  74. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  75. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  76. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  77. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  78. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  79. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  80. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  81. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  82. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  83. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  84. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  85. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  86. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  87. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  88. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  89. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  90. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  91. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_security.py +0 -0
  92. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  93. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  94. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  95. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  96. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/tests/test_validate.py +0 -0
  97. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/build/user_libs.py +0 -0
  98. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/catalogue.py +0 -0
  99. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/__init__.py +0 -0
  100. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/_common.py +0 -0
  101. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/_drop_warning.py +0 -0
  102. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/adapt.py +0 -0
  103. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/config.py +0 -0
  104. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/diff.py +0 -0
  105. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/init_state.py +0 -0
  106. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/list_targets.py +0 -0
  107. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/reconcile.py +0 -0
  108. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/render.py +0 -0
  109. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/commands/scaffold.py +0 -0
  110. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/config.py +0 -0
  111. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/render.py +0 -0
  112. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/safety.py +0 -0
  113. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/scope.py +0 -0
  114. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle/user_config.py +0 -0
  115. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  116. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle.egg-info/entry_points.txt +0 -0
  117. {agentbundle-0.3.1 → agentbundle-0.5.0}/agentbundle.egg-info/top_level.txt +0 -0
  118. {agentbundle-0.3.1 → agentbundle-0.5.0}/setup.cfg +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.3.1
3
+ Version: 0.5.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
7
7
  Project-URL: Homepage, https://github.com/eugenelim/agent-ready-repo
8
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/how-to/install-agentbundle-from-clone.md
9
+ Project-URL: Documentation, https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/install-agentbundle-from-clone.md
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: License :: OSI Approved :: MIT License
@@ -39,6 +39,12 @@ That lands `core`, the flagship pack and the loop itself, in your repo. Claude C
39
39
  # See what's in a catalogue
40
40
  agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
41
41
 
42
+ # See the catalogue's curated install profiles
43
+ agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
44
+
45
+ # Install a whole curated profile — a single-scope set of packs — in one command
46
+ agentbundle install --profile inception git+https://github.com/eugenelim/agent-ready-repo
47
+
42
48
  # Install the flagship loop into this repo
43
49
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
44
50
 
@@ -52,7 +58,7 @@ agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-rep
52
58
  agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
53
59
  ```
54
60
 
55
- A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override.
61
+ A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override. A **profile** is a catalogue-curated, single-scope set of packs you install in one command — it declares its own scope, so `--scope` doesn't apply.
56
62
 
57
63
  ## Build your own catalogue
58
64
 
@@ -60,17 +66,33 @@ A catalogue is a git URL or a local path. Installs auto-detect your agent; pass
60
66
 
61
67
  ```text
62
68
  my-pack/
63
- pack.toml # name, version, adapter-contract, install scope
64
- .apm/
65
- skills/<name>/SKILL.md # one folder per skill
66
- agents/<name>.md # subagents
67
- hooks/<name>.py # lifecycle hooks
68
- seeds/ # files scaffolded into the adopter repo
69
+ pack.toml # name, version, adapter-contract, install scope,
70
+ # plus rich metadata (license, maintainers, links,
71
+ # categories, keywords) and a README pointer
72
+ .claude-plugin/
73
+ plugin.json # Claude Code plugin manifest (hand-authored)
74
+ README.md # the pack's portable doc — projected with the pack
75
+ .apm/ # primitives — projected by the build pipeline
76
+ skills/<name>/
77
+ SKILL.md # the skill body; one folder per skill
78
+ references/ # progressive-disclosure docs, loaded on demand
79
+ assets/ # templates the skill copies into the repo
80
+ agents/<name>.md # subagents
81
+ hooks/<name>.py # lifecycle hooks
82
+ seeds/ # files scaffolded into the adopter repo
69
83
  ```
70
84
 
85
+ `pack.toml` is the **single source of truth** for a pack's metadata. Declare
86
+ `license`, `[[pack.maintainers]]`, `[pack.links]`, `categories`, and
87
+ `keywords` once; the build projects the cleanly-mappable subset — plus the
88
+ pack's `README.md` — into each distribution route's manifest (the `plugin.json`
89
+ / `marketplace.json` entry), so the catalogue describes each pack richly rather
90
+ than with a single sentence. Extra fields stay in `pack.toml`; the projection
91
+ is deliberately lossy per tool.
92
+
71
93
  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.
72
94
 
73
- 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/how-to/author-a-skill.md).
95
+ 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).
74
96
 
75
97
  ## Credentials
76
98
 
@@ -21,6 +21,12 @@ That lands `core`, the flagship pack and the loop itself, in your repo. Claude C
21
21
  # See what's in a catalogue
22
22
  agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
23
23
 
24
+ # See the catalogue's curated install profiles
25
+ agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
26
+
27
+ # Install a whole curated profile — a single-scope set of packs — in one command
28
+ agentbundle install --profile inception git+https://github.com/eugenelim/agent-ready-repo
29
+
24
30
  # Install the flagship loop into this repo
25
31
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
26
32
 
@@ -34,7 +40,7 @@ agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-rep
34
40
  agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
35
41
  ```
36
42
 
37
- A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override.
43
+ A catalogue is a git URL or a local path. Installs auto-detect your agent; pass `--adapter` to override. A **profile** is a catalogue-curated, single-scope set of packs you install in one command — it declares its own scope, so `--scope` doesn't apply.
38
44
 
39
45
  ## Build your own catalogue
40
46
 
@@ -42,17 +48,33 @@ A catalogue is a git URL or a local path. Installs auto-detect your agent; pass
42
48
 
43
49
  ```text
44
50
  my-pack/
45
- pack.toml # name, version, adapter-contract, install scope
46
- .apm/
47
- skills/<name>/SKILL.md # one folder per skill
48
- agents/<name>.md # subagents
49
- hooks/<name>.py # lifecycle hooks
50
- seeds/ # files scaffolded into the adopter repo
51
+ pack.toml # name, version, adapter-contract, install scope,
52
+ # plus rich metadata (license, maintainers, links,
53
+ # categories, keywords) and a README pointer
54
+ .claude-plugin/
55
+ plugin.json # Claude Code plugin manifest (hand-authored)
56
+ README.md # the pack's portable doc — projected with the pack
57
+ .apm/ # primitives — projected by the build pipeline
58
+ skills/<name>/
59
+ SKILL.md # the skill body; one folder per skill
60
+ references/ # progressive-disclosure docs, loaded on demand
61
+ assets/ # templates the skill copies into the repo
62
+ agents/<name>.md # subagents
63
+ hooks/<name>.py # lifecycle hooks
64
+ seeds/ # files scaffolded into the adopter repo
51
65
  ```
52
66
 
67
+ `pack.toml` is the **single source of truth** for a pack's metadata. Declare
68
+ `license`, `[[pack.maintainers]]`, `[pack.links]`, `categories`, and
69
+ `keywords` once; the build projects the cleanly-mappable subset — plus the
70
+ pack's `README.md` — into each distribution route's manifest (the `plugin.json`
71
+ / `marketplace.json` entry), so the catalogue describes each pack richly rather
72
+ than with a single sentence. Extra fields stay in `pack.toml`; the projection
73
+ is deliberately lossy per tool.
74
+
53
75
  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.
54
76
 
55
- 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/how-to/author-a-skill.md).
77
+ 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).
56
78
 
57
79
  ## Credentials
58
80
 
@@ -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
- version = "0.13"
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": {
@@ -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
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "type": "object",
3
+ "required": ["scope", "description", "packs"],
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "scope": {"enum": ["repo", "user"]},
7
+ "description": {"type": "string"},
8
+ "packs": {
9
+ "type": "array",
10
+ "minItems": 1,
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["pack"],
14
+ "additionalProperties": false,
15
+ "properties": {
16
+ "pack": {"type": "string"}
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
@@ -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
@@ -299,6 +299,16 @@ def run_all(
299
299
  # ---------------------------------------------------------------------------
300
300
 
301
301
 
302
+ # Adapters whose hook-wiring projects via `merge-into-agent-json` (a
303
+ # pack-owned `.kiro/agents/<attach-to-agent>.json`): the legacy `kiro`
304
+ # block and the `kiro-cli` adapter. The `attach-to-agent` rails below
305
+ # validate that merge target, so they fire for this family. `kiro-ide`
306
+ # (and the `kiro` *install alias*, which routes to kiro-ide) DROPS
307
+ # hook-wiring and is intentionally absent — there is no merge target to
308
+ # validate. Mirrors install.py's merge dispatch.
309
+ _MERGE_INTO_AGENT_JSON_ADAPTERS = frozenset({"kiro", "kiro-cli"})
310
+
311
+
302
312
  def check_kiro_attach_to_agent(
303
313
  pack_name: str,
304
314
  wiring_tomls: dict[str, dict],
@@ -325,7 +335,7 @@ def check_kiro_attach_to_agent(
325
335
  target_adapters: iterable of adapter names the pack is being
326
336
  validated against. No-op when ``kiro`` is absent.
327
337
  """
328
- if "kiro" not in set(target_adapters or ()):
338
+ if not (set(target_adapters or ()) & _MERGE_INTO_AGENT_JSON_ADAPTERS):
329
339
  return None
330
340
  for wiring_name, body in wiring_tomls.items():
331
341
  attach = body.get("attach-to-agent") if isinstance(body, dict) else None
@@ -485,7 +495,7 @@ def check_kiro_wiring(
485
495
  would let a pack reach outside its source tree. A wiring TOML that
486
496
  fails to parse counts as a refusal on its own.
487
497
  """
488
- if "kiro" not in set(target_adapters or ()):
498
+ if not (set(target_adapters or ()) & _MERGE_INTO_AGENT_JSON_ADAPTERS):
489
499
  return None
490
500
 
491
501
  loaded = _load_pack_hook_wiring_safely(pack_path, pack_name)
@@ -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
- entries.append(json.loads(manifest.read_text(encoding="utf-8")))
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) then 0.13 (docs/specs/gemini-full-parity).
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.13")
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 test_contract_version_is_0_13(self) -> None:
154
- """AC13 contract bumped to 0.13."""
155
- self.assertEqual(self.contract["contract"]["version"], "0.13")
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.assertEqual(len(pack_dirs), 11, f"expected 11 packs, found {len(pack_dirs)}")
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.13 (docs/specs/gemini-full-parity, atop
138
- copilot-skills-and-web's 0.12 and RFC-0026 / cursor-full-parity's 0.11).
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.13",
143
- "adapter.toml [contract] version must be '0.13' after gemini-full-parity",
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: