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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/PKG-INFO +1 -1
  2. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/__init__.py +1 -1
  3. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/apps_service.py +45 -10
  4. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/claude/service.py +19 -2
  5. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/service.py +17 -5
  6. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/interfaces/service.py +8 -1
  7. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/service.py +4 -1
  8. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/executor.py +8 -6
  9. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/models.py +1 -1
  10. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/planner.py +18 -4
  11. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/PKG-INFO +1 -1
  12. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/SOURCES.txt +1 -0
  13. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/pyproject.toml +1 -1
  14. code_agnostic-0.7.0/tests/test_cli_apply_codex_agent_cleanup.py +71 -0
  15. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_disable_cleanup.py +48 -0
  16. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_workspace_config_sync.py +77 -0
  17. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/LICENSE +0 -0
  18. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/README.md +0 -0
  19. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/__main__.py +0 -0
  20. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/__init__.py +0 -0
  21. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/claude.py +0 -0
  22. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/codex.py +0 -0
  23. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/compilers.py +0 -0
  24. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/copilot.py +0 -0
  25. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/models.py +0 -0
  26. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/opencode.py +0 -0
  27. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/agents/parser.py +0 -0
  28. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/__init__.py +0 -0
  29. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/app_id.py +0 -0
  30. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/claude/__init__.py +0 -0
  31. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/claude/config_repository.py +0 -0
  32. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/claude/mapper.py +0 -0
  33. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/__init__.py +0 -0
  34. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/config_repository.py +0 -0
  35. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/mapper.py +0 -0
  36. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/schema.json +0 -0
  37. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/codex/schema_repository.py +0 -0
  38. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/__init__.py +0 -0
  39. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/compiled_planning.py +0 -0
  40. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/framework.py +0 -0
  41. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
  42. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
  43. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
  44. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/loader.py +0 -0
  45. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/models.py +0 -0
  46. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/schema.py +0 -0
  47. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/symlink_planning.py +0 -0
  48. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/common/utils.py +0 -0
  49. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/copilot/__init__.py +0 -0
  50. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/copilot/config_repository.py +0 -0
  51. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/copilot/mapper.py +0 -0
  52. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/copilot/service.py +0 -0
  53. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/__init__.py +0 -0
  54. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/config_repository.py +0 -0
  55. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/mapper.py +0 -0
  56. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/schema.json +0 -0
  57. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/schema_repository.py +0 -0
  58. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/cursor/service.py +0 -0
  59. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/__init__.py +0 -0
  60. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/config_repository.py +0 -0
  61. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/mapper.py +0 -0
  62. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/schema.json +0 -0
  63. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/apps/opencode/schema_repository.py +0 -0
  64. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/__init__.py +0 -0
  65. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/aliases.py +0 -0
  66. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/__init__.py +0 -0
  67. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/agents.py +0 -0
  68. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/apply.py +0 -0
  69. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/apps.py +0 -0
  70. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
  71. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/import_.py +0 -0
  72. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/mcp.py +0 -0
  73. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/plan.py +0 -0
  74. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/projects.py +0 -0
  75. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/restore.py +0 -0
  76. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/rules.py +0 -0
  77. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/skills.py +0 -0
  78. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/status.py +0 -0
  79. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/validate.py +0 -0
  80. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/commands/workspaces.py +0 -0
  81. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/helpers.py +0 -0
  82. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/cli/options.py +0 -0
  83. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/constants.py +0 -0
  84. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/core/__init__.py +0 -0
  85. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/core/project_repository.py +0 -0
  86. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/core/repository.py +0 -0
  87. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/core/workspace_repository.py +0 -0
  88. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/errors.py +0 -0
  89. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/generated_artifacts.py +0 -0
  90. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/git_exclude_service.py +0 -0
  91. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/imports/__init__.py +0 -0
  92. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/imports/adapters.py +0 -0
  93. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/imports/filesystem.py +0 -0
  94. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/imports/models.py +0 -0
  95. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/imports/service.py +0 -0
  96. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/lossiness.py +0 -0
  97. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/mcp_service.py +0 -0
  98. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/project_artifacts.py +0 -0
  99. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/rules/__init__.py +0 -0
  100. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/rules/compilers.py +0 -0
  101. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/rules/models.py +0 -0
  102. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/rules/parser.py +0 -0
  103. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/rules/repository.py +0 -0
  104. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/skills/__init__.py +0 -0
  105. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/skills/compilers.py +0 -0
  106. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/skills/install_sources.py +0 -0
  107. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/skills/models.py +0 -0
  108. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/skills/parser.py +0 -0
  109. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/__init__.py +0 -0
  110. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/loaders.py +0 -0
  111. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
  112. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
  113. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
  114. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
  115. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
  116. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/status.py +0 -0
  117. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/__init__.py +0 -0
  118. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/enums.py +0 -0
  119. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/import_selector.py +0 -0
  120. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/renderers.py +0 -0
  121. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/sections.py +0 -0
  122. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/tui/tables.py +0 -0
  123. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/utils.py +0 -0
  124. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/validation.py +0 -0
  125. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/workspace_artifacts.py +0 -0
  126. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic/workspaces.py +0 -0
  127. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/dependency_links.txt +0 -0
  128. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/entry_points.txt +0 -0
  129. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/requires.txt +0 -0
  130. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/code_agnostic.egg-info/top_level.txt +0 -0
  131. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/setup.cfg +0 -0
  132. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_agents.py +0 -0
  133. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_aliases.py +0 -0
  134. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apply_apps.py +0 -0
  135. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apply_codex.py +0 -0
  136. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apply_cursor.py +0 -0
  137. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apply_mcp_cleanup.py +0 -0
  138. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apply_target.py +0 -0
  139. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_apps.py +0 -0
  140. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_explain_lossiness.py +0 -0
  141. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_flags.py +0 -0
  142. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_git_exclude.py +0 -0
  143. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_import.py +0 -0
  144. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_import_interactive.py +0 -0
  145. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_mcp.py +0 -0
  146. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_module_organization.py +0 -0
  147. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_plan.py +0 -0
  148. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_projects.py +0 -0
  149. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_restore.py +0 -0
  150. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_rules.py +0 -0
  151. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_skills.py +0 -0
  152. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_status.py +0 -0
  153. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_validate.py +0 -0
  154. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_workspace_resolution.py +0 -0
  155. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_cli_workspaces.py +0 -0
  156. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_common_mcp_to_dto.py +0 -0
  157. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_common_repository.py +0 -0
  158. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_compiled_planning.py +0 -0
  159. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_dto_to_common_mcp.py +0 -0
  160. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_git_exclude_service.py +0 -0
  161. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_mcp_service.py +0 -0
  162. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_planner_executor.py +0 -0
  163. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_planner_rules.py +0 -0
  164. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_project_config_sync.py +0 -0
  165. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_skill_install_sources.py +0 -0
  166. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_symlink_planning.py +0 -0
  167. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_sync_plan_model.py +0 -0
  168. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_transactional_executor.py +0 -0
  169. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_utils.py +0 -0
  170. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_version.py +0 -0
  171. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_workspace_repo_status.py +0 -0
  172. {code_agnostic-0.5.1 → code_agnostic-0.7.0}/tests/test_workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-agnostic
