agentbundle 0.28.1__tar.gz → 0.29.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 (200) hide show
  1. {agentbundle-0.28.1 → agentbundle-0.29.0}/PKG-INFO +1 -1
  2. {agentbundle-0.28.1 → agentbundle-0.29.0}/README.md +8 -0
  3. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/pack.schema.json +16 -2
  4. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/cli.py +5 -4
  5. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/_common.py +6 -2
  6. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/install.py +278 -49
  7. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_installed.py +10 -3
  8. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/show.py +12 -6
  9. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/uninstall.py +99 -3
  10. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/config.py +3 -1
  11. agentbundle-0.29.0/agentbundle/local_exclude.py +375 -0
  12. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/scope.py +18 -1
  13. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/version.py +1 -1
  14. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/PKG-INFO +1 -1
  15. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/SOURCES.txt +1 -0
  16. {agentbundle-0.28.1 → agentbundle-0.29.0}/pyproject.toml +1 -1
  17. {agentbundle-0.28.1 → agentbundle-0.29.0}/README-pypi.md +0 -0
  18. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/__init__.py +0 -0
  19. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/__main__.py +0 -0
  20. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/adapter.schema.json +0 -0
  21. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/adapter.toml +0 -0
  22. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/guides/_shared/reference/catalogue-authoring-standards.md +0 -0
  23. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/guides/_shared/reference/catalogue-ci-contract.md +0 -0
  24. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/manifest.json +0 -0
  25. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/AGENTS.md +0 -0
  26. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/README.md +0 -0
  27. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/README.md +0 -0
  28. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/evals/eval_queries.json +0 -0
  29. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/pack.toml +0 -0
  30. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/AGENTS.md +0 -0
  31. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/README.md +0 -0
  32. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/_example/README.md +0 -0
  33. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/_example/profile.toml +0 -0
  34. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue.schema.json +0 -0
  35. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/guide.schema.json +0 -0
  36. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/install-defaults.toml +0 -0
  37. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/install-marker.py +0 -0
  38. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
  39. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
  40. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/profile.schema.json +0 -0
  41. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/skill-manifest.schema.json +0 -0
  42. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/skill.schema.json +0 -0
  43. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/target-vocab.toml +0 -0
  44. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/__init__.py +0 -0
  45. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/__main__.py +0 -0
  46. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapter_root_bins.py +0 -0
  47. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/__init__.py +0 -0
  48. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/claude_code.py +0 -0
  49. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/codex.py +0 -0
  50. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/copilot.py +0 -0
  51. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/cursor.py +0 -0
  52. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/gemini.py +0 -0
  53. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro.py +0 -0
  54. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  55. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  56. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/contract.py +0 -0
  57. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/lint_packs.py +0 -0
  58. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/main.py +0 -0
  59. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/phase_order.py +0 -0
  60. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/__init__.py +0 -0
  61. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  62. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  63. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  64. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/direct_directory.py +0 -0
  65. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  66. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/hook_id.py +0 -0
  67. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  68. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  69. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/merge_json.py +0 -0
  70. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  71. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/composite-agents-md.toml +0 -0
  72. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/composite-marketplace.toml +0 -0
  73. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/marketplace.toml +0 -0
  74. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-apm-package.toml +0 -0
  75. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-claude-plugin.toml +0 -0
  76. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-overlay.toml +0 -0
  77. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/self-host.toml +0 -0
  78. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/scope_rails.py +0 -0
  79. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/self_host.py +0 -0
  80. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/shared_libs.py +0 -0
  81. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/target_resolver.py +0 -0
  82. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/__init__.py +0 -0
  83. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  84. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  85. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  86. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
  87. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
  88. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  89. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  90. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  91. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
  92. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  93. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
  94. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
  95. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  96. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract.py +0 -0
  97. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  98. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  99. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  100. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  101. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  102. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
  103. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  104. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  105. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  106. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  107. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
  108. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  109. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  110. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  111. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
  112. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
  113. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  114. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  115. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_security.py +0 -0
  116. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
  117. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
  118. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
  119. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  120. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shared_prefix_contract.py +0 -0
  121. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  122. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  123. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  124. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_validate.py +0 -0
  125. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_workspace_status_projection.py +0 -0
  126. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
  127. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/user_libs.py +0 -0
  128. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/validate.py +0 -0
  129. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue.py +0 -0
  130. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/__init__.py +0 -0
  131. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/archive.py +0 -0
  132. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/build.py +0 -0
  133. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/config.py +0 -0
  134. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/defaults.py +0 -0
  135. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/diagnostics.py +0 -0
  136. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/identity.py +0 -0
  137. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/initialise.py +0 -0
  138. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/initialise_self_hosted.py +0 -0
  139. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/lint.py +0 -0
  140. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/package.py +0 -0
  141. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/results.py +0 -0
  142. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/self_host.py +0 -0
  143. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/self_host_windows.py +0 -0
  144. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/skill_spec_lint.py +0 -0
  145. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/toml_emit.py +0 -0
  146. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/verify.py +0 -0
  147. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/categories.py +0 -0
  148. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/__init__.py +0 -0
  149. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/_drop_warning.py +0 -0
  150. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/adapt.py +0 -0
  151. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_build.py +0 -0
  152. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_init.py +0 -0
  153. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_lint.py +0 -0
  154. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_package.py +0 -0
  155. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_self_host.py +0 -0
  156. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_sync_defaults.py +0 -0
  157. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_tooling_stub.py +0 -0
  158. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_verify.py +0 -0
  159. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/config.py +0 -0
  160. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/diff.py +0 -0
  161. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/docs.py +0 -0
  162. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/init_state.py +0 -0
  163. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_packs.py +0 -0
  164. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_profiles.py +0 -0
  165. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_targets.py +0 -0
  166. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/oplog_cmd.py +0 -0
  167. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/pack_config_cmd.py +0 -0
  168. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/pack_evals.py +0 -0
  169. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/package_catalogue.py +0 -0
  170. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/profile.py +0 -0
  171. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/reconcile.py +0 -0
  172. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/render.py +0 -0
  173. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/scaffold.py +0 -0
  174. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/upgrade.py +0 -0
  175. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/validate.py +0 -0
  176. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/https_catalogue.py +0 -0
  177. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/oplog.py +0 -0
  178. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/pack_inventory.py +0 -0
  179. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/render.py +0 -0
  180. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/safety.py +0 -0
  181. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/scaffold.py +0 -0
  182. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/source_defaults.py +0 -0
  183. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/statelock.py +0 -0
  184. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/user_config.py +0 -0
  185. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/workspace_mcp.py +0 -0
  186. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  187. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/entry_points.txt +0 -0
  188. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/requires.txt +0 -0
  189. {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/top_level.txt +0 -0
  190. {agentbundle-0.28.1 → agentbundle-0.29.0}/setup.cfg +0 -0
  191. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_adapter_permissions_projection.py +0 -0
  192. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_linear_primitive.py +0 -0
  193. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_loop_engine_events_jsonl.py +0 -0
  194. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_elicit.py +0 -0
  195. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_event_bridge.py +0 -0
  196. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_git.py +0 -0
  197. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_lifecycle.py +0 -0
  198. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_stdin.py +0 -0
  199. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_tools.py +0 -0
  200. {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_status_engine_autonomous.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.28.1
3
+ Version: 0.29.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
@@ -26,6 +26,14 @@ agentbundle install --pack desk-research --scope user
26
26
 
27
27
  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.
28
28
 
29
+ **Try a pack without committing anything** — so the pack is fully live for your session but completely invisible to git:
30
+
31
+ ```bash
32
+ agentbundle install --pack core --scope local
33
+ ```
34
+
35
+ Local-scope packs land at the same paths as a repo install, but every file is registered in `.git/info/exclude` — so `git status` stays clean and nothing gets accidentally committed. Uninstall removes both the files and the exclude entries; the repo returns to exactly the state it was in before. Requires a git working tree. Useful for trialling a pack in a repo you don't own, or testing an update before rolling it out to the team.
36
+
29
37
  The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install the same pack for each agent at the same scope — they coexist, and the agents that read `.agents/skills/` (codex, cursor, gemini, copilot) share one skill copy instead of fighting over it. 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.
30
38
 
31
39
  ## More commands
@@ -75,7 +75,7 @@
75
75
  "allowed-scopes": {
76
76
  "type": "array",
77
77
  "minItems": 1,
78
- "items": {"enum": ["repo", "user"]}
78
+ "items": {"enum": ["repo", "user", "local"]}
79
79
  },
80
80
  "user-scope-hooks": {"type": "boolean"},
81
81
  "allowed-adapters": {
@@ -98,7 +98,21 @@
98
98
  "properties": {
99
99
  "allowed-scopes": {"contains": {"enum": ["repo"]}}
100
100
  }
101
- }
101
+ },
102
+ "allOf": [
103
+ {
104
+ "if": {
105
+ "properties": {
106
+ "allowed-scopes": {"contains": {"enum": ["local"]}}
107
+ }
108
+ },
109
+ "then": {
110
+ "properties": {
111
+ "allowed-scopes": {"contains": {"enum": ["repo"]}}
112
+ }
113
+ }
114
+ }
115
+ ]
102
116
  },
