code-agnostic 0.3.15__tar.gz → 0.3.17__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 (173) hide show
  1. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/PKG-INFO +39 -30
  2. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/README.md +38 -29
  3. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/__init__.py +1 -1
  4. code_agnostic-0.3.17/code_agnostic/agents/compilers.py +176 -0
  5. code_agnostic-0.3.17/code_agnostic/agents/copilot.py +117 -0
  6. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/parser.py +1 -1
  7. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/app_id.py +14 -0
  8. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/mapper.py +28 -2
  9. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/schema.json +72 -0
  10. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/loader.py +1 -0
  11. code_agnostic-0.3.17/code_agnostic/apps/copilot/config_repository.py +65 -0
  12. code_agnostic-0.3.17/code_agnostic/apps/copilot/mapper.py +97 -0
  13. code_agnostic-0.3.17/code_agnostic/apps/copilot/service.py +140 -0
  14. code_agnostic-0.3.17/code_agnostic/apps/opencode/__init__.py +1 -0
  15. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/agents.py +6 -1
  16. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/apply.py +9 -6
  17. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/rules.py +6 -1
  18. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/skills.py +63 -13
  19. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/status.py +1 -1
  20. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/helpers.py +7 -0
  21. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/constants.py +4 -0
  22. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/core/repository.py +24 -8
  23. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/imports/adapters.py +12 -0
  24. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/imports/service.py +75 -0
  25. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/lossiness.py +32 -12
  26. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/models.py +3 -0
  27. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/planner.py +19 -0
  28. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/project_artifacts.py +3 -0
  29. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/skills/compilers.py +12 -0
  30. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/skills/parser.py +1 -1
  31. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/spec/loaders.py +2 -2
  32. code_agnostic-0.3.17/code_agnostic/spec/schemas/agent.v1.schema.json +148 -0
  33. code_agnostic-0.3.17/code_agnostic/spec/schemas/rule.v1.schema.json +49 -0
  34. code_agnostic-0.3.17/code_agnostic/spec/schemas/skill.v1.schema.json +78 -0
  35. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/status.py +2 -1
  36. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/renderers.py +57 -13
  37. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/tables.py +3 -1
  38. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/workspace_artifacts.py +17 -0
  39. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/PKG-INFO +39 -30
  40. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/SOURCES.txt +5 -0
  41. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/pyproject.toml +1 -1
  42. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_agents.py +20 -0
  43. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_apply_apps.py +252 -0
  44. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_apps.py +5 -3
  45. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_explain_lossiness.py +71 -0
  46. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_import.py +68 -0
  47. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_rules.py +16 -0
  48. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_skills.py +123 -0
  49. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_status.py +26 -0
  50. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_workspaces.py +41 -0
  51. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_common_repository.py +3 -4
  52. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_project_config_sync.py +31 -0
  53. code_agnostic-0.3.15/code_agnostic/agents/compilers.py +0 -45
  54. code_agnostic-0.3.15/code_agnostic/spec/schemas/agent.v1.schema.json +0 -70
  55. code_agnostic-0.3.15/code_agnostic/spec/schemas/rule.v1.schema.json +0 -22
  56. code_agnostic-0.3.15/code_agnostic/spec/schemas/skill.v1.schema.json +0 -33
  57. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/LICENSE +0 -0
  58. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/__main__.py +0 -0
  59. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/__init__.py +0 -0
  60. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/claude.py +0 -0
  61. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/codex.py +0 -0
  62. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/models.py +0 -0
  63. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/agents/opencode.py +0 -0
  64. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/__init__.py +0 -0
  65. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/apps_service.py +0 -0
  66. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/claude/__init__.py +0 -0
  67. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/claude/config_repository.py +0 -0
  68. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/claude/mapper.py +0 -0
  69. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/claude/service.py +0 -0
  70. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/__init__.py +0 -0
  71. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/config_repository.py +0 -0
  72. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/schema_repository.py +0 -0
  73. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/codex/service.py +0 -0
  74. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/__init__.py +0 -0
  75. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/compiled_planning.py +0 -0
  76. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/framework.py +0 -0
  77. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
  78. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
  79. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
  80. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/interfaces/service.py +0 -0
  81. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/models.py +0 -0
  82. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/schema.py +0 -0
  83. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/symlink_planning.py +0 -0
  84. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/common/utils.py +0 -0
  85. {code_agnostic-0.3.15/code_agnostic/apps/cursor → code_agnostic-0.3.17/code_agnostic/apps/copilot}/__init__.py +0 -0
  86. {code_agnostic-0.3.15/code_agnostic/apps/opencode → code_agnostic-0.3.17/code_agnostic/apps/cursor}/__init__.py +0 -0
  87. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/cursor/config_repository.py +0 -0
  88. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/cursor/mapper.py +0 -0
  89. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/cursor/schema.json +0 -0
  90. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/cursor/schema_repository.py +0 -0
  91. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/cursor/service.py +0 -0
  92. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/opencode/config_repository.py +0 -0
  93. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/opencode/mapper.py +0 -0
  94. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/opencode/schema.json +0 -0
  95. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/opencode/schema_repository.py +0 -0
  96. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/apps/opencode/service.py +0 -0
  97. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/__init__.py +0 -0
  98. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/aliases.py +0 -0
  99. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/__init__.py +0 -0
  100. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/apps.py +0 -0
  101. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
  102. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/import_.py +0 -0
  103. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/mcp.py +0 -0
  104. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/plan.py +0 -0
  105. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/projects.py +0 -0
  106. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/restore.py +0 -0
  107. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/validate.py +0 -0
  108. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/commands/workspaces.py +0 -0
  109. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/cli/options.py +0 -0
  110. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/core/__init__.py +0 -0
  111. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/core/project_repository.py +0 -0
  112. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/core/workspace_repository.py +0 -0
  113. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/errors.py +0 -0
  114. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/executor.py +0 -0
  115. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/generated_artifacts.py +0 -0
  116. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/git_exclude_service.py +0 -0
  117. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/imports/__init__.py +0 -0
  118. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/imports/filesystem.py +0 -0
  119. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/imports/models.py +0 -0
  120. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/mcp_service.py +0 -0
  121. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/rules/__init__.py +0 -0
  122. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/rules/compilers.py +0 -0
  123. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/rules/models.py +0 -0
  124. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/rules/parser.py +0 -0
  125. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/rules/repository.py +0 -0
  126. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/skills/__init__.py +0 -0
  127. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/skills/install_sources.py +0 -0
  128. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/skills/models.py +0 -0
  129. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/spec/__init__.py +0 -0
  130. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
  131. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
  132. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/__init__.py +0 -0
  133. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/enums.py +0 -0
  134. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/import_selector.py +0 -0
  135. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/tui/sections.py +0 -0
  136. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/utils.py +0 -0
  137. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/validation.py +0 -0
  138. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic/workspaces.py +0 -0
  139. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/dependency_links.txt +0 -0
  140. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/entry_points.txt +0 -0
  141. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/requires.txt +0 -0
  142. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/code_agnostic.egg-info/top_level.txt +0 -0
  143. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/setup.cfg +0 -0
  144. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_aliases.py +0 -0
  145. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_apply_codex.py +0 -0
  146. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_apply_cursor.py +0 -0
  147. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_apply_target.py +0 -0
  148. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_flags.py +0 -0
  149. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_git_exclude.py +0 -0
  150. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_import_interactive.py +0 -0
  151. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_mcp.py +0 -0
  152. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_module_organization.py +0 -0
  153. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_plan.py +0 -0
  154. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_projects.py +0 -0
  155. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_restore.py +0 -0
  156. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_validate.py +0 -0
  157. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_cli_workspace_resolution.py +0 -0
  158. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_common_mcp_to_dto.py +0 -0
  159. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_compiled_planning.py +0 -0
  160. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_dto_to_common_mcp.py +0 -0
  161. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_git_exclude_service.py +0 -0
  162. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_mcp_service.py +0 -0
  163. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_planner_executor.py +0 -0
  164. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_planner_rules.py +0 -0
  165. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_skill_install_sources.py +0 -0
  166. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_symlink_planning.py +0 -0
  167. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_sync_plan_model.py +0 -0
  168. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_transactional_executor.py +0 -0
  169. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_utils.py +0 -0
  170. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_version.py +0 -0
  171. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_workspace_config_sync.py +0 -0
  172. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_workspace_repo_status.py +0 -0
  173. {code_agnostic-0.3.15 → code_agnostic-0.3.17}/tests/test_workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-agnostic
