code-agnostic 0.4.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/PKG-INFO +1 -1
  2. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/__init__.py +1 -1
  3. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/claude.py +1 -1
  4. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/codex.py +2 -2
  5. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/parser.py +2 -2
  6. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/apps_service.py +2 -2
  7. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/claude/service.py +0 -7
  8. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/config_repository.py +2 -2
  9. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/service.py +1 -1
  10. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/framework.py +2 -2
  11. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/interfaces/service.py +18 -2
  12. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/schema.py +1 -1
  13. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/copilot/service.py +1 -5
  14. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/service.py +1 -3
  15. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/service.py +1 -5
  16. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/aliases.py +3 -1
  17. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/workspaces.py +2 -1
  18. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/helpers.py +9 -2
  19. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/options.py +17 -6
  20. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/executor.py +7 -7
  21. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/imports/adapters.py +6 -2
  22. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/imports/service.py +13 -10
  23. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/planner.py +57 -66
  24. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/project_artifacts.py +6 -2
  25. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/rules/compilers.py +2 -1
  26. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/rules/parser.py +2 -1
  27. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/skills/parser.py +6 -3
  28. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/status.py +4 -4
  29. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/renderers.py +3 -1
  30. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/sections.py +5 -1
  31. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/tables.py +6 -5
  32. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/PKG-INFO +1 -1
  33. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/pyproject.toml +6 -1
  34. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/LICENSE +0 -0
  35. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/README.md +0 -0
  36. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/__main__.py +0 -0
  37. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/__init__.py +0 -0
  38. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/compilers.py +0 -0
  39. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/copilot.py +0 -0
  40. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/models.py +0 -0
  41. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/agents/opencode.py +0 -0
  42. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/__init__.py +0 -0
  43. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/app_id.py +0 -0
  44. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/claude/__init__.py +0 -0
  45. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/claude/config_repository.py +0 -0
  46. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/claude/mapper.py +0 -0
  47. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/__init__.py +0 -0
  48. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/mapper.py +0 -0
  49. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/schema.json +0 -0
  50. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/codex/schema_repository.py +0 -0
  51. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/__init__.py +0 -0
  52. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/compiled_planning.py +0 -0
  53. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
  54. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
  55. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
  56. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/loader.py +0 -0
  57. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/models.py +0 -0
  58. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/symlink_planning.py +0 -0
  59. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/common/utils.py +0 -0
  60. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/copilot/__init__.py +0 -0
  61. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/copilot/config_repository.py +0 -0
  62. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/copilot/mapper.py +0 -0
  63. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/__init__.py +0 -0
  64. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/config_repository.py +0 -0
  65. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/mapper.py +0 -0
  66. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/schema.json +0 -0
  67. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/cursor/schema_repository.py +0 -0
  68. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/__init__.py +0 -0
  69. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/config_repository.py +0 -0
  70. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/mapper.py +0 -0
  71. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/schema.json +0 -0
  72. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/apps/opencode/schema_repository.py +0 -0
  73. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/__init__.py +0 -0
  74. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/__init__.py +0 -0
  75. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/agents.py +0 -0
  76. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/apply.py +0 -0
  77. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/apps.py +0 -0
  78. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
  79. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/import_.py +0 -0
  80. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/mcp.py +0 -0
  81. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/plan.py +0 -0
  82. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/projects.py +0 -0
  83. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/restore.py +0 -0
  84. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/rules.py +0 -0
  85. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/skills.py +0 -0
  86. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/status.py +0 -0
  87. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/cli/commands/validate.py +0 -0
  88. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/constants.py +0 -0
  89. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/core/__init__.py +0 -0
  90. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/core/project_repository.py +0 -0
  91. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/core/repository.py +0 -0
  92. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/core/workspace_repository.py +0 -0
  93. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/errors.py +0 -0
  94. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/generated_artifacts.py +0 -0
  95. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/git_exclude_service.py +0 -0
  96. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/imports/__init__.py +0 -0
  97. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/imports/filesystem.py +0 -0
  98. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/imports/models.py +0 -0
  99. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/lossiness.py +0 -0
  100. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/mcp_service.py +0 -0
  101. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/models.py +0 -0
  102. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/rules/__init__.py +0 -0
  103. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/rules/models.py +0 -0
  104. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/rules/repository.py +0 -0
  105. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/skills/__init__.py +0 -0
  106. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/skills/compilers.py +0 -0
  107. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/skills/install_sources.py +0 -0
  108. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/skills/models.py +0 -0
  109. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/__init__.py +0 -0
  110. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/loaders.py +0 -0
  111. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
  112. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
  113. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
  114. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
  115. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
  116. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/__init__.py +0 -0
  117. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/enums.py +0 -0
  118. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/tui/import_selector.py +0 -0
  119. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/utils.py +0 -0
  120. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/validation.py +0 -0
  121. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/workspace_artifacts.py +0 -0
  122. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic/workspaces.py +0 -0
  123. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/SOURCES.txt +0 -0
  124. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/dependency_links.txt +0 -0
  125. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/entry_points.txt +0 -0
  126. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/requires.txt +0 -0
  127. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/code_agnostic.egg-info/top_level.txt +0 -0
  128. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/setup.cfg +0 -0
  129. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_agents.py +0 -0
  130. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_aliases.py +0 -0
  131. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apply_apps.py +0 -0
  132. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apply_codex.py +0 -0
  133. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apply_cursor.py +0 -0
  134. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apply_mcp_cleanup.py +0 -0
  135. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apply_target.py +0 -0
  136. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_apps.py +0 -0
  137. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_disable_cleanup.py +0 -0
  138. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_explain_lossiness.py +0 -0
  139. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_flags.py +0 -0
  140. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_git_exclude.py +0 -0
  141. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_import.py +0 -0
  142. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_import_interactive.py +0 -0
  143. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_mcp.py +0 -0
  144. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_module_organization.py +0 -0
  145. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_plan.py +0 -0
  146. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_projects.py +0 -0
  147. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_restore.py +0 -0
  148. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_rules.py +0 -0
  149. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_skills.py +0 -0
  150. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_status.py +0 -0
  151. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_validate.py +0 -0
  152. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_workspace_resolution.py +0 -0
  153. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_cli_workspaces.py +0 -0
  154. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_common_mcp_to_dto.py +0 -0
  155. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_common_repository.py +0 -0
  156. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_compiled_planning.py +0 -0
  157. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_dto_to_common_mcp.py +0 -0
  158. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_git_exclude_service.py +0 -0
  159. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_mcp_service.py +0 -0
  160. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_planner_executor.py +0 -0
  161. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_planner_rules.py +0 -0
  162. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_project_config_sync.py +0 -0
  163. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_skill_install_sources.py +0 -0
  164. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_symlink_planning.py +0 -0
  165. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_sync_plan_model.py +0 -0
  166. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_transactional_executor.py +0 -0
  167. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_utils.py +0 -0
  168. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_version.py +0 -0
  169. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_workspace_config_sync.py +0 -0
  170. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_workspace_repo_status.py +0 -0
  171. {code_agnostic-0.4.0 → code_agnostic-0.5.1}/tests/test_workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-agnostic
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Centralized hub for LLM coding config: MCP, skills, rules, and agents.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.1"
@@ -6,7 +6,7 @@ import re
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
- import yaml # type: ignore[import-untyped]
9
+ import yaml
10
10
 