3
- Version: 0.5.1
3
+ Version: 0.7.0
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.5.1"
3
+ __version__ = "0.7.0"
@@ -6,6 +6,7 @@ from code_agnostic.apps.common.framework import (
6
6
  create_registered_app_service,
7
7
  list_registered_app_services,
8
8
  )
9
+ from code_agnostic.apps.claude.service import ClaudeConfigService
9
10
  from code_agnostic.apps.common.interfaces.service import IAppConfigService
10
11
  from code_agnostic.apps.common.symlink_planning import (
11
12
  load_state_links,
@@ -113,6 +114,8 @@ class AppsService:
113
114
  self._cleanup_paths(global_paths, scope, app_id.value, skipped)
114
115
  )
115
116
  actions.extend(self._cleanup_global_mcp(app_id, global_mcp))
117
+ if app_id == AppId.CLAUDE:
118
+ actions.extend(self._cleanup_claude_projects(global_mcp))
116
119
 
117
120
  # --- Workspace scopes (ws:<app>:*) ---
118
121
  ws_prefix = f"ws:{app_id.value}:"
@@ -201,14 +204,11 @@ class AppsService:
201
204
  def _cleanup_global_mcp(
202
205
  self, app_id: AppId, managed_mcp: dict[str, Any]
203
206
  ) -> list[Action]:
204
- scope = app_scope(app_id, "mcp")
205
- raw = managed_mcp.get(scope, [])
206
- managed = (
207
- {item for item in raw if isinstance(item, str)}
208
- if isinstance(raw, list)
209
- else set()
207
+ managed = self._names_for_scope(managed_mcp, app_scope(app_id, "mcp"))
208
+ managed_agents = self._names_for_scope(
209
+ managed_mcp, app_scope(app_id, "agents_registry")
210
210
  )
211
- if not managed:
211
+ if not managed and not managed_agents:
212
212
  return []
213
213
  try:
214
214
  service = create_registered_app_service(app_id)
@@ -216,9 +216,44 @@ class AppsService:
216
216
  return []
217
217
  if not service.repository.config_path.exists():
218
218
  return []
219
- # Empty desired + our previously-managed names → prune only our servers,
220
- # keep the user's, and clear managed_mcp state (mcp_managed becomes []).
221
- return [service.build_action({}, previously_managed=managed)]
219
+ # Empty desired + our previously-managed names → prune only what we wrote
220
+ # (MCP servers and agent-registry entries), keep the user's, and clear the
221
+ # ownership state (managed_entries becomes empty).
222
+ return [
223
+ service.build_action(
224
+ {},
225
+ previously_managed=managed,
226
+ previously_managed_agents=managed_agents,
227
+ )
228
+ ]
229
+
230
+ def _cleanup_claude_projects(self, managed_mcp: dict[str, Any]) -> list[Action]:
231
+ managed = self._names_for_scope(
232
+ managed_mcp, app_scope(AppId.CLAUDE, "projects")
233
+ )
234
+ if not managed:
235
+ return []
236
+ try:
237
+ service = create_registered_app_service(AppId.CLAUDE)
238
+ except (KeyError, ValueError):
239
+ return []
240
+ if not isinstance(service, ClaudeConfigService):
241
+ return []
242
+ if not service.repository.config_path.exists():
243
+ return []
244
+ # Empty desired + our previously-managed project paths → prune only the
245
+ # `mcpServers` sub-keys we wrote, keep the rest of each project entry, and
246
+ # clear the ownership state (managed_entries becomes empty).
247
+ return [
248
+ service.build_project_mcp_action({}, previously_managed_projects=managed)
249
+ ]
250
+
251
+ @staticmethod
252
+ def _names_for_scope(group: dict[str, Any], scope: str) -> set[str]:
253
+ raw = group.get(scope, [])
254
+ if not isinstance(raw, list):
255
+ return set()
256
+ return {item for item in raw if isinstance(item, str)}
222
257
 
223
258
  def list_status_rows(self) -> list[AppStatusRow]:
224
259
  apps = self.load_apps()
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from code_agnostic.agents.claude import claude_agent_target_path
6
6
  from code_agnostic.agents.compilers import ClaudeAgentCompiler
7
7
  from code_agnostic.agents.parser import parse_agent
8
- from code_agnostic.apps.app_id import AppId, app_label
8
+ from code_agnostic.apps.app_id import AppId, app_label, app_scope
9
9
  from code_agnostic.apps.claude.config_repository import ClaudeConfigRepository
10
10
  from code_agnostic.apps.claude.mapper import ClaudeMCPMapper
11
11
  from code_agnostic.apps.common.framework import RegisteredAppConfigService
@@ -72,6 +72,7 @@ class ClaudeConfigService(RegisteredAppConfigService):
72
72
  self,
73
73
  project_servers: dict[Path, dict[str, MCPServerDTO]],
74
74
  base_payload: dict[str, Any] | None = None,
75
+ previously_managed_projects: set[str] | None = None,
75
76
  ) -> Action:
76
77
  existing = self._repository.load_config()
77
78
  if existing or self.repository.config_path.exists():
@@ -86,8 +87,10 @@ class ClaudeConfigService(RegisteredAppConfigService):
86
87
  else:
87
88
  projects = deepcopy(projects)
88
89
 
90
+ desired_keys: set[str] = set()
89
91
  for project_path, servers in project_servers.items():
90
92
  key = str(project_path.resolve())
93
+ desired_keys.add(key)
91
94
  project = projects.get(key)
92
95
  if not isinstance(project, dict):
93
96
  project = {}
@@ -96,10 +99,18 @@ class ClaudeConfigService(RegisteredAppConfigService):
96
99
  project["mcpServers"] = self.mapper.from_common(servers)
97
100
  projects[key] = project
98
101
 
102
+ # Prune the `mcpServers` sub-key from project entries we previously wrote
103
+ # but no longer sync (e.g. a repo removed from a workspace). Leave the rest
104
+ # of the project entry (history etc.) and non-managed projects untouched.
105
+ for stale_key in (previously_managed_projects or set()) - desired_keys:
106
+ stale_project = projects.get(stale_key)
107
+ if isinstance(stale_project, dict):
108
+ stale_project.pop("mcpServers", None)
109
+
99
110
  merged["projects"] = projects