103
117
  "evals": {
104
118
  "type": "object",
@@ -33,6 +33,7 @@ import re
33
33
  import sys
34
34
  from typing import Sequence
35
35
 
36
+ from agentbundle.scope import LEGAL_SCOPES
36
37
  from agentbundle.version import CLI_VERSION, SPEC_VERSION
37
38
 
38
39
  # Path-bearing argparse-attribute names. The set is curated rather than
@@ -258,8 +259,8 @@ def _build_parser() -> argparse.ArgumentParser:
258
259
  sp.add_argument("--root", default=".")
259
260
  sp.add_argument(
260
261
  "--scope",
261
- choices=("repo", "user"),
262
- help="Limit the listing to one scope. Default: both user and repo.",
262
+ choices=tuple(sorted(LEGAL_SCOPES)),
263
+ help="Limit the listing to one scope. Default: all scopes (repo, user, local).",
263
264
  )
264
265
  sp.add_argument(
265
266
  "--no-check",
@@ -387,7 +388,7 @@ def _build_parser() -> argparse.ArgumentParser:
387
388
  ),
388
389
  )
389
390
  sp.add_argument("--output", default=".")
390
- sp.add_argument("--scope", choices=("repo", "user"))
391
+ sp.add_argument("--scope", choices=tuple(sorted(LEGAL_SCOPES)))
391
392
  sp.add_argument(
392
393
  "--force",
393
394
  action="store_true",
@@ -627,7 +628,7 @@ def _build_parser() -> argparse.ArgumentParser:
627
628
  )