11
11
  from code_agnostic.agents.models import Agent
12
12
 
@@ -7,9 +7,9 @@ from pathlib import Path
7
7
  from typing import Any
8
8
 
9
9
  try:
10
- import tomllib
10
+ import tomllib # type: ignore[import-not-found]
11
11
  except ModuleNotFoundError: # pragma: no cover
12
- import tomli as tomllib # type: ignore
12
+ import tomli as tomllib
13
13
 
14
14
  import tomlkit
15
15
 
@@ -83,7 +83,7 @@ def parse_agent(path: Path) -> Agent:
83
83
 
84
84
 
85
85
  def serialize_agent(agent: Agent, *, target_app: str | None = None) -> str:
86
- fm: dict = {}
86
+ fm: dict[str, Any] = {}
87
87
  if agent.metadata.name:
88
88
  fm["name"] = agent.metadata.name
89
89
  if agent.metadata.description:
@@ -105,7 +105,7 @@ def serialize_agent(agent: Agent, *, target_app: str | None = None) -> str:
105
105
  if nickname_candidates:
106
106
  fm["nickname_candidates"] = list(nickname_candidates)
107
107
 
108
- tools: dict = {}
108
+ tools: dict[str, Any] = {}
109
109
  if agent.metadata.tools.read is not True:
