agentbundle 0.6.0__tar.gz → 0.7.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.6.0 → agentbundle-0.7.0}/PKG-INFO +32 -18
  2. {agentbundle-0.6.0 → agentbundle-0.7.0}/README.md +31 -17
  3. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/adapter.toml +8 -1
  4. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/pack.schema.json +19 -0
  5. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_cursor.py +4 -3
  6. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_gemini.py +3 -3
  7. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +4 -4
  8. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_contract.py +7 -7
  9. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_pack_schema.py +79 -0
  10. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/cli.py +57 -14
  11. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/_common.py +48 -0
  12. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/install.py +290 -8
  13. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/reconcile.py +5 -12
  14. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/uninstall.py +80 -13
  15. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/upgrade.py +95 -6
  16. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/version.py +1 -1
  17. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle.egg-info/PKG-INFO +32 -18
  18. {agentbundle-0.6.0 → agentbundle-0.7.0}/pyproject.toml +1 -1
  19. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/__init__.py +0 -0
  20. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/__main__.py +0 -0
  21. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/adapter.schema.json +0 -0
  22. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/install-marker.py +0 -0
  23. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
  24. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
  25. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/_data/profile.schema.json +0 -0
  26. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/__init__.py +0 -0
  27. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/__main__.py +0 -0
  28. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapter_root_bins.py +0 -0
  29. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/__init__.py +0 -0
  30. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/claude_code.py +0 -0
  31. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/codex.py +0 -0
  32. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/copilot.py +0 -0
  33. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/cursor.py +0 -0
  34. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/gemini.py +0 -0
  35. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/kiro.py +0 -0
  36. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  37. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  38. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/contract.py +0 -0
  39. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/lint_packs.py +0 -0
  40. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/main.py +0 -0
  41. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/phase_order.py +0 -0
  42. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/__init__.py +0 -0
  43. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  44. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  45. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  46. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/direct_directory.py +0 -0
  47. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  48. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/hook_id.py +0 -0
  49. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  50. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  51. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/merge_json.py +0 -0
  52. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  53. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/scope_rails.py +0 -0
  54. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/self_host.py +0 -0
  55. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/shared_libs.py +0 -0
  56. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/target_resolver.py +0 -0
  57. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/__init__.py +0 -0
  58. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  59. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  60. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  61. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  62. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  63. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  64. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  65. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
  66. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
  67. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  68. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  69. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  70. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  71. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  72. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  73. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
  74. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  75. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  76. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  77. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  78. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  79. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  80. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  81. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
  82. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
  83. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  84. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  85. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_security.py +0 -0
  86. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
  87. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  88. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  89. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  90. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  91. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/tests/test_validate.py +0 -0
  92. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/user_libs.py +0 -0
  93. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/build/validate.py +0 -0
  94. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/catalogue.py +0 -0
  95. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/categories.py +0 -0
  96. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/__init__.py +0 -0
  97. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/_drop_warning.py +0 -0
  98. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/adapt.py +0 -0
  99. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/config.py +0 -0
  100. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/diff.py +0 -0
  101. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/init_state.py +0 -0
  102. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/list_packs.py +0 -0
  103. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/list_profiles.py +0 -0
  104. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/list_targets.py +0 -0
  105. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/profile.py +0 -0
  106. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/render.py +0 -0
  107. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/scaffold.py +0 -0
  108. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/commands/validate.py +0 -0
  109. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/config.py +0 -0
  110. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/render.py +0 -0
  111. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/safety.py +0 -0
  112. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/scope.py +0 -0
  113. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle/user_config.py +0 -0
  114. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle.egg-info/SOURCES.txt +0 -0
  115. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  116. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle.egg-info/entry_points.txt +0 -0
  117. {agentbundle-0.6.0 → agentbundle-0.7.0}/agentbundle.egg-info/top_level.txt +0 -0
  118. {agentbundle-0.6.0 → agentbundle-0.7.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.7.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
+ ```
32
+
33
+ **Install into a repo** — so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
34
+
35
+ ```bash
31
36
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
32
37
  ```
33
38
 
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.
39
+ 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.
35
40
 
36
- ## Common commands
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 git+https://github.com/eugenelim/agent-ready-repo --scope user
45
+ ```
46
+
47
+ User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
48
+
49
+ A catalogue is a git URL or a local path, and the install auto-detects your agent (`--adapter` overrides).
41
50
 
42
- # See the catalogue's curated install profiles
51
+ ## More commands
52
+
53
+ ```bash
54
+ # See what a catalogue offers
55
+ agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
43
56
  agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
44
57
 
45
58
  # Install a whole curated profile — a single-scope set of packs — in one command
46
59
  agentbundle install --profile inception git+https://github.com/eugenelim/agent-ready-repo
47
60
 
48
- # Install the flagship loop into this repo
49
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
50
-
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
-
54
- # Preview an install without writing a file
61
+ # Preview any install without writing a file
55
62
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --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 git+https://github.com/eugenelim/agent-ready-repo
66
+ agentbundle upgrade --pack core git+https://github.com/eugenelim/agent-ready-repo --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
+ ```
14
+
15
+ **Install into a repo** — so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
16
+
17
+ ```bash
13
18
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
14
19
  ```
15
20
 
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.
21
+ 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.
17
22
 
18
- ## Common commands
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 git+https://github.com/eugenelim/agent-ready-repo --scope user
27
+ ```
28
+
29
+ User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
30
+
31
+ A catalogue is a git URL or a local path, and the install auto-detects your agent (`--adapter` overrides).
23
32
 
24
- # See the catalogue's curated install profiles
33
+ ## More commands
34
+
35
+ ```bash
36
+ # See what a catalogue offers
37
+ agentbundle list-packs git+https://github.com/eugenelim/agent-ready-repo
25
38
  agentbundle list-profiles git+https://github.com/eugenelim/agent-ready-repo
26
39
 
27
40
  # Install a whole curated profile — a single-scope set of packs — in one command
28
41
  agentbundle install --profile inception git+https://github.com/eugenelim/agent-ready-repo
29
42
 
30
- # Install the flagship loop into this repo
31
- agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo
32
-
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
-
36
- # Preview an install without writing a file
43
+ # Preview any install without writing a file
37
44
  agentbundle install --pack core git+https://github.com/eugenelim/agent-ready-repo --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 git+https://github.com/eugenelim/agent-ready-repo
48
+ agentbundle upgrade --pack core git+https://github.com/eugenelim/agent-ready-repo --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.
@@ -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": {
@@ -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."""
@@ -172,13 +172,13 @@ class KiroIdeAdapterTests(unittest.TestCase):
172
172
  self.assertIn(".kiro.hook", target_repo)
173
173
 
174
174
  def test_contract_version_is_0_9(self) -> None:
175
- """Contract version is 0.15 (docs/specs/kiro-cli-agent-skill-resources,
176
- atop enriched-pack-manifest's 0.14 and gemini-full-parity's 0.13).
175
+ """Contract version is 0.16 (docs/specs/consolidated-pack-layout,
176
+ atop kiro-cli-agent-skill-resources' 0.15 and enriched-pack-manifest's 0.14).
177
177
  Name preserved to keep the diff small."""
178
178
  self.assertEqual(
179
179
  self.contract["contract"]["version"],
180
- "0.15",
181
- "adapter.toml [contract] version must be '0.15' after kiro-cli-agent-skill-resources",
180
+ "0.16",
181
+ "adapter.toml [contract] version must be '0.16' after consolidated-pack-layout",
182
182
  )
183
183
 
184
184
  def test_kiro_ide_hook_projects_with_flat_prefix_path(self) -> None:
@@ -452,16 +452,16 @@ class ContractV05Tests(unittest.TestCase):
452
452
  self.schema = _load_schema()
453
453
 
454
454
  def test_contract_version_is_v05(self) -> None:
455
- """tomllib.loads of adapter.toml returns contract.version == "0.15"
456
- (bumped from enriched-pack-manifest's "0.14" by
457
- docs/specs/kiro-cli-agent-skill-resources: kiro-cli agents inject a
458
- skill-resources glob so headless `--agent` runs reach skills).
459
- Class/method names preserved to avoid churn.
455
+ """tomllib.loads of adapter.toml returns contract.version == "0.16"
456
+ (bumped from kiro-cli-agent-skill-resources' "0.15" by
457
+ docs/specs/consolidated-pack-layout: pack.toml gains an optional
458
+ scope-keyed [pack.layout] table). Class/method names preserved to
459
+ avoid churn.
460
460
  """
461
461
  self.assertEqual(
462
462
  self.contract["contract"]["version"],
463
- "0.15",
464
- "adapter.toml [contract] version must be '0.15' after kiro-cli-agent-skill-resources",
463
+ "0.16",
464
+ "adapter.toml [contract] version must be '0.16' after consolidated-pack-layout",
465
465
  )
466
466
 
467
467
  def test_claude_code_install_routes_includes_apm(self) -> None:
@@ -399,6 +399,85 @@ description = "Core skills."
399
399
  self.assertEqual(errors, [], "\n".join(errors))
400
400
 
401
401
 
402
+ class PackSchemaLayoutTests(unittest.TestCase):
403
+ """consolidated-pack-layout T1: optional scope-keyed [pack.layout] table.
404
+
405
+ pack.schema.json gains an optional `[pack.layout]` table with optional
406
+ `.repo` / `.user` sub-tables, each carrying a `parent` base and/or a
407
+ within-pack `template` path. Every field is optional and the change is
408
+ additive — a manifest omitting it must still validate (legacy invariance),
409
+ and a pack may declare only `.repo` (the three current consumers do, since
410
+ their output is per-repo and they have no sensible absolute user default).
411
+ """
412
+
413
+ def test_accepts_layout_with_repo_and_user(self) -> None:
414
+ """A manifest carrying [pack.layout.repo] and [pack.layout.user] validates."""
415
+ from agentbundle.build.validate import validate
416
+
417
+ schema = _load_schema()
418
+ toml_text = """
419
+ [pack]
420
+ name = "research"
421
+ version = "0.5.0"
422
+
423
+ [pack.layout.repo]
424
+ parent = ".context/research"
425
+
426
+ [pack.layout.user]
427
+ parent = "~/research-projects"
428
+ template = "references/agentbundle-layout.toml"
429
+ """
430
+ instance = _parse_toml(toml_text)
431
+ errors = validate(instance, schema)
432
+ self.assertEqual(errors, [], "\n".join(errors))
433
+
434
+ def test_accepts_layout_repo_only(self) -> None:
435
+ """A pack may declare only [pack.layout.repo] — the consumer shape."""
436
+ from agentbundle.build.validate import validate
437
+
438
+ schema = _load_schema()
439
+ toml_text = """
440
+ [pack]
441
+ name = "product-engineering"
442
+ version = "0.5.0"
443
+
444
+ [pack.layout.repo]
445
+ parent = "docs/product"
446
+ """
447
+ instance = _parse_toml(toml_text)
448
+ errors = validate(instance, schema)
449
+ self.assertEqual(errors, [], "\n".join(errors))
450
+
451
+ def test_accepts_manifest_omitting_layout(self) -> None:
452
+ """Optionality: a manifest with no [pack.layout] validates (legacy invariance)."""
453
+ from agentbundle.build.validate import validate
454
+
455
+ schema = _load_schema()
456
+ toml_text = """
457
+ [pack]
458
+ name = "core"
459
+ version = "0.4.0"
460
+ """
461
+ instance = _parse_toml(toml_text)
462
+ errors = validate(instance, schema)
463
+ self.assertEqual(errors, [], "\n".join(errors))
464
+
465
+ def test_rejects_non_string_parent(self) -> None:
466
+ """[pack.layout.repo].parent must be a string; a non-string is rejected."""
467
+ from agentbundle.build.validate import validate
468
+
469
+ schema = _load_schema()
470
+ instance = {
471
+ "pack": {
472
+ "name": "core",
473
+ "version": "0.1.0",
474
+ "layout": {"repo": {"parent": 42}},
475
+ }
476
+ }
477
+ errors = validate(instance, schema)
478
+ self.assertTrue(errors, "schema accepted a non-string layout.repo.parent")
479
+
480
+
402
481
  class PackSchemaLoadsTests(unittest.TestCase):
403
482
  """Smoke test: the schema file loads and has the expected top-level shape."""
404
483
 
@@ -1,4 +1,4 @@
1
- """`agentbundle` CLI dispatcher — argparse over the eleven F-cli subcommands.
1
+ """`agentbundle` CLI dispatcher — argparse over the F-cli subcommands.
2
2
 
3
3
  Subcommand order on the parser matches the canonical install-workflow order
4
4
  from the spec (discovery-first): `list-packs`, `list-profiles`, `list-targets`,
@@ -11,9 +11,12 @@ Each subcommand's `run(args) -> int` lives under `agentbundle.commands.*`;
11
11
  this module wires `argparse` and prints `--version`. No business logic here.
12
12
 
13
13
  RFC-0004 surface additions:
14
- - `--scope {repo,user}` on install, uninstall, upgrade, diff, init-state,
15
- list-targets (the six subcommands enumerated in spec § *Install-scope
16
- dimension*).
14
+ - `--scope {repo,user}` on install, uninstall, upgrade, diff, init-state
15
+ (the spec § *Install-scope dimension* subcommands). The original RFC-0004
16
+ set also listed `list-targets`, and `reconcile` carried a single-value
17
+ `--scope user`; both were dead (parsed-but-never-read / only-legal-value-
18
+ equals-default) and dropped in the CLI-hygiene sweep, so passing `--scope`
19
+ to either now surfaces `unknown flag for <verb>: --scope`.
17
20
  - `--force` on install only (cross-scope conflict bypass; see
18
21
  spec § *Dual-scope install conflict*).
19
22
  - Forbidden flags on the five excluded subcommands surface with the
@@ -202,12 +205,11 @@ def _build_parser() -> argparse.ArgumentParser:
202
205
  sp.add_argument("catalogue", help="Catalogue URI (local path or git+https://...).")
203
206
  sp.set_defaults(func=_lazy("list_profiles"))
204
207
 
205
- # --- list-targets --- (--scope as read-only filter)
208
+ # --- list-targets --- (no flags; queries the adapter registry)
206
209
  sp = subparsers.add_parser(
207
210
  "list-targets",
208
211
  help="List adapter targets the CLI supports (claude-code, kiro-ide, kiro-cli, kiro (deprecated → kiro-ide), copilot, codex).",
209
212
  )
210
- sp.add_argument("--scope", choices=("repo", "user"))
211
213
  sp.set_defaults(func=_lazy("list_targets"))
212
214
 
213
215
  # --- scaffold --- (no --scope; always repo-targeted)
@@ -309,6 +311,17 @@ def _build_parser() -> argparse.ArgumentParser:
309
311
  "successful preview, even with Tier-2 collisions present."
310
312
  ),
311
313
  )
314
+ sp.add_argument(
315
+ "--yes",
316
+ action="store_true",
317
+ help=(
318
+ "Answer yes to install's interactive confirmations: the --force "
319
+ "destructive-cleanup prompt (removing leftover files), and the "
320
+ "offer to upgrade a pack already installed at the requested scope. "
321
+ "Required for non-interactive use of those paths; without it they "
322
+ "prompt on a TTY and refuse rather than block on a non-TTY."
323
+ ),
324
+ )
312
325
  sp.set_defaults(func=_lazy("install"))
313
326
 
314
327
  # --- validate --- (no --scope; schema + rails A/B/C)
@@ -374,15 +387,28 @@ def _build_parser() -> argparse.ArgumentParser:
374
387
  help="Upgrade a pack or a single primitive within a pack.",
375
388
  )
376
389
  sp.add_argument("--pack", required=True)
377
- sp.add_argument("--to", required=True, dest="to_version", help="Target pack version.")
378
- sp.add_argument("--skill")
379
- sp.add_argument("--agent")
380
- sp.add_argument("--hook")
381
- sp.add_argument("--seed")
382
- sp.add_argument("--command")
390
+ # The five per-primitive flags are mutually exclusive: a pack-version
391
+ # upgrade is for the whole pack or exactly one named primitive, never two
392
+ # at once. Grouping them lets argparse reject `--skill a --agent b` rather
393
+ # than silently upgrading only the first.
394
+ prim_group = sp.add_mutually_exclusive_group()
395
+ prim_group.add_argument("--skill")
396
+ prim_group.add_argument("--agent")
397
+ prim_group.add_argument("--hook")
398
+ prim_group.add_argument("--seed")
399
+ prim_group.add_argument("--command")
383
400
  sp.add_argument("catalogue", help="Catalogue URI to fetch the new version from.")
384
401
  sp.add_argument("--root", default=".")
385
402
  sp.add_argument("--scope", choices=("repo", "user"))
403
+ sp.add_argument(
404
+ "--yes",
405
+ action="store_true",
406
+ help=(
407
+ "Skip the upgrade confirmation prompt. Required for non-interactive "
408
+ "use (CI, pipes); without it the upgrade asks before writing, and "
409
+ "refuses rather than blocking when stdin is not a TTY."
410
+ ),
411
+ )
386
412
  sp.add_argument(
387
413
  "--dry-run",
388
414
  action="store_true",
@@ -402,6 +428,24 @@ def _build_parser() -> argparse.ArgumentParser:
402
428
  sp.add_argument("--pack", required=True)
403
429
  sp.add_argument("--root", default=".")
404
430
  sp.add_argument("--scope", choices=("repo", "user"))
431
+ sp.add_argument(
432
+ "--yes",
433
+ action="store_true",
434
+ help=(
435
+ "Skip the uninstall confirmation prompt. Required for non-interactive "
436
+ "use (CI, pipes); without it the uninstall asks before removing any "
437
+ "file, and refuses rather than blocking when stdin is not a TTY."
438
+ ),
439
+ )
440
+ sp.add_argument(
441
+ "--dry-run",
442
+ action="store_true",
443
+ help=(
444
+ "Preview the per-file plan (remove tier-1 / keep tier-2) without "
445
+ "removing anything — no file removed, no hook-wiring unproject, no "
446
+ "state change. Exits 0."
447
+ ),
448
+ )
405
449
  sp.set_defaults(func=_lazy("uninstall"))
406
450
 
407
451
  # --- init-state --- (--scope selector; --migrate flag)
@@ -453,10 +497,9 @@ def _build_parser() -> argparse.ArgumentParser:
453
497
  "RFC-0005: read-only orphan reporter — walks Claude Code "
454
498
  "settings.json and Kiro agent JSONs named in user-scope state, "
455
499
  "reports entries the file/state pair disagrees on. Read-only; "
456
- "no --apply flag."
500
+ "no --apply flag. User-scope only; no --scope flag."
457
501
  ),
458
502
  )
459
- sp.add_argument("--scope", choices=("user",), default="user")
460
503
  sp.set_defaults(func=_lazy("reconcile"))
461
504
 
462
505
  return parser
@@ -243,3 +243,51 @@ def summarize_plan(actions: list[str]) -> str:
243
243
  ]
244
244
  body = ", ".join(parts) if parts else "no files"
245
245
  return f"dry-run: {len(actions)} file(s) — {body}. Nothing written."
246
+
247
+
248
+ # ---------------------------------------------------------------------------
249
+ # Destructive-confirmation mechanics (shared by `uninstall`, `install --force`,
250
+ # the `install`→`upgrade` offer, and `upgrade`)
251
+ # ---------------------------------------------------------------------------
252
+
253
+
254
+ def confirm_or_refuse(
255
+ *,
256
+ yes: bool,
257
+ question: str,
258
+ refuse_message: str,
259
+ abort_message: str,
260
+ ) -> bool:
261
+ """Decide whether a destructive command may proceed.
262
+
263
+ The single home for the confirm / non-TTY-refuse / ``--yes`` mechanics first
264
+ introduced for ``upgrade`` in PR #374, so ``uninstall``, ``install --force``,
265
+ and the ``install``→``upgrade`` offer share one implementation:
266
+
267
+ - ``yes`` (the ``--yes`` flag) → return ``True`` without touching stdin.
268
+ - non-interactive stdin (``not sys.stdin.isatty()``) → print
269
+ ``refuse_message`` to stderr and return ``False`` (never block on
270
+ ``input()``).
271
+ - interactive stdin → prompt with ``question``; return ``True`` only when
272
+ the reply is ``y``/``yes`` (case-insensitive, stripped). Any other reply
273
+ — including EOF — prints ``abort_message`` to stderr and returns
274
+ ``False``.
275
+
276
+ The caller owns the ``--dry-run`` short-circuit (a dry run writes nothing, so
277
+ it must return *before* calling this), and owns every command-specific
278
+ message string passed in here — so each call site preserves its own exact
279
+ stderr contract.
280
+ """
281
+ if yes:
282
+ return True
283
+ if not sys.stdin.isatty():
284
+ print(refuse_message, file=sys.stderr)
285
+ return False
286
+ try:
287
+ reply = input(question)
288
+ except EOFError:
289
+ reply = ""
290
+ if reply.strip().lower() not in ("y", "yes"):
291
+ print(abort_message, file=sys.stderr)
292
+ return False
293
+ return True