628
629
  sp.add_argument("--pack", required=True)
629
630
  sp.add_argument("--root", default=".")
630
- sp.add_argument("--scope", choices=("repo", "user"))
631
+ sp.add_argument("--scope", choices=tuple(sorted(LEGAL_SCOPES)))
631
632
  sp.add_argument(
632
633
  "--adapter",
633
634
  choices=_shipped_adapters_choices(),
@@ -43,11 +43,15 @@ def resolve_catalogue_uri(args: argparse.Namespace) -> str:
43
43
  def resolve_state_path(scope: str, root: Path) -> Path:
44
44
  """Return the state-file path for *scope* under *root*.
45
45
 
46
- ``scope="repo"`` → ``<root>/.agentbundle-state.toml``
47
- ``scope="user"`` → ``<root>/.agentbundle/state.toml``
46
+ ``scope="repo"`` → ``<root>/.agentbundle-state.toml``
47
+ ``scope="user"`` → ``<root>/.agentbundle/state.toml``
48
+ ``scope="local"`` → ``<root>/.agentbundle-local-state.toml``
49
+ (RFC-0080: per-clone, never-committed; never added to .gitignore)
48
50
  """
49
51
  if scope == "user":
50
52
  return root / ".agentbundle" / "state.toml"
53
+ if scope == "local":
54
+ return root / ".agentbundle-local-state.toml"
51
55
  return root / ".agentbundle-state.toml"
52
56
 
53
57
 
@@ -254,8 +254,11 @@ def run(args: argparse.Namespace) -> int:
254
254
  emit_install_routes: bool = bool(args.emit_install_routes)
255
255
  else:
256
256
  # Absent attribute → repo-scope legacy callers want dist-tree
257
- # shape; user-scope callers want the new path-jail.
258
- emit_install_routes = cli_scope != "user"
257
+ # shape; user-scope and local-scope callers want the new path-jail.
258
+ # RFC-0080: use `not in ("user", "local")` rather than `== "repo"` so
259
+ # legacy/programmatic callers with cli_scope=None continue to get the
260
+ # dist-tree path (None not in ("user", "local") is True).
261
+ emit_install_routes = cli_scope not in ("user", "local")
259
262
 
260
263
  # Range-check the CLI-supplied pack name before any I/O. The manifest's
261
264
  # `pack.name` is checked by `_assert_pack_metadata_shape` below; this
@@ -387,6 +390,16 @@ def run(args: argparse.Namespace) -> int:
387
390
  # precedent). The mutex consults `requested_scope`, not
388
391
  # `args.scope`, so a pack whose `[scope] default-scope = "user"`
389
392
  # surfaces the binding correctly when `--scope` is omitted.
393
+ if requested_scope == "local" and emit_install_routes:
394
+ # AC9 (RFC-0080): --emit-install-routes is incompatible with --scope local.
395
+ # The plugins route (RFC-0008) has its own local-scope behaviour; the
396
+ # catalogue-publishing opt-in must not run for per-clone installs.
397
+ print(
398
+ "install: --emit-install-routes is not supported at --scope local "
399
+ "(see RFC-0008 for the plugins route's own local-scope behaviour)",
400
+ file=sys.stderr,
401
+ )
402
+ return 1
390
403
  if requested_scope == "user" and emit_install_routes:
391
404
  print(
392
405
  "install: --emit-install-routes is bound to --scope repo",
@@ -405,6 +418,21 @@ def run(args: argparse.Namespace) -> int:
405
418
  )
406
419
  return 1
407
420
 
421
+ # AC8 (RFC-0080): local scope requires a git work tree. Check before any
422
+ # git-dependent helper (derive_worktree_id, get_exclude_path) can be called.
423
+ # The check is cheap (one git invocation) and surfaces a clear message rather
424
+ # than a cryptic failure deep in the exclude-block path.
425
+ if requested_scope == "local":
426
+ from agentbundle.local_exclude import is_git_repo
427
+
428
+ if not is_git_repo(output_root):
429
+ print(
430
+ f"install: --scope local requires a git work tree; "
431
+ f"{output_root} does not appear to be inside one",
432
+ file=sys.stderr,
433
+ )
434
+ return 1
435
+
408
436
  # ── Step 3: Pre-flight — load state at *both* scopes ──────────────────────
409
437
  # Read-only loads (for_write=False) — we do the v0.1 refusal *only*
410
438
  # for scopes we're about to write to, after we know which they are.
@@ -452,18 +480,27 @@ def run(args: argparse.Namespace) -> int:
452
480
  user_root = None
453
481
  user_state_path = None
454
482
 
483
+ # RFC-0080: local state — read the per-clone state file for cross-scope
484
+ # conflict detection and dependency resolution.
485
+ local_state_path_pre = output_root / ".agentbundle-local-state.toml"
486
+ from agentbundle.config import State as _State
487
+
488
+ try:
489
+ local_state: State = load_state(local_state_path_pre)
490
+ except ConfigError:
491
+ local_state = _State()
492
+
455
493
  # ── Step 3b: Dependency gate — [pack.dependencies.required] ──────────────
456
- # Resolves required deps against the union of repo + user state (AC17).
494
+ # Resolves required deps against the union of repo + user + local state (AC17/AC23b).
457
495
  # Gate runs before any write (and before the already-installed check, so
458
496
  # dep errors surface even when another early-exit would fire).
459
- from agentbundle.config import State as _State
460
-
461
497
  _effective_user_state: State = user_state if user_state is not None else _State()
462
498
  try:
463
499
  validate_dependencies_required(
464
500
  pack_toml,
465
501
  repo_state=repo_state,
466
502
  user_state=_effective_user_state,
503
+ local_state=local_state if requested_scope == "local" else None,
467
504
  # pack-profiles AC7: under a profile install, a required dep may be
468
505
  # satisfied by another pack in the same batch. None for single-pack.
469
506
  also_installing=getattr(args, "_batch_packs", None),
@@ -480,10 +517,16 @@ def run(args: argparse.Namespace) -> int:
480
517
  # resolved temp dir in that case. `or catalogue_uri` also handles the
481
518
  # `_source_uri = None` case (attribute present but None).
482
519
  _guard_source_uri = getattr(args, "_source_uri", None) or catalogue_uri
520
+ # Three-way selection: local reads local_state (not repo_state) so
521
+ # source-conflict checking uses the correct state for each scope.
522
+ _src_conflict_state = (
523
+ local_state if requested_scope == "local"
524
+ else (repo_state if requested_scope == "repo" else user_state)
525
+ )
483
526
  _src_conflict = _check_source_conflict(
484
527
  pack_name,
485
528
  requested_scope,
486
- repo_state if requested_scope == "repo" else user_state,
529
+ _src_conflict_state,
487
530
  _guard_source_uri,
488
531
  )
489
532
  if _src_conflict is not None:
@@ -510,7 +553,7 @@ def run(args: argparse.Namespace) -> int:
510
553
  _pack_contract_version = pack_spec_version(pack_toml)
511
554
  repo_target_adapter: str | None = None
512
555
  allowed_prefixes_repo: list[str] | None = None
513
- if requested_scope == "repo" and not emit_install_routes:
556
+ if requested_scope in ("repo", "local") and not emit_install_routes:
514
557
  try:
515
558
  repo_target_adapter = _resolve_target_adapter(
516
559
  pack_dir,
@@ -615,6 +658,13 @@ def run(args: argparse.Namespace) -> int:
615
658
  if user_target_adapter is not None
616
659
  else user_state.has_pack(pack_name)
617
660
  )
661
+ # RFC-0080: local scope — adapter-identity check (AC21b: refuse only when
662
+ # the exact (pack, adapter) row already exists, not pack-level boolean).
663
+ installed_at_local = (
664
+ local_state.row(pack_name, repo_target_adapter) is not None
665
+ if repo_target_adapter is not None
666
+ else local_state.has_pack(pack_name)
667
+ )
618
668
 
619
669
  # ── Step 4: Branch on already-installed shape ─────────────────────────────
620
670
  # 4a. Already at requested scope → offer to upgrade instead; --force does
@@ -663,8 +713,41 @@ def run(args: argparse.Namespace) -> int:
663
713
  user_target_adapter if requested_scope == "user" else repo_target_adapter
664
714
  ),
665
715
  )