110
110
  tools["read"] = agent.metadata.tools.read
111
111
  if agent.metadata.tools.write is not True:
@@ -7,7 +7,6 @@ from code_agnostic.apps.common.framework import (
7
7
  list_registered_app_services,
8
8
  )
9
9
  from code_agnostic.apps.common.interfaces.service import IAppConfigService
10
- from code_agnostic.apps.common.interfaces.repositories import ISourceRepository
11
10
  from code_agnostic.apps.common.symlink_planning import (
12
11
  load_state_links,
13
12
  load_state_paths,
@@ -15,6 +14,7 @@ from code_agnostic.apps.common.symlink_planning import (
15
14
  plan_stale_group,
16
15
  )
17
16
  from code_agnostic.core.project_repository import ProjectConfigRepository
17
+ from code_agnostic.core.repository import CoreRepository
18
18
  from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
19
19
  from code_agnostic.executor import SyncExecutor
20
20
  from code_agnostic.models import Action, AppStatusRow, AppSyncStatus, SyncPlan
@@ -24,7 +24,7 @@ from code_agnostic.utils import read_json_safe, write_json
24
24
 
25
25
 
26
26
  class AppsService:
27
- def __init__(self, core_repository: ISourceRepository) -> None:
27
+ def __init__(self, core_repository: CoreRepository) -> None:
28
28
  self.core_repository = core_repository
29
29
 
30
30
  @property
@@ -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
 
@@ -3,9 +3,9 @@ from pathlib import Path
3
3
  from typing import Any
4
4
 
5
5
  try:
6
- import tomllib
6
+ import tomllib # type: ignore[import-not-found]
7
7
  except ModuleNotFoundError: # pragma: no cover
8
- import tomli as tomllib # type: ignore
8
+ import tomli as tomllib
9
9
 
10
10
  import tomlkit
11
11
 
@@ -88,7 +88,7 @@ class CodexConfigService(RegisteredAppConfigService):
88
88
  def mcp_config_key(self) -> str:
89
89
  return "mcp_servers"
90
90
 
91
- def validate_config(self, payload: Any) -> None:
91
+ def _validate_schema(self, payload: dict[str, Any]) -> None:
92
92
  error = next(iter(self._validator.iter_errors(payload)), None)
93
93
  if error is not None:
94
94
  raise InvalidConfigSchemaError(
@@ -20,12 +20,12 @@ class AppServiceRegistryMeta(ABCMeta):
20
20
  bases: tuple[type, ...],
21
21
  namespace: dict[str, Any],
22
22
  **kwargs: Any,
23
- ):
23
+ ) -> "AppServiceRegistryMeta":
24
24
  cls = super().__new__(mcls, name, bases, namespace, **kwargs)
25
25
  app_id = getattr(cls, "APP_ID", None)
26
26
  is_abstract = bool(getattr(cls, "__abstractmethods__", False))
27
27
  if app_id is not None and not is_abstract:
28
- mcls._registry[app_id] = cast(type["RegisteredAppConfigService"], cls) # type: ignore[assignment]
28
+ mcls._registry[app_id] = cast(type["RegisteredAppConfigService"], cls)
29
29
  return cls
30
30
 
31
31
 
@@ -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
- raise NotImplementedError
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:
@@ -51,6 +51,6 @@ class JsonSchemaRepository(ISchemaRepository):
51
51
  now = time.time()
52
52
  if cached and now - cached[0] < self.ttl_seconds:
53
53
  return cached[1]
54
- schema = json.loads(path.read_text(encoding="utf-8"))
54
+ schema: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
55
55
  _SCHEMA_CACHE[key] = (now, schema)