3
- Version: 0.3.15
3
+ Version: 0.3.17
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
@@ -56,9 +56,10 @@ AI coding tools each want config in a different place and format. When you use m
56
56
  ~/.cursor/ Compiled & synced for Cursor
57
57
  ~/.codex/ Compiled & synced for Codex (or CODEX_HOME)
58
58
  ~/.claude.json and ~/.claude/ Compiled & synced for Claude Code
59
+ ~/.copilot/ and repo .github/ Compiled & synced for GitHub Copilot
59
60
  ```
60
61
 
61
- Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex, and `CLAUDE.local.md` memory for Claude Code.
62
+ Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex/Copilot, and `CLAUDE.local.md` memory for Claude Code.
62
63
 
63
64
  Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are still supported for migration, but bundle directories are the preferred source format for new config.
64
65
 
@@ -77,8 +78,9 @@ Today the implementation is still mixed: some assets are compiled and some are s
77
78
 
78
79
  Workspace sync may generate repo-local outputs, but those outputs are not
79
80
  source. Project-local skill folders generated inside a repo, such as
80
- `.agents/skills` or `.opencode/skills`, are also generated outputs; install
81
- skills into managed project source first, then run `plan` / `apply`.
81
+ `.agents/skills`, `.opencode/skills`, or `.github/skills`, are also generated
82
+ outputs; install skills into managed project source first, then run `plan` /
83
+ `apply`.
82
84
 
83
85
  ## Install
84
86
 
@@ -114,6 +116,7 @@ code-agnostic import apply -a codex
114
116
  code-agnostic apps enable -a cursor
115
117
  code-agnostic apps enable -a opencode
116
118
  code-agnostic apps enable -a claude
119
+ code-agnostic apps enable -a copilot
117
120
 
118
121
  # Preview and apply
119
122
  code-agnostic validate
@@ -123,19 +126,19 @@ code-agnostic apply
123
126
 
124
127
  ## Editor compatibility
125
128
 
126
- | Feature | OpenCode | Cursor | Codex | Claude Code |
127
- |---------|:--------:|:------:|:-----:|:-----------:|
128
- | MCP sync | yes | yes | yes | yes |
129
- | Rules sync (cross-compiled) | yes | yes | yes | yes |
130
- | Skills sync | yes | yes | yes | yes |
131
- | Agents sync | yes | yes | yes | yes |
132
- | Workspace root `AGENTS.md` link | yes | yes | yes | yes |
133
- | Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
134
- | Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
135
- | Nested `AGENTS.md` discovery | -- | yes | yes | -- |
136
- | Workspace propagation | yes | yes | yes | yes |
137
- | Import from | yes | yes | yes | yes |
138
- | Interactive import (TUI) | yes | yes | yes | yes |
129
+ | Feature | OpenCode | Cursor | Codex | Claude Code | GitHub Copilot |
130
+ |---------|:--------:|:------:|:-----:|:-----------:|:--------------:|
131
+ | MCP sync | yes | yes | yes | yes | yes |
132
+ | Rules sync (cross-compiled) | yes | yes | yes | yes | yes |
133
+ | Skills sync | yes | yes | yes | yes | yes |
134
+ | Agents sync | yes | yes | yes | yes | yes |
135
+ | Workspace root `AGENTS.md` link | yes | yes | yes | yes | yes |
136
+ | Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- | -- |
137
+ | Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes | yes |
138
+ | Nested `AGENTS.md` discovery | -- | yes | yes | -- | yes |
139
+ | Workspace propagation | yes | yes | yes | yes | yes |
140
+ | Import from | yes | yes | yes | yes | yes |
141
+ | Interactive import (TUI) | yes | yes | yes | yes | yes |
139
142
 
140
143
  `yes` means the resource type is synced for that editor. Some metadata is still
141
144
  target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
@@ -143,7 +146,7 @@ that are omitted or rejected for a selected target.
143
146
 
144
147
  Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
145
148
 
146
- OpenCode workspace configs write project-root `opencode.json` files that include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
149
+ OpenCode workspace configs write project-root `opencode.json` files that include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`. Copilot repo-shared MCP, skills, and agents are written under `.github/`.
147
150
 
