code-agnostic 0.2.0__tar.gz → 0.2.2__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.2.0 → code_agnostic-0.2.2}/PKG-INFO +6 -4
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/README.md +5 -3
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/app_id.py +11 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/git_exclude_service.py +11 -1
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/planner.py +1 -1
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/PKG-INFO +6 -4
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/pyproject.toml +1 -1
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_apply_apps.py +2 -5
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_plan.py +2 -2
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_workspaces.py +2 -2
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_git_exclude_service.py +2 -2
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_planner_rules.py +7 -5
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_workspace_config_sync.py +34 -142
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/LICENSE +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/schema.json +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/status.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/renderers.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/setup.cfg +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_utils.py +0 -0
- {code_agnostic-0.2.0 → code_agnostic-0.2.2}/tests/test_workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-agnostic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -97,11 +97,11 @@ code-agnostic apply
|
|
|
97
97
|
| Rules sync (cross-compiled) | yes | yes | yes |
|
|
98
98
|
| Skills sync | yes | yes | yes |
|
|
99
99
|
| Agents sync | yes | yes | -- |
|
|
100
|
-
| Workspace propagation | yes |
|
|
100
|
+
| Workspace propagation | yes | -- | yes |
|
|
101
101
|
| Import from | yes | yes | yes |
|
|
102
102
|
| Interactive import (TUI) | yes | yes | yes |
|
|
103
103
|
|
|
104
|
-
Codex does not support agents natively.
|
|
104
|
+
Codex does not support agents natively. Workspace propagation is intentionally disabled for Cursor 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
|
|
105
105
|
|
|
106
106
|
## Features
|
|
107
107
|
|
|
@@ -160,7 +160,9 @@ code-agnostic agents list
|
|
|
160
160
|
|
|
161
161
|
### Workspaces
|
|
162
162
|
|
|
163
|
-
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks.
|
|
163
|
+
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks for OpenCode and Codex.
|
|
164
|
+
|
|
165
|
+
`.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).
|
|
164
166
|
|
|
165
167
|
```bash
|
|
166
168
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -75,11 +75,11 @@ code-agnostic apply
|
|
|
75
75
|
| Rules sync (cross-compiled) | yes | yes | yes |
|
|
76
76
|
| Skills sync | yes | yes | yes |
|
|
77
77
|
| Agents sync | yes | yes | -- |
|
|
78
|
-
| Workspace propagation | yes |
|
|
78
|
+
| Workspace propagation | yes | -- | yes |
|
|
79
79
|
| Import from | yes | yes | yes |
|
|
80
80
|
| Interactive import (TUI) | yes | yes | yes |
|
|
81
81
|
|
|
82
|
-
Codex does not support agents natively.
|
|
82
|
+
Codex does not support agents natively. Workspace propagation is intentionally disabled for Cursor 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
|
|
83
83
|
|
|
84
84
|
## Features
|
|
85
85
|
|
|
@@ -138,7 +138,9 @@ code-agnostic agents list
|
|
|
138
138
|
|
|
139
139
|
### Workspaces
|
|
140
140
|
|
|
141
|
-
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks.
|
|
141
|
+
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks for OpenCode and Codex.
|
|
142
|
+
|
|
143
|
+
`.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).
|
|
142
144
|
|
|
143
145
|
```bash
|
|
144
146
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -17,6 +17,7 @@ class AppMetadata:
|
|
|
17
17
|
toggleable: bool
|
|
18
18
|
importable: bool
|
|
19
19
|
supports_import_agents: bool
|
|
20
|
+
supports_workspace_propagation: bool
|
|
20
21
|
project_dir_name: str | None = None
|
|
21
22
|
|
|
22
23
|
|
|
@@ -28,6 +29,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
28
29
|
toggleable=False,
|
|
29
30
|
importable=False,
|
|
30
31
|
supports_import_agents=True,
|
|
32
|
+
supports_workspace_propagation=False,
|
|
31
33
|
project_dir_name=None,
|
|
32
34
|
),
|
|
33
35
|
AppId.OPENCODE: AppMetadata(
|
|
@@ -37,6 +39,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
37
39
|
toggleable=True,
|
|
38
40
|
importable=True,
|
|
39
41
|
supports_import_agents=True,
|
|
42
|
+
supports_workspace_propagation=True,
|
|
40
43
|
project_dir_name=".opencode",
|
|
41
44
|
),
|
|
42
45
|
AppId.CURSOR: AppMetadata(
|
|
@@ -46,6 +49,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
46
49
|
toggleable=True,
|
|
47
50
|
importable=True,
|
|
48
51
|
supports_import_agents=True,
|
|
52
|
+
supports_workspace_propagation=False,
|
|
49
53
|
project_dir_name=".cursor",
|
|
50
54
|
),
|
|
51
55
|
AppId.CODEX: AppMetadata(
|
|
@@ -55,6 +59,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
55
59
|
toggleable=True,
|
|
56
60
|
importable=True,
|
|
57
61
|
supports_import_agents=False,
|
|
62
|
+
supports_workspace_propagation=True,
|
|
58
63
|
project_dir_name=".codex",
|
|
59
64
|
),
|
|
60
65
|
}
|
|
@@ -74,6 +79,7 @@ def app_ids_by_capability(
|
|
|
74
79
|
targetable: bool | None = None,
|
|
75
80
|
toggleable: bool | None = None,
|
|
76
81
|
importable: bool | None = None,
|
|
82
|
+
workspace_propagation: bool | None = None,
|
|
77
83
|
) -> list[AppId]:
|
|
78
84
|
ids: list[AppId] = []
|
|
79
85
|
for app_id, metadata in APP_CATALOG.items():
|
|
@@ -83,5 +89,10 @@ def app_ids_by_capability(
|
|
|
83
89
|
continue
|
|
84
90
|
if importable is not None and metadata.importable != importable:
|
|
85
91
|
continue
|
|
92
|
+
if (
|
|
93
|
+
workspace_propagation is not None
|
|
94
|
+
and metadata.supports_workspace_propagation != workspace_propagation
|
|
95
|
+
):
|
|
96
|
+
continue
|
|
86
97
|
ids.append(app_id)
|
|
87
98
|
return sorted(ids, key=lambda item: item.value)
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from code_agnostic.apps.app_id import app_metadata
|
|
8
9
|
from code_agnostic.constants import AGENTS_FILENAME, CLAUDE_FILENAME
|
|
9
10
|
from code_agnostic.core.repository import CoreRepository
|
|
10
11
|
from code_agnostic.utils import read_json_safe, write_json
|
|
@@ -46,7 +47,16 @@ class GitExcludeService:
|
|
|
46
47
|
if not config.get("include_defaults", True):
|
|
47
48
|
return extras
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
workspace_apps: list[str] = []
|
|
51
|
+
for app_name in enabled_apps:
|
|
52
|
+
try:
|
|
53
|
+
metadata = app_metadata(app_name)
|
|
54
|
+
except ValueError:
|
|
55
|
+
continue
|
|
56
|
+
if metadata.supports_workspace_propagation:
|
|
57
|
+
workspace_apps.append(app_name)
|
|
58
|
+
|
|
59
|
+
defaults = [f".{app_name}" for app_name in workspace_apps] + [
|
|
50
60
|
AGENTS_FILENAME,
|
|
51
61
|
CLAUDE_FILENAME,
|
|
52
62
|
]
|
|
@@ -147,7 +147,7 @@ class SyncPlanner:
|
|
|
147
147
|
|
|
148
148
|
for svc in self.app_services:
|
|
149
149
|
meta = app_metadata(svc.app_id)
|
|
150
|
-
if meta.project_dir_name is None:
|
|
150
|
+
if meta.project_dir_name is None or not meta.supports_workspace_propagation:
|
|
151
151
|
continue
|
|
152
152
|
|
|
153
153
|
ws_project_root = ws_source.root / meta.project_dir_name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-agnostic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -97,11 +97,11 @@ code-agnostic apply
|
|
|
97
97
|
| Rules sync (cross-compiled) | yes | yes | yes |
|
|
98
98
|
| Skills sync | yes | yes | yes |
|
|
99
99
|
| Agents sync | yes | yes | -- |
|
|
100
|
-
| Workspace propagation | yes |
|
|
100
|
+
| Workspace propagation | yes | -- | yes |
|
|
101
101
|
| Import from | yes | yes | yes |
|
|
102
102
|
| Interactive import (TUI) | yes | yes | yes |
|
|
103
103
|
|
|
104
|
-
Codex does not support agents natively.
|
|
104
|
+
Codex does not support agents natively. Workspace propagation is intentionally disabled for Cursor 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
|
|
105
105
|
|
|
106
106
|
## Features
|
|
107
107
|
|
|
@@ -160,7 +160,9 @@ code-agnostic agents list
|
|
|
160
160
|
|
|
161
161
|
### Workspaces
|
|
162
162
|
|
|
163
|
-
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks.
|
|
163
|
+
Register workspace directories. Repos inside them get rules, skills, and agents propagated as symlinks for OpenCode and Codex.
|
|
164
|
+
|
|
165
|
+
`.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).
|
|
164
166
|
|
|
165
167
|
```bash
|
|
166
168
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -45,7 +45,7 @@ def test_apply_all_with_cursor_and_codex_writes_both(
|
|
|
45
45
|
assert (tmp_path / ".codex" / "config.toml").exists()
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
48
|
+
def test_apply_cursor_target_does_not_apply_workspace_links(
|
|
49
49
|
minimal_shared_config: Path,
|
|
50
50
|
tmp_path: Path,
|
|
51
51
|
core_root: Path,
|
|
@@ -79,11 +79,8 @@ def test_apply_cursor_target_also_applies_workspace_links(
|
|
|
79
79
|
apply_result = cli_runner.invoke(cli, ["apply", "-a", "cursor"])
|
|
80
80
|
assert apply_result.exit_code == 0
|
|
81
81
|
|
|
82
|
-
# Cursor compiles rules to .mdc files in .cursor/rules/, then symlinks the dir
|
|
83
|
-
compiled_rules_dir = ws_config_dir / ".cursor" / "rules"
|
|
84
82
|
repo_rules_link = workspace_root / "service-a" / ".cursor" / "rules"
|
|
85
|
-
assert repo_rules_link.
|
|
86
|
-
assert repo_rules_link.resolve() == compiled_rules_dir.resolve()
|
|
83
|
+
assert not repo_rules_link.exists()
|
|
87
84
|
|
|
88
85
|
|
|
89
86
|
def test_apply_cursor_aborts_on_invalid_cursor_json(
|
|
@@ -15,7 +15,7 @@ def test_plan_shows_invalid_json_error_for_mcp_base(
|
|
|
15
15
|
assert "Invalid JSON format" in result.output
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def test_plan_target_cursor_excludes_workspace_actions(
|
|
19
19
|
minimal_shared_config: Path,
|
|
20
20
|
tmp_path: Path,
|
|
21
21
|
core_root: Path,
|
|
@@ -40,7 +40,7 @@ def test_plan_target_cursor_includes_workspace_actions(
|
|
|
40
40
|
plan_result = cli_runner.invoke(cli, ["plan", "-a", "cursor"])
|
|
41
41
|
assert plan_result.exit_code == 0
|
|
42
42
|
assert "cursor" in plan_result.output
|
|
43
|
-
assert "workspace config sync" in plan_result.output
|
|
43
|
+
assert "workspace config sync" not in plan_result.output
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def test_plan_with_no_apps_enabled(minimal_shared_config: Path, cli_runner) -> None:
|
|
@@ -151,7 +151,7 @@ def test_workspaces_git_exclude_writes_enabled_apps_and_default_rules(
|
|
|
151
151
|
assert result.exit_code == 0
|
|
152
152
|
assert "Updated git excludes" in result.output
|
|
153
153
|
|
|
154
|
-
expected_entries = ["
|
|
154
|
+
expected_entries = ["AGENTS.md", "CLAUDE.md"]
|
|
155
155
|
unexpected_entries = [".opencode", ".codex"]
|
|
156
156
|
|
|
157
157
|
for repo_name in ["repo-a", "repo-b"]:
|
|
@@ -194,5 +194,5 @@ def test_workspaces_git_exclude_can_target_single_workspace(
|
|
|
194
194
|
exclude_a = ws_a / "repo-a" / ".git" / "info" / "exclude"
|
|
195
195
|
exclude_b = ws_b / "repo-b" / ".git" / "info" / "exclude"
|
|
196
196
|
|
|
197
|
-
assert ".cursor" in exclude_a.read_text(encoding="utf-8")
|
|
197
|
+
assert ".cursor" not in exclude_a.read_text(encoding="utf-8")
|
|
198
198
|
assert not exclude_b.exists()
|
|
@@ -21,7 +21,7 @@ def service_with_workspace(minimal_shared_config: Path, tmp_path: Path):
|
|
|
21
21
|
|
|
22
22
|
def test_defaults_only(service_with_workspace) -> None:
|
|
23
23
|
entries = service_with_workspace.compute_entries("myws", ["cursor", "codex"])
|
|
24
|
-
assert ".cursor" in entries
|
|
24
|
+
assert ".cursor" not in entries
|
|
25
25
|
assert ".codex" in entries
|
|
26
26
|
assert AGENTS_FILENAME in entries
|
|
27
27
|
assert CLAUDE_FILENAME in entries
|
|
@@ -30,7 +30,7 @@ def test_defaults_only(service_with_workspace) -> None:
|
|
|
30
30
|
def test_custom_patterns_merged(service_with_workspace) -> None:
|
|
31
31
|
service_with_workspace.add_pattern("myws", "*.generated")
|
|
32
32
|
entries = service_with_workspace.compute_entries("myws", ["cursor"])
|
|
33
|
-
assert ".cursor" in entries
|
|
33
|
+
assert ".cursor" not in entries
|
|
34
34
|
assert AGENTS_FILENAME in entries
|
|
35
35
|
assert "*.generated" in entries
|
|
36
36
|
|
|
@@ -63,7 +63,9 @@ def test_plan_workspace_no_rules(setup_workspace, enable_app) -> None:
|
|
|
63
63
|
assert len(rule_actions) == 0
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def test_plan_rules_compiled_only_for_workspace_propagation_apps(
|
|
67
|
+
setup_workspace, enable_app
|
|
68
|
+
) -> None:
|
|
67
69
|
enable_app("opencode")
|
|
68
70
|
enable_app("cursor")
|
|
69
71
|
ws = setup_workspace
|
|
@@ -79,14 +81,14 @@ def test_plan_rules_compiled_per_app(setup_workspace, enable_app) -> None:
|
|
|
79
81
|
plan = apps.plan_for_target("all")
|
|
80
82
|
|
|
81
83
|
rule_actions = [a for a in plan.actions if a.kind == ActionKind.WRITE_RULE]
|
|
82
|
-
#
|
|
83
|
-
assert len(rule_actions)
|
|
84
|
+
# Cursor workspace propagation is intentionally disabled.
|
|
85
|
+
assert len(rule_actions) == 1
|
|
84
86
|
|
|
85
87
|
# OpenCode should get AGENTS.md
|
|
86
88
|
opencode_rules = [a for a in rule_actions if "opencode" in a.detail]
|
|
87
89
|
assert len(opencode_rules) >= 1
|
|
88
90
|
assert any("AGENTS.md" in a.detail for a in opencode_rules)
|
|
89
91
|
|
|
90
|
-
# Cursor should
|
|
92
|
+
# Cursor should not compile workspace rules.
|
|
91
93
|
cursor_rules = [a for a in rule_actions if "cursor" in a.detail]
|
|
92
|
-
assert
|
|
94
|
+
assert cursor_rules == []
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Tests for workspace-level config sync (MCP, skills, agents, rules)."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from code_agnostic.apps.codex.config_repository import CodexConfigRepository
|
|
@@ -158,7 +157,7 @@ def test_workspace_rules_symlinks_planned_for_each_repo(
|
|
|
158
157
|
# --- Workspace MCP config sync ---
|
|
159
158
|
|
|
160
159
|
|
|
161
|
-
def
|
|
160
|
+
def test_workspace_mcp_sync_skips_cursor_workspace_propagation(
|
|
162
161
|
minimal_shared_config: Path,
|
|
163
162
|
core_root: Path,
|
|
164
163
|
tmp_path: Path,
|
|
@@ -180,28 +179,9 @@ def test_workspace_mcp_sync_to_cursor_project_dirs(
|
|
|
180
179
|
cursor_root = tmp_path / ".cursor"
|
|
181
180
|
plan = SyncPlanner(core=core, app_services=[_cursor_service(cursor_root)]).build()
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if a.app == "workspace"
|
|
187
|
-
and a.kind in (ActionKind.WRITE_JSON, ActionKind.WRITE_TEXT)
|
|
188
|
-
]
|
|
189
|
-
assert len(mcp_actions) == 1
|
|
190
|
-
assert mcp_actions[0].workspace == "myws"
|
|
191
|
-
|
|
192
|
-
# The MCP config should be rendered once into workspace config dir
|
|
193
|
-
expected_path = ws_config / ".cursor" / "mcp.json"
|
|
194
|
-
assert mcp_actions[0].path == expected_path
|
|
195
|
-
|
|
196
|
-
# And then symlinked into each repo
|
|
197
|
-
link_actions = [
|
|
198
|
-
a
|
|
199
|
-
for a in plan.actions
|
|
200
|
-
if a.kind == ActionKind.SYMLINK and a.scope == "ws:cursor:repo_mcp"
|
|
201
|
-
]
|
|
202
|
-
assert len(link_actions) == 1
|
|
203
|
-
assert link_actions[0].path == workspace_root / "repo-a" / ".cursor" / "mcp.json"
|
|
204
|
-
assert link_actions[0].source == expected_path
|
|
182
|
+
workspace_actions = [a for a in plan.actions if a.app == "workspace"]
|
|
183
|
+
assert workspace_actions == []
|
|
184
|
+
assert not (ws_config / ".cursor").exists()
|
|
205
185
|
|
|
206
186
|
|
|
207
187
|
def test_workspace_mcp_sync_to_codex_project_dirs(
|
|
@@ -248,7 +228,7 @@ def test_workspace_mcp_sync_to_codex_project_dirs(
|
|
|
248
228
|
# --- Workspace skill symlinks ---
|
|
249
229
|
|
|
250
230
|
|
|
251
|
-
def
|
|
231
|
+
def test_workspace_skills_skip_cursor_workspace_propagation(
|
|
252
232
|
minimal_shared_config: Path,
|
|
253
233
|
core_root: Path,
|
|
254
234
|
tmp_path: Path,
|
|
@@ -267,33 +247,19 @@ def test_workspace_skills_symlinked_to_repo_project_dirs(
|
|
|
267
247
|
cursor_root = tmp_path / ".cursor"
|
|
268
248
|
plan = SyncPlanner(core=core, app_services=[_cursor_service(cursor_root)]).build()
|
|
269
249
|
|
|
270
|
-
|
|
271
|
-
ws_entry_actions = [
|
|
272
|
-
a
|
|
273
|
-
for a in plan.actions
|
|
274
|
-
if a.kind == ActionKind.SYMLINK and a.scope == "ws:cursor:skills_entries"
|
|
275
|
-
]
|
|
276
|
-
assert len(ws_entry_actions) == 1
|
|
277
|
-
assert ws_entry_actions[0].workspace == "myws"
|
|
278
|
-
assert ws_entry_actions[0].path == ws_config / ".cursor" / "skills" / "my-skill"
|
|
279
|
-
assert ws_entry_actions[0].source == ws_config / "skills" / "my-skill"
|
|
280
|
-
|
|
281
|
-
# Repo links its skills dir to workspace skills dir
|
|
282
|
-
repo_dir_actions = [
|
|
250
|
+
cursor_workspace_actions = [
|
|
283
251
|
a
|
|
284
252
|
for a in plan.actions
|
|
285
|
-
if a.
|
|
253
|
+
if a.scope is not None and a.scope.startswith("ws:cursor:")
|
|
286
254
|
]
|
|
287
|
-
assert
|
|
288
|
-
assert
|
|
289
|
-
assert repo_dir_actions[0].path == workspace_root / "repo-a" / ".cursor" / "skills"
|
|
290
|
-
assert repo_dir_actions[0].source == ws_config / ".cursor" / "skills"
|
|
255
|
+
assert cursor_workspace_actions == []
|
|
256
|
+
assert not (workspace_root / "repo-a" / ".cursor").exists()
|
|
291
257
|
|
|
292
258
|
|
|
293
259
|
# --- Workspace agent symlinks ---
|
|
294
260
|
|
|
295
261
|
|
|
296
|
-
def
|
|
262
|
+
def test_workspace_agents_skip_cursor_workspace_propagation(
|
|
297
263
|
minimal_shared_config: Path,
|
|
298
264
|
core_root: Path,
|
|
299
265
|
tmp_path: Path,
|
|
@@ -312,25 +278,13 @@ def test_workspace_agents_symlinked_to_repo_project_dirs(
|
|
|
312
278
|
cursor_root = tmp_path / ".cursor"
|
|
313
279
|
plan = SyncPlanner(core=core, app_services=[_cursor_service(cursor_root)]).build()
|
|
314
280
|
|
|
315
|
-
|
|
316
|
-
a
|
|
317
|
-
for a in plan.actions
|
|
318
|
-
if a.kind == ActionKind.SYMLINK and a.scope == "ws:cursor:agents_entries"
|
|
319
|
-
]
|
|
320
|
-
assert len(ws_entry_actions) == 1
|
|
321
|
-
assert ws_entry_actions[0].workspace == "myws"
|
|
322
|
-
assert ws_entry_actions[0].path == ws_config / ".cursor" / "agents" / "planner.md"
|
|
323
|
-
assert ws_entry_actions[0].source == ws_config / "agents" / "planner.md"
|
|
324
|
-
|
|
325
|
-
repo_dir_actions = [
|
|
281
|
+
cursor_workspace_actions = [
|
|
326
282
|
a
|
|
327
283
|
for a in plan.actions
|
|
328
|
-
if a.
|
|
284
|
+
if a.scope is not None and a.scope.startswith("ws:cursor:")
|
|
329
285
|
]
|
|
330
|
-
assert
|
|
331
|
-
assert
|
|
332
|
-
assert repo_dir_actions[0].path == workspace_root / "repo-a" / ".cursor" / "agents"
|
|
333
|
-
assert repo_dir_actions[0].source == ws_config / ".cursor" / "agents"
|
|
286
|
+
assert cursor_workspace_actions == []
|
|
287
|
+
assert not (workspace_root / "repo-a" / ".cursor").exists()
|
|
334
288
|
|
|
335
289
|
|
|
336
290
|
def test_workspace_agents_not_synced_to_codex(
|
|
@@ -379,23 +333,22 @@ def test_executor_persists_workspace_state_separately(
|
|
|
379
333
|
(ws_config / "rules").mkdir(parents=True, exist_ok=True)
|
|
380
334
|
(ws_config / "rules" / "shared.md").write_text("rules", encoding="utf-8")
|
|
381
335
|
|
|
382
|
-
|
|
383
|
-
plan = SyncPlanner(core=core, app_services=[
|
|
336
|
+
codex_root = tmp_path / ".codex"
|
|
337
|
+
plan = SyncPlanner(core=core, app_services=[_codex_service(codex_root)]).build()
|
|
384
338
|
|
|
385
339
|
applied, failed, failures = SyncExecutor(core=core).execute(plan)
|
|
386
340
|
assert failed == 0
|
|
387
341
|
|
|
388
|
-
# Workspace state persisted to workspace state file
|
|
389
|
-
# Cursor compiles rules to .mdc in .cursor/rules/ and symlinks the dir
|
|
342
|
+
# Workspace state persisted to workspace state file.
|
|
390
343
|
ws_repo = WorkspaceConfigRepository(root=ws_config)
|
|
391
344
|
ws_state = ws_repo.load_state()
|
|
392
345
|
managed = ws_state["managed_links"]
|
|
393
|
-
assert "
|
|
394
|
-
assert len(managed["
|
|
346
|
+
assert "rules" in managed
|
|
347
|
+
assert len(managed["rules"]) == 1
|
|
395
348
|
|
|
396
349
|
# Global state should not contain workspace links
|
|
397
350
|
global_state = core.load_state()
|
|
398
|
-
assert "
|
|
351
|
+
assert "rules" not in global_state.get("managed_links", {})
|
|
399
352
|
|
|
400
353
|
|
|
401
354
|
# --- Full roundtrip with apply ---
|
|
@@ -428,12 +381,10 @@ def test_full_workspace_config_roundtrip(
|
|
|
428
381
|
(ws_config / "agents").mkdir(parents=True)
|
|
429
382
|
(ws_config / "agents" / "ws-agent.md").write_text("a", encoding="utf-8")
|
|
430
383
|
|
|
431
|
-
cursor_root = tmp_path / ".cursor"
|
|
432
384
|
plan = SyncPlanner(
|
|
433
385
|
core=core,
|
|
434
386
|
app_services=[
|
|
435
387
|
_opencode_service(core, opencode_root),
|
|
436
|
-
_cursor_service(cursor_root),
|
|
437
388
|
],
|
|
438
389
|
).build()
|
|
439
390
|
|
|
@@ -451,70 +402,21 @@ def test_full_workspace_config_roundtrip(
|
|
|
451
402
|
assert link.is_symlink()
|
|
452
403
|
assert link.resolve() == compiled_agents.resolve()
|
|
453
404
|
|
|
454
|
-
#
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
assert ws_root_cursor_rules.is_symlink()
|
|
458
|
-
assert ws_root_cursor_rules.resolve() == compiled_cursor_rules.resolve()
|
|
459
|
-
|
|
460
|
-
for repo_name in ["repo-a", "repo-b"]:
|
|
461
|
-
repo_rules = workspace_root / repo_name / ".cursor" / "rules"
|
|
462
|
-
assert repo_rules.is_symlink()
|
|
463
|
-
assert repo_rules.resolve() == compiled_cursor_rules.resolve()
|
|
464
|
-
|
|
465
|
-
# Check MCP config rendered into workspace, then linked into repos
|
|
466
|
-
ws_cursor_mcp = ws_config / ".cursor" / "mcp.json"
|
|
467
|
-
assert ws_cursor_mcp.exists()
|
|
468
|
-
payload = json.loads(ws_cursor_mcp.read_text(encoding="utf-8"))
|
|
469
|
-
assert "ws-server" in payload.get("mcpServers", {})
|
|
470
|
-
|
|
471
|
-
# Workspace root also gets the shared links (for multi-root workspace sessions)
|
|
472
|
-
ws_root_cursor_mcp = workspace_root / ".cursor" / "mcp.json"
|
|
473
|
-
assert ws_root_cursor_mcp.is_symlink()
|
|
474
|
-
assert ws_root_cursor_mcp.resolve() == ws_cursor_mcp.resolve()
|
|
475
|
-
|
|
405
|
+
# Cursor workspace propagation is intentionally disabled.
|
|
406
|
+
assert not (ws_config / ".cursor").exists()
|
|
407
|
+
assert not (workspace_root / ".cursor").exists()
|
|
476
408
|
for repo_name in ["repo-a", "repo-b"]:
|
|
477
|
-
|
|
478
|
-
assert cursor_mcp.is_symlink()
|
|
479
|
-
assert cursor_mcp.resolve() == ws_cursor_mcp.resolve()
|
|
480
|
-
|
|
481
|
-
# Check skill links in workspace project dir and repo linkage
|
|
482
|
-
ws_skill_link = ws_config / ".cursor" / "skills" / "ws-skill"
|
|
483
|
-
assert ws_skill_link.is_symlink()
|
|
484
|
-
|
|
485
|
-
ws_root_skill_dir = workspace_root / ".cursor" / "skills"
|
|
486
|
-
assert ws_root_skill_dir.is_symlink()
|
|
487
|
-
|
|
488
|
-
for repo_name in ["repo-a", "repo-b"]:
|
|
489
|
-
skill_dir = workspace_root / repo_name / ".cursor" / "skills"
|
|
490
|
-
assert skill_dir.is_symlink()
|
|
491
|
-
skill_link = skill_dir / "ws-skill"
|
|
492
|
-
assert skill_link.is_symlink()
|
|
493
|
-
|
|
494
|
-
# Check agent links in workspace project dir and repo linkage
|
|
495
|
-
ws_agent_link = ws_config / ".cursor" / "agents" / "ws-agent.md"
|
|
496
|
-
assert ws_agent_link.is_symlink()
|
|
497
|
-
|
|
498
|
-
ws_root_agent_dir = workspace_root / ".cursor" / "agents"
|
|
499
|
-
assert ws_root_agent_dir.is_symlink()
|
|
500
|
-
|
|
501
|
-
for repo_name in ["repo-a", "repo-b"]:
|
|
502
|
-
agent_dir = workspace_root / repo_name / ".cursor" / "agents"
|
|
503
|
-
assert agent_dir.is_symlink()
|
|
504
|
-
agent_link = agent_dir / "ws-agent.md"
|
|
505
|
-
assert agent_link.is_symlink()
|
|
409
|
+
assert not (workspace_root / repo_name / ".cursor").exists()
|
|
506
410
|
|
|
507
411
|
|
|
508
412
|
# --- Stale workspace link cleanup ---
|
|
509
413
|
|
|
510
414
|
|
|
511
|
-
def
|
|
415
|
+
def test_workspace_rules_not_linked_for_cursor(
|
|
512
416
|
minimal_shared_config: Path,
|
|
513
417
|
core_root: Path,
|
|
514
418
|
tmp_path: Path,
|
|
515
419
|
) -> None:
|
|
516
|
-
import shutil
|
|
517
|
-
|
|
518
420
|
workspace_root = tmp_path / "workspace"
|
|
519
421
|
workspace_root.mkdir()
|
|
520
422
|
(workspace_root / "repo-a" / ".git").mkdir(parents=True)
|
|
@@ -530,20 +432,10 @@ def test_workspace_stale_rules_cleanup_on_config_removal(
|
|
|
530
432
|
plan = SyncPlanner(core=core, app_services=[_cursor_service(cursor_root)]).build()
|
|
531
433
|
|
|
532
434
|
SyncExecutor(core=core).execute(plan)
|
|
533
|
-
|
|
534
|
-
link = workspace_root / "repo-a" / ".cursor" / "rules"
|
|
535
|
-
assert link.is_symlink()
|
|
536
|
-
|
|
537
|
-
# Remove rules directory
|
|
538
|
-
shutil.rmtree(ws_config / "rules")
|
|
539
|
-
|
|
540
|
-
plan2 = SyncPlanner(core=core, app_services=[_cursor_service(cursor_root)]).build()
|
|
541
|
-
|
|
542
|
-
SyncExecutor(core=core).execute(plan2)
|
|
543
|
-
assert not link.is_symlink()
|
|
435
|
+
assert not (workspace_root / "repo-a" / ".cursor" / "rules").exists()
|
|
544
436
|
|
|
545
437
|
|
|
546
|
-
def
|
|
438
|
+
def test_workspace_stale_skills_cleanup_when_skills_removed_for_codex(
|
|
547
439
|
minimal_shared_config: Path,
|
|
548
440
|
core_root: Path,
|
|
549
441
|
tmp_path: Path,
|
|
@@ -560,32 +452,32 @@ def test_workspace_stale_skills_cleanup_when_skills_removed(
|
|
|
560
452
|
(ws_config / "skills" / "my-skill").mkdir(parents=True)
|
|
561
453
|
(ws_config / "skills" / "my-skill" / "SKILL.md").write_text("s", encoding="utf-8")
|
|
562
454
|
|
|
563
|
-
|
|
564
|
-
plan = SyncPlanner(core=core, app_services=[
|
|
455
|
+
codex_root = tmp_path / ".codex"
|
|
456
|
+
plan = SyncPlanner(core=core, app_services=[_codex_service(codex_root)]).build()
|
|
565
457
|
|
|
566
458
|
SyncExecutor(core=core).execute(plan)
|
|
567
|
-
skill_dir_link = workspace_root / "repo-a" / ".
|
|
459
|
+
skill_dir_link = workspace_root / "repo-a" / ".codex" / "skills"
|
|
568
460
|
assert skill_dir_link.is_symlink()
|
|
569
461
|
|
|
570
462
|
# Verify state was persisted with workspace scopes
|
|
571
463
|
ws_repo = WorkspaceConfigRepository(root=ws_config)
|
|
572
464
|
state = ws_repo.load_state()
|
|
573
|
-
assert "ws:
|
|
574
|
-
assert "ws:
|
|
465
|
+
assert "ws:codex:skills_entries" in state["managed_links"]
|
|
466
|
+
assert "ws:codex:repo_skills_dir" in state["managed_links"]
|
|
575
467
|
|
|
576
468
|
# Remove all skills from workspace config
|
|
577
469
|
import shutil
|
|
578
470
|
|
|
579
471
|
shutil.rmtree(ws_config / "skills")
|
|
580
472
|
|
|
581
|
-
plan2 = SyncPlanner(core=core, app_services=[
|
|
473
|
+
plan2 = SyncPlanner(core=core, app_services=[_codex_service(codex_root)]).build()
|
|
582
474
|
|
|
583
475
|
# Should have remove actions for stale workspace entry and repo dir link
|
|
584
476
|
remove_actions = [
|
|
585
477
|
a
|
|
586
478
|
for a in plan2.actions
|
|
587
479
|
if a.kind == ActionKind.REMOVE_SYMLINK
|
|
588
|
-
and a.scope in ("ws:
|
|
480
|
+
and a.scope in ("ws:codex:skills_entries", "ws:codex:repo_skills_dir")
|
|
589
481
|
]
|
|
590
482
|
assert len(remove_actions) == 2
|
|
591
483
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/common/interfaces/repositories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/config_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_agnostic-0.2.0 → code_agnostic-0.2.2}/code_agnostic/apps/opencode/schema_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|