56
56
  return schema
@@ -55,11 +55,7 @@ class CopilotConfigService(RegisteredAppConfigService):
55
55
  def mcp_config_key(self) -> str:
56
56
  return "mcpServers"
57
57
 
58
- def validate_config(self, payload: Any) -> None:
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 validate_config(self, payload: Any) -> None:
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 validate_config(self, payload: Any) -> None:
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
@@ -14,7 +14,9 @@ class AliasedGroup(click.Group):
14
14
  def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
15
15
  return super().get_command(ctx, self.ALIASES.get(cmd_name, cmd_name))
16
16
 
17
- def resolve_command(self, ctx: click.Context, args: list[str]):
17
+ def resolve_command(
18
+ self, ctx: click.Context, args: list[str]
19
+ ) -> tuple[str | None, click.Command | None, list[str]]:
18
20
  if args and args[0] in self.ALIASES:
19
21
  args[0] = self.ALIASES[args[0]]
20
22
  return super().resolve_command(ctx, args)
@@ -1,6 +1,7 @@
1
1
  """Workspaces group commands."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  import click
6
7
  from rich.console import Console
@@ -63,7 +64,7 @@ def workspaces_list(obj: dict[str, str]) -> None:
63
64
  core = CoreRepository()
64
65
  workspace_service = WorkspaceService()
65
66
 
66
- overview: list[dict] = []
67
+ overview: list[dict[str, Any]] = []
67
68
  try:
68
69
  workspaces = core.load_workspaces()
69
70
  except SyncAppError as exc:
@@ -7,7 +7,12 @@ import click
7
7
  from code_agnostic.apps.apps_service import AppsService
8
8
  from code_agnostic.core.repository import CoreRepository
9
9
  from code_agnostic.errors import SyncAppError
10
- from code_agnostic.models import ActionStatus, EditorStatusRow, EditorSyncStatus
10
+ from code_agnostic.models import (
11
+ ActionStatus,
12
+ EditorStatusRow,
13
+ EditorSyncStatus,
14
+ SyncPlan,
15
+ )
11
16
 
12
17
 
13
18
  def _workspace_entries_by_name(core: CoreRepository) -> dict[str, dict[str, str]]:
@@ -64,7 +69,9 @@ def reject_symlinked_source_dir(path: Path, resource_type: str) -> None:
64
69
  )
65
70
 
66
71
 
67
- def status_row_for_app(app_name: str, plan, apps: AppsService) -> EditorStatusRow:
72
+ def status_row_for_app(
73
+ app_name: str, plan: SyncPlan, apps: AppsService
74
+ ) -> EditorStatusRow:
68
75
  if not apps.is_enabled(app_name):
69
76
  return EditorStatusRow(
70
77
  name=app_name,
@@ -1,6 +1,7 @@
1
1
  """Shared option decorators for CLI commands."""
2
2
 
3
3
  from collections.abc import Callable
4
+ from typing import Any
4
5
 
5
6
  import click
6
7
 
@@ -38,7 +39,9 @@ def _import_source_values() -> list[str]:
38
39
  ]
39
40
 
40
41
 
41
- def app_option(required: bool = False) -> Callable:
42
+ def app_option(
43
+ required: bool = False,
44
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
42
45
  return click.option(
43
46
  "-a",
44
47
  "--app",
@@ -49,7 +52,9 @@ def app_option(required: bool = False) -> Callable:
49
52
  )
50
53
 
51
54
 
52
- def manageable_app_option(required: bool = True) -> Callable:
55
+ def manageable_app_option(
56
+ required: bool = True,
57
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
53
58
  return click.option(
54
59
  "-a",
55
60
  "--app",
@@ -59,7 +64,9 @@ def manageable_app_option(required: bool = True) -> Callable:
59
64
  )
60
65
 
61
66
 
62
- def import_app_option(required: bool = True) -> Callable:
67
+ def import_app_option(
68
+ required: bool = True,
69
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
63
70
  return click.option(
64
71
  "-a",
65
72
  "--app",
@@ -69,7 +76,9 @@ def import_app_option(required: bool = True) -> Callable:
69
76
  )
70
77
 
71
78
 
72
- def workspace_option(required: bool = False) -> Callable:
79
+ def workspace_option(
80
+ required: bool = False,
81
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
73
82
  return click.option(
74
83
  "-w",
75
84
  "--workspace",
@@ -79,7 +88,9 @@ def workspace_option(required: bool = False) -> Callable:
79
88
  )
80
89
 
81
90
 
82
- def project_option(required: bool = False) -> Callable:
91
+ def project_option(
92
+ required: bool = False,
93
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
83
94
  return click.option(
84
95
  "--project",
85
96
  required=required,
@@ -88,5 +99,5 @@ def project_option(required: bool = False) -> Callable:
88
99
  )
89
100
 
90
101
 
91
- def verbose_option() -> Callable:
102
+ def verbose_option() -> Callable[[Callable[..., Any]], Callable[..., Any]]:
92
103
  return click.option("-v", "--verbose", is_flag=True, default=False)
@@ -7,13 +7,13 @@ from pathlib import Path
7
7
  from typing import Any
8
8
  from typing import Protocol
9
9
 
10
- from code_agnostic.apps.common.interfaces.repositories import ISourceRepository
11
10
  from code_agnostic.constants import (
12
11
  SYNC_REVISIONS_DIRNAME,
13
12
  SYNC_STAGING_DIRNAME,
14
13
  SYNC_STATE_FILENAME,
15
14
  )
16
15
  from code_agnostic.core.project_repository import ProjectConfigRepository
16
+ from code_agnostic.core.repository import CoreRepository
17
17
  from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
18
18
  from code_agnostic.models import Action, ActionKind, ActionStatus, SyncPlan
19
19
  from code_agnostic.utils import write_json
@@ -21,7 +21,7 @@ from code_agnostic.utils import write_json
21
21
 
22
22
  @dataclass
23
23
  class ExecutionContext:
24
- core: ISourceRepository
24
+ core: CoreRepository
25
25
 
26
26
 
27
27
  @dataclass(frozen=True)
@@ -189,7 +189,7 @@ class WriteRuleHandler:
189
189
 
190
190
 
191
191
  class SyncExecutor:
192
- def __init__(self, core: ISourceRepository) -> None:
192
+ def __init__(self, core: CoreRepository) -> None:
193
193
  self.context = ExecutionContext(core=core)
194
194
  self.handlers: dict[ActionKind, ActionHandler] = {
195
195
  ActionKind.WRITE_JSON: WriteJsonHandler(),
@@ -421,12 +421,12 @@ class SyncExecutor:
421
421
  if workspace is not None and project is not None:
422
422
  raise ValueError("Choose only one scope: workspace or project.")
423
423
 
424
- if workspace is None and project is None:
425
- root = self.context.core.root
426
- elif workspace is not None:
424
+ if workspace is not None:
427
425
  root = self.context.core.workspace_config_dir(workspace)
428
- else:
426
+ elif project is not None:
429
427
  root = self.context.core.project_config_dir(project)
428
+ else:
429
+ root = self.context.core.root
430
430
 
431
431
  revision_record = self._build_revision_record(
432
432
  root=root, workspace=workspace, project=project, revision_id="restore"
@@ -1,5 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
+ from typing import Any, cast
3
4
 
4
5
  from code_agnostic.apps.app_id import AppId
5
6
  from code_agnostic.apps.codex.config_repository import CodexConfigRepository
@@ -24,8 +25,11 @@ class ImportAdapter:
24
25
  mapper: IAppMCPMapper
25
26
  config_repository: object
26
27
 
27
- def load_mcp_payload(self) -> dict:
28
- return self.config_repository.load_mcp_payload() # type: ignore[attr-defined]
28
+ def load_mcp_payload(self) -> dict[str, Any]:
29
+ return cast(
30
+ "dict[str, Any]",
31
+ self.config_repository.load_mcp_payload(), # type: ignore[attr-defined]
32
+ )
29
33
 
30
34
 
31
35
  def create_import_adapter(app: str, source_root: Path | None = None) -> ImportAdapter:
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+ from typing import Any
2
3
 
3
4
  from code_agnostic.agents.codex import normalize_codex_agent_filename, parse_codex_agent
4
5
  from code_agnostic.agents.parser import serialize_agent
@@ -8,7 +9,7 @@ from code_agnostic.apps.common.models import MCPServerDTO
8
9
  from code_agnostic.apps.common.utils import common_mcp_to_dto, dto_to_common_mcp
9
10
  from code_agnostic.core.repository import CoreRepository
10
11
  from code_agnostic.errors import InvalidConfigSchemaError, InvalidJsonFormatError
11
- from code_agnostic.imports.adapters import create_import_adapter
12
+ from code_agnostic.imports.adapters import ImportAdapter, create_import_adapter
12
13
  from code_agnostic.imports.filesystem import (
13
14
  content_equal,
14
15
  copy_path,
@@ -261,7 +262,9 @@ class ImportService:
261
262
  unsupported.append(ImportSection.AGENTS)
262
263
  return unsupported
263
264
 
264
- def _plan_mcp(self, adapter, conflict_policy: ConflictPolicy):
265
+ def _plan_mcp(
266
+ self, adapter: ImportAdapter, conflict_policy: ConflictPolicy
267
+ ) -> tuple[list[ImportAction], list[str], list[str]]:
265
268
  actions: list[ImportAction] = []
266
269
  errors: list[str] = []
267
270
  skipped: list[str] = []
@@ -272,7 +275,7 @@ class ImportService:
272
275
  return actions, [str(exc)], skipped
273
276
 
274
277
  if not source_dto:
275
- source_config = adapter.config_repository.config_path
278
+ source_config = adapter.config_repository.config_path # type: ignore[attr-defined]
276
279
  if source_config.exists():
277
280
  skipped.append(
278
281
  f"No MCP servers found in source config: {source_config}"
@@ -368,7 +371,7 @@ class ImportService:
368
371
  else:
369
372
  skipped.append(f"MCP server skipped due to conflict: {name}")
370
373
 
371
- normalized_payload = {}
374
+ normalized_payload: dict[str, Any] = {}
372
375
  existing_schema = existing_payload.get("$schema")
373
376
  if isinstance(existing_schema, str) and existing_schema:
374
377
  normalized_payload["$schema"] = existing_schema
@@ -399,7 +402,7 @@ class ImportService:
399
402
  target_dir: Path,
400
403
  conflict_policy: ConflictPolicy,
401
404
  follow_symlinks: bool,
402
- ):
405
+ ) -> tuple[list[ImportAction], list[str], list[str]]:
403
406
  actions: list[ImportAction] = []
404
407
  errors: list[str] = []
405
408
  skipped: list[str] = []
@@ -518,10 +521,10 @@ class ImportService:
518
521
 
519
522
  def _plan_codex_agents(
520
523
  self,
521
- adapter,
524
+ adapter: ImportAdapter,
522
525
  conflict_policy: ConflictPolicy,
523
526
  follow_symlinks: bool,
524
- ):
527
+ ) -> tuple[list[ImportAction], list[str], list[str]]:
525
528
  actions: list[ImportAction] = []
526
529
  errors: list[str] = []
527
530
  skipped: list[str] = []
@@ -584,10 +587,10 @@ class ImportService:
584
587
 
585
588
  def _plan_copilot_agents(
586
589
  self,
587
- adapter,
590
+ adapter: ImportAdapter,
588
591
  conflict_policy: ConflictPolicy,
589
592
  follow_symlinks: bool,
590
- ):
593
+ ) -> tuple[list[ImportAction], list[str], list[str]]:
591
594
  actions: list[ImportAction] = []
592
595
  errors: list[str] = []
593
596
  skipped: list[str] = []
@@ -707,7 +710,7 @@ class ImportService:
707
710
  section: ImportSection,
708
711
  detail_name: str,
709
712
  target: Path,
710
- payload: dict,
713
+ payload: dict[str, Any],
711
714
  conflict_policy: ConflictPolicy,
712
715
  ) -> tuple[ImportAction, str | None, str | None]:
713
716
  existing_payload, existing_error = read_json_safe(target)