100
111
  self.validate_config(merged)
101
112
 
102
- return Action(
113
+ action = Action(
103
114
  kind=self.action_kind,
104
115
  path=self.repository.config_path,
105
116
  status=self.derive_status(existing, merged),
@@ -107,6 +118,12 @@ class ClaudeConfigService(RegisteredAppConfigService):
107
118
  payload=merged,
108
119
  app=self.app_id.value,
109
120
  )
121
+ # Track which project paths we own so a later source removal prunes only
122
+ # our `mcpServers` sub-keys without touching the user's project entries.
123
+ projects_scope = app_scope(self.app_id, "projects")
124
+ action.scope = projects_scope
125
+ action.managed_entries = {projects_scope: sorted(desired_keys)}
126
+ return action
110
127
 
111
128
  def plan_skill_actions(
112
129
  self,
@@ -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,
@@ -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
- merged["agents"] = self._merge_agents_payload(
149
- merged.get("agents"),
150
- self._build_agent_registry(agent_sources),
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.mcp_managed = sorted(desired_mcp)
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(
@@ -270,6 +270,7 @@ class IAppConfigService(ABC):
270
270
  common_servers: dict[str, MCPServerDTO],
271
271
  agent_sources: list[Path] | None = None,
272
272
  previously_managed: set[str] | None = None,
273
+ previously_managed_agents: set[str] | None = None,
273
274
  *,
274
275
  replace_mcp: bool = False,
275
276
  ) -> Action:
@@ -296,7 +297,9 @@ class IAppConfigService(ABC):
296
297
  # User-shared global config: track which servers we own so a later
297
298
  # source removal can be pruned without touching the user's servers.
298
299
  action.scope = app_scope(self.app_id, "mcp")
299
- action.mcp_managed = sorted(desired_mcp)
300
+ action.managed_entries = {
301
+ app_scope(self.app_id, "mcp"): sorted(desired_mcp)
302
+ }
300
303
  return action
301
304
 
302
305
  def build_plan(
@@ -313,6 +316,9 @@ class IAppConfigService(ABC):
313
316
  previously_managed_mcp = self._load_state_names(
314
317
  managed_mcp_group, app_scope(self.app_id, "mcp")
315
318
  )
319
+ previously_managed_agents = self._load_state_names(
320
+ managed_mcp_group, app_scope(self.app_id, "agents_registry")
321
+ )
316
322
 
317
323
  skill_actions, skill_skipped = self._build_compiled_group(
318
324
  sources=source_repository.list_skill_sources(),
@@ -341,6 +347,7 @@ class IAppConfigService(ABC):
341
347
  common_servers,
342
348
  agent_sources=source_repository.list_agent_sources(),
343
349
  previously_managed=previously_managed_mcp,
350
+ previously_managed_agents=previously_managed_agents,
344
351
  ),
345
352
  *skill_actions,
346
353
  *agent_actions,
@@ -137,6 +137,7 @@ class OpenCodeConfigService(RegisteredAppConfigService):
137
137
  common_servers: dict[str, MCPServerDTO],
138
138
  agent_sources: list[Path] | None = None,
139
139
  previously_managed: set[str] | None = None,
140
+ previously_managed_agents: set[str] | None = None,
140
141
  *,
141
142
  replace_mcp: bool = False,
142
143
  ) -> Action:
@@ -174,7 +175,9 @@ class OpenCodeConfigService(RegisteredAppConfigService):
174
175
  )
175
176
  if not replace_mcp:
176
177
  action.scope = app_scope(self.app_id, "mcp")
177
- action.mcp_managed = sorted(desired_mcp)
178
+ action.managed_entries = {
179
+ app_scope(self.app_id, "mcp"): sorted(desired_mcp)
180
+ }
178
181
  return action
179
182
 
180
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.mcp_managed is not None:
774
- # User-shared MCP config: track the server names we own, never the
775
- # shared config file itself (so cleanup prunes our servers only).
776
- global_mcp_touched.add(action.scope)
777
- if action.mcp_managed:
778
- global_mcp[action.scope] = sorted(set(action.mcp_managed))
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:
@@ -74,7 +74,7 @@ class Action:
74
74
  scope: str | None = None
75
75
  workspace: str | None = None
76
76
  project: str | None = None
77
- mcp_managed: list[str] | None = None
77
+ managed_entries: dict[str, list[str]] | None = None
78
78
 
79
79
 
80
80
  @dataclass
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from code_agnostic.apps.app_id import AppId, app_metadata
3
+ from code_agnostic.apps.app_id import AppId, app_metadata, app_scope
4
4
  from code_agnostic.apps.common.compiled_planning import (
5
5
  plan_owned_compiled_text_action,
6
6
  )
@@ -283,15 +283,29 @@ class SyncPlanner:
283
283
  if existing_action is not None and isinstance(existing_action.payload, dict)
284
284
  else None
285
285
  )
286
+ state = self.core.load_state()
287
+ managed_mcp = state.get("managed_mcp")
288
+ if not isinstance(managed_mcp, dict):
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()
295
+ )
286
296
  project_action = claude_service.build_project_mcp_action(
287
297
  self._claude_project_mcp,
288
298
  base_payload=base_payload,
299
+ previously_managed_projects=previously_managed_projects,
289
300
  )
