code-agnostic 0.7.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/PKG-INFO +1 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/apps_service.py +7 -15
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/repositories.py +3 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/service.py +3 -7
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/core/repository.py +5 -29
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/executor.py +7 -7
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/models.py +63 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/planner.py +6 -20
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/status.py +2 -2
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/PKG-INFO +1 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/pyproject.toml +1 -1
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_common_repository.py +4 -17
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_planner_executor.py +2 -4
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_sync_plan_model.py +44 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_workspace_config_sync.py +9 -9
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/LICENSE +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/README.md +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/copilot.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/copilot/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/copilot/config_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/copilot/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/copilot/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/lossiness.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/project_artifacts.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/skills/install_sources.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/renderers.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/setup.cfg +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_codex_agent_cleanup.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_mcp_cleanup.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_disable_cleanup.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_explain_lossiness.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_projects.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_cli_workspaces.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_project_config_sync.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_skill_install_sources.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_utils.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_version.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.7.0 → code_agnostic-0.8.0}/tests/test_workspaces.py +0 -0
|
@@ -103,9 +103,9 @@ class AppsService:
|
|
|
103
103
|
|
|
104
104
|
# --- Global scopes (app:<app>:*) ---
|
|
105
105
|
global_state = core.load_state()
|
|
106
|
-
global_links =
|
|
107
|
-
global_paths =
|
|
108
|
-
global_mcp =
|
|
106
|
+
global_links = global_state.managed_links
|
|
107
|
+
global_paths = global_state.managed_paths
|
|
108
|
+
global_mcp = global_state.managed_mcp
|
|
109
109
|
global_prefix = f"app:{app_id.value}:"
|
|
110
110
|
for scope in sorted(s for s in global_links if s.startswith(global_prefix)):
|
|
111
111
|
actions.extend(self._cleanup_links(global_links, scope, app_id.value))
|
|
@@ -123,8 +123,8 @@ class AppsService:
|
|
|
123
123
|
ws_name = workspace["name"]
|
|
124
124
|
ws_repo = WorkspaceConfigRepository(root=core.workspace_config_dir(ws_name))
|
|
125
125
|
ws_state = ws_repo.load_state()
|
|
126
|
-
ws_links =
|
|
127
|
-
ws_paths =
|
|
126
|
+
ws_links = ws_state.managed_links
|
|
127
|
+
ws_paths = ws_state.managed_paths
|
|
128
128
|
for scope in sorted(s for s in ws_links if s.startswith(ws_prefix)):
|
|
129
129
|
for action in self._cleanup_links(ws_links, scope, "workspace"):
|
|
130
130
|
action.workspace = ws_name
|
|
@@ -144,8 +144,8 @@ class AppsService:
|
|
|
144
144
|
root=project_config_dir(core, project_name)
|
|
145
145
|
)
|
|
146
146
|
project_state = project_repo.load_state()
|
|
147
|
-
project_links =
|
|
148
|
-
project_paths =
|
|
147
|
+
project_links = project_state.managed_links
|
|
148
|
+
project_paths = project_state.managed_paths
|
|
149
149
|
for scope in sorted(
|
|
150
150
|
s for s in project_links if s.startswith(project_prefix)
|
|
151
151
|
):
|
|
@@ -163,14 +163,6 @@ class AppsService:
|
|
|
163
163
|
|
|
164
164
|
return SyncPlan(actions=actions, errors=[], skipped=skipped)
|
|
165
165
|
|
|
166
|
-
@staticmethod
|
|
167
|
-
def _normalize_group(value: Any) -> dict[str, Any]:
|
|
168
|
-
return (
|
|
169
|
-
{scope: paths for scope, paths in value.items() if isinstance(scope, str)}
|
|
170
|
-
if isinstance(value, dict)
|
|
171
|
-
else {}
|
|
172
|
-
)
|
|
173
|
-
|
|
174
166
|
@staticmethod
|
|
175
167
|
def _cleanup_links(group: dict[str, Any], scope: str, app: str) -> list[Action]:
|
|
176
168
|
return plan_stale_group(
|
{code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/repositories.py
RENAMED
|
@@ -2,6 +2,8 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from code_agnostic.models import SyncState
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class ISchemaRepository(ABC):
|
|
7
9
|
@abstractmethod
|
|
@@ -59,7 +61,7 @@ class IConfigRepository(ABC):
|
|
|
59
61
|
raise NotImplementedError
|
|
60
62
|
|
|
61
63
|
@abstractmethod
|
|
62
|
-
def load_state(self) ->
|
|
64
|
+
def load_state(self) -> SyncState:
|
|
63
65
|
raise NotImplementedError
|
|
64
66
|
|
|
65
67
|
@abstractmethod
|
|
@@ -135,10 +135,6 @@ class IAppConfigService(ABC):
|
|
|
135
135
|
def compiled_resource_ownership_policy(self) -> OwnershipPolicy:
|
|
136
136
|
return OwnershipPolicy.OWNED_ONLY
|
|
137
137
|
|
|
138
|
-
@staticmethod
|
|
139
|
-
def _normalize_managed_group(value: Any) -> dict[str, Any]:
|
|
140
|
-
return value if isinstance(value, dict) else {}
|
|
141
|
-
|
|
142
138
|
def _plan_compiled_text_actions(
|
|
143
139
|
self,
|
|
144
140
|
*,
|
|
@@ -308,9 +304,9 @@ class IAppConfigService(ABC):
|
|
|
308
304
|
source_repository: ISourceRepository,
|
|
309
305
|
) -> SyncPlan:
|
|
310
306
|
state = source_repository.load_state()
|
|
311
|
-
managed_links_group =
|
|
312
|
-
managed_paths_group =
|
|
313
|
-
managed_mcp_group =
|
|
307
|
+
managed_links_group = state.managed_links
|
|
308
|
+
managed_paths_group = state.managed_paths
|
|
309
|
+
managed_mcp_group = state.managed_mcp
|
|
314
310
|
skill_scope = app_scope(self.app_id, "skills")
|
|
315
311
|
agent_scope = app_scope(self.app_id, "agents")
|
|
316
312
|
previously_managed_mcp = self._load_state_names(
|
|
@@ -10,6 +10,7 @@ from code_agnostic.errors import (
|
|
|
10
10
|
InvalidJsonFormatError,
|
|
11
11
|
MissingConfigFileError,
|
|
12
12
|
)
|
|
13
|
+
from code_agnostic.models import SyncState
|
|
13
14
|
from code_agnostic.spec.loaders import (
|
|
14
15
|
load_mcp_base as load_mcp_bundle,
|
|
15
16
|
validate_schema_payload,
|
|
@@ -110,36 +111,11 @@ class BaseSourceRepository(ISourceRepository):
|
|
|
110
111
|
result.append(child)
|
|
111
112
|
return result
|
|
112
113
|
|
|
113
|
-
def load_state(self) ->
|
|
114
|
+
def load_state(self) -> SyncState:
|
|
114
115
|
payload, error = read_json_safe(self.state_json)
|
|
115
|
-
if error is not None
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
"managed_agent_links": [],
|
|
119
|
-
"managed_workspace_links": [],
|
|
120
|
-
"managed_links": {},
|
|
121
|
-
"managed_paths": {},
|
|
122
|
-
"managed_mcp": {},
|
|
123
|
-
}
|
|
124
|
-
payload.setdefault("managed_skill_links", [])
|
|
125
|
-
payload.setdefault("managed_agent_links", [])
|
|
126
|
-
payload.setdefault("managed_workspace_links", [])
|
|
127
|
-
payload.setdefault("managed_links", {})
|
|
128
|
-
payload.setdefault("managed_paths", {})
|
|
129
|
-
payload.setdefault("managed_mcp", {})
|
|
130
|
-
if not isinstance(payload["managed_skill_links"], list):
|
|
131
|
-
payload["managed_skill_links"] = []
|
|
132
|
-
if not isinstance(payload["managed_agent_links"], list):
|
|
133
|
-
payload["managed_agent_links"] = []
|
|
134
|
-
if not isinstance(payload["managed_workspace_links"], list):
|
|
135
|
-
payload["managed_workspace_links"] = []
|
|
136
|
-
if not isinstance(payload["managed_links"], dict):
|
|
137
|
-
payload["managed_links"] = {}
|
|
138
|
-
if not isinstance(payload["managed_paths"], dict):
|
|
139
|
-
payload["managed_paths"] = {}
|
|
140
|
-
if not isinstance(payload["managed_mcp"], dict):
|
|
141
|
-
payload["managed_mcp"] = {}
|
|
142
|
-
return payload
|
|
116
|
+
if error is not None:
|
|
117
|
+
return SyncState()
|
|
118
|
+
return SyncState.from_payload(payload)
|
|
143
119
|
|
|
144
120
|
def save_state(self, data: dict[str, Any]) -> None:
|
|
145
121
|
write_json(self.state_json, data)
|
|
@@ -830,17 +830,17 @@ class SyncExecutor:
|
|
|
830
830
|
global_state = {
|
|
831
831
|
"updated_at": updated_at,
|
|
832
832
|
"managed_links": self._merge_managed_links(
|
|
833
|
-
existing=existing_global_state.
|
|
833
|
+
existing=existing_global_state.managed_links,
|
|
834
834
|
touched_scopes=global_touched_scopes,
|
|
835
835
|
current_links=global_links,
|
|
836
836
|
),
|
|
837
837
|
"managed_paths": self._merge_managed_links(
|
|
838
|
-
existing=existing_global_state.
|
|
838
|
+
existing=existing_global_state.managed_paths,
|
|
839
839
|
touched_scopes=global_touched_scopes,
|
|
840
840
|
current_links=global_paths,
|
|
841
841
|
),
|
|
842
842
|
"managed_mcp": self._merge_managed_links(
|
|
843
|
-
existing=existing_global_state.
|
|
843
|
+
existing=existing_global_state.managed_mcp,
|
|
844
844
|
touched_scopes=global_mcp_touched,
|
|
845
845
|
current_links=global_mcp,
|
|
846
846
|
),
|
|
@@ -863,12 +863,12 @@ class SyncExecutor:
|
|
|
863
863
|
ws_state = {
|
|
864
864
|
"updated_at": updated_at,
|
|
865
865
|
"managed_links": self._merge_managed_links(
|
|
866
|
-
existing=existing_workspace_state.
|
|
866
|
+
existing=existing_workspace_state.managed_links,
|
|
867
867
|
touched_scopes=workspace_touched_scopes[ws_name],
|
|
868
868
|
current_links=workspace_links.get(ws_name, {}),
|
|
869
869
|
),
|
|
870
870
|
"managed_paths": self._merge_managed_links(
|
|
871
|
-
existing=existing_workspace_state.
|
|
871
|
+
existing=existing_workspace_state.managed_paths,
|
|
872
872
|
touched_scopes=workspace_touched_scopes[ws_name],
|
|
873
873
|
current_links=workspace_paths.get(ws_name, {}),
|
|
874
874
|
),
|
|
@@ -893,12 +893,12 @@ class SyncExecutor:
|
|
|
893
893
|
project_state = {
|
|
894
894
|
"updated_at": updated_at,
|
|
895
895
|
"managed_links": self._merge_managed_links(
|
|
896
|
-
existing=existing_project_state.
|
|
896
|
+
existing=existing_project_state.managed_links,
|
|
897
897
|
touched_scopes=project_touched_scopes[project_name],
|
|
898
898
|
current_links=project_links.get(project_name, {}),
|
|
899
899
|
),
|
|
900
900
|
"managed_paths": self._merge_managed_links(
|
|
901
|
-
existing=existing_project_state.
|
|
901
|
+
existing=existing_project_state.managed_paths,
|
|
902
902
|
touched_scopes=project_touched_scopes[project_name],
|
|
903
903
|
current_links=project_paths.get(project_name, {}),
|
|
904
904
|
),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Any
|
|
@@ -146,6 +146,68 @@ class SyncPlan:
|
|
|
146
146
|
)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class SyncState:
|
|
151
|
+
"""Typed view of a repository's `.sync-state.json`.
|
|
152
|
+
|
|
153
|
+
All defensive normalization (missing keys, wrong JSON types, stray non-string
|
|
154
|
+
entries) happens once in `from_payload`; consumers read the typed fields
|
|
155
|
+
directly instead of re-guarding with `isinstance` at every call site.
|
|
156
|
+
|
|
157
|
+
`managed_links`/`managed_paths`/`managed_mcp` are `scope -> names` maps (the
|
|
158
|
+
live ownership model). The three `managed_*_links` lists are vestigial — no
|
|
159
|
+
production reader consumes them — but are kept for on-disk round-trips.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
managed_links: dict[str, list[str]] = field(default_factory=dict)
|
|
163
|
+
managed_paths: dict[str, list[str]] = field(default_factory=dict)
|
|
164
|
+
managed_mcp: dict[str, list[str]] = field(default_factory=dict)
|
|
165
|
+
managed_skill_links: list[str] = field(default_factory=list)
|
|
166
|
+
managed_agent_links: list[str] = field(default_factory=list)
|
|
167
|
+
managed_workspace_links: list[str] = field(default_factory=list)
|
|
168
|
+
updated_at: str | None = None
|
|
169
|
+
skipped: list[str] = field(default_factory=list)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def from_payload(cls, payload: Any) -> "SyncState":
|
|
173
|
+
if not isinstance(payload, dict):
|
|
174
|
+
return cls()
|
|
175
|
+
updated_at = payload.get("updated_at")
|
|
176
|
+
return cls(
|
|
177
|
+
managed_links=cls._coerce_group(payload.get("managed_links")),
|
|
178
|
+
managed_paths=cls._coerce_group(payload.get("managed_paths")),
|
|
179
|
+
managed_mcp=cls._coerce_group(payload.get("managed_mcp")),
|
|
180
|
+
managed_skill_links=cls._coerce_str_list(
|
|
181
|
+
payload.get("managed_skill_links")
|
|
182
|
+
),
|
|
183
|
+
managed_agent_links=cls._coerce_str_list(
|
|
184
|
+
payload.get("managed_agent_links")
|
|
185
|
+
),
|
|
186
|
+
managed_workspace_links=cls._coerce_str_list(
|
|
187
|
+
payload.get("managed_workspace_links")
|
|
188
|
+
),
|
|
189
|
+
updated_at=updated_at if isinstance(updated_at, str) else None,
|
|
190
|
+
skipped=cls._coerce_str_list(payload.get("skipped")),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _coerce_group(value: Any) -> dict[str, list[str]]:
|
|
195
|
+
if not isinstance(value, dict):
|
|
196
|
+
return {}
|
|
197
|
+
result: dict[str, list[str]] = {}
|
|
198
|
+
for scope, names in value.items():
|
|
199
|
+
if not isinstance(scope, str) or not isinstance(names, list):
|
|
200
|
+
continue
|
|
201
|
+
result[scope] = [name for name in names if isinstance(name, str)]
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _coerce_str_list(value: Any) -> list[str]:
|
|
206
|
+
if not isinstance(value, list):
|
|
207
|
+
return []
|
|
208
|
+
return [item for item in value if isinstance(item, str)]
|
|
209
|
+
|
|
210
|
+
|
|
149
211
|
@dataclass(frozen=True)
|
|
150
212
|
class WorkspaceConfig:
|
|
151
213
|
name: str
|
|
@@ -284,14 +284,8 @@ class SyncPlanner:
|
|
|
284
284
|
else None
|
|
285
285
|
)
|
|
286
286
|
state = self.core.load_state()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
managed_mcp = {}
|
|
290
|
-
prev_projects_raw = managed_mcp.get(app_scope(AppId.CLAUDE, "projects"), [])
|
|
291
|
-
previously_managed_projects = (
|
|
292
|
-
{path for path in prev_projects_raw if isinstance(path, str)}
|
|
293
|
-
if isinstance(prev_projects_raw, list)
|
|
294
|
-
else set()
|
|
287
|
+
previously_managed_projects = set(
|
|
288
|
+
state.managed_mcp.get(app_scope(AppId.CLAUDE, "projects"), [])
|
|
295
289
|
)
|
|
296
290
|
project_action = claude_service.build_project_mcp_action(
|
|
297
291
|
self._claude_project_mcp,
|
|
@@ -366,12 +360,8 @@ class SyncPlanner:
|
|
|
366
360
|
skill_sources = project_source.list_skill_sources()
|
|
367
361
|
|
|
368
362
|
state = project_source.load_state()
|
|
369
|
-
managed_links = state.
|
|
370
|
-
|
|
371
|
-
managed_links = {}
|
|
372
|
-
managed_paths = state.get("managed_paths", {})
|
|
373
|
-
if not isinstance(managed_paths, dict):
|
|
374
|
-
managed_paths = {}
|
|
363
|
+
managed_links = state.managed_links
|
|
364
|
+
managed_paths = state.managed_paths
|
|
375
365
|
|
|
376
366
|
actions: list[Action] = []
|
|
377
367
|
skipped: list[str] = []
|
|
@@ -485,12 +475,8 @@ class SyncPlanner:
|
|
|
485
475
|
|
|
486
476
|
repos = self.workspace_service.discover_git_repos(workspace_path)
|
|
487
477
|
state = ws_source.load_state()
|
|
488
|
-
managed_links = state.
|
|
489
|
-
|
|
490
|
-
managed_links = {}
|
|
491
|
-
managed_paths = state.get("managed_paths", {})
|
|
492
|
-
if not isinstance(managed_paths, dict):
|
|
493
|
-
managed_paths = {}
|
|
478
|
+
managed_links = state.managed_links
|
|
479
|
+
managed_paths = state.managed_paths
|
|
494
480
|
|
|
495
481
|
has_config = ws_source.has_any_config()
|
|
496
482
|
if not has_config and not repos:
|
|
@@ -157,8 +157,8 @@ class StatusService:
|
|
|
157
157
|
root=project_config_dir(source_repo, project_name)
|
|
158
158
|
)
|
|
159
159
|
state = project_source.load_state()
|
|
160
|
-
has_managed_paths = bool(state.
|
|
161
|
-
has_managed_links = bool(state.
|
|
160
|
+
has_managed_paths = bool(state.managed_paths)
|
|
161
|
+
has_managed_links = bool(state.managed_links)
|
|
162
162
|
has_project_actions = any(
|
|
163
163
|
f":{project_name}:" in (action.scope or "")
|
|
164
164
|
for action in project_actions
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
|
|
6
6
|
from code_agnostic.errors import InvalidConfigSchemaError, InvalidJsonFormatError
|
|
7
7
|
from code_agnostic.core.repository import CoreRepository
|
|
8
|
+
from code_agnostic.models import SyncState
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@pytest.fixture
|
|
@@ -17,14 +18,7 @@ def test_load_state_defaults_when_missing(core_repo: CoreRepository) -> None:
|
|
|
17
18
|
|
|
18
19
|
state = repo.load_state()
|
|
19
20
|
|
|
20
|
-
assert state ==
|
|
21
|
-
"managed_skill_links": [],
|
|
22
|
-
"managed_agent_links": [],
|
|
23
|
-
"managed_workspace_links": [],
|
|
24
|
-
"managed_links": {},
|
|
25
|
-
"managed_paths": {},
|
|
26
|
-
"managed_mcp": {},
|
|
27
|
-
}
|
|
21
|
+
assert state == SyncState()
|
|
28
22
|
|
|
29
23
|
|
|
30
24
|
def test_add_and_remove_workspace_persists_config(
|
|
@@ -203,14 +197,7 @@ def test_load_state_with_corrupted_json(
|
|
|
203
197
|
|
|
204
198
|
state = repo.load_state()
|
|
205
199
|
|
|
206
|
-
assert state ==
|
|
207
|
-
"managed_skill_links": [],
|
|
208
|
-
"managed_agent_links": [],
|
|
209
|
-
"managed_workspace_links": [],
|
|
210
|
-
"managed_links": {},
|
|
211
|
-
"managed_paths": {},
|
|
212
|
-
"managed_mcp": {},
|
|
213
|
-
}
|
|
200
|
+
assert state == SyncState()
|
|
214
201
|
|
|
215
202
|
|
|
216
203
|
def test_load_state_coerces_managed_skill_links_string_to_list(
|
|
@@ -224,7 +211,7 @@ def test_load_state_coerces_managed_skill_links_string_to_list(
|
|
|
224
211
|
|
|
225
212
|
state = repo.load_state()
|
|
226
213
|
|
|
227
|
-
assert state
|
|
214
|
+
assert state.managed_skill_links == []
|
|
228
215
|
|
|
229
216
|
|
|
230
217
|
def test_list_skill_sources_when_dir_missing(core_repo: CoreRepository) -> None:
|
|
@@ -150,7 +150,7 @@ def test_build_plan_and_apply_create_opencode_and_workspace_links(
|
|
|
150
150
|
|
|
151
151
|
ws_repo = WorkspaceConfigRepository(root=ws_config_dir)
|
|
152
152
|
ws_state = ws_repo.load_state()
|
|
153
|
-
assert len(ws_state
|
|
153
|
+
assert len(ws_state.managed_paths["rules"]) == 1
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
def test_plan_marks_config_create_when_missing(
|
|
@@ -283,9 +283,7 @@ def test_targeted_execute_preserves_workspace_state_for_other_apps(
|
|
|
283
283
|
assert failures == []
|
|
284
284
|
assert applied > 0
|
|
285
285
|
|
|
286
|
-
managed_paths = WorkspaceConfigRepository(root=ws_config).load_state()
|
|
287
|
-
"managed_paths"
|
|
288
|
-
]
|
|
286
|
+
managed_paths = WorkspaceConfigRepository(root=ws_config).load_state().managed_paths
|
|
289
287
|
assert "ws:codex:workspace_root_mcp" in managed_paths
|
|
290
288
|
assert "ws:codex:repo_mcp" in managed_paths
|
|
291
289
|
assert "ws:opencode:workspace_root_mcp" in managed_paths
|
|
@@ -5,6 +5,7 @@ from code_agnostic.models import (
|
|
|
5
5
|
ActionKind,
|
|
6
6
|
ActionStatus,
|
|
7
7
|
SyncPlan,
|
|
8
|
+
SyncState,
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
|
|
@@ -205,3 +206,46 @@ def test_is_valid_with_errors() -> None:
|
|
|
205
206
|
plan = SyncPlan(actions=[], errors=[ValueError("err")], skipped=[])
|
|
206
207
|
|
|
207
208
|
assert plan.is_valid() is False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_sync_state_from_non_dict_payload_is_empty() -> None:
|
|
212
|
+
assert SyncState.from_payload(None) == SyncState()
|
|
213
|
+
assert SyncState.from_payload("garbage") == SyncState()
|
|
214
|
+
assert SyncState.from_payload([1, 2, 3]) == SyncState()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_sync_state_from_payload_normalizes_all_fields() -> None:
|
|
218
|
+
state = SyncState.from_payload(
|
|
219
|
+
{
|
|
220
|
+
"managed_links": {"app:cursor:skills": ["/a", 5, "/b"]},
|
|
221
|
+
"managed_paths": {"scope": ["/p"], 7: ["ignored"]},
|
|
222
|
+
"managed_mcp": {"app:claude:projects": ["/repo"]},
|
|
223
|
+
"managed_skill_links": ["x", 1, "y"],
|
|
224
|
+
"skipped": ["s1"],
|
|
225
|
+
"updated_at": "2026-07-21T00:00:00",
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# non-str entries and non-str scope keys are dropped
|
|
230
|
+
assert state.managed_links == {"app:cursor:skills": ["/a", "/b"]}
|
|
231
|
+
assert state.managed_paths == {"scope": ["/p"]}
|
|
232
|
+
assert state.managed_mcp == {"app:claude:projects": ["/repo"]}
|
|
233
|
+
assert state.managed_skill_links == ["x", "y"]
|
|
234
|
+
assert state.skipped == ["s1"]
|
|
235
|
+
assert state.updated_at == "2026-07-21T00:00:00"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_sync_state_from_payload_coerces_wrong_types() -> None:
|
|
239
|
+
state = SyncState.from_payload(
|
|
240
|
+
{
|
|
241
|
+
"managed_links": "not-a-dict",
|
|
242
|
+
"managed_paths": {"scope": "not-a-list"},
|
|
243
|
+
"managed_skill_links": "not-a-list",
|
|
244
|
+
"updated_at": 12345,
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
assert state.managed_links == {}
|
|
249
|
+
assert state.managed_paths == {} # non-list value drops the scope
|
|
250
|
+
assert state.managed_skill_links == []
|
|
251
|
+
assert state.updated_at is None
|
|
@@ -32,7 +32,7 @@ from code_agnostic.constants import (
|
|
|
32
32
|
from code_agnostic.core.repository import CoreRepository
|
|
33
33
|
from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
|
|
34
34
|
from code_agnostic.executor import SyncExecutor
|
|
35
|
-
from code_agnostic.models import ActionKind, ActionStatus
|
|
35
|
+
from code_agnostic.models import ActionKind, ActionStatus, SyncState
|
|
36
36
|
from code_agnostic.planner import SyncPlanner
|
|
37
37
|
|
|
38
38
|
|
|
@@ -153,11 +153,11 @@ def test_workspace_config_repository_state_roundtrip(tmp_path: Path) -> None:
|
|
|
153
153
|
repo = WorkspaceConfigRepository(root=ws_root)
|
|
154
154
|
|
|
155
155
|
state = repo.load_state()
|
|
156
|
-
assert isinstance(state,
|
|
156
|
+
assert isinstance(state, SyncState)
|
|
157
157
|
|
|
158
158
|
repo.save_state({"managed_links": {"rules": ["/path/to/link"]}})
|
|
159
159
|
loaded = repo.load_state()
|
|
160
|
-
assert loaded
|
|
160
|
+
assert loaded.managed_links["rules"] == ["/path/to/link"]
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
# --- Workspace rules files ---
|
|
@@ -920,8 +920,8 @@ def test_workspace_targeted_plan_cleans_stale_cursor_workspace_outputs(
|
|
|
920
920
|
assert not stale_mcp.exists()
|
|
921
921
|
assert not stale_agent.exists()
|
|
922
922
|
state = ws_repo.load_state()
|
|
923
|
-
assert "ws:cursor:repo_mcp" not in state
|
|
924
|
-
assert "ws:cursor:repo_agents_dir" not in state
|
|
923
|
+
assert "ws:cursor:repo_mcp" not in state.managed_paths
|
|
924
|
+
assert "ws:cursor:repo_agents_dir" not in state.managed_links
|
|
925
925
|
|
|
926
926
|
|
|
927
927
|
def test_workspace_plan_skips_legacy_link_cleanup_when_same_path_is_now_generated(
|
|
@@ -1419,13 +1419,13 @@ def test_executor_persists_workspace_state_separately(
|
|
|
1419
1419
|
# Workspace state persisted to workspace state file.
|
|
1420
1420
|
ws_repo = WorkspaceConfigRepository(root=ws_config)
|
|
1421
1421
|
ws_state = ws_repo.load_state()
|
|
1422
|
-
managed = ws_state
|
|
1422
|
+
managed = ws_state.managed_paths
|
|
1423
1423
|
assert "rules" in managed
|
|
1424
1424
|
assert len(managed["rules"]) == 1
|
|
1425
1425
|
|
|
1426
1426
|
# Global state should not contain workspace links
|
|
1427
1427
|
global_state = core.load_state()
|
|
1428
|
-
assert "rules" not in global_state.
|
|
1428
|
+
assert "rules" not in global_state.managed_links
|
|
1429
1429
|
|
|
1430
1430
|
|
|
1431
1431
|
# --- Full roundtrip with apply ---
|
|
@@ -1548,7 +1548,7 @@ def test_workspace_stale_skills_cleanup_when_skills_removed_for_codex(
|
|
|
1548
1548
|
# Verify state was persisted with workspace scopes
|
|
1549
1549
|
ws_repo = WorkspaceConfigRepository(root=ws_config)
|
|
1550
1550
|
state = ws_repo.load_state()
|
|
1551
|
-
assert "ws:codex:repo_skills_dir" in state
|
|
1551
|
+
assert "ws:codex:repo_skills_dir" in state.managed_paths
|
|
1552
1552
|
|
|
1553
1553
|
# Remove all skills from workspace config
|
|
1554
1554
|
import shutil
|
|
@@ -1597,7 +1597,7 @@ def test_workspace_stale_skills_cleanup_when_skills_removed_for_claude(
|
|
|
1597
1597
|
|
|
1598
1598
|
ws_repo = WorkspaceConfigRepository(root=ws_config)
|
|
1599
1599
|
state = ws_repo.load_state()
|
|
1600
|
-
assert "ws:claude:repo_skills_dir" in state
|
|
1600
|
+
assert "ws:claude:repo_skills_dir" in state.managed_paths
|
|
1601
1601
|
|
|
1602
1602
|
import shutil
|
|
1603
1603
|
|
|
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
|
{code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/common/interfaces/__init__.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
|
{code_agnostic-0.7.0 → code_agnostic-0.8.0}/code_agnostic/apps/opencode/config_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.7.0 → code_agnostic-0.8.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
|