716
+ # AC21b (RFC-0080): local reinstall guard uses adapter-identity check.
717
+ # upgrade.run does not support local scope in v1; refuse without offering.
718
+ # A second *different* adapter for the same pack falls through to the
719
+ # union-write path (AC14b). `installed_at_local` already uses adapter-identity.
720
+ if requested_scope == "local" and installed_at_local:
721
+ print(
722
+ f"install: {pack_name} already installed at local scope "
723
+ f"(adapter: {repo_target_adapter}); "
724
+ "uninstall --scope local then re-install to refresh",
725
+ file=sys.stderr,
726
+ )
727
+ return 1
666
728
 
667
729
  # 4b. Already at the *other* scope, no --force → refuse cross-scope.
730
+ # RFC-0080 AC11: repo/local mutual exclusion is --force-immune.
731
+ # AC12: user/local coexists (user-scope files land in ~/.claude/, outside
732
+ # the working tree and the exclude block).
733
+ if requested_scope == "local" and installed_at_repo:
734
+ # Force-immune: local scope cannot coexist with a repo-scope install.
735
+ print(
736
+ f"install: {pack_name} already installed at repo scope; "
737
+ "--scope local and --scope repo are mutually exclusive; "
738
+ "uninstall the repo install first",
739
+ file=sys.stderr,
740
+ )
741
+ return 1
742
+ if requested_scope == "repo" and installed_at_local:
743
+ # Force-immune: repo scope cannot coexist with a local-scope install.
744
+ print(
745
+ f"install: {pack_name} already installed at local scope; "
746
+ "--scope repo and --scope local are mutually exclusive; "
747
+ "uninstall the local install first",
748
+ file=sys.stderr,
749
+ )
750
+ return 1
668
751
  other_scope = "user" if requested_scope == "repo" else "repo"
