code-agnostic 0.5.0__tar.gz → 0.6.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.
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/PKG-INFO +1 -1
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/apps_service.py +21 -10
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/claude/service.py +0 -7
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/service.py +18 -6
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/service.py +26 -3
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/copilot/service.py +1 -5
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/service.py +1 -3
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/service.py +5 -6
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/executor.py +8 -6
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/models.py +1 -1
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/planner.py +1 -1
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/PKG-INFO +1 -1
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/SOURCES.txt +1 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/pyproject.toml +1 -1
- code_agnostic-0.6.0/tests/test_cli_apply_codex_agent_cleanup.py +71 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/LICENSE +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/README.md +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/copilot.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/copilot/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/copilot/config_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/copilot/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/lossiness.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/project_artifacts.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/skills/install_sources.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/status.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/renderers.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/setup.cfg +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apply_mcp_cleanup.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_disable_cleanup.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_explain_lossiness.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_projects.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_cli_workspaces.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_project_config_sync.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_skill_install_sources.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_utils.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_version.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.5.0 → code_agnostic-0.6.0}/tests/test_workspaces.py +0 -0
|
@@ -201,14 +201,11 @@ class AppsService:
|
|
|
201
201
|
def _cleanup_global_mcp(
|
|
202
202
|
self, app_id: AppId, managed_mcp: dict[str, Any]
|
|
203
203
|
) -> list[Action]:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
{item for item in raw if isinstance(item, str)}
|
|
208
|
-
if isinstance(raw, list)
|
|
209
|
-
else set()
|
|
204
|
+
managed = self._names_for_scope(managed_mcp, app_scope(app_id, "mcp"))
|
|
205
|
+
managed_agents = self._names_for_scope(
|
|
206
|
+
managed_mcp, app_scope(app_id, "agents_registry")
|
|
210
207
|
)
|
|
211
|
-
if not managed:
|
|
208
|
+
if not managed and not managed_agents:
|
|
212
209
|
return []
|
|
213
210
|
try:
|
|
214
211
|
service = create_registered_app_service(app_id)
|
|
@@ -216,9 +213,23 @@ class AppsService:
|
|
|
216
213
|
return []
|
|
217
214
|
if not service.repository.config_path.exists():
|
|
218
215
|
return []
|
|
219
|
-
# Empty desired + our previously-managed names → prune only
|
|
220
|
-
# keep the user's, and clear
|
|
221
|
-
|
|
216
|
+
# Empty desired + our previously-managed names → prune only what we wrote
|
|
217
|
+
# (MCP servers and agent-registry entries), keep the user's, and clear the
|
|
218
|
+
# ownership state (managed_entries becomes empty).
|
|
219
|
+
return [
|
|
220
|
+
service.build_action(
|
|
221
|
+
{},
|
|
222
|
+
previously_managed=managed,
|
|
223
|
+
previously_managed_agents=managed_agents,
|
|
224
|
+
)
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def _names_for_scope(group: dict[str, Any], scope: str) -> set[str]:
|
|
229
|
+
raw = group.get(scope, [])
|
|
230
|
+
if not isinstance(raw, list):
|
|
231
|
+
return set()
|
|
232
|
+
return {item for item in raw if isinstance(item, str)}
|
|
222
233
|
|
|
223
234
|
def list_status_rows(self) -> list[AppStatusRow]:
|
|
224
235
|
apps = self.load_apps()
|
|
@@ -12,7 +12,6 @@ from code_agnostic.apps.common.framework import RegisteredAppConfigService
|
|
|
12
12
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
13
13
|
from code_agnostic.apps.common.interfaces.repositories import IAppConfigRepository
|
|
14
14
|
from code_agnostic.apps.common.models import MCPServerDTO
|
|
15
|
-
from code_agnostic.errors import InvalidConfigSchemaError
|
|
16
15
|
from code_agnostic.models import Action, ActionKind, ActionStatus
|
|
17
16
|
from code_agnostic.skills.compilers import ClaudeSkillCompiler
|
|
18
17
|
from code_agnostic.skills.parser import parse_skill
|
|
@@ -57,12 +56,6 @@ class ClaudeConfigService(RegisteredAppConfigService):
|
|
|
57
56
|
def mcp_config_key(self) -> str:
|
|
58
57
|
return "mcpServers"
|
|
59
58
|
|
|
60
|
-
def validate_config(self, payload: Any) -> None:
|
|
61
|
-
if not isinstance(payload, dict):
|
|
62
|
-
raise InvalidConfigSchemaError(
|
|
63
|
-
self.repository.config_path, "must be a JSON object"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
59
|
def build_action_payload(self, payload: dict[str, Any]) -> Any:
|
|
67
60
|
return payload
|
|
68
61
|
|
|
@@ -16,6 +16,7 @@ from code_agnostic.apps.codex.config_repository import CodexConfigRepository
|
|
|
16
16
|
from code_agnostic.apps.codex.mapper import CodexMCPMapper
|
|
17
17
|
from code_agnostic.apps.codex.schema_repository import CodexSchemaRepository
|
|
18
18
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
19
|
+
from code_agnostic.apps.common.utils import apply_mcp_servers
|
|
19
20
|
from code_agnostic.apps.common.interfaces.repositories import (
|
|
20
21
|
IAppConfigRepository,
|
|
21
22
|
ISchemaRepository,
|
|
@@ -88,7 +89,7 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
88
89
|
def mcp_config_key(self) -> str:
|
|
89
90
|
return "mcp_servers"
|
|
90
91
|
|
|
91
|
-
def
|
|
92
|
+
def _validate_schema(self, payload: dict[str, Any]) -> None:
|
|
92
93
|
error = next(iter(self._validator.iter_errors(payload)), None)
|
|
93
94
|
if error is not None:
|
|
94
95
|
raise InvalidConfigSchemaError(
|
|
@@ -118,6 +119,7 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
118
119
|
common_servers: dict[str, MCPServerDTO],
|
|
119
120
|
agent_sources: list[Path] | None = None,
|
|
120
121
|
previously_managed: set[str] | None = None,
|
|
122
|
+
previously_managed_agents: set[str] | None = None,
|
|
121
123
|
*,
|
|
122
124
|
replace_mcp: bool = False,
|
|
123
125
|
) -> Action:
|
|
@@ -144,10 +146,17 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
144
146
|
self.set_mcp_payload(
|
|
145
147
|
merged, desired_mcp, previously_managed, replace=replace_mcp
|
|
146
148
|
)
|
|
147
|
-
if agent_sources
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
self.
|
|
149
|
+
registry = self._build_agent_registry(agent_sources) if agent_sources else {}
|
|
150
|
+
if replace_mcp:
|
|
151
|
+
if registry:
|
|
152
|
+
merged["agents"] = self._merge_agents_payload(
|
|
153
|
+
merged.get("agents"), registry
|
|
154
|
+
)
|
|
155
|
+
elif registry or previously_managed_agents:
|
|
156
|
+
# Ownership-aware: prune agent-registry entries we previously wrote
|
|
157
|
+
# that are gone from the source, keep base-config/user agents.
|
|
158
|
+
merged["agents"] = apply_mcp_servers(
|
|
159
|
+
merged.get("agents"), registry, previously_managed_agents
|
|
151
160
|
)
|
|
152
161
|
self.validate_config(merged)
|
|
153
162
|
|
|
@@ -161,7 +170,10 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
161
170
|
)
|
|
162
171
|
if not replace_mcp:
|
|
163
172
|
action.scope = app_scope(self.app_id, "mcp")
|
|
164
|
-
action.
|
|
173
|
+
action.managed_entries = {
|
|
174
|
+
app_scope(self.app_id, "mcp"): sorted(desired_mcp),
|
|
175
|
+
app_scope(self.app_id, "agents_registry"): sorted(registry),
|
|
176
|
+
}
|
|
165
177
|
return action
|
|
166
178
|
|
|
167
179
|
def _merge_agents_payload(
|
|
@@ -12,6 +12,7 @@ from code_agnostic.apps.common.interfaces.repositories import IAppConfigReposito
|
|
|
12
12
|
from code_agnostic.apps.common.interfaces.repositories import ISourceRepository
|
|
13
13
|
from code_agnostic.apps.common.models import MCPServerDTO
|
|
14
14
|
from code_agnostic.apps.common.utils import apply_mcp_servers
|
|
15
|
+
from code_agnostic.errors import InvalidConfigSchemaError
|
|
15
16
|
from code_agnostic.apps.common.symlink_planning import (
|
|
16
17
|
load_state_links,
|
|
17
18
|
load_state_paths,
|
|
@@ -59,9 +60,24 @@ class IAppConfigService(ABC):
|
|
|
59
60
|
"""Native config key that holds the MCP server map for this editor."""
|
|
60
61
|
raise NotImplementedError
|
|
61
62
|
|
|
62
|
-
@abstractmethod
|
|
63
63
|
def validate_config(self, payload: Any) -> None:
|
|
64
|
-
|
|
64
|
+
"""Single validation contract for every editor config.
|
|
65
|
+
|
|
66
|
+
Empty/absent config is always valid; anything present must be a JSON
|
|
67
|
+
object; schema-backed editors add their own checks via
|
|
68
|
+
``_validate_schema``.
|
|
69
|
+
"""
|
|
70
|
+
if payload is None or payload == {}:
|
|
71
|
+
return
|
|
72
|
+
if not isinstance(payload, dict):
|
|
73
|
+
raise InvalidConfigSchemaError(
|
|
74
|
+
self.repository.config_path, "must be a JSON object"
|
|
75
|
+
)
|
|
76
|
+
self._validate_schema(payload)
|
|
77
|
+
|
|
78
|
+
def _validate_schema(self, payload: dict[str, Any]) -> None:
|
|
79
|
+
"""Editor-specific validation hook. Default: no schema."""
|
|
80
|
+
return
|
|
65
81
|
|
|
66
82
|
@abstractmethod
|
|
67
83
|
def build_action_payload(self, payload: dict[str, Any]) -> Any:
|
|
@@ -254,6 +270,7 @@ class IAppConfigService(ABC):
|
|
|
254
270
|
common_servers: dict[str, MCPServerDTO],
|
|
255
271
|
agent_sources: list[Path] | None = None,
|
|
256
272
|
previously_managed: set[str] | None = None,
|
|
273
|
+
previously_managed_agents: set[str] | None = None,
|
|
257
274
|
*,
|
|
258
275
|
replace_mcp: bool = False,
|
|
259
276
|
) -> Action:
|
|
@@ -280,7 +297,9 @@ class IAppConfigService(ABC):
|
|
|
280
297
|
# User-shared global config: track which servers we own so a later
|
|
281
298
|
# source removal can be pruned without touching the user's servers.
|
|
282
299
|
action.scope = app_scope(self.app_id, "mcp")
|
|
283
|
-
action.
|
|
300
|
+
action.managed_entries = {
|
|
301
|
+
app_scope(self.app_id, "mcp"): sorted(desired_mcp)
|
|
302
|
+
}
|
|
284
303
|
return action
|
|
285
304
|
|
|
286
305
|
def build_plan(
|
|
@@ -297,6 +316,9 @@ class IAppConfigService(ABC):
|
|
|
297
316
|
previously_managed_mcp = self._load_state_names(
|
|
298
317
|
managed_mcp_group, app_scope(self.app_id, "mcp")
|
|
299
318
|
)
|
|
319
|
+
previously_managed_agents = self._load_state_names(
|
|
320
|
+
managed_mcp_group, app_scope(self.app_id, "agents_registry")
|
|
321
|
+
)
|
|
300
322
|
|
|
301
323
|
skill_actions, skill_skipped = self._build_compiled_group(
|
|
302
324
|
sources=source_repository.list_skill_sources(),
|
|
@@ -325,6 +347,7 @@ class IAppConfigService(ABC):
|
|
|
325
347
|
common_servers,
|
|
326
348
|
agent_sources=source_repository.list_agent_sources(),
|
|
327
349
|
previously_managed=previously_managed_mcp,
|
|
350
|
+
previously_managed_agents=previously_managed_agents,
|
|
328
351
|
),
|
|
329
352
|
*skill_actions,
|
|
330
353
|
*agent_actions,
|
|
@@ -55,11 +55,7 @@ class CopilotConfigService(RegisteredAppConfigService):
|
|
|
55
55
|
def mcp_config_key(self) -> str:
|
|
56
56
|
return "mcpServers"
|
|
57
57
|
|
|
58
|
-
def
|
|
59
|
-
if not isinstance(payload, dict):
|
|
60
|
-
raise InvalidConfigSchemaError(
|
|
61
|
-
self.repository.config_path, "must be a JSON object"
|
|
62
|
-
)
|
|
58
|
+
def _validate_schema(self, payload: dict[str, Any]) -> None:
|
|
63
59
|
mcp_servers = payload.get("mcpServers")
|
|
64
60
|
if mcp_servers is not None and not isinstance(mcp_servers, dict):
|
|
65
61
|
raise InvalidConfigSchemaError(
|
|
@@ -68,9 +68,7 @@ class CursorConfigService(RegisteredAppConfigService):
|
|
|
68
68
|
def mcp_config_key(self) -> str:
|
|
69
69
|
return "mcpServers"
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
if payload == {}:
|
|
73
|
-
return
|
|
71
|
+
def _validate_schema(self, payload: dict[str, Any]) -> None:
|
|
74
72
|
error = next(iter(self._validator.iter_errors(payload)), None)
|
|
75
73
|
if error is not None:
|
|
76
74
|
raise InvalidConfigSchemaError(
|
|
@@ -104,11 +104,7 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
104
104
|
def mcp_config_key(self) -> str:
|
|
105
105
|
return "mcp"
|
|
106
106
|
|
|
107
|
-
def
|
|
108
|
-
if not isinstance(payload, dict):
|
|
109
|
-
raise InvalidConfigSchemaError(
|
|
110
|
-
self.repository.config_path, "must be a JSON object"
|
|
111
|
-
)
|
|
107
|
+
def _validate_schema(self, payload: dict[str, Any]) -> None:
|
|
112
108
|
for error in self._validator.iter_errors(payload):
|
|
113
109
|
if _is_unknown_provider_model_enum_error(error):
|
|
114
110
|
continue
|
|
@@ -141,6 +137,7 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
141
137
|
common_servers: dict[str, MCPServerDTO],
|
|
142
138
|
agent_sources: list[Path] | None = None,
|
|
143
139
|
previously_managed: set[str] | None = None,
|
|
140
|
+
previously_managed_agents: set[str] | None = None,
|
|
144
141
|
*,
|
|
145
142
|
replace_mcp: bool = False,
|
|
146
143
|
) -> Action:
|
|
@@ -178,7 +175,9 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
178
175
|
)
|
|
179
176
|
if not replace_mcp:
|
|
180
177
|
action.scope = app_scope(self.app_id, "mcp")
|
|
181
|
-
action.
|
|
178
|
+
action.managed_entries = {
|
|
179
|
+
app_scope(self.app_id, "mcp"): sorted(desired_mcp)
|
|
180
|
+
}
|
|
182
181
|
return action
|
|
183
182
|
|
|
184
183
|
def _validate_common_mcp(self, common_servers: dict[str, MCPServerDTO]) -> None:
|
|
@@ -770,12 +770,14 @@ class SyncExecutor:
|
|
|
770
770
|
if action.scope is None:
|
|
771
771
|
continue
|
|
772
772
|
|
|
773
|
-
if action.
|
|
774
|
-
# User-shared
|
|
775
|
-
#
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
773
|
+
if action.managed_entries is not None:
|
|
774
|
+
# User-shared config: track the named entries we own (MCP servers,
|
|
775
|
+
# agent-registry names, …) by scope — never the shared config file
|
|
776
|
+
# itself, so cleanup prunes only what we wrote.
|
|
777
|
+
for entry_scope, names in action.managed_entries.items():
|
|
778
|
+
global_mcp_touched.add(entry_scope)
|
|
779
|
+
if names:
|
|
780
|
+
global_mcp[entry_scope] = sorted(set(names))
|
|
779
781
|
continue
|
|
780
782
|
|
|
781
783
|
if action.workspace is not None:
|
|
@@ -291,7 +291,7 @@ class SyncPlanner:
|
|
|
291
291
|
# Carry global MCP ownership tracking onto the merged Claude action so
|
|
292
292
|
# top-level `mcpServers` orphans still get pruned on later applies.
|
|
293
293
|
project_action.scope = existing_action.scope
|
|
294
|
-
project_action.
|
|
294
|
+
project_action.managed_entries = existing_action.managed_entries
|
|
295
295
|
actions = [
|
|
296
296
|
action
|
|
297
297
|
for action in plan.actions
|
|
@@ -128,6 +128,7 @@ tests/test_cli_agents.py
|
|
|
128
128
|
tests/test_cli_aliases.py
|
|
129
129
|
tests/test_cli_apply_apps.py
|
|
130
130
|
tests/test_cli_apply_codex.py
|
|
131
|
+
tests/test_cli_apply_codex_agent_cleanup.py
|
|
131
132
|
tests/test_cli_apply_cursor.py
|
|
132
133
|
tests/test_cli_apply_mcp_cleanup.py
|
|
133
134
|
tests/test_cli_apply_target.py
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Codex agent-registry ownership: an agent removed from the source is pruned
|
|
2
|
+
from `[agents.*]` in ~/.codex/config.toml on the next apply, while base-config
|
|
3
|
+
agent settings and user-added entries survive."""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from code_agnostic.__main__ import cli
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import tomllib
|
|
11
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
12
|
+
import tomli as tomllib # type: ignore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _write_agent(core_root: Path, name: str) -> None:
|
|
16
|
+
agent = core_root / "agents" / f"{name}.md"
|
|
17
|
+
agent.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
agent.write_text(
|
|
19
|
+
f"---\nname: {name}\ndescription: {name} specialist\n---\n\nDo {name}.\n",
|
|
20
|
+
encoding="utf-8",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_apply_codex_prunes_removed_agent_from_registry(
|
|
25
|
+
minimal_shared_config: Path,
|
|
26
|
+
core_root: Path,
|
|
27
|
+
tmp_path: Path,
|
|
28
|
+
cli_runner,
|
|
29
|
+
enable_app,
|
|
30
|
+
) -> None:
|
|
31
|
+
enable_app("codex")
|
|
32
|
+
# Base-config agent settings + a user-managed agent entry must be preserved.
|
|
33
|
+
(core_root / "config" / "codex.base.json").write_text(
|
|
34
|
+
'{"agents": {"max_depth": 2}}', encoding="utf-8"
|
|
35
|
+
)
|
|
36
|
+
(tmp_path / ".codex").mkdir(parents=True, exist_ok=True)
|
|
37
|
+
(tmp_path / ".codex" / "config.toml").write_text(
|
|
38
|
+
'[agents.mine]\ndescription = "user agent"\nconfig_file = "agents/mine.toml"\n',
|
|
39
|
+
encoding="utf-8",
|
|
40
|
+
)
|
|
41
|
+
_write_agent(core_root, "planner")
|
|
42
|
+
_write_agent(core_root, "reviewer")
|
|
43
|
+
|
|
44
|
+
assert cli_runner.invoke(cli, ["apply", "-a", "codex"]).exit_code == 0
|
|
45
|
+
cfg = tomllib.loads(
|
|
46
|
+
(tmp_path / ".codex" / "config.toml").read_text(encoding="utf-8")
|
|
47
|
+
)
|
|
48
|
+
assert cfg["agents"]["max_depth"] == 2
|
|
49
|
+
assert set(cfg["agents"]) >= {"planner", "reviewer", "mine", "max_depth"}
|
|
50
|
+
|
|
51
|
+
state_path = core_root / ".sync-state.json"
|
|
52
|
+
import json
|
|
53
|
+
|
|
54
|
+
state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
55
|
+
assert state["managed_mcp"]["app:codex:agents_registry"] == ["planner", "reviewer"]
|
|
56
|
+
|
|
57
|
+
# Remove reviewer from the source and re-apply.
|
|
58
|
+
(core_root / "agents" / "reviewer.md").unlink()
|
|
59
|
+
assert cli_runner.invoke(cli, ["apply", "-a", "codex"]).exit_code == 0
|
|
60
|
+
|
|
61
|
+
cfg = tomllib.loads(
|
|
62
|
+
(tmp_path / ".codex" / "config.toml").read_text(encoding="utf-8")
|
|
63
|
+
)
|
|
64
|
+
agents = cfg["agents"]
|
|
65
|
+
assert "reviewer" not in agents, "our removed agent must be pruned from registry"
|
|
66
|
+
assert "planner" in agents
|
|
67
|
+
assert agents["mine"]["description"] == "user agent", "user entry preserved"
|
|
68
|
+
assert agents["max_depth"] == 2, "base-config agent settings preserved"
|
|
69
|
+
|
|
70
|
+
state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
71
|
+
assert state["managed_mcp"]["app:codex:agents_registry"] == ["planner"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/common/interfaces/repositories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/config_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.5.0 → code_agnostic-0.6.0}/code_agnostic/apps/opencode/schema_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|