148
151
  Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
149
152
 
@@ -157,14 +160,13 @@ Plan-then-apply workflow. Preview every change before it touches disk.
157
160
  code-agnostic validate # check canonical source files
158
161
  code-agnostic plan -a cursor # dry-run for one editor
159
162
  code-agnostic plan # dry-run for all
160
- code-agnostic apply # apply changes for all enabled editors
161
163
  code-agnostic status # check drift and disabled app states
162
164
  code-agnostic explain-lossiness # show fields omitted or rejected per editor
163
165
  ```
164
166
 
165
167
  Bare `plan` and `apply` target every enabled editor; bare `status` also shows
166
- disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, or `-a claude`
167
- when you want one editor at a time.
168
+ disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, `-a claude`, or
169
+ `-a copilot` when you want one editor at a time.
168
170
 
169
171
  If managed outputs need repair after an apply, restore the active synced revision:
170
172
 
@@ -264,14 +266,19 @@ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
264
266
  skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
265
267
  inspected with `code-agnostic skills list -w <name>`. Project-local skills live
266
268
  under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
267
- the registered project directory by `plan` / `apply`. Codex generated skill
268
- outputs are written to `~/.agents/skills`, while Codex agents and config remain
269
- under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
270
- skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
271
- with workspace/project copies under repo-local `.claude/skills` and
272
- `.claude/agents`.
273
-
274
- If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
269
+ the registered project directory by `plan` / `apply`. The current first-class
270
+ list/remove commands cover global and workspace scopes; project skill inventory
271
+ and removal are not supported yet, and `code-agnostic projects list` only shows
272
+ a coarse `[skills]` marker today. Codex generated skill outputs are written to
273
+ `~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
274
+ set, defaulting to `~/.codex`. Claude Code generated skills and agents are
275
+ written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
276
+ copies under repo-local `.claude/skills` and `.claude/agents`. GitHub Copilot
277
+ generated skills and agents are written under `~/.copilot/skills` and
278
+ `~/.copilot/agents`, with repo-shared copies under `.github/skills` and
279
+ `.github/agents`.
280
+
281
+ If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, `.claude/skills`, or `.github/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
275
282
 
276
283
  Planned convenience command:
277
284
 
@@ -285,7 +292,7 @@ first implementation slice.
285
292
 
286
293
  ### Workspaces
287
294
 
288
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
295
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Copilot receives repo-shared `.github/mcp.json`, `.github/skills`, and `.github/agents`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, Claude, and Copilot; user-created target app skill folders remain unmanaged unless they were generated from managed source.
289
296
 
290
297
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
291
298
 
@@ -313,6 +320,7 @@ Migrate existing config from any supported editor into the hub.
313
320
  code-agnostic import plan -a codex
314
321
  code-agnostic import apply -a codex
315
322
  code-agnostic import plan -a claude
323
+ code-agnostic import plan -a copilot
316
324
  code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
317
325
  code-agnostic import plan -a codex -i # interactive TUI picker
318
326
  ```