669
752
  other_already = installed_at_user if requested_scope == "repo" else installed_at_repo
670
753
  if other_already and not force:
@@ -774,6 +857,26 @@ def run(args: argparse.Namespace) -> int:
774
857
  already_installed=installed_at_repo,
775
858
  )
776
859
  )
860
+ elif scope_value == "local":
861
+ # RFC-0080: local scope shares repo-style paths and the same
862
+ # allowed_prefixes as repo scope; state file is the per-clone
863
+ # .agentbundle-local-state.toml (never committed).
864
+ local_state_path = output_root / ".agentbundle-local-state.toml"
865
+ try:
866
+ local_state_for_write = load_state(local_state_path, for_write=True)
867
+ except ConfigError as exc:
868
+ print(f"install: {exc}", file=sys.stderr)
869
+ return 1
870
+ plans.append(
871
+ _ScopePlan(
872
+ scope="local",
873
+ root=output_root,
874
+ state_path=local_state_path,
875
+ allowed_prefixes=allowed_prefixes_repo,
876
+ state=local_state_for_write,
877
+ already_installed=installed_at_local,
878
+ )
879
+ )
777
880
  else:
778
881
  # User scope: surface unresolvable $HOME *now* so failures
779
882
  # land in pre-flight, before any write.
@@ -856,7 +959,7 @@ def run(args: argparse.Namespace) -> int:
856
959
 
857
960
  for plan in plans:
858
961
  scope_adapter = (
859
- repo_target_adapter if plan.scope == "repo" else user_target_adapter
962
+ repo_target_adapter if plan.scope in ("repo", "local") else user_target_adapter
860
963
  )
861
964
  if scope_adapter is None:
862
965
  continue
@@ -926,7 +1029,7 @@ def run(args: argparse.Namespace) -> int:
926
1029
  # post-render and pre-path-jail.
927
1030
  # `user_target_adapter` resolved earlier (alongside allowed_prefixes_user).
928
1031
  try:
929
- if any(p.scope == "repo" for p in plans):
1032
+ if any(p.scope in ("repo", "local") for p in plans):
930
1033
  if emit_install_routes:
931
1034
  # Legacy dist-tree producer (RFC-0012 § *CLI surface*'s
932
1035
  # catalogue-publishing opt-in).
@@ -1015,7 +1118,7 @@ def run(args: argparse.Namespace) -> int:
1015
1118
  # This catches a pack whose projection rule resolves under
1016
1119
  # ~/Documents/ before any byte is written.
1017
1120
  for plan in plans:
1018
- projection = repo_projection if plan.scope == "repo" else user_projection
1121
+ projection = repo_projection if plan.scope in ("repo", "local") else user_projection
1019
1122
  if projection is None:
1020
1123
  continue
1021
1124
  try:
@@ -1042,7 +1145,7 @@ def run(args: argparse.Namespace) -> int:
1042
1145
  for plan in plans:
1043
1146
  if plan.already_installed:
1044
1147
  continue
1045
- projection = repo_projection if plan.scope == "repo" else user_projection
1148
+ projection = repo_projection if plan.scope in ("repo", "local") else user_projection
1046
1149
  if projection is None:
1047
1150
  continue
1048
1151
  for relpath, content in sorted(projection.items()):
@@ -1083,14 +1186,14 @@ def run(args: argparse.Namespace) -> int:
1083
1186
  if plan.already_installed:
1084
1187
  continue
1085
1188
 
1086
- projection = repo_projection if plan.scope == "repo" else user_projection
1189
+ projection = repo_projection if plan.scope in ("repo", "local") else user_projection
1087
1190
  if projection is None:
1088
1191
  projection = {}
1089
1192
 
1090
1193
  # The adapter this scope's row is keyed by (RFC-0052). emit-install-
1091
1194
  # routes (legacy dist-tree) has no per-IDE adapter → claude-code.
1092
1195
  scope_adapter = (
1093
- repo_target_adapter if plan.scope == "repo" else user_target_adapter
1196
+ repo_target_adapter if plan.scope in ("repo", "local") else user_target_adapter
1094
1197
  ) or "claude-code"
1095
1198
 
1096
1199
  # ── Footprint gate (RFC-0052 / ADR-0039) ──────────────────────────
@@ -1144,6 +1247,62 @@ def run(args: argparse.Namespace) -> int:
1144
1247
  )
1145
1248
  return 1
1146
1249
 
