code-agnostic 0.3.16__tar.gz → 0.3.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/PKG-INFO +33 -24
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/README.md +32 -23
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/__init__.py +1 -1
- code_agnostic-0.3.18/code_agnostic/agents/compilers.py +176 -0
- code_agnostic-0.3.18/code_agnostic/agents/copilot.py +117 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/parser.py +1 -1
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/app_id.py +14 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/apps_service.py +2 -1
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/schema.json +325 -50
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/loader.py +1 -0
- code_agnostic-0.3.18/code_agnostic/apps/copilot/config_repository.py +65 -0
- code_agnostic-0.3.18/code_agnostic/apps/copilot/mapper.py +97 -0
- code_agnostic-0.3.18/code_agnostic/apps/copilot/service.py +140 -0
- code_agnostic-0.3.18/code_agnostic/apps/opencode/__init__.py +1 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/apply.py +8 -2
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/plan.py +14 -3
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/constants.py +4 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/imports/adapters.py +12 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/imports/service.py +75 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/lossiness.py +21 -11
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/models.py +3 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/planner.py +44 -22
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/project_artifacts.py +3 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/skills/compilers.py +12 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/skills/parser.py +1 -1
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/spec/loaders.py +2 -2
- code_agnostic-0.3.18/code_agnostic/spec/schemas/agent.v1.schema.json +148 -0
- code_agnostic-0.3.18/code_agnostic/spec/schemas/rule.v1.schema.json +49 -0
- code_agnostic-0.3.18/code_agnostic/spec/schemas/skill.v1.schema.json +78 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/renderers.py +13 -4
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/workspace_artifacts.py +17 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/PKG-INFO +33 -24
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/SOURCES.txt +5 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/pyproject.toml +1 -1
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_apply_apps.py +287 -1
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_apply_codex.py +4 -2
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_apps.py +1 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_explain_lossiness.py +38 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_import.py +44 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_plan.py +12 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_workspace_config_sync.py +5 -1
- code_agnostic-0.3.16/code_agnostic/agents/compilers.py +0 -87
- code_agnostic-0.3.16/code_agnostic/spec/schemas/agent.v1.schema.json +0 -70
- code_agnostic-0.3.16/code_agnostic/spec/schemas/rule.v1.schema.json +0 -22
- code_agnostic-0.3.16/code_agnostic/spec/schemas/skill.v1.schema.json +0 -33
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/LICENSE +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.16/code_agnostic/apps/cursor → code_agnostic-0.3.18/code_agnostic/apps/copilot}/__init__.py +0 -0
- {code_agnostic-0.3.16/code_agnostic/apps/opencode → code_agnostic-0.3.18/code_agnostic/apps/cursor}/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/skills/install_sources.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/status.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/setup.cfg +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_projects.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_cli_workspaces.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_project_config_sync.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_skill_install_sources.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_version.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.16 → code_agnostic-0.3.18}/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.
|
|
3
|
+
Version: 0.3.18
|
|
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
|
|
81
|
-
skills into managed project source first, then run `plan` /
|
|
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`,
|
|
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
|
|
|
@@ -271,9 +273,12 @@ a coarse `[skills]` marker today. Codex generated skill outputs are written to
|
|
|
271
273
|
`~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
|
|
272
274
|
set, defaulting to `~/.codex`. Claude Code generated skills and agents are
|
|
273
275
|
written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
|
|
274
|
-
copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
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`.
|
|
275
280
|
|
|
276
|
-
If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.
|
|
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.
|
|
277
282
|
|
|
278
283
|
Planned convenience command:
|
|
279
284
|
|
|
@@ -287,7 +292,7 @@ first implementation slice.
|
|
|
287
292
|
|
|
288
293
|
### Workspaces
|
|
289
294
|
|
|
290
|
-
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
|
|
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.
|
|
291
296
|
|
|
292
297
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
293
298
|
|
|
@@ -300,7 +305,10 @@ code-agnostic workspaces list
|
|
|
300
305
|
|
|
301
306
|
Prevent synced paths from showing up in `git status`. Managed per-workspace with customizable patterns.
|
|
302
307
|
|
|
308
|
+
Writing `.git/info/exclude` is always explicit — `apply` does **not** touch it by default. Opt in per run with `apply --apply-excludes`, or use the standalone command:
|
|
309
|
+
|
|
303
310
|
```bash
|
|
311
|
+
code-agnostic apply --apply-excludes # apply + write excludes
|
|
304
312
|
code-agnostic workspaces git-exclude # all workspaces
|
|
305
313
|
code-agnostic workspaces git-exclude -w myproject # one workspace
|
|
306
314
|
code-agnostic workspaces exclude-add --pattern "*.generated" -w myproject
|
|
@@ -315,6 +323,7 @@ Migrate existing config from any supported editor into the hub.
|
|
|
315
323
|
code-agnostic import plan -a codex
|
|
316
324
|
code-agnostic import apply -a codex
|
|
317
325
|
code-agnostic import plan -a claude
|
|
326
|
+
code-agnostic import plan -a copilot
|
|
318
327
|
code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
|
|
319
328
|
code-agnostic import plan -a codex -i # interactive TUI picker
|
|
320
329
|
```
|
|
@@ -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
|
|
56
|
-
skills into managed project source first, then run `plan` /
|
|
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`,
|
|
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
|
|
|
@@ -246,9 +248,12 @@ a coarse `[skills]` marker today. Codex generated skill outputs are written to
|
|
|
246
248
|
`~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
|
|
247
249
|
set, defaulting to `~/.codex`. Claude Code generated skills and agents are
|
|
248
250
|
written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
|
|
249
|
-
copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
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`.
|
|
250
255
|
|
|
251
|
-
If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.
|
|
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.
|
|
252
257
|
|
|
253
258
|
Planned convenience command:
|
|
254
259
|
|
|
@@ -262,7 +267,7 @@ first implementation slice.
|
|
|
262
267
|
|
|
263
268
|
### Workspaces
|
|
264
269
|
|
|
265
|
-
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
|
|
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.
|
|
266
271
|
|
|
267
272
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
268
273
|
|
|
@@ -275,7 +280,10 @@ code-agnostic workspaces list
|
|
|
275
280
|
|
|
276
281
|
Prevent synced paths from showing up in `git status`. Managed per-workspace with customizable patterns.
|
|
277
282
|
|
|
283
|
+
Writing `.git/info/exclude` is always explicit — `apply` does **not** touch it by default. Opt in per run with `apply --apply-excludes`, or use the standalone command:
|
|
284
|
+
|
|
278
285
|
```bash
|
|
286
|
+
code-agnostic apply --apply-excludes # apply + write excludes
|
|
279
287
|
code-agnostic workspaces git-exclude # all workspaces
|
|
280
288
|
code-agnostic workspaces git-exclude -w myproject # one workspace
|
|
281
289
|
code-agnostic workspaces exclude-add --pattern "*.generated" -w myproject
|
|
@@ -290,6 +298,7 @@ Migrate existing config from any supported editor into the hub.
|
|
|
290
298
|
code-agnostic import plan -a codex
|
|
291
299
|
code-agnostic import apply -a codex
|
|
292
300
|
code-agnostic import plan -a claude
|
|
301
|
+
code-agnostic import plan -a copilot
|
|
293
302
|
code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
|
|
294
303
|
code-agnostic import plan -a codex -i # interactive TUI picker
|
|
295
304
|
```
|
|
@@ -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
|
|
|
@@ -84,7 +84,7 @@ class AppsService:
|
|
|
84
84
|
apps = self.load_apps()
|
|
85
85
|
return [name for name in self.available_apps() if apps.get(name, False)]
|
|
86
86
|
|
|
87
|
-
def plan_for_target(self, target: str) -> SyncPlan:
|
|
87
|
+
def plan_for_target(self, target: str, *, apply_excludes: bool = False) -> SyncPlan:
|
|
88
88
|
normalized = target.lower()
|
|
89
89
|
if normalized != "all" and not self.is_enabled(normalized):
|
|
90
90
|
return SyncPlan([], [], [f"{normalized} is disabled for sync."])
|
|
@@ -94,6 +94,7 @@ class AppsService:
|
|
|
94
94
|
core=self.core_repository,
|
|
95
95
|
app_services=app_services,
|
|
96
96
|
include_workspace=True,
|
|
97
|
+
include_git_excludes=apply_excludes,
|
|
97
98
|
).build()
|
|
98
99
|
if normalized == "all":
|
|
99
100
|
if (
|