code-agnostic 0.3.3__tar.gz → 0.3.4__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.3 → code_agnostic-0.3.4}/PKG-INFO +5 -5
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/README.md +4 -4
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/schema.json +18 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/service.py +7 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/compiled_planning.py +12 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/interfaces/service.py +23 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/utils.py +46 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/service.py +2 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/config_repository.py +5 -3
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/service.py +28 -2
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/constants.py +6 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/git_exclude_service.py +6 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/planner.py +124 -90
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/utils.py +14 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/PKG-INFO +5 -5
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/pyproject.toml +1 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_apply_apps.py +169 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_apply_target.py +51 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_workspaces.py +6 -3
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_common_mcp_to_dto.py +64 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_git_exclude_service.py +6 -1
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_planner_executor.py +40 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_transactional_executor.py +51 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_workspace_config_sync.py +201 -9
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/LICENSE +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/lossiness.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/status.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/renderers.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/setup.cfg +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_explain_lossiness.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.3 → code_agnostic-0.3.4}/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.4
|
|
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
|
|
@@ -102,7 +102,7 @@ code-agnostic apply
|
|
|
102
102
|
| Agents sync | yes | yes | yes |
|
|
103
103
|
| Workspace root `AGENTS.md` link | yes | yes | yes |
|
|
104
104
|
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- |
|
|
105
|
-
| Repo/subdir gets shared workspace `AGENTS.md` today | yes | -- |
|
|
105
|
+
| Repo/subdir gets shared workspace `AGENTS.md` today | yes | -- | yes |
|
|
106
106
|
| Root-level `AGENTS.md` discovery only | -- | yes | yes |
|
|
107
107
|
| Workspace propagation | yes | -- | yes |
|
|
108
108
|
| Import from | yes | yes | yes |
|
|
@@ -110,9 +110,9 @@ code-agnostic apply
|
|
|
110
110
|
|
|
111
111
|
Cursor workspace propagation is intentionally disabled to avoid duplicate MCP initialization in multi-root workspaces: https://forum.cursor.com/t/mcp-multi-root-workspace-causes-duplicate-mcp-server-initialization-4x-createclient-actions/144003
|
|
112
112
|
|
|
113
|
-
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions.
|
|
113
|
+
OpenCode workspace configs 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`.
|
|
114
114
|
|
|
115
|
-
Cursor documents `AGENTS.md` as a root-level project file. Codex documents `AGENTS.md` discovery, but not a native config include for an extra workspace file. In practice that means subdirectories and repos opened below the workspace root will not reliably get the shared workspace `AGENTS.md` today for Cursor
|
|
115
|
+
Cursor documents `AGENTS.md` as a root-level project file. Codex documents `AGENTS.md` discovery, but not a native config include for an extra workspace file. In practice that means subdirectories and repos opened below the workspace root will not reliably get the shared workspace `AGENTS.md` today for Cursor.
|
|
116
116
|
|
|
117
117
|
## Features
|
|
118
118
|
|
|
@@ -171,7 +171,7 @@ code-agnostic agents list
|
|
|
171
171
|
|
|
172
172
|
### Workspaces
|
|
173
173
|
|
|
174
|
-
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md`
|
|
174
|
+
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 reference the shared workspace file through `instructions`. Repo-local app config, skills, and agents are propagated for OpenCode and Codex.
|
|
175
175
|
|
|
176
176
|
`.cursor` workspace propagation is intentionally disabled to avoid duplicate MCP initialization when opening multi-root workspaces in Cursor (related issue: https://forum.cursor.com/t/mcp-multi-root-workspace-causes-duplicate-mcp-server-initialization-4x-createclient-actions/144003).
|
|
177
177
|
|
|
@@ -79,7 +79,7 @@ code-agnostic apply
|
|
|
79
79
|
| Agents sync | yes | yes | yes |
|
|
80
80
|
| Workspace root `AGENTS.md` link | yes | yes | yes |
|
|
81
81
|
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- |
|
|
82
|
-
| Repo/subdir gets shared workspace `AGENTS.md` today | yes | -- |
|
|
82
|
+
| Repo/subdir gets shared workspace `AGENTS.md` today | yes | -- | yes |
|
|
83
83
|
| Root-level `AGENTS.md` discovery only | -- | yes | yes |
|
|
84
84
|
| Workspace propagation | yes | -- | yes |
|
|
85
85
|
| Import from | yes | yes | yes |
|
|
@@ -87,9 +87,9 @@ code-agnostic apply
|
|
|
87
87
|
|
|
88
88
|
Cursor workspace propagation is intentionally disabled to avoid duplicate MCP initialization in multi-root workspaces: https://forum.cursor.com/t/mcp-multi-root-workspace-causes-duplicate-mcp-server-initialization-4x-createclient-actions/144003
|
|
89
89
|
|
|
90
|
-
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions.
|
|
90
|
+
OpenCode workspace configs 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`.
|
|
91
91
|
|
|
92
|
-
Cursor documents `AGENTS.md` as a root-level project file. Codex documents `AGENTS.md` discovery, but not a native config include for an extra workspace file. In practice that means subdirectories and repos opened below the workspace root will not reliably get the shared workspace `AGENTS.md` today for Cursor
|
|
92
|
+
Cursor documents `AGENTS.md` as a root-level project file. Codex documents `AGENTS.md` discovery, but not a native config include for an extra workspace file. In practice that means subdirectories and repos opened below the workspace root will not reliably get the shared workspace `AGENTS.md` today for Cursor.
|
|
93
93
|
|
|
94
94
|
## Features
|
|
95
95
|
|
|
@@ -148,7 +148,7 @@ code-agnostic agents list
|
|
|
148
148
|
|
|
149
149
|
### Workspaces
|
|
150
150
|
|
|
151
|
-
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md`
|
|
151
|
+
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 reference the shared workspace file through `instructions`. Repo-local app config, skills, and agents are propagated for OpenCode and Codex.
|
|
152
152
|
|
|
153
153
|
`.cursor` workspace propagation is intentionally disabled to avoid duplicate MCP initialization when opening multi-root workspaces in Cursor (related issue: https://forum.cursor.com/t/mcp-multi-root-workspace-causes-duplicate-mcp-server-initialization-4x-createclient-actions/144003).
|
|
154
154
|
|
|
@@ -56,32 +56,49 @@
|
|
|
56
56
|
},
|
|
57
57
|
"description": "Per-tool approval settings for a single MCP server tool."
|
|
58
58
|
},
|
|
59
|
+
"McpServerOAuthConfig": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"properties": {
|
|
63
|
+
"client_id": { "type": "string" }
|
|
64
|
+
},
|
|
65
|
+
"description": "OAuth client settings used when Codex launches an MCP OAuth flow."
|
|
66
|
+
},
|
|
59
67
|
"RawMcpServerConfig": {
|
|
60
68
|
"type": "object",
|
|
61
69
|
"additionalProperties": false,
|
|
62
70
|
"properties": {
|
|
63
71
|
"args": { "type": "array", "items": { "type": "string" } },
|
|
64
|
-
"bearer_token": { "type": "string" },
|
|
65
72
|
"bearer_token_env_var": { "type": "string" },
|
|
66
73
|
"command": { "type": "string" },
|
|
67
74
|
"cwd": { "type": "string" },
|
|
75
|
+
"default_tools_approval_mode": {
|
|
76
|
+
"allOf": [{ "$ref": "#/definitions/AppToolApproval" }],
|
|
77
|
+
"default": null
|
|
78
|
+
},
|
|
68
79
|
"disabled_tools": { "type": "array", "items": { "type": "string" } },
|
|
69
80
|
"enabled": { "type": "boolean" },
|
|
70
81
|
"enabled_tools": { "type": "array", "items": { "type": "string" } },
|
|
71
82
|
"env": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
72
83
|
"env_http_headers": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
73
84
|
"env_vars": { "type": "array", "items": { "type": "string" } },
|
|
85
|
+
"experimental_environment": { "type": "string" },
|
|
74
86
|
"http_headers": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
75
87
|
"name": {
|
|
76
88
|
"type": "string",
|
|
77
89
|
"default": null,
|
|
78
90
|
"description": "Legacy display-name field accepted for backward compatibility."
|
|
79
91
|
},
|
|
92
|
+
"oauth": {
|
|
93
|
+
"allOf": [{ "$ref": "#/definitions/McpServerOAuthConfig" }],
|
|
94
|
+
"default": null
|
|
95
|
+
},
|
|
80
96
|
"oauth_resource": { "type": "string", "default": null },
|
|
81
97
|
"required": { "type": "boolean" },
|
|
82
98
|
"scopes": { "type": "array", "items": { "type": "string" } },
|
|
83
99
|
"startup_timeout_ms": { "type": "integer", "minimum": 0 },
|
|
84
100
|
"startup_timeout_sec": { "type": "number" },
|
|
101
|
+
"supports_parallel_tool_calls": { "type": "boolean" },
|
|
85
102
|
"tool_timeout_sec": { "type": "number" },
|
|
86
103
|
"tools": {
|
|
87
104
|
"type": "object",
|
|
@@ -26,7 +26,7 @@ from code_agnostic.errors import (
|
|
|
26
26
|
InvalidJsonFormatError,
|
|
27
27
|
)
|
|
28
28
|
from code_agnostic.models import Action, ActionKind, ActionStatus
|
|
29
|
-
from code_agnostic.utils import read_json_safe
|
|
29
|
+
from code_agnostic.utils import merge_dict_overlay, read_json_safe
|
|
30
30
|
from code_agnostic.skills.compilers import CodexSkillCompiler
|
|
31
31
|
from code_agnostic.skills.parser import parse_skill
|
|
32
32
|
|
|
@@ -134,6 +134,10 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
134
134
|
merged.get("agents"), value
|
|
135
135
|
)
|
|
136
136
|
continue
|
|
137
|
+
current = merged.get(key)
|
|
138
|
+
if isinstance(current, dict) and isinstance(value, dict):
|
|
139
|
+
merged[key] = merge_dict_overlay(current, value)
|
|
140
|
+
continue
|
|
137
141
|
merged[key] = deepcopy(value)
|
|
138
142
|
self.set_mcp_payload(merged, desired_mcp)
|
|
139
143
|
if agent_sources:
|
|
@@ -196,6 +200,7 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
196
200
|
compiler = CodexSkillCompiler()
|
|
197
201
|
return self._plan_compiled_text_actions(
|
|
198
202
|
sources=sources,
|
|
203
|
+
target_dir=target_dir,
|
|
199
204
|
scope=scope,
|
|
200
205
|
app=app,
|
|
201
206
|
managed_paths=managed_paths,
|
|
@@ -244,6 +249,7 @@ class CodexConfigService(RegisteredAppConfigService):
|
|
|
244
249
|
|
|
245
250
|
return self._plan_compiled_text_actions(
|
|
246
251
|
sources=sources,
|
|
252
|
+
target_dir=target_dir,
|
|
247
253
|
scope=scope,
|
|
248
254
|
app=app,
|
|
249
255
|
managed_paths=managed_paths,
|
|
@@ -3,6 +3,18 @@ from pathlib import Path
|
|
|
3
3
|
from code_agnostic.models import Action, ActionKind, ActionStatus
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
def find_replaceable_symlink_ancestor(target: Path, managed_root: Path) -> Path | None:
|
|
7
|
+
current = target
|
|
8
|
+
while True:
|
|
9
|
+
if current.is_symlink() and (
|
|
10
|
+
current == managed_root or current.is_relative_to(managed_root)
|
|
11
|
+
):
|
|
12
|
+
return current
|
|
13
|
+
if current.parent == current:
|
|
14
|
+
return None
|
|
15
|
+
current = current.parent
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
def _symlink_ancestor_state(
|
|
7
19
|
target: Path, removable_link_paths: set[Path]
|
|
8
20
|
) -> tuple[bool, bool]:
|
|
@@ -4,7 +4,10 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from code_agnostic.apps.app_id import AppId, app_scope
|
|
7
|
-
from code_agnostic.apps.common.compiled_planning import
|
|
7
|
+
from code_agnostic.apps.common.compiled_planning import (
|
|
8
|
+
find_replaceable_symlink_ancestor,
|
|
9
|
+
plan_compiled_text_action,
|
|
10
|
+
)
|
|
8
11
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
9
12
|
from code_agnostic.apps.common.interfaces.repositories import IAppConfigRepository
|
|
10
13
|
from code_agnostic.apps.common.interfaces.repositories import ISourceRepository
|
|
@@ -99,6 +102,7 @@ class IAppConfigService(ABC):
|
|
|
99
102
|
self,
|
|
100
103
|
*,
|
|
101
104
|
sources: list[Path],
|
|
105
|
+
target_dir: Path,
|
|
102
106
|
scope: str,
|
|
103
107
|
app: str,
|
|
104
108
|
managed_paths: list[Path],
|
|
@@ -114,10 +118,28 @@ class IAppConfigService(ABC):
|
|
|
114
118
|
actions: list[Action] = []
|
|
115
119
|
desired_paths: list[Path] = []
|
|
116
120
|
skipped: list[str] = []
|
|
121
|
+
scheduled_removals: set[Path] = set()
|
|
117
122
|
|
|
118
123
|
for source in sources:
|
|
119
124
|
target, payload = compile_source(source)
|
|
120
125
|
desired_paths.append(target)
|
|
126
|
+
replaceable_symlink = find_replaceable_symlink_ancestor(target, target_dir)
|
|
127
|
+
if (
|
|
128
|
+
replaceable_symlink is not None
|
|
129
|
+
and replaceable_symlink not in scheduled_removals
|
|
130
|
+
):
|
|
131
|
+
scheduled_removals.add(replaceable_symlink)
|
|
132
|
+
removable_link_set.add(replaceable_symlink.resolve(strict=False))
|
|
133
|
+
actions.append(
|
|
134
|
+
Action(
|
|
135
|
+
kind=ActionKind.REMOVE_SYMLINK,
|
|
136
|
+
path=replaceable_symlink,
|
|
137
|
+
status=ActionStatus.REMOVE,
|
|
138
|
+
detail=f"replace compiled {scope} symlink",
|
|
139
|
+
app=app,
|
|
140
|
+
scope=scope,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
121
143
|
action = plan_compiled_text_action(
|
|
122
144
|
target=target,
|
|
123
145
|
payload=payload,
|
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from code_agnostic.apps.app_id import AppId, app_ids_by_capability
|
|
3
4
|
from code_agnostic.apps.common.models import MCPAuthDTO, MCPServerDTO, MCPServerType
|
|
5
|
+
from code_agnostic.constants import (
|
|
6
|
+
MCP_APP_EXCLUDE_PREFIX,
|
|
7
|
+
MCP_APP_INCLUDE_PREFIX,
|
|
8
|
+
MCP_APP_TARGET_SEPARATOR,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_TARGETABLE_APP_NAMES = {
|
|
13
|
+
app_id.value for app_id in app_ids_by_capability(targetable=True)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _split_app_targeted_key(key: str) -> tuple[str, str, str] | None:
|
|
18
|
+
if not key or key[0] not in (MCP_APP_INCLUDE_PREFIX, MCP_APP_EXCLUDE_PREFIX):
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
marker = key[0]
|
|
22
|
+
remainder = key[1:]
|
|
23
|
+
app_name, separator, server_name = remainder.partition(MCP_APP_TARGET_SEPARATOR)
|
|
24
|
+
if (
|
|
25
|
+
separator != MCP_APP_TARGET_SEPARATOR
|
|
26
|
+
or not server_name
|
|
27
|
+
or app_name not in _TARGETABLE_APP_NAMES
|
|
28
|
+
):
|
|
29
|
+
return None
|
|
30
|
+
return marker, app_name, server_name
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def mcp_servers_for_app(
|
|
34
|
+
mcp_servers: dict[str, Any], app_id: AppId | str
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
target = app_id.value if isinstance(app_id, AppId) else app_id
|
|
37
|
+
filtered: dict[str, Any] = {}
|
|
38
|
+
for name, raw in mcp_servers.items():
|
|
39
|
+
targeted = _split_app_targeted_key(name)
|
|
40
|
+
if targeted is None:
|
|
41
|
+
filtered[name] = raw
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
marker, app_name, server_name = targeted
|
|
45
|
+
if marker == MCP_APP_INCLUDE_PREFIX and app_name == target:
|
|
46
|
+
filtered[server_name] = raw
|
|
47
|
+
elif marker == MCP_APP_EXCLUDE_PREFIX and app_name != target:
|
|
48
|
+
filtered[server_name] = raw
|
|
49
|
+
return filtered
|
|
4
50
|
|
|
5
51
|
|
|
6
52
|
def _coerce_timeout_ms(value: Any) -> int | None:
|
|
@@ -115,6 +115,7 @@ class CursorConfigService(RegisteredAppConfigService):
|
|
|
115
115
|
compiler = CursorSkillCompiler()
|
|
116
116
|
return self._plan_compiled_text_actions(
|
|
117
117
|
sources=sources,
|
|
118
|
+
target_dir=target_dir,
|
|
118
119
|
scope=scope,
|
|
119
120
|
app=app,
|
|
120
121
|
managed_paths=managed_paths,
|
|
@@ -147,6 +148,7 @@ class CursorConfigService(RegisteredAppConfigService):
|
|
|
147
148
|
compiler = CursorAgentCompiler()
|
|
148
149
|
return self._plan_compiled_text_actions(
|
|
149
150
|
sources=sources,
|
|
151
|
+
target_dir=target_dir,
|
|
150
152
|
scope=scope,
|
|
151
153
|
app=app,
|
|
152
154
|
managed_paths=managed_paths,
|
{code_agnostic-0.3.3 → code_agnostic-0.3.4}/code_agnostic/apps/opencode/config_repository.py
RENAMED
|
@@ -9,7 +9,7 @@ from code_agnostic.constants import (
|
|
|
9
9
|
SKILLS_DIRNAME,
|
|
10
10
|
)
|
|
11
11
|
from code_agnostic.errors import InvalidConfigSchemaError, InvalidJsonFormatError
|
|
12
|
-
from code_agnostic.utils import read_json_safe, write_json
|
|
12
|
+
from code_agnostic.utils import merge_dict_overlay, read_json_safe, write_json
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class OpenCodeConfigRepository(IAppConfigRepository):
|
|
@@ -69,6 +69,10 @@ class OpenCodeConfigRepository(IAppConfigRepository):
|
|
|
69
69
|
for key, value in base.items():
|
|
70
70
|
if key == "permission" and isinstance(value, list):
|
|
71
71
|
continue
|
|
72
|
+
current = merged.get(key)
|
|
73
|
+
if isinstance(current, dict) and isinstance(value, dict):
|
|
74
|
+
merged[key] = merge_dict_overlay(current, value)
|
|
75
|
+
continue
|
|
72
76
|
merged[key] = deepcopy(value)
|
|
73
77
|
merged["mcp"] = deepcopy(mapped_mcp)
|
|
74
78
|
return merged
|
|
@@ -101,5 +105,3 @@ class OpenCodeConfigRepository(IAppConfigRepository):
|
|
|
101
105
|
|
|
102
106
|
if tools:
|
|
103
107
|
merged["tools"] = tools
|
|
104
|
-
|
|
105
|
-
merged.pop("permission", None)
|
|
@@ -26,6 +26,29 @@ from code_agnostic.skills.compilers import OpenCodeSkillCompiler
|
|
|
26
26
|
from code_agnostic.skills.parser import parse_skill
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _is_unknown_provider_model_enum_error(error: Any) -> bool:
|
|
30
|
+
if error.validator != "enum":
|
|
31
|
+
return False
|
|
32
|
+
model = error.instance
|
|
33
|
+
if not isinstance(model, str) or "/" not in model:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
known_models = error.validator_value
|
|
37
|
+
if not isinstance(known_models, list):
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
known_providers = {
|
|
41
|
+
item.split("/", 1)[0]
|
|
42
|
+
for item in known_models
|
|
43
|
+
if isinstance(item, str) and "/" in item
|
|
44
|
+
}
|
|
45
|
+
if not known_providers:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
provider = model.split("/", 1)[0]
|
|
49
|
+
return provider not in known_providers
|
|
50
|
+
|
|
51
|
+
|
|
29
52
|
class OpenCodeConfigService(RegisteredAppConfigService):
|
|
30
53
|
APP_ID = AppId.OPENCODE
|
|
31
54
|
APP_LABEL = app_label(APP_ID)
|
|
@@ -82,8 +105,9 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
82
105
|
raise InvalidConfigSchemaError(
|
|
83
106
|
self.repository.config_path, "must be a JSON object"
|
|
84
107
|
)
|
|
85
|
-
error
|
|
86
|
-
|
|
108
|
+
for error in self._validator.iter_errors(payload):
|
|
109
|
+
if _is_unknown_provider_model_enum_error(error):
|
|
110
|
+
continue
|
|
87
111
|
raise InvalidConfigSchemaError(
|
|
88
112
|
self.repository.config_path, format_schema_error(error)
|
|
89
113
|
)
|
|
@@ -149,6 +173,7 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
149
173
|
compiler = OpenCodeSkillCompiler()
|
|
150
174
|
return self._plan_compiled_text_actions(
|
|
151
175
|
sources=sources,
|
|
176
|
+
target_dir=target_dir,
|
|
152
177
|
scope=scope,
|
|
153
178
|
app=app,
|
|
154
179
|
managed_paths=managed_paths,
|
|
@@ -181,6 +206,7 @@ class OpenCodeConfigService(RegisteredAppConfigService):
|
|
|
181
206
|
compiler = OpenCodeAgentCompiler()
|
|
182
207
|
return self._plan_compiled_text_actions(
|
|
183
208
|
sources=sources,
|
|
209
|
+
target_dir=target_dir,
|
|
184
210
|
scope=scope,
|
|
185
211
|
app=app,
|
|
186
212
|
managed_paths=managed_paths,
|
|
@@ -2,6 +2,7 @@ from typing import Final
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
AGENTS_FILENAME: Final[str] = "AGENTS.md"
|
|
5
|
+
CODEX_AGENTS_OVERRIDE_FILENAME: Final[str] = "AGENTS.override.md"
|
|
5
6
|
CLAUDE_FILENAME: Final[str] = "CLAUDE.md"
|
|
6
7
|
GIT_DIRNAME: Final[str] = ".git"
|
|
7
8
|
SYNC_STATE_FILENAME: Final[str] = ".sync-state.json"
|
|
@@ -20,6 +21,11 @@ OPENCODE_CONFIG_FILENAME: Final[str] = "opencode.json"
|
|
|
20
21
|
CURSOR_CONFIG_FILENAME: Final[str] = "mcp.json"
|
|
21
22
|
CODEX_CONFIG_FILENAME: Final[str] = "config.toml"
|
|
22
23
|
|
|
24
|
+
MCP_SERVERS_KEY: Final[str] = "mcpServers"
|
|
25
|
+
MCP_APP_INCLUDE_PREFIX: Final[str] = "@"
|
|
26
|
+
MCP_APP_EXCLUDE_PREFIX: Final[str] = "!"
|
|
27
|
+
MCP_APP_TARGET_SEPARATOR: Final[str] = "-"
|
|
28
|
+
|
|
23
29
|
WORKSPACE_IGNORED_DIRS: Final[tuple[str, ...]] = (
|
|
24
30
|
"node_modules",
|
|
25
31
|
".venv",
|
|
@@ -6,7 +6,11 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from code_agnostic.apps.app_id import app_metadata
|
|
9
|
-
from code_agnostic.constants import
|
|
9
|
+
from code_agnostic.constants import (
|
|
10
|
+
AGENTS_FILENAME,
|
|
11
|
+
CLAUDE_FILENAME,
|
|
12
|
+
CODEX_AGENTS_OVERRIDE_FILENAME,
|
|
13
|
+
)
|
|
10
14
|
from code_agnostic.core.repository import CoreRepository
|
|
11
15
|
from code_agnostic.utils import read_json_safe, write_json
|
|
12
16
|
|
|
@@ -58,6 +62,7 @@ class GitExcludeService:
|
|
|
58
62
|
|
|
59
63
|
defaults = [f".{app_name}" for app_name in workspace_apps] + [
|
|
60
64
|
AGENTS_FILENAME,
|
|
65
|
+
CODEX_AGENTS_OVERRIDE_FILENAME,
|
|
61
66
|
CLAUDE_FILENAME,
|
|
62
67
|
]
|
|
63
68
|
return defaults + extras
|