1250
+ # ── Local-scope: write exclude block before files (AC21 commit order) ──
1251
+ # Per RFC-0080 / plan T8+T9: write .git/info/exclude block for this
1252
+ # pack+worktree BEFORE any projected file is written to disk. Files
1253
+ # are git-invisible even if the process dies between this step and the
1254
+ # file writes; the no-footprint guarantee holds across abrupt death.
1255
+ # Rollback order (AC21): delete written files FIRST, then restore the
1256
+ # exclude block — deleting before restoring prevents a transient window
1257
+ # where files are on disk but not excluded.
1258
+ _local_exclude_path: Path | None = None
1259
+ _local_prior_exclude: bytes | None = None
1260
+ _local_written_files: list[Path] = []
1261
+ if plan.scope == "local":
1262
+ from agentbundle.local_exclude import (
1263
+ derive_worktree_id,
1264
+ get_exclude_path,
1265
+ rollback_exclude_block,
1266
+ snapshot_exclude,
1267
+ write_exclude_block,
1268
+ )
1269
+
1270
+ try:
1271
+ _local_worktree_id = derive_worktree_id(plan.root)
1272
+ _local_exclude_path = get_exclude_path(plan.root)
1273
+ except RuntimeError as _git_exc:
1274
+ # Mid-run git failure (git binary removed, repo deleted mid-install).
1275
+ # The AC8 pre-flight already verified the repo exists; this is a
1276
+ # transient environment failure. Nothing has been written yet.
1277
+ print(
1278
+ f"install: git error during local-scope setup: {_git_exc}",
1279
+ file=sys.stderr,
1280
+ )
1281
+ return 1
1282
+ _local_prior_exclude = snapshot_exclude(_local_exclude_path)
1283
+
1284
+ # Union of patterns: existing other-adapter rows for this pack
1285
+ # (so the block always covers the full installed footprint) plus
1286
+ # the new projection's relpaths. The state file is always included
1287
+ # so it is also git-invisible.
1288
+ _union_relpaths: set[str] = set()
1289
+ for (pname, _), pst in local_state.packs.items():
1290
+ if pname == pack_name:
1291
+ _union_relpaths.update(pst.files.keys())
1292
+ _union_relpaths.update(projection.keys())
1293
+ _exclude_patterns = (
1294
+ ["/.agentbundle-local-state.toml"]
1295
+ + ["/" + r for r in sorted(_union_relpaths)]
1296
+ )
1297
+ try:
1298
+ write_exclude_block(
1299
+ _local_exclude_path, pack_name, _local_worktree_id,
1300
+ _exclude_patterns,
1301
+ )
1302
+ except OSError as exc:
1303
+ print(f"install: failed to write exclude block: {exc}", file=sys.stderr)
1304
+ return 1
1305
+
1147
1306
  # Reset the PackState for this scope's install.
1148
1307
  prior = plan.state.row(pack_name, scope_adapter)
1149
1308
  new_pack_state = PackState(
@@ -1195,11 +1354,21 @@ def run(args: argparse.Namespace) -> int:
1195
1354
  try:
1196
1355
  safety.write_companion(plan.root, relpath, content)
1197
1356
  except safety.PathJailError as exc:
1357
+ if plan.scope == "local" and _local_exclude_path is not None:
1358
+ for _p in _local_written_files:
1359
+ with contextlib.suppress(OSError):
1360
+ _p.unlink(missing_ok=True)
1361
+ rollback_exclude_block(_local_exclude_path, _local_prior_exclude)
1198
1362
  print(f"install: {exc}", file=sys.stderr)
1199
1363
  return 1
1200
- plan.new_companions.append(
1201
- safety.companion_path(Path(relpath)).as_posix()
1202
- )
1364
+ companion_relpath = safety.companion_path(Path(relpath)).as_posix()
1365
+ plan.new_companions.append(companion_relpath)
1366
+ # AC21 rollback: track the companion file so that a subsequent
1367
+ # state-write failure removes it along with regular projected files.
1368
+ # Without this, the exclude-block restore after rollback would
1369
+ # leave the .upstream companion git-visible.
1370
+ if plan.scope == "local":
1371
+ _local_written_files.append(plan.root / companion_relpath)
1203
1372
  else:
1204
1373
  try:
1205
1374
  safety.write_jailed(
@@ -1210,8 +1379,15 @@ def run(args: argparse.Namespace) -> int:
1210
1379
  allowed_prefixes=plan.allowed_prefixes,
1211
1380
  )
1212
1381
  except safety.PathJailError as exc:
1382
+ if plan.scope == "local" and _local_exclude_path is not None:
1383
+ for _p in _local_written_files:
1384
+ with contextlib.suppress(OSError):
1385
+ _p.unlink(missing_ok=True)
1386
+ rollback_exclude_block(_local_exclude_path, _local_prior_exclude)
1213
1387
  print(f"install: {exc}", file=sys.stderr)
1214
1388
  return 1
1389
+ if plan.scope == "local":
1390
+ _local_written_files.append(plan.root / relpath)
1215
1391
  new_pack_state.files[relpath] = {
1216
1392
  "sha": safety.sha256_bytes(content),
1217
1393
  "from-pack-version": pack_version,
@@ -1331,12 +1507,13 @@ def run(args: argparse.Namespace) -> int:
1331
1507
  owned_rows=owned_rows,
1332
1508
  root=plan.root,
1333
1509
  )
1334
- elif plan.scope == "repo" and repo_target_adapter is not None:
1335
- # RFC-0012: record the resolved adapter on every repo-scope
1510
+ elif plan.scope in ("repo", "local") and repo_target_adapter is not None:
1511
+ # RFC-0012: record the resolved adapter on every repo/local-scope
1336
1512
  # per-IDE install. State-hint short-circuit at upgrade time
1337
1513
  # (AC10b parity at repo scope) depends on this. Skipped
1338
1514
  # when `--emit-install-routes` is set — the legacy dist-tree
1339
- # producer has no single adapter to pin.
1515
+ # producer has no single adapter to pin. Local scope uses the
1516
+ # same repo_target_adapter (RFC-0080 D1: same adapter resolution).
1340
1517
  new_pack_state.adapter = repo_target_adapter
1341
1518
 
1342
1519
  from agentbundle.config import STATE_SCHEMA_VERSION
@@ -1353,7 +1530,14 @@ def run(args: argparse.Namespace) -> int:
1353
1530
  # `~/.agentbundle/state.toml` which already matches the prefix.
1354
1531
  state_relpath = str(plan.state_path.relative_to(plan.root))
1355
1532
  state_prefixes = plan.allowed_prefixes
1356
- if plan.scope == "repo" and state_relpath == ".agentbundle-state.toml":
1533
+ # State files are CLI-owned metadata, not pack-projected content; skip
1534
+ # the adapter prefix check (the jail-under-root check still fires).
1535
+ # Covers both repo (.agentbundle-state.toml) and local scope
1536
+ # (.agentbundle-local-state.toml) — neither matches any adapter prefix.
1537
+ if plan.scope in ("repo", "local") and state_relpath in (
1538
+ ".agentbundle-state.toml",
1539
+ ".agentbundle-local-state.toml",
1540
+ ):
1357
1541
  state_prefixes = None
1358
1542
 
1359
1543
  # Persist under the cross-process lock (RFC-0052 T0): re-read the
@@ -1375,21 +1559,41 @@ def run(args: argparse.Namespace) -> int:
1375
1559
  root=plan.root,
1376
1560
  relpath=state_relpath,
1377
1561
  )
