agentbundle 0.6.0__tar.gz → 0.8.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 (123) hide show
  1. {agentbundle-0.6.0 → agentbundle-0.8.0}/PKG-INFO +35 -21
  2. {agentbundle-0.6.0 → agentbundle-0.8.0}/README.md +34 -20
  3. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/adapter.toml +8 -1
  4. agentbundle-0.8.0/agentbundle/_data/install-defaults.toml +10 -0
  5. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/pack.schema.json +19 -0
  6. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/codex.py +1 -1
  7. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/cursor.py +2 -2
  8. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/gemini.py +2 -2
  9. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/kiro.py +1 -1
  10. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/kiro_ide.py +1 -1
  11. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/main.py +4 -4
  12. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/codex_agent_toml.py +1 -1
  13. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/copilot_agent_md.py +1 -1
  14. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/copilot_hooks_json.py +1 -1
  15. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/gemini_command_toml.py +1 -1
  16. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/kiro_ide_hook.py +1 -1
  17. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/merge_into_agent_json.py +1 -1
  18. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/merge_json.py +1 -1
  19. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/user_merge_json.py +1 -1
  20. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/self_host.py +106 -22
  21. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_cursor.py +4 -3
  22. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_gemini.py +3 -3
  23. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +4 -4
  24. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_contract.py +7 -7
  25. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_pack_schema.py +79 -0
  26. agentbundle-0.8.0/agentbundle/build/tests/test_self_host_fixture_guard.py +88 -0
  27. agentbundle-0.8.0/agentbundle/build/tests/test_self_host_recipe_config.py +113 -0
  28. agentbundle-0.8.0/agentbundle/build/tests/test_writers_emit_lf.py +169 -0
  29. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/cli.py +97 -18
  30. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/_common.py +167 -0
  31. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/config.py +6 -0
  32. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/install.py +316 -10
  33. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/list_packs.py +20 -30
  34. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/list_profiles.py +12 -16
  35. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/reconcile.py +5 -12
  36. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/uninstall.py +80 -13
  37. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/upgrade.py +105 -7
  38. agentbundle-0.8.0/agentbundle/source_defaults.py +342 -0
  39. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/user_config.py +53 -5
  40. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/version.py +1 -1
  41. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle.egg-info/PKG-INFO +35 -21
  42. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle.egg-info/SOURCES.txt +5 -0
  43. {agentbundle-0.6.0 → agentbundle-0.8.0}/pyproject.toml +1 -1
  44. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/__init__.py +0 -0
  45. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/__main__.py +0 -0
  46. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/adapter.schema.json +0 -0
  47. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/install-marker.py +0 -0
  48. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
  49. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
  50. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/_data/profile.schema.json +0 -0
  51. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/__init__.py +0 -0
  52. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/__main__.py +0 -0
  53. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapter_root_bins.py +0 -0
  54. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/__init__.py +0 -0
  55. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/claude_code.py +0 -0
  56. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/copilot.py +0 -0
  57. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  58. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/contract.py +0 -0
  59. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/lint_packs.py +0 -0
  60. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/phase_order.py +0 -0
  61. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/__init__.py +0 -0
  62. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/direct_directory.py +0 -0
  63. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/projections/hook_id.py +0 -0
  64. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/scope_rails.py +0 -0
  65. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/shared_libs.py +0 -0
  66. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/target_resolver.py +0 -0
  67. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/__init__.py +0 -0
  68. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  69. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  70. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  71. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  72. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  73. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  74. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  75. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
  76. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
  77. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  78. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  79. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  80. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  81. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  82. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  83. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
  84. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  85. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  86. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  87. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  88. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  89. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  90. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  91. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
  92. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
  93. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  94. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  95. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_security.py +0 -0
  96. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
  97. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  98. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  99. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  100. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  101. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/tests/test_validate.py +0 -0
  102. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/user_libs.py +0 -0
  103. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/build/validate.py +0 -0
  104. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/catalogue.py +0 -0
  105. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/categories.py +0 -0
  106. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/__init__.py +0 -0
  107. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/_drop_warning.py +0 -0
  108. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/adapt.py +0 -0
  109. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/diff.py +0 -0
  110. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/init_state.py +0 -0
  111. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/list_targets.py +0 -0
  112. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/profile.py +0 -0
  113. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/render.py +0 -0
  114. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/scaffold.py +0 -0
  115. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/commands/validate.py +0 -0
  116. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/config.py +0 -0
  117. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/render.py +0 -0
  118. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/safety.py +0 -0
  119. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle/scope.py +0 -0
  120. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  121. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle.egg-info/entry_points.txt +0 -0
  122. {agentbundle-0.6.0 → agentbundle-0.8.0}/agentbundle.egg-info/top_level.txt +0 -0
  123. {agentbundle-0.6.0 → agentbundle-0.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.6.0
3
+ Version: 0.8.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
@@ -22,43 +22,57 @@ Description-Content-Type: text/markdown
22
22
  [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
23
23
  [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://github.com/eugenelim/agent-ready-repo#license)
24
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.
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. One pack, one command, every major agent — Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro (both the CLI and the IDE).
26
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.
27
+ ## Quick start
28
28
 
29
29
  ```bash
30
30
  pip install agentbundle
31
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
32
31
  ```
33
32
 
34
- That lands `core`, the flagship pack and the loop itself, in your repo. Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro — both the CLI and the IDE — all read it, subagents and skills included.
33
+ **Install into a repo** so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
35
34
 
36
- ## Common commands
35
+ ```bash
36
+ agentbundle install --pack core
37
+ ```
38
+
39
+ No catalogue argument needed: it defaults to the agent-ready-repo catalogue. It lands in the repo's agent config — subagents and skills included — and you commit it like any other project file. This is the default scope: the pack belongs to the project and the whole team.
40
+
41
+ **Install for yourself, everywhere** — so a pack follows you across every project, with no per-repo setup:
37
42
 
38
43
  ```bash
39
- # See what's in a catalogue
40
- agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
44
+ agentbundle install --pack research --scope user
45
+ ```
41
46
 
42
- # See the catalogue's curated install profiles
43
- agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
47
+ User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
44
48
 
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
49
+ The install auto-detects your agent (`--adapter` overrides). To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
47
50
 
48
- # Install the flagship loop into this repo
49
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
51
+ ## More commands
50
52
 
51
- # Install a pack at user scope, so it follows you across every project
52
- agentbundle install --pack research git+https://github.com/eugenelim/agent-ready-repo --scope user
53
+ ```bash
54
+ # See what the catalogue offers (bare uses the default; or name one explicitly)
55
+ agentbundle list-packs
56
+ agentbundle list-profiles
57
+
58
+ # Install a whole curated profile — a single-scope set of packs — in one command
59
+ agentbundle install --profile inception
53
60
 
54
- # Preview an install without writing a file
55
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --dry-run
61
+ # Preview any install without writing a file
62
+ agentbundle install --pack core --dry-run
56
63
 
57
- # Upgrade a pack; it reports the .upstream files you need to reconcile
58
- agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
64
+ # Upgrade to the version the catalogue ships shows installed → target, asks first
65
+ agentbundle upgrade --pack core
66
+ agentbundle upgrade --pack core --yes # skip the prompt (CI)
67
+
68
+ # Uninstall — previews remove (Tier-1) vs keep (your edits), asks first
69
+ agentbundle uninstall --pack core --dry-run
70
+ agentbundle uninstall --pack core --yes
59
71
  ```
60
72
 
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.
73
+ 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. **Upgrade takes no version** — the target is whatever the catalogue you point at declares; to pin a past version, point the catalogue at that git ref. Install a pack that's **already there** and `agentbundle` offers to `upgrade` it instead (`--yes` runs it straight away).
74
+
75
+ **Mutating commands ask first.** `uninstall`, the `--force` cleanup, and the upgrade offer all preview what they'll do and confirm before touching anything; `--dry-run` previews without writing, and `--yes` skips the prompt for non-interactive / CI use (where, without it, they refuse rather than hang).
62
76
 
63
77
  ## Build your own catalogue
64
78
 
@@ -4,43 +4,57 @@
4
4
  [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
5
5
  [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://github.com/eugenelim/agent-ready-repo#license)
6
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.
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. One pack, one command, every major agent — Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro (both the CLI and the IDE).
8
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.
9
+ ## Quick start
10
10
 
11
11
  ```bash
12
12
  pip install agentbundle
13
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
14
13
  ```
15
14
 
16
- That lands `core`, the flagship pack and the loop itself, in your repo. Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro — both the CLI and the IDE — all read it, subagents and skills included.
15
+ **Install into a repo** so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
17
16
 
18
- ## Common commands
17
+ ```bash
18
+ agentbundle install --pack core
19
+ ```
20
+
21
+ No catalogue argument needed: it defaults to the agent-ready-repo catalogue. It lands in the repo's agent config — subagents and skills included — and you commit it like any other project file. This is the default scope: the pack belongs to the project and the whole team.
22
+
23
+ **Install for yourself, everywhere** — so a pack follows you across every project, with no per-repo setup:
19
24
 
20
25
  ```bash
21
- # See what's in a catalogue
22
- agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
26
+ agentbundle install --pack research --scope user
27
+ ```
23
28
 
24
- # See the catalogue's curated install profiles
25
- agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
29
+ User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
26
30
 
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
31
+ The install auto-detects your agent (`--adapter` overrides). To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
29
32
 
30
- # Install the flagship loop into this repo
31
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
33
+ ## More commands
32
34
 
33
- # Install a pack at user scope, so it follows you across every project
34
- agentbundle install --pack research git+https://github.com/eugenelim/agent-ready-repo --scope user
35
+ ```bash
36
+ # See what the catalogue offers (bare uses the default; or name one explicitly)
37
+ agentbundle list-packs
38
+ agentbundle list-profiles
39
+
40
+ # Install a whole curated profile — a single-scope set of packs — in one command
41
+ agentbundle install --profile inception
35
42
 
36
- # Preview an install without writing a file
37
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --dry-run
43
+ # Preview any install without writing a file
44
+ agentbundle install --pack core --dry-run
38
45
 
39
- # Upgrade a pack; it reports the .upstream files you need to reconcile
40
- agentbundle upgrade --pack core --to 0.4.0 git+https://github.com/eugenelim/agent-ready-repo
46
+ # Upgrade to the version the catalogue ships shows installed → target, asks first
47
+ agentbundle upgrade --pack core
48
+ agentbundle upgrade --pack core --yes # skip the prompt (CI)
49
+
50
+ # Uninstall — previews remove (Tier-1) vs keep (your edits), asks first
51
+ agentbundle uninstall --pack core --dry-run
52
+ agentbundle uninstall --pack core --yes
41
53
  ```
42
54
 
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.
55
+ 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. **Upgrade takes no version** — the target is whatever the catalogue you point at declares; to pin a past version, point the catalogue at that git ref. Install a pack that's **already there** and `agentbundle` offers to `upgrade` it instead (`--yes` runs it straight away).
56
+
57
+ **Mutating commands ask first.** `uninstall`, the `--force` cleanup, and the upgrade offer all preview what they'll do and confirm before touching anything; `--dry-run` previews without writing, and `--yes` skips the prompt for non-interactive / CI use (where, without it, they refuse rather than hang).
44
58
 
45
59
  ## Build your own catalogue
46
60
 
@@ -77,7 +77,14 @@
77
77
  # the default agent's `.kiro/skills` auto-discovery (RFC-0022 E4; kiro
78
78
  # #6887/#6888/#4993). Additive `inject-resources` on BOTH the kiro-cli and
79
79
  # kiro-ide agent projections (CLI → agent JSON, IDE → quoted .md frontmatter).
80
- version = "0.15"
80
+ # RFC-0040 / ADR-0030 / docs/specs/consolidated-pack-layout (v0.16): pack.toml
81
+ # gains an optional scope-keyed [pack.layout] table (optional .repo / .user
82
+ # sub-tables, each a `parent` base and/or a within-pack template-path) naming
83
+ # where a consuming pack's durable output lands. Consumed by the new installer
84
+ # `_append_layout_section` step (append-if-exists / never-create / never-
85
+ # overwrite) into an adopter-owned `agentbundle-layout.toml`. Additive +
86
+ # optional — packs pinned below v0.16 build and validate unchanged.
87
+ version = "0.16"
81
88
 
82
89
  # Sibling schemas this contract references — pack metadata and the
83
90
  # per-pack Claude-plugin manifest. Defined by spec AC #3 + #4.
@@ -0,0 +1,10 @@
1
+ # Packaged install default — layer 4 of the catalogue-source resolution chain
2
+ # (RFC-0046 / ADR-0036). When `agentbundle install`/`upgrade` is run with no
3
+ # `catalogue` positional and layers 1–3 (explicit arg, user config, editable
4
+ # detection) yield nothing, this source is used.
5
+ #
6
+ # Private-fork pattern: blank the `source` value below (or delete this file
7
+ # entirely) to disable the packaged GitHub default — layers 1–3 still apply, so
8
+ # an editable clone or a `config set source` still resolves.
9
+ [defaults]
10
+ source = "git+https://github.com/eugenelim/agent-ready-repo"
@@ -131,6 +131,25 @@
131
131
  "type": "string",
132
132
  "pattern": "^[^/].*"
133
133
  }
134
+ },
135
+ "layout": {
136
+ "type": "object",
137
+ "properties": {
138
+ "repo": {
139
+ "type": "object",
140
+ "properties": {
141
+ "parent": {"type": "string"},
142
+ "template": {"type": "string"}
143
+ }
144
+ },
145
+ "user": {
146
+ "type": "object",
147
+ "properties": {
148
+ "parent": {"type": "string"},
149
+ "template": {"type": "string"}
150
+ }
151
+ }
152
+ }
134
153
  }
135
154
  },
136
155
  "if": {
@@ -72,7 +72,7 @@ def project_packs(pack_paths: list[Path], contract: dict, output_root: Path) ->
72
72
  dir=str(agents_md.parent),
73
73
  )
74
74
  try:
75
- with os.fdopen(fd, "w", encoding="utf-8") as handle:
75
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
76
76
  handle.write(stripped)
77
77
  os.replace(tmp_path, agents_md)
78
78
  except BaseException:
@@ -251,7 +251,7 @@ def _project_agent_as_md(
251
251
  out_fm["readonly"] = readonly
252
252
 
253
253
  output_text = _serialize_frontmatter_md(out_fm) + body
254
- (target_dir / entry.name).write_text(output_text, encoding="utf-8")
254
+ (target_dir / entry.name).write_text(output_text, encoding="utf-8", newline="\n")
255
255
 
256
256
 
257
257
  def _derive_readonly(frontmatter: dict[str, Any]) -> bool | None:
@@ -357,7 +357,7 @@ def _project_hooks_json(source_dir: Path, output_root: Path, rule: dict) -> None
357
357
  target_path.parent.mkdir(parents=True, exist_ok=True)
358
358
  target_path.write_text(
359
359
  json.dumps(existing, indent=2, sort_keys=True) + "\n",
360
- encoding="utf-8",
360
+ encoding="utf-8", newline="\n",
361
361
  )
362
362
 
363
363
 
@@ -220,7 +220,7 @@ def _project_agent_as_md(
220
220
  frontmatter, body = _split_frontmatter(entry.read_text(encoding="utf-8"))
221
221
  out_fm = _apply_mapping(frontmatter, mapping, entry.stem)
222
222
  output_text = _serialize_frontmatter_md(out_fm) + body
223
- (target_dir / entry.name).write_text(output_text, encoding="utf-8")
223
+ (target_dir / entry.name).write_text(output_text, encoding="utf-8", newline="\n")
224
224
 
225
225
 
226
226
  def _apply_mapping(
@@ -386,7 +386,7 @@ def _project_settings_json(
386
386
  target_path.parent.mkdir(parents=True, exist_ok=True)
387
387
  target_path.write_text(
388
388
  json.dumps(existing, indent=2, sort_keys=True) + "\n",
389
- encoding="utf-8",
389
+ encoding="utf-8", newline="\n",
390
390
  )
391
391
 
392
392
 
@@ -297,7 +297,7 @@ def _project_agent_as_json(
297
297
  destination = target_dir / (entry.stem + ".json")
298
298
  destination.write_text(
299
299
  json.dumps(agent_json, indent=2, sort_keys=False) + "\n",
300
- encoding="utf-8",
300
+ encoding="utf-8", newline="\n",
301
301
  )
302
302
 
303
303
 
@@ -255,7 +255,7 @@ def _project_agent_as_md(
255
255
 
256
256
  output_text = _serialize_frontmatter_md(rewritten) + body
257
257
  destination = target_dir / entry.name # preserves .md extension
258
- destination.write_text(output_text, encoding="utf-8")
258
+ destination.write_text(output_text, encoding="utf-8", newline="\n")
259
259
 
260
260
 
261
261
  def _serialize_frontmatter_md(fields: dict[str, Any]) -> str:
@@ -498,7 +498,7 @@ def _run_per_pack_single(
498
498
  )
499
499
  destination.write_text(
500
500
  json.dumps(derived, indent=2, sort_keys=False) + "\n",
501
- encoding="utf-8",
501
+ encoding="utf-8", newline="\n",
502
502
  )
503
503
  # Defence-in-depth: re-validate the written file against the schema
504
504
  # to catch any serialise/parse divergence introduced by json.dumps.
@@ -547,7 +547,7 @@ def _run_per_pack_apm(recipe: Recipe, packs: list[Pack], output_dir: Path) -> di
547
547
  pack_metadata = tomllib.loads((pack.path / "pack.toml").read_text(encoding="utf-8"))
548
548
  (per_pack_output / "apm.yml").write_text(
549
549
  _render_apm_yml(pack_metadata.get("pack", {})),
550
- encoding="utf-8",
550
+ encoding="utf-8", newline="\n",
551
551
  )
552
552
  apm_source = pack.path / ".apm"
553
553
  if apm_source.exists():
@@ -568,7 +568,7 @@ def _run_per_pack_apm(recipe: Recipe, packs: list[Pack], output_dir: Path) -> di
568
568
  (hooks_dir / "install-marker.py").write_bytes(writer_bytes)
569
569
  (hooks_dir / "install-marker.json").write_text(
570
570
  json.dumps(_APM_INSTALL_MARKER_HOOK_JSON, indent=2) + "\n",
571
- encoding="utf-8",
571
+ encoding="utf-8", newline="\n",
572
572
  )
573
573
 
574
574
  # Project pack.toml verbatim. The writer reads it for
@@ -630,7 +630,7 @@ def _run_aggregate(recipe: Recipe, output_dir: Path) -> dict:
630
630
  output_path.parent.mkdir(parents=True, exist_ok=True)
631
631
  output_path.write_text(
632
632
  json.dumps({"plugins": entries}, indent=2, sort_keys=True) + "\n",
633
- encoding="utf-8",
633
+ encoding="utf-8", newline="\n",
634
634
  )
635
635
  return {"recipe": recipe.name, "type": recipe.type, "entries": len(entries)}
636
636
 
@@ -331,4 +331,4 @@ def project_codex_agent_toml(
331
331
  )
332
332
  toml_lines = _emit_fields(rewritten, body)
333
333
  destination = target_dir / (entry.stem + ".toml")
334
- destination.write_text("\n".join(toml_lines) + "\n", encoding="utf-8")
334
+ destination.write_text("\n".join(toml_lines) + "\n", encoding="utf-8", newline="\n")
@@ -208,4 +208,4 @@ def project_copilot_agent_md(
208
208
  _validate_tools(frontmatter["tools"])
209
209
  emitted["tools"] = frontmatter["tools"]
210
210
  destination = target_dir / (entry.stem + ".agent.md")
211
- destination.write_text(_emit(emitted, body), encoding="utf-8")
211
+ destination.write_text(_emit(emitted, body), encoding="utf-8", newline="\n")
@@ -138,5 +138,5 @@ def project_copilot_hooks_json(
138
138
  destination = target_dir / (entry.stem + ".json")
139
139
  destination.write_text(
140
140
  json.dumps(document, indent=2, sort_keys=False) + "\n",
141
- encoding="utf-8",
141
+ encoding="utf-8", newline="\n",
142
142
  )
@@ -227,4 +227,4 @@ def project_gemini_command_toml(
227
227
 
228
228
  destination = target_root / relative.with_suffix(".toml")
229
229
  destination.parent.mkdir(parents=True, exist_ok=True)
230
- destination.write_text("\n".join(lines) + "\n", encoding="utf-8")
230
+ destination.write_text("\n".join(lines) + "\n", encoding="utf-8", newline="\n")
@@ -191,7 +191,7 @@ def project(
191
191
  # POSIX-friendliness.
192
192
  target_path.write_text(
193
193
  json.dumps(body, indent=2, sort_keys=False) + "\n",
194
- encoding="utf-8",
194
+ encoding="utf-8", newline="\n",
195
195
  )
196
196
 
197
197
 
@@ -243,7 +243,7 @@ def _atomic_write(target_path: Path, data: dict) -> None:
243
243
  serialised = json.dumps(data, indent=2, sort_keys=False) + "\n"
244
244
  with tempfile.NamedTemporaryFile(
245
245
  mode="w",
246
- encoding="utf-8",
246
+ encoding="utf-8", newline="\n",
247
247
  dir=str(target_path.parent),
248
248
  prefix=target_path.name + ".",
249
249
  suffix=".tmp",
@@ -54,5 +54,5 @@ def project_merge_json(source_dir: Path, output_root: Path, rule: dict) -> None:
54
54
  target_path.parent.mkdir(parents=True, exist_ok=True)
55
55
  target_path.write_text(
56
56
  json.dumps(existing, indent=2, sort_keys=True) + "\n",
57
- encoding="utf-8",
57
+ encoding="utf-8", newline="\n",
58
58
  )
@@ -296,7 +296,7 @@ def _atomic_write(target_path: Path, data: dict) -> None:
296
296
  serialised = json.dumps(data, indent=2, sort_keys=False) + "\n"
297
297
  with tempfile.NamedTemporaryFile(
298
298
  mode="w",
299
- encoding="utf-8",
299
+ encoding="utf-8", newline="\n",
300
300
  dir=str(target_path.parent),
301
301
  prefix=target_path.name + ".",
302
302
  suffix=".tmp",
@@ -24,7 +24,9 @@ consumer.
24
24
  Self-host scope (see docs/specs/self-hosting/spec.md § Phased rollout):
25
25
  the `SELF_HOST_ADAPTERS` allow-list runs `claude-code` and `codex`.
26
26
  Kiro and Copilot stay distribution-only so self-host does not project
27
- `.kiro/` or `.github/instructions/`.
27
+ `.kiro/` or `.github/instructions/`. Both `SELF_HOST_ADAPTERS` and
28
+ `SELF_HOST_PACKS` are sourced from `recipes/self-host.toml` (see the
29
+ `_DEFAULT_*` block below); the values named here are the current defaults.
28
30
  """
29
31
 
30
32
  from __future__ import annotations
@@ -79,27 +81,76 @@ TARGET_PATHS = (
79
81
  Path("AGENTS.md"),
80
82
  )
81
83
 
82
- # Self-host allow-list (see self-hosting spec § Phased rollout).
83
- # Kiro and Copilot remain in the contract for distribution builds but
84
- # are excluded from the self-host runner.
85
- SELF_HOST_ADAPTERS: tuple[str, ...] = ("claude-code", "codex")
86
-
87
- # Self-host *pack* allow-list. This repo is the catalogue's home, not
88
- # an adopter `make build-self` should only project the in-house
89
- # packs into the working tree. User-scope-default packs (architect,
90
- # atlassian, contracts, converters, credential-brokers, figma) are
91
- # advertised via `marketplace.json` but their primitives don't belong
92
- # in this repo's `.claude/skills/` (or future `.kiro/skills/`) tree.
93
- # `monorepo-extras` is repo-only by metadata but the bundle is not a
94
- # canonical monorepo consumer of `new-package`, so it's left out too.
95
- # `_aggregate_marketplace` intentionally ignores this filter — the
96
- # catalogue advertises every pack.
97
- SELF_HOST_PACKS: tuple[str, ...] = (
84
+ # Built-in fallbacks for the self-host pack / adapter allow-lists. The
85
+ # authoritative values live in `recipes/self-host.toml` (read at import below);
86
+ # these are used only when that recipe is missing a key or can't be read, so
87
+ # module import stays total. Kiro and Copilot remain in the contract for
88
+ # distribution builds but are excluded from the self-host runner. This repo is
89
+ # the catalogue's home, not an adopter, so `make build-self` only projects the
90
+ # in-house packs; `_aggregate_marketplace` intentionally ignores the pack
91
+ # filter the catalogue advertises every pack. See the recipe for the full
92
+ # rationale behind the pack selection.
93
+ _DEFAULT_SELF_HOST_ADAPTERS: tuple[str, ...] = ("claude-code", "codex")
94
+ _DEFAULT_SELF_HOST_PACKS: tuple[str, ...] = (
98
95
  "core",
99
96
  "governance-extras",
100
97
  "user-guide-diataxis",
101
98
  )
102
99
 
100
+ _SELF_HOST_RECIPE = "self-host"
101
+
102
+
103
+ def _read_recipe_text(name: str) -> str | None:
104
+ """Return the text of `recipes/<name>.toml`, or None if unreadable.
105
+
106
+ Mirrors `build.main`'s recipe resolution: filesystem first (dev / editable
107
+ install), then `importlib.resources` (zipapp, where the package lives inside
108
+ a `.pyz` archive that `Path.exists()` cannot traverse).
109
+ """
110
+ recipe_path = Path(__file__).resolve().parent / "recipes" / f"{name}.toml"
111
+ if recipe_path.exists():
112
+ return recipe_path.read_text(encoding="utf-8")
113
+ try:
114
+ from importlib.resources import files
115
+
116
+ resource = files("agentbundle.build").joinpath(f"recipes/{name}.toml")
117
+ if resource.is_file():
118
+ return resource.read_text(encoding="utf-8")
119
+ except (FileNotFoundError, ModuleNotFoundError):
120
+ pass
121
+ return None
122
+
123
+
124
+ def _extract_self_host_lists(
125
+ recipe: dict,
126
+ ) -> tuple[tuple[str, ...], tuple[str, ...]]:
127
+ """Pull `(packs, adapters)` from a parsed self-host recipe, substituting the
128
+ built-in defaults for any list that is absent or empty."""
129
+ body = recipe.get("recipe", {})
130
+ packs = tuple(body.get("packs", {}).get("include", ())) or _DEFAULT_SELF_HOST_PACKS
131
+ adapters = (
132
+ tuple(body.get("adapters", {}).get("targets", ()))
133
+ or _DEFAULT_SELF_HOST_ADAPTERS
134
+ )
135
+ return packs, adapters
136
+
137
+
138
+ def _load_self_host_lists() -> tuple[tuple[str, ...], tuple[str, ...]]:
139
+ """Read the self-host allow-lists from the recipe. Total: any read or parse
140
+ failure falls back to the built-in defaults so module import never raises."""
141
+ try:
142
+ text = _read_recipe_text(_SELF_HOST_RECIPE)
143
+ if text is None:
144
+ return _DEFAULT_SELF_HOST_PACKS, _DEFAULT_SELF_HOST_ADAPTERS
145
+ return _extract_self_host_lists(tomllib.loads(text))
146
+ except Exception:
147
+ # Unreadable (non-UTF-8 / permission / IO) or malformed recipe — fall
148
+ # back so module import is total (AC3).
149
+ return _DEFAULT_SELF_HOST_PACKS, _DEFAULT_SELF_HOST_ADAPTERS
150
+
151
+
152
+ SELF_HOST_PACKS, SELF_HOST_ADAPTERS = _load_self_host_lists()
153
+
103
154
 
104
155
  def _filter_self_host_packs(pack_paths: list[Path]) -> list[Path]:
105
156
  """Return the subset of *pack_paths* whose directory name is in
@@ -195,7 +246,7 @@ def resolve_markers(
195
246
  text,
196
247
  )
197
248
  if replaced != text:
198
- path.write_text(replaced, encoding="utf-8")
249
+ path.write_text(replaced, encoding="utf-8", newline="\n")
199
250
  modified += 1
200
251
  return modified
201
252
 
@@ -270,7 +321,7 @@ def _compose_agents_md(
270
321
  body = body_path.read_text(encoding="utf-8").replace("\r\n", "\n")
271
322
  if body and not body.endswith("\n"):
272
323
  body += "\n"
273
- target_path.write_text(body, encoding="utf-8")
324
+ target_path.write_text(body, encoding="utf-8", newline="\n")
274
325
 
275
326
  if footer_path.exists():
276
327
  text = target_path.read_text(encoding="utf-8")
@@ -279,7 +330,7 @@ def _compose_agents_md(
279
330
  text += "\n"
280
331
  if footer and not footer.endswith("\n"):
281
332
  footer += "\n"
282
- target_path.write_text(text + footer, encoding="utf-8")
333
+ target_path.write_text(text + footer, encoding="utf-8", newline="\n")
283
334
  return target_path
284
335
 
285
336
 
@@ -329,6 +380,7 @@ EXCLUDED_PATTERNS: tuple[str, ...] = (
329
380
  "README.md", # root-level; nested README.md not excluded
330
381
  "LICENSE-*",
331
382
  ".gitignore",
383
+ ".gitattributes",
332
384
  ".github/**",
333
385
  "AGENTS.local.md",
334
386
  "AGENTS.md", # root-level; nested AGENTS.md not excluded
@@ -535,7 +587,7 @@ def _aggregate_marketplace(
535
587
  }
536
588
  target.write_text(
537
589
  json.dumps(payload, indent=2, sort_keys=True) + "\n",
538
- encoding="utf-8",
590
+ encoding="utf-8", newline="\n",
539
591
  )
540
592
  return target
541
593
 
@@ -1463,10 +1515,42 @@ def run_build_check_drift_gates(
1463
1515
  return 0
1464
1516
 
1465
1517
 
1518
+ def _refuse_fixture_packs_dir(packs_dir: Path, *, dry_run: bool) -> int | None:
1519
+ """Refuse a real-write self-host whose `packs_dir` points into
1520
+ `tests/fixtures/` (which would overwrite the working tree with fixture
1521
+ data), unless `ALLOW_FIXTURE_PACKS` is set.
1522
+
1523
+ This is the cross-platform home of the guard that used to live only in the
1524
+ Makefile `build-self` recipe — so the make-free entry
1525
+ `python -m agentbundle.build self` (the only way to run build-self on
1526
+ Windows) is protected too. Returns a non-zero exit code to refuse, or
1527
+ `None` to proceed. Dry-run writes to a shadow temp dir, so it is never
1528
+ guarded (matching the `run_self_host` dirty-tree check). `as_posix()`
1529
+ normalises separators so the match is Windows-safe.
1530
+ """
1531
+ if dry_run or os.environ.get("ALLOW_FIXTURE_PACKS"):
1532
+ return None
1533
+ # Trailing slash mirrors the historical Makefile glob `*tests/fixtures/*`
1534
+ # exactly — so a sibling like `my-tests/fixtures-backup/` doesn't over-match.
1535
+ if "tests/fixtures/" in packs_dir.as_posix():
1536
+ print(
1537
+ "self-host: refusing — --packs-dir points into tests/fixtures/; "
1538
+ "this would overwrite your working tree with fixture data. Set "
1539
+ "ALLOW_FIXTURE_PACKS=1 to override, or use --packs-dir packs.",
1540
+ file=sys.stderr,
1541
+ )
1542
+ return 2
1543
+ return None
1544
+
1545
+
1466
1546
  def cmd_self(args) -> int:
1547
+ packs_dir = Path(args.packs_dir).resolve()
1548
+ refusal = _refuse_fixture_packs_dir(packs_dir, dry_run=args.dry_run)
1549
+ if refusal is not None:
1550
+ return refusal
1467
1551
  return run_self_host(
1468
1552
  working_tree=Path(args.output_dir).resolve(),
1469
- packs_dir=Path(args.packs_dir).resolve(),
1553
+ packs_dir=packs_dir,
1470
1554
  dry_run=args.dry_run,
1471
1555
  force=args.force,
1472
1556
  no_symlink=getattr(args, "no_symlink", False),
@@ -53,10 +53,11 @@ class CursorContractTests(unittest.TestCase):
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
55
  0.12 (copilot-skills-and-web), 0.13 (docs/specs/gemini-full-parity),
56
- 0.14 (docs/specs/enriched-pack-manifest), then 0.15
57
- (docs/specs/kiro-cli-agent-skill-resources).
56
+ 0.14 (docs/specs/enriched-pack-manifest), 0.15
57
+ (docs/specs/kiro-cli-agent-skill-resources), then 0.16
58
+ (docs/specs/consolidated-pack-layout).
58
59
  Name preserved to keep the diff small."""
59
- self.assertEqual(self.contract["contract"]["version"], "0.15")
60
+ self.assertEqual(self.contract["contract"]["version"], "0.16")
60
61
 
61
62
  def test_cursor_block_projects_five_primitives(self) -> None:
62
63
  """AC2 — the five standard primitives are in the projection array."""
@@ -151,9 +151,9 @@ class GeminiContractTests(unittest.TestCase):
151
151
  cls.contract = load_contract(CONTRACT_PATH)
152
152
 
153
153
  def test_contract_version_is_0_14(self) -> None:
154
- """Contract version is 0.15 (docs/specs/kiro-cli-agent-skill-resources
155
- bumped it from enriched-pack-manifest's 0.14). Name preserved."""
156
- self.assertEqual(self.contract["contract"]["version"], "0.15")
154
+ """Contract version is 0.16 (docs/specs/consolidated-pack-layout
155
+ bumped it from kiro-cli-agent-skill-resources' 0.15). Name preserved."""
156
+ self.assertEqual(self.contract["contract"]["version"], "0.16")
157
157
 
158
158
  def test_gemini_block_projects_five_primitives(self) -> None:
159
159
  """AC2 — five standard primitives with their gemini targets."""