@@ -355,6 +363,7 @@ The compiler migration is documented in:
355
363
  - [x] Interactive TUI for import selection
356
364
  - [x] Claude Code support
357
365
  - [x] Project-scoped skill installs and sync
366
+ - [ ] Project-scoped skill inventory and remove commands
358
367
  - [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
359
368
  - [ ] Shell auto-complete
360
369
  - [ ] Full TUI mode (command palette + menus)
@@ -31,9 +31,10 @@ AI coding tools each want config in a different place and format. When you use m
31
31
  ~/.cursor/ Compiled & synced for Cursor
32
32
  ~/.codex/ Compiled & synced for Codex (or CODEX_HOME)
33
33
  ~/.claude.json and ~/.claude/ Compiled & synced for Claude Code
34
+ ~/.copilot/ and repo .github/ Compiled & synced for GitHub Copilot
34
35
  ```
35
36
 
36
- Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex, and `CLAUDE.local.md` memory for Claude Code.
37
+ Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex/Copilot, and `CLAUDE.local.md` memory for Claude Code.
37
38
 
38
39
  Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are still supported for migration, but bundle directories are the preferred source format for new config.
39
40
 
@@ -52,8 +53,9 @@ Today the implementation is still mixed: some assets are compiled and some are s
52
53
 
53
54
  Workspace sync may generate repo-local outputs, but those outputs are not
54
55
  source. Project-local skill folders generated inside a repo, such as
55
- `.agents/skills` or `.opencode/skills`, are also generated outputs; install
56
- skills into managed project source first, then run `plan` / `apply`.
56
+ `.agents/skills`, `.opencode/skills`, or `.github/skills`, are also generated
57
+ outputs; install skills into managed project source first, then run `plan` /
58
+ `apply`.
57
59
 
58
60
  ## Install
59
61
 
@@ -89,6 +91,7 @@ code-agnostic import apply -a codex
89
91
  code-agnostic apps enable -a cursor
90
92
  code-agnostic apps enable -a opencode
91
93
  code-agnostic apps enable -a claude
94
+ code-agnostic apps enable -a copilot
92
95
 
93
96
  # Preview and apply
94
97
  code-agnostic validate
@@ -98,19 +101,19 @@ code-agnostic apply
98
101
 
99
102
  ## Editor compatibility
100
103
 
101
- | Feature | OpenCode | Cursor | Codex | Claude Code |
102
- |---------|:--------:|:------:|:-----:|:-----------:|
103
- | MCP sync | yes | yes | yes | yes |
104
- | Rules sync (cross-compiled) | yes | yes | yes | yes |
105
- | Skills sync | yes | yes | yes | yes |
106
- | Agents sync | yes | yes | yes | yes |
107
- | Workspace root `AGENTS.md` link | yes | yes | yes | yes |
108
- | Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
109
- | Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
110
- | Nested `AGENTS.md` discovery | -- | yes | yes | -- |
111
- | Workspace propagation | yes | yes | yes | yes |
112
- | Import from | yes | yes | yes | yes |
113
- | Interactive import (TUI) | yes | yes | yes | yes |
104
+ | Feature | OpenCode | Cursor | Codex | Claude Code | GitHub Copilot |
105
+ |---------|:--------:|:------:|:-----:|:-----------:|:--------------:|
106
+ | MCP sync | yes | yes | yes | yes | yes |
107
+ | Rules sync (cross-compiled) | yes | yes | yes | yes | yes |
108
+ | Skills sync | yes | yes | yes | yes | yes |
109
+ | Agents sync | yes | yes | yes | yes | yes |
110
+ | Workspace root `AGENTS.md` link | yes | yes | yes | yes | yes |
111
+ | Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- | -- |
112
+ | Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes | yes |
113
+ | Nested `AGENTS.md` discovery | -- | yes | yes | -- | yes |
114
+ | Workspace propagation | yes | yes | yes | yes | yes |
115
+ | Import from | yes | yes | yes | yes | yes |
116
+ | Interactive import (TUI) | yes | yes | yes | yes | yes |
114
117
 
115
118
  `yes` means the resource type is synced for that editor. Some metadata is still
116
119
  target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
@@ -118,7 +121,7 @@ that are omitted or rejected for a selected target.
118
121
 
119
122
  Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
120
123
 
121
- OpenCode workspace configs write project-root `opencode.json` files that include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
124
+ OpenCode workspace configs write project-root `opencode.json` files that include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`. Copilot repo-shared MCP, skills, and agents are written under `.github/`.
122
125
 
123
126
  Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
124
127
 
@@ -132,14 +135,13 @@ Plan-then-apply workflow. Preview every change before it touches disk.
132
135
  code-agnostic validate # check canonical source files
133
136
  code-agnostic plan -a cursor # dry-run for one editor
134
137
  code-agnostic plan # dry-run for all
135
- code-agnostic apply # apply changes for all enabled editors
136
138
  code-agnostic status # check drift and disabled app states
137
139
  code-agnostic explain-lossiness # show fields omitted or rejected per editor
138
140
  ```
139
141
 
140
142
  Bare `plan` and `apply` target every enabled editor; bare `status` also shows
141
- disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, or `-a claude`
142
- when you want one editor at a time.
143
+ disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, `-a claude`, or
144
+ `-a copilot` when you want one editor at a time.
143
145
 
144
146
  If managed outputs need repair after an apply, restore the active synced revision:
145
147
 
@@ -239,14 +241,19 @@ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
239
241
  skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
240
242
  inspected with `code-agnostic skills list -w <name>`. Project-local skills live
241
243
  under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
242
- the registered project directory by `plan` / `apply`. Codex generated skill
243
- outputs are written to `~/.agents/skills`, while Codex agents and config remain
244
- under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
245
- skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
246
- with workspace/project copies under repo-local `.claude/skills` and
247
- `.claude/agents`.
248
-
249
- If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
244
+ the registered project directory by `plan` / `apply`. The current first-class
245
+ list/remove commands cover global and workspace scopes; project skill inventory
246
+ and removal are not supported yet, and `code-agnostic projects list` only shows
247
+ a coarse `[skills]` marker today. Codex generated skill outputs are written to
248
+ `~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
249
+ set, defaulting to `~/.codex`. Claude Code generated skills and agents are
250
+ written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
251
+ copies under repo-local `.claude/skills` and `.claude/agents`. GitHub Copilot
252
+ generated skills and agents are written under `~/.copilot/skills` and
253
+ `~/.copilot/agents`, with repo-shared copies under `.github/skills` and
254
+ `.github/agents`.
255
+
256
+ If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, `.claude/skills`, or `.github/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
250
257
 
251
258
  Planned convenience command:
252
259
 
@@ -260,7 +267,7 @@ first implementation slice.
260
267
 
261
268
  ### Workspaces
262
269
 
263
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
270
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Copilot receives repo-shared `.github/mcp.json`, `.github/skills`, and `.github/agents`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, Claude, and Copilot; user-created target app skill folders remain unmanaged unless they were generated from managed source.
264
271
 
265
272
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
266
273
 
@@ -288,6 +295,7 @@ Migrate existing config from any supported editor into the hub.
288
295
  code-agnostic import plan -a codex
289
296
  code-agnostic import apply -a codex
290
297
  code-agnostic import plan -a claude
298
+ code-agnostic import plan -a copilot
291
299
  code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
292
300
  code-agnostic import plan -a codex -i # interactive TUI picker
293
301
  ```
@@ -330,6 +338,7 @@ The compiler migration is documented in:
330
338
  - [x] Interactive TUI for import selection
331
339
  - [x] Claude Code support
332
340
  - [x] Project-scoped skill installs and sync
341
+ - [ ] Project-scoped skill inventory and remove commands
333
342
  - [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
334
343
  - [ ] Shell auto-complete
335
344
  - [ ] Full TUI mode (command palette + menus)
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.3.15"
3
+ __version__ = "0.3.17"
@@ -0,0 +1,176 @@
1
+ """Per-editor agent compilers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+ from code_agnostic.agents.claude import serialize_claude_agent
11
+ from code_agnostic.agents.codex import serialize_codex_agent
12
+ from code_agnostic.agents.models import Agent
13
+ from code_agnostic.agents.opencode import serialize_opencode_agent
14
+ from code_agnostic.errors import InvalidConfigSchemaError
15
+
16
+ _CURSOR_AGENT_FRONTMATTER_KEYS = frozenset(
17
+ {"name", "description", "model", "readonly", "is_background"}
18
+ )
19
+ _COPILOT_AGENT_FRONTMATTER_KEYS = frozenset(
20
+ {
21
+ "name",
22
+ "description",
23
+ "model",
24
+ "target",
25
+ "disable-model-invocation",
26
+ "user-invocable",
27
+ "infer",
28
+ "mcp-servers",
29
+ "metadata",
30
+ }
31
+ )
32
+
33
+
34
+ class IAgentCompiler(ABC):
35
+ @abstractmethod
36
+ def compile(self, agent: Agent) -> str:
37
+ """Return compiled agent content for target editor."""
38
+
39
+
40
+ class OpenCodeAgentCompiler(IAgentCompiler):
41
+ """Cross-compile for OpenCode agents."""
42
+
43
+ def compile(self, agent: Agent) -> str:
44
+ return serialize_opencode_agent(agent)
45
+
46
+
47
+ class CursorAgentCompiler(IAgentCompiler):
48
+ """Cross-compile for Cursor subagents."""
49
+
50
+ def compile(self, agent: Agent) -> str:
51
+ return _serialize_cursor_agent(agent)
52
+
53
+
54
+ def _serialize_cursor_agent(agent: Agent) -> str:
55
+ overrides = dict(agent.metadata.app_overrides.get("cursor", {}))
56
+ fm: dict[str, Any] = {}
57
+ if agent.metadata.name:
58
+ fm["name"] = agent.metadata.name
59
+ if agent.metadata.description:
60
+ fm["description"] = agent.metadata.description
61
+
62
+ model = overrides.pop("model", agent.metadata.model)
63
+ if model:
64
+ fm["model"] = model
65
+
66
+ if agent.metadata.tools.write is False:
67
+ fm["readonly"] = True
68
+
69
+ for key in sorted(overrides):
70
+ if key not in _CURSOR_AGENT_FRONTMATTER_KEYS:
71
+ allowed = ", ".join(sorted(_CURSOR_AGENT_FRONTMATTER_KEYS))
72
+ raise InvalidConfigSchemaError(
73
+ agent.source_path,
74
+ f"x-cursor.{key} is not supported in cursor agent "
75
+ f"frontmatter; allowed keys: {allowed}",
76
+ )
77
+ fm[key] = overrides[key]
78
+
79
+ parts: list[str] = []
80
+ if fm:
81
+ parts.append("---")
82
+ parts.append(yaml.dump(fm, default_flow_style=False, sort_keys=False).rstrip())
83
+ parts.append("---")
84
+ parts.append("")
85
+ parts.append(agent.content)
86
+ return "\n".join(parts)
87
+
88
+
89
+ class CodexAgentCompiler(IAgentCompiler):
90
+ """Cross-compile for Codex subagents."""
91
+
92
+ def compile(self, agent: Agent) -> str:
93
+ return serialize_codex_agent(agent)
94
+
95
+
96
+ class CopilotAgentCompiler(IAgentCompiler):
97
+ """Cross-compile for GitHub Copilot custom agents."""
98
+
99
+ def compile(self, agent: Agent) -> str:
100
+ return _serialize_copilot_agent(agent)
101
+
102
+
103
+ def _serialize_copilot_agent(agent: Agent) -> str:
104
+ overrides = dict(agent.metadata.app_overrides.get("copilot", {}))
105
+ fm: dict[str, Any] = {}
106
+
107
+ name = overrides.pop("name", agent.metadata.name or agent.name)
108
+ if name:
109
+ fm["name"] = name
110
+
111
+ description = overrides.pop("description", agent.metadata.description)
112
+ if not description:
113
+ raise InvalidConfigSchemaError(
114
+ agent.source_path,
115
+ "Copilot agents require a description",
116
+ )
117
+ fm["description"] = description
118
+
119
+ model = overrides.pop("model", agent.metadata.model)
120
+ if model:
121
+ fm["model"] = model
122
+
123
+ tools = _copilot_tools(agent)
124
+ if tools is not None:
125
+ fm["tools"] = tools
126
+
127
+ for key in sorted(overrides):
128
+ if key not in _COPILOT_AGENT_FRONTMATTER_KEYS:
129
+ allowed = ", ".join(sorted(_COPILOT_AGENT_FRONTMATTER_KEYS))
130
+ raise InvalidConfigSchemaError(
131
+ agent.source_path,
132
+ f"x-copilot.{key} is not supported in copilot agent "
133
+ f"frontmatter; allowed keys: {allowed}",
134
+ )
135
+ if key in fm:
136
+ continue
137
+ fm[key] = overrides[key]
138
+
139
+ parts: list[str] = []
140
+ if fm:
141
+ parts.append("---")
142
+ parts.append(yaml.dump(fm, default_flow_style=False, sort_keys=False).rstrip())
143
+ parts.append("---")
144
+ parts.append("")
145
+ parts.append(agent.content)
146
+ return "\n".join(parts)
147
+
148
+
149
+ def _copilot_tools(agent: Agent) -> list[str] | None:
150
+ permissions = agent.metadata.tools
151
+ if permissions.read is True and permissions.write is True and not permissions.mcp:
152
+ return None
153
+
154
+ tools: list[str] = []
155
+ if permissions.read:
156
+ tools.append("read")
157
+ if permissions.write:
158
+ tools.append("edit")
159
+
160
+ for item in agent.metadata.tools.mcp:
161
+ server = item.get("server")
162
+ tool = item.get("tool")
163
+ if not server:
164
+ raise InvalidConfigSchemaError(
165
+ agent.source_path,
166
+ "Copilot agent MCP tools require a server",
167
+ )
168
+ tools.append(f"{server}/{tool}" if tool else f"{server}/*")
169
+ return tools
170
+
171
+
172
+ class ClaudeAgentCompiler(IAgentCompiler):
173
+ """Cross-compile for Claude Code subagents."""
174
+
175
+ def compile(self, agent: Agent) -> str:
176
+ return serialize_claude_agent(agent)
@@ -0,0 +1,117 @@
1
+ """GitHub Copilot custom agent Markdown conversion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import yaml
11
+
12
+ from code_agnostic.agents.models import Agent, AgentMetadata, AgentToolPermissions
13
+
14
+ _FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
15
+ _READ_ALIASES = {"read", "notebookread", "view"}
16
+ _WRITE_ALIASES = {"edit", "multiedit", "write", "notebookedit"}
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class CopilotAgentParseResult:
21
+ agent: Agent
22
+ warnings: list[str]
23
+
24
+
25
+ def parse_copilot_agent(path: Path) -> CopilotAgentParseResult:
26
+ text = path.read_text(encoding="utf-8")
27
+ raw: dict[str, Any]
28
+ match = _FRONTMATTER_RE.match(text)
29
+ if match:
30
+ raw = yaml.safe_load(match.group(1)) or {}
31
+ content = text[match.end() :]
32
+ else:
33
+ raw = {}
34
+ content = text
35
+ if not isinstance(raw, dict):
36
+ raw = {}
37
+
38
+ fallback_name = _copilot_agent_stem(path)
39
+ tools, warnings = _parse_copilot_tools(raw.get("tools"), path)
40
+ agent = Agent(
41
+ name=fallback_name,
42
+ source_path=path,
43
+ metadata=AgentMetadata(
44
+ name=str(raw.get("name", fallback_name)),
45
+ description=str(raw.get("description", "")),
46
+ model=str(raw.get("model", "")),
47
+ tools=tools,
48
+ ),
49
+ content=content,
50
+ )
51
+ return CopilotAgentParseResult(agent=agent, warnings=warnings)
52
+
53
+
54
+ def copilot_agent_filename(path: Path) -> str:
55
+ return f"{_copilot_agent_stem(path)}.agent.md"
56
+
57
+
58
+ def _copilot_agent_stem(path: Path) -> str:
59
+ name = path.name
60
+ if name.endswith(".agent.md"):
61
+ return name[: -len(".agent.md")]
62
+ if name.endswith(".md"):
63
+ return name[: -len(".md")]
64
+ return path.stem
65
+
66
+
67
+ def _parse_copilot_tools(
68
+ raw: Any, path: Path
69
+ ) -> tuple[AgentToolPermissions, list[str]]:
70
+ if raw is None:
71
+ return AgentToolPermissions(), []
72
+
73
+ tokens = _coerce_tool_tokens(raw)
74
+ if tokens is None:
75
+ return AgentToolPermissions(), [
76
+ f"Skipped unrepresentable Copilot tools: {path}"
77
+ ]
78
+
79
+ if "*" in tokens:
80
+ return AgentToolPermissions(), []
81
+
82
+ read = False
83
+ write = False
84
+ mcp: list[dict[str, str]] = []
85
+ unsupported: list[str] = []
86
+ for token in tokens:
87
+ normalized = token.lower()
88
+ if normalized in _READ_ALIASES:
89
+ read = True
90
+ continue
91
+ if normalized in _WRITE_ALIASES:
92
+ write = True
93
+ continue
94
+ if "/" in token:
95
+ server, tool = token.split("/", 1)
96
+ if server and tool:
97
+ item = {"server": server}
98
+ if tool != "*":
99
+ item["tool"] = tool
100
+ mcp.append(item)
101
+ continue
102
+ unsupported.append(token)
103
+
104
+ if unsupported:
105
+ return AgentToolPermissions(), [
106
+ f"Skipped unrepresentable Copilot tools in {path}: {', '.join(unsupported)}"
107
+ ]
108
+
109
+ return AgentToolPermissions(read=read, write=write, mcp=mcp), []
110
+
111
+
112
+ def _coerce_tool_tokens(raw: Any) -> list[str] | None:
113
+ if isinstance(raw, str):
114
+ return [item.strip() for item in raw.split(",") if item.strip()]
115
+ if isinstance(raw, list):
116
+ return [item for item in raw if isinstance(item, str)]
117
+ return None
@@ -19,7 +19,7 @@ from code_agnostic.agents.models import (
19
19
  from code_agnostic.spec.loaders import load_agent_bundle
20
20
 
21
21
  _FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
22
- _APP_OVERRIDE_PREFIXES = ("cursor", "codex", "opencode", "claude")
22
+ _APP_OVERRIDE_PREFIXES = ("cursor", "codex", "opencode", "claude", "copilot")
23
23
 
24
24
 
25
25
  def parse_agent(path: Path) -> Agent:
@@ -6,6 +6,8 @@ from code_agnostic.constants import (
6
6
  CLAUDE_PROJECT_DIRNAME,
7
7
  CODEX_CONFIG_FILENAME,
8
8
  CODEX_PROJECT_DIRNAME,
9
+ COPILOT_PROJECT_CONFIG_FILENAME,
10
+ COPILOT_PROJECT_DIRNAME,
9
11
  CURSOR_CONFIG_FILENAME,
10
12
  CURSOR_PROJECT_DIRNAME,
11
13
  OPENCODE_CONFIG_FILENAME,
@@ -19,6 +21,7 @@ class AppId(str, Enum):
19
21
  CURSOR = "cursor"
20
22
  CODEX = "codex"
21
23
  CLAUDE = "claude"
24
+ COPILOT = "copilot"
22
25
 
23
26
 
24
27
  @dataclass(frozen=True)
@@ -89,6 +92,17 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
89
92
  project_dir_name=CLAUDE_PROJECT_DIRNAME,
90
93
  config_filename=CLAUDE_CONFIG_FILENAME,
91
94
  ),
95
+ AppId.COPILOT: AppMetadata(
96
+ app_id=AppId.COPILOT,
97
+ label="GitHub Copilot",
98
+ targetable=True,
99
+ toggleable=True,
100
+ importable=True,
101
+ supports_import_agents=True,
102
+ supports_workspace_propagation=True,
103
+ project_dir_name=COPILOT_PROJECT_DIRNAME,
104
+ config_filename=COPILOT_PROJECT_CONFIG_FILENAME,
105
+ ),
92
106
  }
93
107
 
94
108