1378
- except safety.PathJailError as exc:
1379
- print(f"install: {exc}", file=sys.stderr)
1380
- return 1
1381
- except ConfigError as exc:
1382
- # persist_state_locked re-reads state under the lock; a concurrent
1383
- # process could have rewritten it to a foreign schema between
1384
- # pre-flight and now. The projected files are already on disk —
1385
- # surface that so the adopter knows to reconcile and re-run rather
1386
- # than seeing a bare traceback over a half-recorded install.
1387
- print(
1388
- f"install: files were written but state was not recorded: "
1389
- f"{exc}; reconcile {plan.state_path} and re-run",
1390
- file=sys.stderr,
1391
- )
1392
- return 1
1562
+ except Exception as exc: # noqa: BLE001
1563
+ # AC21 rollback: on ANY state-write failure, undo projected files
1564
+ # and the exclude block so the local-scope install leaves no trace.
1565
+ # Inline (no nested function) to avoid B023 loop-variable closure.
1566
+ if plan.scope == "local" and _local_exclude_path is not None:
1567
+ from agentbundle.local_exclude import (
1568
+ rollback_exclude_block as _roll_excl,
1569
+ )
1570
+
1571
+ for _p in _local_written_files:
1572
+ with contextlib.suppress(OSError):
1573
+ _p.unlink(missing_ok=True)
1574
+ _roll_excl(_local_exclude_path, _local_prior_exclude)
1575
+ if isinstance(exc, safety.PathJailError):
1576
+ print(f"install: {exc}", file=sys.stderr)
1577
+ return 1
1578
+ if isinstance(exc, ConfigError):
1579
+ # persist_state_locked re-reads state under the lock; a concurrent
1580
+ # process could have rewritten it to a foreign schema between
1581
+ # pre-flight and now. For non-local scopes: files are on disk —
1582
+ # surface that so the adopter can reconcile. For local scope:
1583
+ # rollback already ran, so no files remain.
1584
+ if plan.scope == "local":
1585
+ print(
1586
+ f"install: state write failed, rollback complete: {exc}",
1587
+ file=sys.stderr,
1588
+ )
1589
+ else:
1590
+ print(
1591
+ f"install: files were written but state was not recorded: "
1592
+ f"{exc}; reconcile {plan.state_path} and re-run",
1593
+ file=sys.stderr,
1594
+ )
1595
+ return 1
1596
+ raise
1393
1597
 
1394
1598
  # Cross-adapter disclosure rail (RFC-0052 Decision 7): if this
1395
1599
  # install wrote to a `shared` prefix, name the prefix's other shipped
@@ -1442,6 +1646,11 @@ def run(args: argparse.Namespace) -> int:
1442
1646
  # `[pack.layout]` (most), and for any scope whose sub-table is omitted.
1443
1647
  pack_layout = pack_toml.get("pack", {}).get("layout", {})
1444
1648
  for plan in plans:
