code-agnostic 0.3.7__tar.gz → 0.3.9__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.7 → code_agnostic-0.3.9}/PKG-INFO +26 -7
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/README.md +25 -6
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/schema.json +8 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/schema.json +1 -10
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/workspaces.py +3 -3
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/imports/service.py +5 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/lossiness.py +30 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/status.py +63 -2
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/renderers.py +60 -2
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/utils.py +2 -2
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/PKG-INFO +26 -7
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/pyproject.toml +1 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_aliases.py +1 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_explain_lossiness.py +32 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_flags.py +1 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_import.py +25 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_status.py +44 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_workspaces.py +2 -1
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_utils.py +45 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/LICENSE +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/planner.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/setup.cfg +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_version.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.7 → code_agnostic-0.3.9}/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.9
|
|
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
|
|
@@ -119,6 +119,10 @@ code-agnostic apply
|
|
|
119
119
|
| Import from | yes | yes | yes | yes |
|
|
120
120
|
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
121
121
|
|
|
122
|
+
`yes` means the resource type is synced for that editor. Some metadata is still
|
|
123
|
+
target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
|
|
124
|
+
that are omitted or rejected for a selected target.
|
|
125
|
+
|
|
122
126
|
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
|
|
123
127
|
|
|
124
128
|
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`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
|
|
@@ -160,19 +164,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
|
|
|
160
164
|
|
|
161
165
|
### Rules with metadata
|
|
162
166
|
|
|
163
|
-
|
|
167
|
+
New rules should use bundle directories with schema-validated metadata and a
|
|
168
|
+
separate prompt body:
|
|
164
169
|
|
|
165
|
-
```
|
|
166
|
-
|
|
170
|
+
```text
|
|
171
|
+
rules/python-style/
|
|
172
|
+
├── meta.yaml
|
|
173
|
+
└── prompt.md
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
# rules/python-style/meta.yaml
|
|
178
|
+
spec_version: v1
|
|
179
|
+
kind: rule
|
|
167
180
|
description: "Python coding standards"
|
|
168
181
|
globs: ["*.py"]
|
|
169
182
|
always_apply: false
|
|
170
|
-
|
|
183
|
+
```
|
|
171
184
|
|
|
185
|
+
```markdown
|
|
186
|
+
<!-- rules/python-style/prompt.md -->
|
|
172
187
|
Always use type hints. Prefer dataclasses over dicts.
|
|
173
188
|
```
|
|
174
189
|
|
|
175
190
|
Cross-compiled per editor: Cursor gets `.mdc` files with native frontmatter, OpenCode/Codex get `AGENTS.md` sections.
|
|
191
|
+
Legacy single-file rule markdown with YAML frontmatter remains supported for migration.
|
|
176
192
|
|
|
177
193
|
```bash
|
|
178
194
|
code-agnostic rules list
|
|
@@ -181,7 +197,10 @@ code-agnostic rules remove --name python-style
|
|
|
181
197
|
|
|
182
198
|
### Skills and agents
|
|
183
199
|
|
|
184
|
-
|
|
200
|
+
Use bundle directories for new skills and agents, then let `code-agnostic`
|
|
201
|
+
cross-compile them per editor. Install or edit skills in the `code-agnostic`
|
|
202
|
+
source of truth, then run `plan` / `apply`; do not hand-copy generated skills
|
|
203
|
+
into `.codex`, `.cursor`, `.agents`, or OpenCode directories.
|
|
185
204
|
|
|
186
205
|
```bash
|
|
187
206
|
code-agnostic skills list
|
|
@@ -262,7 +281,7 @@ The compiler migration is documented in:
|
|
|
262
281
|
|
|
263
282
|
- [x] Plan/apply/status sync engine
|
|
264
283
|
- [x] MCP server sync across editors
|
|
265
|
-
- [x] Skills and agents sync
|
|
284
|
+
- [x] Skills and agents sync across editors
|
|
266
285
|
- [x] Workspace propagation into git repos
|
|
267
286
|
- [x] Import from existing editor configs
|
|
268
287
|
- [x] Consistent CLI with named flags and aliases
|
|
@@ -96,6 +96,10 @@ code-agnostic apply
|
|
|
96
96
|
| Import from | yes | yes | yes | yes |
|
|
97
97
|
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
98
98
|
|
|
99
|
+
`yes` means the resource type is synced for that editor. Some metadata is still
|
|
100
|
+
target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
|
|
101
|
+
that are omitted or rejected for a selected target.
|
|
102
|
+
|
|
99
103
|
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
|
|
100
104
|
|
|
101
105
|
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`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
|
|
@@ -137,19 +141,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
|
|
|
137
141
|
|
|
138
142
|
### Rules with metadata
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
New rules should use bundle directories with schema-validated metadata and a
|
|
145
|
+
separate prompt body:
|
|
141
146
|
|
|
142
|
-
```
|
|
143
|
-
|
|
147
|
+
```text
|
|
148
|
+
rules/python-style/
|
|
149
|
+
├── meta.yaml
|
|
150
|
+
└── prompt.md
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
# rules/python-style/meta.yaml
|
|
155
|
+
spec_version: v1
|
|
156
|
+
kind: rule
|
|
144
157
|
description: "Python coding standards"
|
|
145
158
|
globs: ["*.py"]
|
|
146
159
|
always_apply: false
|
|
147
|
-
|
|
160
|
+
```
|
|
148
161
|
|
|
162
|
+
```markdown
|
|
163
|
+
<!-- rules/python-style/prompt.md -->
|
|
149
164
|
Always use type hints. Prefer dataclasses over dicts.
|
|
150
165
|
```
|
|
151
166
|
|
|
152
167
|
Cross-compiled per editor: Cursor gets `.mdc` files with native frontmatter, OpenCode/Codex get `AGENTS.md` sections.
|
|
168
|
+
Legacy single-file rule markdown with YAML frontmatter remains supported for migration.
|
|
153
169
|
|
|
154
170
|
```bash
|
|
155
171
|
code-agnostic rules list
|
|
@@ -158,7 +174,10 @@ code-agnostic rules remove --name python-style
|
|
|
158
174
|
|
|
159
175
|
### Skills and agents
|
|
160
176
|
|
|
161
|
-
|
|
177
|
+
Use bundle directories for new skills and agents, then let `code-agnostic`
|
|
178
|
+
cross-compile them per editor. Install or edit skills in the `code-agnostic`
|
|
179
|
+
source of truth, then run `plan` / `apply`; do not hand-copy generated skills
|
|
180
|
+
into `.codex`, `.cursor`, `.agents`, or OpenCode directories.
|
|
162
181
|
|
|
163
182
|
```bash
|
|
164
183
|
code-agnostic skills list
|
|
@@ -239,7 +258,7 @@ The compiler migration is documented in:
|
|
|
239
258
|
|
|
240
259
|
- [x] Plan/apply/status sync engine
|
|
241
260
|
- [x] MCP server sync across editors
|
|
242
|
-
- [x] Skills and agents sync
|
|
261
|
+
- [x] Skills and agents sync across editors
|
|
243
262
|
- [x] Workspace propagation into git repos
|
|
244
263
|
- [x] Import from existing editor configs
|
|
245
264
|
- [x] Consistent CLI with named flags and aliases
|
|
@@ -102,6 +102,14 @@
|
|
|
102
102
|
"additionalProperties": false,
|
|
103
103
|
"description": "Config values for a single app/connector.",
|
|
104
104
|
"properties": {
|
|
105
|
+
"approvals_reviewer": {
|
|
106
|
+
"allOf": [
|
|
107
|
+
{
|
|
108
|
+
"$ref": "#/definitions/ApprovalsReviewer"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"description": "Reviewer for approval prompts from this app, overriding the thread default."
|
|
112
|
+
},
|
|
105
113
|
"default_tools_approval_mode": {
|
|
106
114
|
"allOf": [
|
|
107
115
|
{
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"branch": {
|
|
59
59
|
"type": "string",
|
|
60
|
-
"description": "Branch or ref
|
|
60
|
+
"description": "Branch or ref to clone and inspect"
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"required": [
|
|
@@ -154,12 +154,6 @@
|
|
|
154
154
|
"websearch": {
|
|
155
155
|
"$ref": "#/$defs/PermissionActionConfig"
|
|
156
156
|
},
|
|
157
|
-
"repo_clone": {
|
|
158
|
-
"$ref": "#/$defs/PermissionRuleConfig"
|
|
159
|
-
},
|
|
160
|
-
"repo_overview": {
|
|
161
|
-
"$ref": "#/$defs/PermissionRuleConfig"
|
|
162
|
-
},
|
|
163
157
|
"lsp": {
|
|
164
158
|
"$ref": "#/$defs/PermissionRuleConfig"
|
|
165
159
|
},
|
|
@@ -961,9 +955,6 @@
|
|
|
961
955
|
"explore": {
|
|
962
956
|
"$ref": "#/$defs/AgentConfig"
|
|
963
957
|
},
|
|
964
|
-
"scout": {
|
|
965
|
-
"$ref": "#/$defs/AgentConfig"
|
|
966
|
-
},
|
|
967
958
|
"title": {
|
|
968
959
|
"$ref": "#/$defs/AgentConfig"
|
|
969
960
|
},
|
|
@@ -15,7 +15,7 @@ from code_agnostic.tui import SyncConsoleUI
|
|
|
15
15
|
from code_agnostic.workspaces import WorkspaceService
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
@click.group(help="Manage workspace roots for repo
|
|
18
|
+
@click.group(help="Manage workspace roots for repo-local sync.")
|
|
19
19
|
def workspaces() -> None:
|
|
20
20
|
pass
|
|
21
21
|
|
|
@@ -39,8 +39,8 @@ def workspaces_add(obj: dict[str, str], name: str, path: Path) -> None:
|
|
|
39
39
|
ui.render_workspace_saved(name, str(path.expanduser().resolve()))
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
@workspaces.command("remove", help="
|
|
43
|
-
@click.option("--name", required=True, help="Workspace name to
|
|
42
|
+
@workspaces.command("remove", help="Unregister a workspace by name.")
|
|
43
|
+
@click.option("--name", required=True, help="Workspace name to unregister.")
|
|
44
44
|
@click.pass_obj
|
|
45
45
|
def workspaces_remove(obj: dict[str, str], name: str) -> None:
|
|
46
46
|
ui = SyncConsoleUI(Console())
|
|
@@ -292,7 +292,11 @@ class ImportService:
|
|
|
292
292
|
else:
|
|
293
293
|
skipped.append(f"MCP server skipped due to conflict: {name}")
|
|
294
294
|
|
|
295
|
-
normalized_payload = {
|
|
295
|
+
normalized_payload = {}
|
|
296
|
+
existing_schema = existing_payload.get("$schema")
|
|
297
|
+
if isinstance(existing_schema, str) and existing_schema:
|
|
298
|
+
normalized_payload["$schema"] = existing_schema
|
|
299
|
+
normalized_payload["mcpServers"] = dto_to_common_mcp(merged)
|
|
296
300
|
actions.append(
|
|
297
301
|
ImportAction(
|
|
298
302
|
section=ImportSection.MCP,
|
|
@@ -211,6 +211,36 @@ class LossinessExplainer:
|
|
|
211
211
|
reason="target does not support agent sandbox_mode",
|
|
212
212
|
)
|
|
213
213
|
)
|
|
214
|
+
if agent.metadata.tools.read is not True:
|
|
215
|
+
findings.extend(
|
|
216
|
+
self._findings_for_targets(
|
|
217
|
+
resource_path=resource_path,
|
|
218
|
+
property_name="tools.read",
|
|
219
|
+
targets=("codex",),
|
|
220
|
+
app=app,
|
|
221
|
+
reason="target does not support agent read permissions",
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
if agent.metadata.tools.write is not True:
|
|
225
|
+
findings.extend(
|
|
226
|
+
self._findings_for_targets(
|
|
227
|
+
resource_path=resource_path,
|
|
228
|
+
property_name="tools.write",
|
|
229
|
+
targets=("codex",),
|
|
230
|
+
app=app,
|
|
231
|
+
reason="target does not support agent write permissions",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
if agent.metadata.tools.mcp:
|
|
235
|
+
findings.extend(
|
|
236
|
+
self._findings_for_targets(
|
|
237
|
+
resource_path=resource_path,
|
|
238
|
+
property_name="tools.mcp",
|
|
239
|
+
targets=("codex",),
|
|
240
|
+
app=app,
|
|
241
|
+
reason="target does not support agent MCP permissions",
|
|
242
|
+
)
|
|
243
|
+
)
|
|
214
244
|
|
|
215
245
|
return findings
|
|
216
246
|
|
|
@@ -12,11 +12,15 @@ from code_agnostic.constants import (
|
|
|
12
12
|
)
|
|
13
13
|
from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
|
|
14
14
|
from code_agnostic.models import (
|
|
15
|
+
Action,
|
|
16
|
+
ActionStatus,
|
|
15
17
|
RepoSyncStatus,
|
|
16
18
|
WorkspaceRepoStatusRow,
|
|
17
19
|
WorkspaceStatusRow,
|
|
18
20
|
WorkspaceSyncStatus,
|
|
19
21
|
)
|
|
22
|
+
from code_agnostic.planner import SyncPlanner
|
|
23
|
+
from code_agnostic.utils import is_under
|
|
20
24
|
from code_agnostic.workspaces import WorkspaceService
|
|
21
25
|
|
|
22
26
|
|
|
@@ -30,6 +34,7 @@ class StatusService:
|
|
|
30
34
|
app_services: list[IAppConfigService] | None = None,
|
|
31
35
|
) -> list[WorkspaceStatusRow]:
|
|
32
36
|
status_rows: list[WorkspaceStatusRow] = []
|
|
37
|
+
workspace_actions = self._workspace_actions(source_repo, app_services)
|
|
33
38
|
|
|
34
39
|
for workspace in source_repo.load_workspaces():
|
|
35
40
|
workspace_name = workspace["name"]
|
|
@@ -75,7 +80,18 @@ class StatusService:
|
|
|
75
80
|
app_metas.append(meta)
|
|
76
81
|
|
|
77
82
|
repo_rows = [
|
|
78
|
-
self._repo_sync_status(
|
|
83
|
+
self._repo_sync_status(
|
|
84
|
+
repo,
|
|
85
|
+
ws_source,
|
|
86
|
+
app_metas,
|
|
87
|
+
workspace_actions=[
|
|
88
|
+
action
|
|
89
|
+
for action in workspace_actions
|
|
90
|
+
if action.workspace == workspace_name
|
|
91
|
+
and is_under(action.path, repo)
|
|
92
|
+
],
|
|
93
|
+
)
|
|
94
|
+
for repo in repos
|
|
79
95
|
]
|
|
80
96
|
|
|
81
97
|
detail = "all git repos synced"
|
|
@@ -98,14 +114,32 @@ class StatusService:
|
|
|
98
114
|
|
|
99
115
|
return status_rows
|
|
100
116
|
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _workspace_actions(
|
|
119
|
+
source_repo: ISourceRepository,
|
|
120
|
+
app_services: list[IAppConfigService] | None,
|
|
121
|
+
) -> list[Action]:
|
|
122
|
+
if not app_services:
|
|
123
|
+
return []
|
|
124
|
+
plan = SyncPlanner(core=source_repo, app_services=app_services).build()
|
|
125
|
+
return [action for action in plan.actions if action.workspace is not None]
|
|
126
|
+
|
|
101
127
|
@staticmethod
|
|
102
128
|
def _repo_sync_status(
|
|
103
129
|
repo_path: Path,
|
|
104
130
|
ws_source: WorkspaceConfigRepository,
|
|
105
131
|
app_metas: list[AppMetadata] | None = None,
|
|
132
|
+
workspace_actions: list[Action] | None = None,
|
|
106
133
|
) -> WorkspaceRepoStatusRow:
|
|
107
134
|
issues: list[str] = []
|
|
108
135
|
|
|
136
|
+
if workspace_actions is not None:
|
|
137
|
+
for action in workspace_actions:
|
|
138
|
+
if action.status == ActionStatus.NOOP:
|
|
139
|
+
continue
|
|
140
|
+
issues.append(StatusService._repo_action_issue(action, repo_path))
|
|
141
|
+
return StatusService._repo_status_row(repo_path, issues)
|
|
142
|
+
|
|
109
143
|
# Check workspace-managed config files in repo project dirs.
|
|
110
144
|
# Workspace rendering creates regular files (not symlinks) in
|
|
111
145
|
# ws_source.root/<project_dir_name>/..., and repos get the same.
|
|
@@ -165,19 +199,46 @@ class StatusService:
|
|
|
165
199
|
f"missing or mismatched {meta.app_id.value} agents link"
|
|
166
200
|
)
|
|
167
201
|
|
|
202
|
+
return StatusService._repo_status_row(repo_path, issues)
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _repo_status_row(repo_path: Path, issues: list[str]) -> WorkspaceRepoStatusRow:
|
|
168
206
|
if not issues:
|
|
169
207
|
return WorkspaceRepoStatusRow(
|
|
170
208
|
repo=repo_path.name,
|
|
171
209
|
status=RepoSyncStatus.SYNCED,
|
|
172
210
|
detail="linked",
|
|
173
211
|
)
|
|
174
|
-
|
|
175
212
|
return WorkspaceRepoStatusRow(
|
|
176
213
|
repo=repo_path.name,
|
|
177
214
|
status=RepoSyncStatus.NEEDS_SYNC,
|
|
178
215
|
detail="; ".join(issues),
|
|
179
216
|
)
|
|
180
217
|
|
|
218
|
+
@staticmethod
|
|
219
|
+
def _repo_action_issue(action: Action, repo_path: Path) -> str:
|
|
220
|
+
rel_path = StatusService._relative_repo_path(action.path, repo_path)
|
|
221
|
+
if action.status == ActionStatus.CREATE:
|
|
222
|
+
return f"missing {rel_path}"
|
|
223
|
+
if action.status in {ActionStatus.UPDATE, ActionStatus.FIX}:
|
|
224
|
+
return f"mismatched {rel_path}"
|
|
225
|
+
if action.status == ActionStatus.CONFLICT:
|
|
226
|
+
return f"conflict at {rel_path}"
|
|
227
|
+
if action.status == ActionStatus.REMOVE:
|
|
228
|
+
return f"stale {rel_path}"
|
|
229
|
+
return f"{action.status.value} {rel_path}"
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def _relative_repo_path(path: Path, repo_path: Path) -> str:
|
|
233
|
+
try:
|
|
234
|
+
return (
|
|
235
|
+
path.resolve(strict=False)
|
|
236
|
+
.relative_to(repo_path.resolve(strict=False))
|
|
237
|
+
.as_posix()
|
|
238
|
+
)
|
|
239
|
+
except ValueError:
|
|
240
|
+
return path.as_posix()
|
|
241
|
+
|
|
181
242
|
|
|
182
243
|
def _claude_project_mcp_exists(repo_path: Path) -> bool:
|
|
183
244
|
payload, error = read_json_safe(Path.home() / CLAUDE_CONFIG_FILENAME)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
from rich.console import Console
|
|
2
2
|
|
|
3
|
-
from code_agnostic.imports.models import
|
|
3
|
+
from code_agnostic.imports.models import (
|
|
4
|
+
ImportActionStatus,
|
|
5
|
+
ImportApplyResult,
|
|
6
|
+
ImportPlan,
|
|
7
|
+
)
|
|
4
8
|
from code_agnostic.models import (
|
|
5
9
|
AppStatusRow,
|
|
6
10
|
EditorStatusRow,
|
|
@@ -148,7 +152,7 @@ class SyncConsoleUI:
|
|
|
148
152
|
def render_workspace_saved(
|
|
149
153
|
self, name: str, path: str, removed: bool = False
|
|
150
154
|
) -> None:
|
|
151
|
-
verb = "
|
|
155
|
+
verb = "unregistered" if removed else "added"
|
|
152
156
|
border_style = UIStyle.YELLOW.value if removed else UIStyle.GREEN.value
|
|
153
157
|
self.console.print(
|
|
154
158
|
UISection.note(
|
|
@@ -330,6 +334,60 @@ class SyncConsoleUI:
|
|
|
330
334
|
UISection.note("skipped", skipped_text, style=UIStyle.YELLOW.value)
|
|
331
335
|
)
|
|
332
336
|
|
|
337
|
+
next_steps = self._import_next_steps(plan, mode)
|
|
338
|
+
if next_steps:
|
|
339
|
+
self.console.print(
|
|
340
|
+
UISection.note("next", next_steps, style=UIStyle.DIM.value)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
def _import_next_steps(plan: ImportPlan, mode: str) -> str | None:
|
|
345
|
+
parts = mode.split(":", 2)
|
|
346
|
+
if len(parts) != 3:
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
_, command, target = parts
|
|
350
|
+
target_flag = f" -a {target}" if target else ""
|
|
351
|
+
conflict_items = [*plan.errors, *plan.skipped]
|
|
352
|
+
has_conflict = any("conflict" in item.lower() for item in conflict_items)
|
|
353
|
+
|
|
354
|
+
if command == "apply":
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
if plan.errors:
|
|
358
|
+
lines = [
|
|
359
|
+
"Fix the errors above, then rerun the import preview.",
|
|
360
|
+
f"- code-agnostic import plan{target_flag}",
|
|
361
|
+
]
|
|
362
|
+
if has_conflict:
|
|
363
|
+
lines.append(
|
|
364
|
+
"For conflicts, choose --on-conflict overwrite or --on-conflict fail."
|
|
365
|
+
)
|
|
366
|
+
return "\n".join(lines)
|
|
367
|
+
|
|
368
|
+
has_writes = any(
|
|
369
|
+
action.status in {ImportActionStatus.CREATE, ImportActionStatus.UPDATE}
|
|
370
|
+
for action in plan.actions
|
|
371
|
+
)
|
|
372
|
+
if has_writes:
|
|
373
|
+
lines = [
|
|
374
|
+
"Review the imported items. If they match what you expect, write them to the hub.",
|
|
375
|
+
f"- code-agnostic import apply{target_flag}",
|
|
376
|
+
]
|
|
377
|
+
if has_conflict:
|
|
378
|
+
lines.append(
|
|
379
|
+
"Skipped conflicts will stay unchanged unless you rerun with --on-conflict overwrite."
|
|
380
|
+
)
|
|
381
|
+
return "\n".join(lines)
|
|
382
|
+
|
|
383
|
+
if has_conflict:
|
|
384
|
+
return (
|
|
385
|
+
"Skipped conflicts were left unchanged.\n"
|
|
386
|
+
f"- code-agnostic import plan{target_flag} --on-conflict overwrite"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return "No import changes needed.\n- code-agnostic validate"
|
|
390
|
+
|
|
333
391
|
def render_import_apply_result(self, result: ImportApplyResult) -> None:
|
|
334
392
|
self.render_apply_result(
|
|
335
393
|
applied=result.applied,
|
|
@@ -21,10 +21,10 @@ def read_json_safe(path: Path) -> tuple[Any | None, str | None]:
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def write_json(path: Path, payload: Any) -> None:
|
|
24
|
+
rendered = json.dumps(payload, indent=2, sort_keys=False) + "\n"
|
|
24
25
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
25
26
|
with path.open("w", encoding="utf-8") as handle:
|
|
26
|
-
|
|
27
|
-
handle.write("\n")
|
|
27
|
+
handle.write(rendered)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def merge_dict_overlay(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-agnostic
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
|
@@ -119,6 +119,10 @@ code-agnostic apply
|
|
|
119
119
|
| Import from | yes | yes | yes | yes |
|
|
120
120
|
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
121
121
|
|
|
122
|
+
`yes` means the resource type is synced for that editor. Some metadata is still
|
|
123
|
+
target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
|
|
124
|
+
that are omitted or rejected for a selected target.
|
|
125
|
+
|
|
122
126
|
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
|
|
123
127
|
|
|
124
128
|
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`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
|
|
@@ -160,19 +164,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
|
|
|
160
164
|
|
|
161
165
|
### Rules with metadata
|
|
162
166
|
|
|
163
|
-
|
|
167
|
+
New rules should use bundle directories with schema-validated metadata and a
|
|
168
|
+
separate prompt body:
|
|
164
169
|
|
|
165
|
-
```
|
|
166
|
-
|
|
170
|
+
```text
|
|
171
|
+
rules/python-style/
|
|
172
|
+
├── meta.yaml
|
|
173
|
+
└── prompt.md
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
# rules/python-style/meta.yaml
|
|
178
|
+
spec_version: v1
|
|
179
|
+
kind: rule
|
|
167
180
|
description: "Python coding standards"
|
|
168
181
|
globs: ["*.py"]
|
|
169
182
|
always_apply: false
|
|
170
|
-
|
|
183
|
+
```
|
|
171
184
|
|
|
185
|
+
```markdown
|
|
186
|
+
<!-- rules/python-style/prompt.md -->
|
|
172
187
|
Always use type hints. Prefer dataclasses over dicts.
|
|
173
188
|
```
|
|
174
189
|
|
|
175
190
|
Cross-compiled per editor: Cursor gets `.mdc` files with native frontmatter, OpenCode/Codex get `AGENTS.md` sections.
|
|
191
|
+
Legacy single-file rule markdown with YAML frontmatter remains supported for migration.
|
|
176
192
|
|
|
177
193
|
```bash
|
|
178
194
|
code-agnostic rules list
|
|
@@ -181,7 +197,10 @@ code-agnostic rules remove --name python-style
|
|
|
181
197
|
|
|
182
198
|
### Skills and agents
|
|
183
199
|
|
|
184
|
-
|
|
200
|
+
Use bundle directories for new skills and agents, then let `code-agnostic`
|
|
201
|
+
cross-compile them per editor. Install or edit skills in the `code-agnostic`
|
|
202
|
+
source of truth, then run `plan` / `apply`; do not hand-copy generated skills
|
|
203
|
+
into `.codex`, `.cursor`, `.agents`, or OpenCode directories.
|
|
185
204
|
|
|
186
205
|
```bash
|
|
187
206
|
code-agnostic skills list
|
|
@@ -262,7 +281,7 @@ The compiler migration is documented in:
|
|
|
262
281
|
|
|
263
282
|
- [x] Plan/apply/status sync engine
|
|
264
283
|
- [x] MCP server sync across editors
|
|
265
|
-
- [x] Skills and agents sync
|
|
284
|
+
- [x] Skills and agents sync across editors
|
|
266
285
|
- [x] Workspace propagation into git repos
|
|
267
286
|
- [x] Import from existing editor configs
|
|
268
287
|
- [x] Consistent CLI with named flags and aliases
|
|
@@ -49,4 +49,4 @@ def test_workspace_alias_remove(
|
|
|
49
49
|
cli_runner.invoke(cli, ["workspace", "add", "--name", "myws", "--path", str(ws)])
|
|
50
50
|
result = cli_runner.invoke(cli, ["workspace", "remove", "--name", "myws"])
|
|
51
51
|
assert result.exit_code == 0
|
|
52
|
-
assert "Workspace
|
|
52
|
+
assert "Workspace unregistered" in result.output
|
|
@@ -126,6 +126,38 @@ def test_explain_lossiness_reports_skill_tool_mappings(
|
|
|
126
126
|
]
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
def test_explain_lossiness_reports_codex_agent_tool_mappings(
|
|
130
|
+
minimal_shared_config: Path,
|
|
131
|
+
core_root: Path,
|
|
132
|
+
cli_runner,
|
|
133
|
+
) -> None:
|
|
134
|
+
agent_dir = core_root / "agents" / "reviewer"
|
|
135
|
+
agent_dir.mkdir(parents=True)
|
|
136
|
+
(agent_dir / "meta.yaml").write_text(
|
|
137
|
+
"spec_version: v1\n"
|
|
138
|
+
"kind: agent\n"
|
|
139
|
+
"name: reviewer\n"
|
|
140
|
+
"tools:\n"
|
|
141
|
+
" read: false\n"
|
|
142
|
+
" write: false\n"
|
|
143
|
+
" mcp:\n"
|
|
144
|
+
" - server: github\n"
|
|
145
|
+
" tool: create_review\n",
|
|
146
|
+
encoding="utf-8",
|
|
147
|
+
)
|
|
148
|
+
(agent_dir / "prompt.md").write_text("Review code.\n", encoding="utf-8")
|
|
149
|
+
|
|
150
|
+
result = cli_runner.invoke(cli, ["explain-lossiness", "--app", "codex"])
|
|
151
|
+
|
|
152
|
+
assert result.exit_code == 0
|
|
153
|
+
assert result.output.splitlines() == [
|
|
154
|
+
"resource_path\tapp\tproperty\tstatus\treason",
|
|
155
|
+
"agents/reviewer\tcodex\ttools.mcp\tignored\ttarget does not support agent MCP permissions",
|
|
156
|
+
"agents/reviewer\tcodex\ttools.read\tignored\ttarget does not support agent read permissions",
|
|
157
|
+
"agents/reviewer\tcodex\ttools.write\tignored\ttarget does not support agent write permissions",
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
|
|
129
161
|
def test_explain_lossiness_reports_workspace_paths(
|
|
130
162
|
minimal_shared_config: Path,
|
|
131
163
|
core_root: Path,
|
|
@@ -99,7 +99,7 @@ def test_workspaces_remove_with_named_flag(
|
|
|
99
99
|
cli_runner.invoke(cli, ["workspaces", "add", "--name", "myws", "--path", str(ws)])
|
|
100
100
|
result = cli_runner.invoke(cli, ["workspaces", "remove", "--name", "myws"])
|
|
101
101
|
assert result.exit_code == 0
|
|
102
|
-
assert "Workspace
|
|
102
|
+
assert "Workspace unregistered" in result.output
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def test_workspaces_git_exclude_with_workspace_flag(
|
|
@@ -60,6 +60,7 @@ def test_import_plan_codex_shows_sections(cli_runner, tmp_path: Path) -> None:
|
|
|
60
60
|
assert "mcp" in result.output
|
|
61
61
|
assert "skills" in result.output
|
|
62
62
|
assert "agents" in result.output
|
|
63
|
+
assert "code-agnostic import apply -a codex" in result.output
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def test_import_apply_codex_imports_mcp_skills_and_agents(
|
|
@@ -257,6 +258,30 @@ def test_import_apply_conflict_policy_fail(cli_runner, tmp_path: Path) -> None:
|
|
|
257
258
|
assert "conflict" in result.output.lower()
|
|
258
259
|
|
|
259
260
|
|
|
261
|
+
def test_import_plan_conflict_skip_shows_overwrite_next_step(
|
|
262
|
+
cli_runner, tmp_path: Path
|
|
263
|
+
) -> None:
|
|
264
|
+
_write_codex_source(
|
|
265
|
+
tmp_path / ".codex",
|
|
266
|
+
{"demo": {"command": "uvx"}},
|
|
267
|
+
with_skill=False,
|
|
268
|
+
)
|
|
269
|
+
core_mcp_path = tmp_path / ".config" / "code-agnostic" / "config" / "mcp.base.json"
|
|
270
|
+
core_mcp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
271
|
+
core_mcp_path.write_text(
|
|
272
|
+
json.dumps({"mcpServers": {"demo": {"url": "https://existing"}}}),
|
|
273
|
+
encoding="utf-8",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
result = cli_runner.invoke(
|
|
277
|
+
cli, ["import", "plan", "-a", "codex", "--include", "mcp"]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
assert result.exit_code == 0
|
|
281
|
+
assert "Skipped conflicts were left unchanged." in result.output
|
|
282
|
+
assert "code-agnostic import plan -a codex --on-conflict overwrite" in result.output
|
|
283
|
+
|
|
284
|
+
|
|
260
285
|
def test_import_plan_default_view_shows_app_labels(cli_runner, tmp_path: Path) -> None:
|
|
261
286
|
_write_codex_source(tmp_path / ".codex", {"demo": {"command": "uvx"}})
|
|
262
287
|
|