290
301
  if existing_action is not None:
291
302
  # Carry global MCP ownership tracking onto the merged Claude action so
292
- # top-level `mcpServers` orphans still get pruned on later applies.
293
- project_action.scope = existing_action.scope
294
- project_action.mcp_managed = existing_action.mcp_managed
303
+ # top-level `mcpServers` orphans still get pruned on later applies. The
304
+ # project-path ownership set by build_project_mcp_action is preserved.
305
+ project_action.scope = existing_action.scope or project_action.scope
306
+ merged_entries = dict(existing_action.managed_entries or {})
307
+ merged_entries.update(project_action.managed_entries or {})
308
+ project_action.managed_entries = merged_entries
295
309
  actions = [
296
310
  action
297
311
  for action in plan.actions
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-agnostic
3
- Version: 0.5.1
3
+ Version: 0.7.0
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
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-agnostic"
7
- version = "0.5.1"
7
+ version = "0.7.0"
8
8
  description = "Centralized hub for LLM coding config: MCP, skills, rules, and agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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"]
@@ -6,6 +6,7 @@ import json
6
6
  from pathlib import Path
7
7
 
8
8
  from code_agnostic.__main__ import cli
9
+ from code_agnostic.core.repository import CoreRepository
9
10
 
10
11
 
11
12
  def _write_skill(core_root: Path, name: str) -> None:
@@ -67,3 +68,50 @@ def test_disable_cursor_removes_skill_and_prunes_owned_mcp(
67
68
  # Flag is off.
68
69
  apps_cfg = json.loads((core_root / "config" / "apps.json").read_text())
69
70
  assert apps_cfg["cursor"] is False
71
+
72
+
73
+ def test_disable_claude_prunes_project_mcp_keeps_user_project(
74
+ minimal_shared_config: Path,
75
+ core_root: Path,
76
+ tmp_path: Path,
77
+ cli_runner,
78
+ enable_app,
79
+ ) -> None:
80
+ enable_app("claude")
81
+ workspace_root = tmp_path / "workspace"
82
+ (workspace_root / "repo-a" / ".git").mkdir(parents=True)
83
+
84
+ core = CoreRepository(core_root)
85
+ core.add_workspace("myws", workspace_root)
86
+ (core.workspace_config_dir("myws") / "mcp.base.json").write_text(
87
+ json.dumps({"mcpServers": {"ws-server": {"url": "https://example.com/mcp"}}}),
88
+ encoding="utf-8",
89
+ )
90
+
91
+ # User's own project entry — must survive disable untouched.
92
+ config_path = tmp_path / ".claude.json"
93
+ user_project = str((tmp_path / "user-repo").resolve())
94
+ config_path.write_text(
95
+ json.dumps(
96
+ {"projects": {user_project: {"mcpServers": {"personal": {"type": "http"}}}}}
97
+ ),
98
+ encoding="utf-8",
99
+ )
100
+
101
+ assert cli_runner.invoke(cli, ["apply", "-a", "claude"]).exit_code == 0
102
+
103
+ projects = json.loads(config_path.read_text())["projects"]
104
+ repo_key = str((workspace_root / "repo-a").resolve())
105
+ assert "mcpServers" in projects[repo_key]
106
+ state = json.loads((core_root / ".sync-state.json").read_text())
107
+ assert repo_key in state["managed_mcp"]["app:claude:projects"]
108
+
109
+ # Disable → our project mcpServers pruned, user's project kept.
110
+ assert cli_runner.invoke(cli, ["apps", "disable", "-a", "claude"]).exit_code == 0
111
+
112
+ projects = json.loads(config_path.read_text())["projects"]
113
+ assert "mcpServers" not in projects.get(repo_key, {})
114
+ assert projects[user_project] == {"mcpServers": {"personal": {"type": "http"}}}
115
+
116
+ state = json.loads((core_root / ".sync-state.json").read_text())
117
+ assert "app:claude:projects" not in state.get("managed_mcp", {})
@@ -1,5 +1,7 @@
1
1
  """Tests for workspace-level config sync (MCP, skills, agents, rules)."""
2
2
 
3
+ import json
4
+ import shutil
3
5
  from pathlib import Path
4
6
 
5
7
  try:
@@ -747,6 +749,81 @@ def test_workspace_claude_mcp_merges_project_entries_into_home_config(
747
749
  }
748
750
 
749
751
 
752
+ def test_workspace_claude_removed_repo_prunes_project_mcp_keeps_others(
753
+ minimal_shared_config: Path,
754
+ core_root: Path,
755
+ tmp_path: Path,
756
+ write_json,
757
+ ) -> None:
758
+ """P1: a repo removed from a workspace has its `projects.<path>.mcpServers`
759
+ pruned from ~/.claude.json on the next apply, while the other repo, the
760
+ workspace root, and a user-added project entry are all preserved."""
761
+ workspace_root = tmp_path / "workspace"
762
+ workspace_root.mkdir()
763
+ repo_a = workspace_root / "repo-a"
764
+ repo_b = workspace_root / "repo-b"
765
+ (repo_a / ".git").mkdir(parents=True)
766
+ (repo_b / ".git").mkdir(parents=True)
767
+
768
+ core = CoreRepository(core_root)
769
+ core.add_workspace("myws", workspace_root)
770
+
771
+ ws_config = core.workspace_config_dir("myws")
772
+ write_json(
773
+ ws_config / "mcp.base.json",
774
+ {"mcpServers": {"ws-server": {"url": "https://example.com/mcp"}}},
775
+ )
776
+
777
+ # User has their own project entry in ~/.claude.json that we must never touch.
778
+ user_project = str((tmp_path / "user-repo").resolve())
779
+ config_path = tmp_path / ".claude.json"
780
+ write_json(
781
+ config_path,
782
+ {"projects": {user_project: {"mcpServers": {"personal": {"type": "http"}}}}},
783
+ )
784
+
785
+ def _apply() -> dict:
786
+ plan = SyncPlanner(
787
+ core=core,
788
+ app_services=[_claude_service(tmp_path / ".claude")],
789
+ ).build()
790
+ _applied, failed, failures = SyncExecutor(core=core).execute(plan)
791
+ assert failed == 0, failures
792
+ return json.loads(config_path.read_text(encoding="utf-8"))
793
+
794
+ payload = _apply()
795
+ projects = payload["projects"]
796
+ assert str(workspace_root.resolve()) in projects
797
+ assert "mcpServers" in projects[str(repo_a.resolve())]
798
+ assert "mcpServers" in projects[str(repo_b.resolve())]
799
+ assert user_project in projects
800
+
801
+ state = json.loads((core_root / ".sync-state.json").read_text(encoding="utf-8"))
802
+ assert set(state["managed_mcp"]["app:claude:projects"]) == {
803
+ str(workspace_root.resolve()),
804
+ str(repo_a.resolve()),
805
+ str(repo_b.resolve()),
806
+ }
807
+
808
+ # Remove repo-a from the workspace and re-apply.
809
+ shutil.rmtree(repo_a)
810
+ payload = _apply()
811
+ projects = payload["projects"]
812
+
813
+ assert "mcpServers" not in projects.get(
814
+ str(repo_a.resolve()), {}
815
+ ), "removed repo's mcpServers must be pruned"
816
+ assert "mcpServers" in projects[str(repo_b.resolve())]
817
+ assert "mcpServers" in projects[str(workspace_root.resolve())]
818
+ assert projects[user_project] == {"mcpServers": {"personal": {"type": "http"}}}
819
+
820
+ state = json.loads((core_root / ".sync-state.json").read_text(encoding="utf-8"))
821
+ assert set(state["managed_mcp"]["app:claude:projects"]) == {
822
+ str(workspace_root.resolve()),
823
+ str(repo_b.resolve()),
824
+ }
825
+
826
+
750
827
  def test_workspace_targeted_plan_does_not_cleanup_other_app_scopes(
751
828
  minimal_shared_config: Path,
752
829
  core_root: Path,
File without changes
File without changes