1649
+ # RFC-0080: local-scope installs are ephemeral (never committed); skip
1650
+ # the install marker and layout writes. Per AC19a ("every successful
1651
+ # repo/user install"), local scope is deliberately excluded here.
1652
+ if plan.scope == "local":
1653
+ continue
1445
1654
  scope_markers = repo_unresolved_markers if plan.scope == "repo" else []
1446
1655
  try:
1447
1656
  _append_install_marker(
@@ -1466,19 +1675,25 @@ def run(args: argparse.Namespace) -> int:
1466
1675
 
1467
1676
  # ── Step 12: Chained adapt (in-process) ──────────────────────────────────
1468
1677
  # Per spec AC19b: invoke `agentbundle.commands.adapt.run` in-process
1469
- # with --values-from <repo>/.adapt-discovery.toml regardless of the
1470
- # install scope (markers are repo-only). AC19d covers the two
1471
- # failure modes.
1472
- repo_plan = next((p for p in plans if p.scope == "repo"), None)
1473
- repo_root_for_adapt = (
1474
- repo_plan.root if repo_plan is not None else Path(args.output).resolve()
1475
- )
1476
- adapt_rc = _chain_adapt(repo_root_for_adapt)
1477
- if adapt_rc != 0:
1478
- # Per AC19d (ii): malformed `.adapt-discovery.toml` causes the
1479
- # chained adapt to raise; install exits non-zero. The marker
1480
- # file was already written in step 11 — that's by design.
1481
- return adapt_rc
1678
+ # with --values-from <repo>/.adapt-discovery.toml unless this is a
1679
+ # local-scope-only install. RFC-0080 amendment to AC19b: local-scope
1680
+ # installs are ephemeral; adapt-discovery re-running would apply to files
1681
+ # the user considers git-invisible, producing confusing churn. The marker
1682
+ # file was deliberately NOT written in step 11 for local scope, so the
1683
+ # adapt-to-project spec comment "invoke regardless of install scope
1684
+ # (markers are repo-only)" does not apply here. AC19d covers the two
1685
+ # failure modes for repo/user scope.
1686
+ if requested_scope != "local":
1687
+ repo_plan = next((p for p in plans if p.scope == "repo"), None)
1688
+ repo_root_for_adapt = (
1689
+ repo_plan.root if repo_plan is not None else Path(args.output).resolve()
1690
+ )
1691
+ adapt_rc = _chain_adapt(repo_root_for_adapt)
1692
+ if adapt_rc != 0:
1693
+ # Per AC19d (ii): malformed `.adapt-discovery.toml` causes the
1694
+ # chained adapt to raise; install exits non-zero. The marker
1695
+ # file was already written in step 11 — that's by design.
1696
+ return adapt_rc
1482
1697
 
1483
1698
  # ── Step 13: Emit installed: lines (repo first, user last) ───────────────
1484
1699
  # RFC-0011 extends user-scope output with ` via <adapter>` and an
@@ -1537,6 +1752,14 @@ def run(args: argparse.Namespace) -> int:
1537
1752
  print(
1538
1753
  f"installed: {pack_name} @ repo via {repo_target_adapter}"
1539
1754
  )
1755
+ elif plan.scope == "local":
1756
+ # RFC-0080: local-scope install; files excluded via info/exclude.
1757
+ from agentbundle.local_exclude import get_exclude_path
1758
+ _step13_exclude = get_exclude_path(plan.root)
1759
+ print(
1760
+ f"installed: {pack_name} @ local"
1761
+ f" (excluded via {_step13_exclude})"
1762
+ )
1540
1763
  else:
1541
1764
  # Defensive fallback: matches pre-RFC-0012 wording for any
1542
1765
  # path the new branches don't capture.
@@ -4042,6 +4265,7 @@ def validate_dependencies_required(
4042
4265
  *,
4043
4266
  repo_state: State,
4044
4267
  user_state: State,
4268
+ local_state: State | None = None,
4045
4269
  also_installing: set[str] | None = None,
4046
4270
  ) -> None:
4047
4271
  """Enforce [pack.dependencies.required] before any file write.
@@ -4084,15 +4308,20 @@ def validate_dependencies_required(
4084
4308
  if not required:
4085
4309
  return
4086
4310
 
4087
- # Union of installed packs across both scopes (pack name → installed
4311
+ # Union of installed packs across all scopes (pack name → installed
4088
4312
  # version string). A pack may have several adapter rows; any row's
4089
4313
  # version satisfies the dependency check (they share the pack version).
4314
+ # RFC-0080 AC23b: local_state included when installing at local scope.
4090
4315
  installed: dict[str, str] = {}
4091
4316
  for (name, _adapter), ps in repo_state.packs.items():
4092
4317
  installed[name] = ps.installed_version
4093
4318
  for (name, _adapter), ps in user_state.packs.items():
4094
4319
  if name not in installed:
4095
4320
  installed[name] = ps.installed_version
4321
+ if local_state is not None:
4322
+ for (name, _adapter), ps in local_state.packs.items():
4323
+ if name not in installed:
4324
+ installed[name] = ps.installed_version
4096
4325
 
4097
4326
  for entry in required:
4098
4327
  if not isinstance(entry, dict):