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.
- {agentbundle-0.28.1 → agentbundle-0.29.0}/PKG-INFO +1 -1
- {agentbundle-0.28.1 → agentbundle-0.29.0}/README.md +8 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/pack.schema.json +16 -2
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/cli.py +5 -4
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/_common.py +6 -2
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/install.py +278 -49
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_installed.py +10 -3
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/show.py +12 -6
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/uninstall.py +99 -3
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/config.py +3 -1
- agentbundle-0.29.0/agentbundle/local_exclude.py +375 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/scope.py +18 -1
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/version.py +1 -1
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/PKG-INFO +1 -1
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/SOURCES.txt +1 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/pyproject.toml +1 -1
- {agentbundle-0.28.1 → agentbundle-0.29.0}/README-pypi.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/__main__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/adapter.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/adapter.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/guides/_shared/reference/catalogue-authoring-standards.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/guides/_shared/reference/catalogue-ci-contract.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/manifest.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/AGENTS.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/README.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/README.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/evals/eval_queries.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/packs/_example/pack.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/AGENTS.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/README.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/_example/README.md +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue-scaffold/profiles/_example/profile.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/catalogue.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/guide.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/install-defaults.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/install-marker.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/profile.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/skill-manifest.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/skill.schema.json +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/_data/target-vocab.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/__main__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapter_root_bins.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/claude_code.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/codex.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/copilot.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/cursor.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/gemini.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/contract.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/lint_packs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/main.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/phase_order.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/direct_directory.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/hook_id.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/merge_json.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/projections/user_merge_json.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/composite-agents-md.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/composite-marketplace.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/marketplace.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-apm-package.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-claude-plugin.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/per-pack-overlay.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/recipes/self-host.toml +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/scope_rails.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/self_host.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/shared_libs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/target_resolver.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_pipeline.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_security.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shared_prefix_contract.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_validate.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_workspace_status_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/user_libs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/build/validate.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/archive.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/build.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/config.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/defaults.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/diagnostics.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/identity.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/initialise.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/initialise_self_hosted.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/lint.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/package.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/results.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/self_host.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/self_host_windows.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/skill_spec_lint.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/toml_emit.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/catalogue_tooling/verify.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/categories.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/__init__.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/_drop_warning.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/adapt.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_build.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_init.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_lint.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_package.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_self_host.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_sync_defaults.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_tooling_stub.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/catalogue_verify.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/config.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/diff.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/docs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/init_state.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_packs.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_profiles.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/list_targets.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/oplog_cmd.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/pack_config_cmd.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/pack_evals.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/package_catalogue.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/profile.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/reconcile.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/render.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/scaffold.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/upgrade.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/commands/validate.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/https_catalogue.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/oplog.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/pack_inventory.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/render.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/safety.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/scaffold.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/source_defaults.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/statelock.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/user_config.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle/workspace_mcp.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/dependency_links.txt +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/entry_points.txt +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/requires.txt +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/agentbundle.egg-info/top_level.txt +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/setup.cfg +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_adapter_permissions_projection.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_linear_primitive.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_loop_engine_events_jsonl.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_elicit.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_event_bridge.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_git.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_lifecycle.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_stdin.py +0 -0
- {agentbundle-0.28.1 → agentbundle-0.29.0}/tests/test_workspace_mcp_tools.py +0 -0
- {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.
|
|
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=(
|
|
262
|
-
help="Limit the listing to one scope. Default:
|
|
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=(
|
|
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=(
|
|
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"``
|
|
47
|
-
``scope="user"``
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1201
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
)
|
|
1392
|
-
|
|
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
|
|
1470
|
-
#
|
|
1471
|
-
#
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
)
|
|
1476
|
-
|
|
1477
|
-
if
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
|
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):
|