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.
Files changed (118) hide show
  1. agentbundle-0.4.0/PKG-INFO +97 -0
  2. agentbundle-0.4.0/README.md +79 -0
  3. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/adapter.toml +9 -1
  4. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/pack.schema.json +38 -0
  5. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/plugin-manifest.derived.schema.json +10 -0
  6. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/plugin-manifest.schema.json +11 -1
  7. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/main.py +108 -0
  8. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/self_host.py +20 -1
  9. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_cursor.py +3 -2
  10. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_gemini.py +18 -4
  11. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +4 -4
  12. agentbundle-0.4.0/agentbundle/build/tests/test_architect_design_reviewer_projection.py +88 -0
  13. agentbundle-0.4.0/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +74 -0
  14. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract.py +8 -8
  15. agentbundle-0.4.0/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +94 -0
  16. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema.py +150 -0
  17. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +69 -0
  18. agentbundle-0.4.0/agentbundle/build/tests/test_projectable_subset.py +188 -0
  19. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_self_host_check.py +39 -4
  20. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/validate.py +10 -1
  21. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/catalogue.py +3 -2
  22. agentbundle-0.4.0/agentbundle/categories.py +60 -0
  23. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/list_packs.py +17 -1
  24. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/validate.py +20 -0
  25. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/config.py +1 -1
  26. agentbundle-0.4.0/agentbundle.egg-info/PKG-INFO +97 -0
  27. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/SOURCES.txt +5 -0
  28. {agentbundle-0.3.0 → agentbundle-0.4.0}/pyproject.toml +3 -3
  29. agentbundle-0.3.0/PKG-INFO +0 -37
  30. agentbundle-0.3.0/README.md +0 -19
  31. agentbundle-0.3.0/agentbundle.egg-info/PKG-INFO +0 -37
  32. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/__init__.py +0 -0
  33. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/__main__.py +0 -0
  34. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/adapter.schema.json +0 -0
  35. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/_data/install-marker.py +0 -0
  36. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/__init__.py +0 -0
  37. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/__main__.py +0 -0
  38. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapter_root_bins.py +0 -0
  39. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/__init__.py +0 -0
  40. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/claude_code.py +0 -0
  41. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/codex.py +0 -0
  42. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/copilot.py +0 -0
  43. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/cursor.py +0 -0
  44. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/gemini.py +0 -0
  45. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro.py +0 -0
  46. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  47. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  48. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/contract.py +0 -0
  49. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/lint_packs.py +0 -0
  50. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/phase_order.py +0 -0
  51. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/__init__.py +0 -0
  52. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  53. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  54. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  55. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/direct_directory.py +0 -0
  56. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  57. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/hook_id.py +0 -0
  58. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  59. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  60. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/merge_json.py +0 -0
  61. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  62. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/scope_rails.py +0 -0
  63. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/shared_libs.py +0 -0
  64. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/target_resolver.py +0 -0
  65. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/__init__.py +0 -0
  66. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  67. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  68. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  69. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  70. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  71. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  72. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  73. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  74. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  75. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  76. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  77. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  78. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  79. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  80. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  81. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  82. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  83. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  84. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  85. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  86. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  87. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  88. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_security.py +0 -0
  89. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  90. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  91. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  92. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  93. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/tests/test_validate.py +0 -0
  94. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/build/user_libs.py +0 -0
  95. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/cli.py +0 -0
  96. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/__init__.py +0 -0
  97. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/_common.py +0 -0
  98. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/_drop_warning.py +0 -0
  99. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/adapt.py +0 -0
  100. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/config.py +0 -0
  101. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/diff.py +0 -0
  102. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/init_state.py +0 -0
  103. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/install.py +0 -0
  104. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/list_targets.py +0 -0
  105. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/reconcile.py +0 -0
  106. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/render.py +0 -0
  107. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/scaffold.py +0 -0
  108. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/uninstall.py +0 -0
  109. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/commands/upgrade.py +0 -0
  110. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/render.py +0 -0
  111. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/safety.py +0 -0
  112. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/scope.py +0 -0
  113. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/user_config.py +0 -0
  114. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle/version.py +0 -0
  115. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  116. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/entry_points.txt +0 -0
  117. {agentbundle-0.3.0 → agentbundle-0.4.0}/agentbundle.egg-info/top_level.txt +0 -0
  118. {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
+ [![PyPI](https://img.shields.io/pypi/v/agentbundle)](https://pypi.org/project/agentbundle/)
22
+ [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
23
+ [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](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
+ [![PyPI](https://img.shields.io/pypi/v/agentbundle)](https://pypi.org/project/agentbundle/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
5
+ [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](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
- 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
  }
@@ -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
- 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:
@@ -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()