code-agnostic 0.3.8__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.8 → code_agnostic-0.3.9}/PKG-INFO +31 -14
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/README.md +30 -11
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/codex.py +3 -6
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/app_id.py +1 -1
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/claude/service.py +66 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/schema.json +8 -0
- code_agnostic-0.3.9/code_agnostic/apps/common/compiled_planning.py +197 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/repositories.py +0 -10
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/service.py +14 -30
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/schema.json +1 -10
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/service.py +0 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/workspaces.py +4 -9
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/git_exclude_service.py +41 -23
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/imports/adapters.py +10 -10
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/imports/service.py +5 -1
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/lossiness.py +30 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/planner.py +12 -15
- code_agnostic-0.3.9/code_agnostic/status.py +253 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/renderers.py +60 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/utils.py +2 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/PKG-INFO +31 -14
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/SOURCES.txt +0 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/requires.txt +0 -2
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/pyproject.toml +2 -4
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_aliases.py +1 -1
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_apply_apps.py +12 -38
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_explain_lossiness.py +32 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_flags.py +1 -1
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_import.py +25 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_plan.py +4 -14
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_status.py +44 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_workspaces.py +17 -54
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_git_exclude_service.py +9 -31
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_utils.py +45 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_workspace_config_sync.py +16 -120
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_workspace_repo_status.py +11 -57
- code_agnostic-0.3.8/code_agnostic/apps/common/compiled_planning.py +0 -115
- code_agnostic-0.3.8/code_agnostic/generated_artifacts.py +0 -134
- code_agnostic-0.3.8/code_agnostic/status.py +0 -143
- code_agnostic-0.3.8/code_agnostic/workspace_artifacts.py +0 -238
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/LICENSE +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/setup.cfg +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.3.8 → code_agnostic-0.3.9}/tests/test_version.py +0 -0
- {code_agnostic-0.3.8 → 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
|
|
@@ -19,8 +19,6 @@ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
|
19
19
|
Requires-Dist: jsonschema>=4.0; extra == "dev"
|
|
20
20
|
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
21
21
|
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
22
|
-
Requires-Dist: types-jsonschema>=4.0; extra == "dev"
|
|
23
|
-
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
24
22
|
Dynamic: license-file
|
|
25
23
|
|
|
26
24
|
# code-agnostic
|
|
@@ -117,15 +115,19 @@ code-agnostic apply
|
|
|
117
115
|
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
|
|
118
116
|
| Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
|
|
119
117
|
| Nested `AGENTS.md` discovery | -- | yes | yes | -- |
|
|
120
|
-
| Workspace propagation | yes |
|
|
118
|
+
| Workspace propagation | yes | -- | yes | yes |
|
|
121
119
|
| Import from | yes | yes | yes | yes |
|
|
122
120
|
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
123
121
|
|
|
124
|
-
|
|
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
|
+
|
|
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
|
|
125
127
|
|
|
126
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`.
|
|
127
129
|
|
|
128
|
-
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
130
|
+
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` still disables Cursor workspace propagation, so it does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
129
131
|
|
|
130
132
|
## Features
|
|
131
133
|
|
|
@@ -162,19 +164,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
|
|
|
162
164
|
|
|
163
165
|
### Rules with metadata
|
|
164
166
|
|
|
165
|
-
|
|
167
|
+
New rules should use bundle directories with schema-validated metadata and a
|
|
168
|
+
separate prompt body:
|
|
166
169
|
|
|
167
|
-
```
|
|
168
|
-
|
|
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
|
|
169
180
|
description: "Python coding standards"
|
|
170
181
|
globs: ["*.py"]
|
|
171
182
|
always_apply: false
|
|
172
|
-
|
|
183
|
+
```
|
|
173
184
|
|
|
185
|
+
```markdown
|
|
186
|
+
<!-- rules/python-style/prompt.md -->
|
|
174
187
|
Always use type hints. Prefer dataclasses over dicts.
|
|
175
188
|
```
|
|
176
189
|
|
|
177
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.
|
|
178
192
|
|
|
179
193
|
```bash
|
|
180
194
|
code-agnostic rules list
|
|
@@ -183,7 +197,10 @@ code-agnostic rules remove --name python-style
|
|
|
183
197
|
|
|
184
198
|
### Skills and agents
|
|
185
199
|
|
|
186
|
-
|
|
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.
|
|
187
204
|
|
|
188
205
|
```bash
|
|
189
206
|
code-agnostic skills list
|
|
@@ -213,9 +230,9 @@ That command should copy the skill into the source of truth and then run the nor
|
|
|
213
230
|
|
|
214
231
|
### Workspaces
|
|
215
232
|
|
|
216
|
-
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`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode,
|
|
233
|
+
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`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Codex, and Claude.
|
|
217
234
|
|
|
218
|
-
|
|
235
|
+
`.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).
|
|
219
236
|
|
|
220
237
|
```bash
|
|
221
238
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -264,7 +281,7 @@ The compiler migration is documented in:
|
|
|
264
281
|
|
|
265
282
|
- [x] Plan/apply/status sync engine
|
|
266
283
|
- [x] MCP server sync across editors
|
|
267
|
-
- [x] Skills and agents sync
|
|
284
|
+
- [x] Skills and agents sync across editors
|
|
268
285
|
- [x] Workspace propagation into git repos
|
|
269
286
|
- [x] Import from existing editor configs
|
|
270
287
|
- [x] Consistent CLI with named flags and aliases
|
|
@@ -92,15 +92,19 @@ code-agnostic apply
|
|
|
92
92
|
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
|
|
93
93
|
| Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
|
|
94
94
|
| Nested `AGENTS.md` discovery | -- | yes | yes | -- |
|
|
95
|
-
| Workspace propagation | yes |
|
|
95
|
+
| Workspace propagation | yes | -- | yes | yes |
|
|
96
96
|
| Import from | yes | yes | yes | yes |
|
|
97
97
|
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
98
98
|
|
|
99
|
-
|
|
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
|
+
|
|
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`.
|
|
102
106
|
|
|
103
|
-
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
107
|
+
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` still disables Cursor workspace propagation, so it does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
104
108
|
|
|
105
109
|
## Features
|
|
106
110
|
|
|
@@ -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
|
|
@@ -188,9 +207,9 @@ That command should copy the skill into the source of truth and then run the nor
|
|
|
188
207
|
|
|
189
208
|
### Workspaces
|
|
190
209
|
|
|
191
|
-
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`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode,
|
|
210
|
+
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`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Codex, and Claude.
|
|
192
211
|
|
|
193
|
-
|
|
212
|
+
`.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).
|
|
194
213
|
|
|
195
214
|
```bash
|
|
196
215
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -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
|
|
@@ -69,13 +69,10 @@ def serialize_codex_agent(agent: Agent) -> str:
|
|
|
69
69
|
description = agent.metadata.description or agent.metadata.name or agent.name
|
|
70
70
|
|
|
71
71
|
doc = tomlkit.document()
|
|
72
|
-
doc.add("name",
|
|
73
|
-
doc.add("description",
|
|
72
|
+
doc.add("name", agent.metadata.name or agent.name)
|
|
73
|
+
doc.add("description", description)
|
|
74
74
|
if agent.metadata.nickname_candidates:
|
|
75
|
-
doc.add(
|
|
76
|
-
"nickname_candidates",
|
|
77
|
-
tomlkit.item(list(agent.metadata.nickname_candidates)),
|
|
78
|
-
)
|
|
75
|
+
doc.add("nickname_candidates", list(agent.metadata.nickname_candidates))
|
|
79
76
|
model = agent.metadata.effective_value("codex", "model")
|
|
80
77
|
if model:
|
|
81
78
|
doc.add("model", model)
|
|
@@ -63,7 +63,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
63
63
|
toggleable=True,
|
|
64
64
|
importable=True,
|
|
65
65
|
supports_import_agents=True,
|
|
66
|
-
supports_workspace_propagation=
|
|
66
|
+
supports_workspace_propagation=False,
|
|
67
67
|
project_dir_name=CURSOR_PROJECT_DIRNAME,
|
|
68
68
|
config_filename=CURSOR_CONFIG_FILENAME,
|
|
69
69
|
),
|
|
@@ -8,6 +8,10 @@ from code_agnostic.agents.parser import parse_agent
|
|
|
8
8
|
from code_agnostic.apps.app_id import AppId, app_label
|
|
9
9
|
from code_agnostic.apps.claude.config_repository import ClaudeConfigRepository
|
|
10
10
|
from code_agnostic.apps.claude.mapper import ClaudeMCPMapper
|
|
11
|
+
from code_agnostic.apps.common.compiled_planning import (
|
|
12
|
+
find_replaceable_symlink_ancestor,
|
|
13
|
+
plan_owned_compiled_text_action,
|
|
14
|
+
)
|
|
11
15
|
from code_agnostic.apps.common.framework import RegisteredAppConfigService
|
|
12
16
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
13
17
|
from code_agnostic.apps.common.interfaces.repositories import IAppConfigRepository
|
|
@@ -131,7 +135,7 @@ class ClaudeConfigService(RegisteredAppConfigService):
|
|
|
131
135
|
removable_links: list[Path],
|
|
132
136
|
) -> tuple[list[Action], list[Path], list[str]]:
|
|
133
137
|
compiler = ClaudeSkillCompiler()
|
|
134
|
-
return self.
|
|
138
|
+
return self._plan_owned_text_actions(
|
|
135
139
|
sources=sources,
|
|
136
140
|
target_dir=target_dir,
|
|
137
141
|
scope=scope,
|
|
@@ -169,7 +173,7 @@ class ClaudeConfigService(RegisteredAppConfigService):
|
|
|
169
173
|
agent = parse_agent(source)
|
|
170
174
|
return claude_agent_target_path(target_dir, agent), compiler.compile(agent)
|
|
171
175
|
|
|
172
|
-
return self.
|
|
176
|
+
return self._plan_owned_text_actions(
|
|
173
177
|
sources=sources,
|
|
174
178
|
target_dir=target_dir,
|
|
175
179
|
scope=scope,
|
|
@@ -182,3 +186,63 @@ class ClaudeConfigService(RegisteredAppConfigService):
|
|
|
182
186
|
update_detail="update compiled claude agent",
|
|
183
187
|
conflict_message="Claude agent sync skipped (conflict): {target}",
|
|
184
188
|
)
|
|
189
|
+
|
|
190
|
+
def _plan_owned_text_actions(
|
|
191
|
+
self,
|
|
192
|
+
*,
|
|
193
|
+
sources: list[Path],
|
|
194
|
+
target_dir: Path,
|
|
195
|
+
scope: str,
|
|
196
|
+
app: str,
|
|
197
|
+
managed_paths: list[Path],
|
|
198
|
+
removable_links: list[Path],
|
|
199
|
+
compile_source,
|
|
200
|
+
create_detail: str,
|
|
201
|
+
noop_detail: str,
|
|
202
|
+
update_detail: str,
|
|
203
|
+
conflict_message: str,
|
|
204
|
+
) -> tuple[list[Action], list[Path], list[str]]:
|
|
205
|
+
managed_path_set = {path.resolve(strict=False) for path in managed_paths}
|
|
206
|
+
removable_link_set = {path.resolve(strict=False) for path in removable_links}
|
|
207
|
+
actions: list[Action] = []
|
|
208
|
+
desired_paths: list[Path] = []
|
|
209
|
+
skipped: list[str] = []
|
|
210
|
+
scheduled_removals: set[Path] = set()
|
|
211
|
+
|
|
212
|
+
for source in sources:
|
|
213
|
+
target, payload = compile_source(source)
|
|
214
|
+
desired_paths.append(target)
|
|
215
|
+
replaceable_symlink = find_replaceable_symlink_ancestor(target, target_dir)
|
|
216
|
+
if (
|
|
217
|
+
replaceable_symlink is not None
|
|
218
|
+
and replaceable_symlink not in scheduled_removals
|
|
219
|
+
):
|
|
220
|
+
scheduled_removals.add(replaceable_symlink)
|
|
221
|
+
removable_link_set.add(replaceable_symlink.resolve(strict=False))
|
|
222
|
+
actions.append(
|
|
223
|
+
Action(
|
|
224
|
+
kind=ActionKind.REMOVE_SYMLINK,
|
|
225
|
+
path=replaceable_symlink,
|
|
226
|
+
status=ActionStatus.REMOVE,
|
|
227
|
+
detail=f"replace compiled {scope} symlink",
|
|
228
|
+
app=app,
|
|
229
|
+
scope=scope,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
action = plan_owned_compiled_text_action(
|
|
233
|
+
target=target,
|
|
234
|
+
payload=payload,
|
|
235
|
+
managed_paths=managed_path_set,
|
|
236
|
+
removable_link_paths=removable_link_set,
|
|
237
|
+
managed_root=target_dir,
|
|
238
|
+
scope=scope,
|
|
239
|
+
app=app,
|
|
240
|
+
create_detail=create_detail,
|
|
241
|
+
noop_detail=noop_detail,
|
|
242
|
+
update_detail=update_detail,
|
|
243
|
+
)
|
|
244
|
+
actions.append(action)
|
|
245
|
+
if action.status == ActionStatus.CONFLICT:
|
|
246
|
+
skipped.append(conflict_message.format(target=target))
|
|
247
|
+
|
|
248
|
+
return actions, desired_paths, skipped
|
|
@@ -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
|
{
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from code_agnostic.models import Action, ActionKind, ActionStatus
|
|
4
|
+
|
|
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
|
+
|
|
18
|
+
def _symlink_ancestor_state(
|
|
19
|
+
target: Path, removable_link_paths: set[Path], managed_root: Path | None
|
|
20
|
+
) -> tuple[bool, bool]:
|
|
21
|
+
current = target
|
|
22
|
+
found_symlink = False
|
|
23
|
+
while True:
|
|
24
|
+
in_managed_root = managed_root is None or (
|
|
25
|
+
current == managed_root or current.is_relative_to(managed_root)
|
|
26
|
+
)
|
|
27
|
+
if current.is_symlink() and in_managed_root:
|
|
28
|
+
found_symlink = True
|
|
29
|
+
current_key = current.resolve(strict=False)
|
|
30
|
+
if current_key in removable_link_paths:
|
|
31
|
+
return True, True
|
|
32
|
+
if current.parent == current:
|
|
33
|
+
return found_symlink, False
|
|
34
|
+
current = current.parent
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def plan_compiled_text_action(
|
|
38
|
+
*,
|
|
39
|
+
target: Path,
|
|
40
|
+
payload: str,
|
|
41
|
+
managed_paths: set[Path],
|
|
42
|
+
removable_link_paths: set[Path] | None = None,
|
|
43
|
+
managed_root: Path | None = None,
|
|
44
|
+
scope: str,
|
|
45
|
+
app: str,
|
|
46
|
+
create_detail: str,
|
|
47
|
+
noop_detail: str,
|
|
48
|
+
update_detail: str,
|
|
49
|
+
conflict_detail: str = "non-managed path exists",
|
|
50
|
+
) -> Action:
|
|
51
|
+
removable = removable_link_paths or set()
|
|
52
|
+
has_symlink_ancestor, is_removable_ancestor = _symlink_ancestor_state(
|
|
53
|
+
target, removable, managed_root
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if has_symlink_ancestor and not is_removable_ancestor:
|
|
57
|
+
return Action(
|
|
58
|
+
kind=ActionKind.WRITE_TEXT,
|
|
59
|
+
path=target,
|
|
60
|
+
status=ActionStatus.CONFLICT,
|
|
61
|
+
detail=conflict_detail,
|
|
62
|
+
payload=payload,
|
|
63
|
+
app=app,
|
|
64
|
+
scope=scope,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if has_symlink_ancestor and is_removable_ancestor:
|
|
68
|
+
if target.is_file():
|
|
69
|
+
existing = target.read_text(encoding="utf-8")
|
|
70
|
+
if existing == payload:
|
|
71
|
+
return Action(
|
|
72
|
+
kind=ActionKind.WRITE_TEXT,
|
|
73
|
+
path=target,
|
|
74
|
+
status=ActionStatus.NOOP,
|
|
75
|
+
detail=noop_detail,
|
|
76
|
+
payload=payload,
|
|
77
|
+
app=app,
|
|
78
|
+
scope=scope,
|
|
79
|
+
)
|
|
80
|
+
return Action(
|
|
81
|
+
kind=ActionKind.WRITE_TEXT,
|
|
82
|
+
path=target,
|
|
83
|
+
status=ActionStatus.UPDATE,
|
|
84
|
+
detail=update_detail,
|
|
85
|
+
payload=payload,
|
|
86
|
+
app=app,
|
|
87
|
+
scope=scope,
|
|
88
|
+
)
|
|
89
|
+
return Action(
|
|
90
|
+
kind=ActionKind.WRITE_TEXT,
|
|
91
|
+
path=target,
|
|
92
|
+
status=ActionStatus.CREATE,
|
|
93
|
+
detail=create_detail,
|
|
94
|
+
payload=payload,
|
|
95
|
+
app=app,
|
|
96
|
+
scope=scope,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if not target.exists() and not target.is_symlink():
|
|
100
|
+
return Action(
|
|
101
|
+
kind=ActionKind.WRITE_TEXT,
|
|
102
|
+
path=target,
|
|
103
|
+
status=ActionStatus.CREATE,
|
|
104
|
+
detail=create_detail,
|
|
105
|
+
payload=payload,
|
|
106
|
+
app=app,
|
|
107
|
+
scope=scope,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if target.is_file():
|
|
111
|
+
existing = target.read_text(encoding="utf-8")
|
|
112
|
+
if existing == payload:
|
|
113
|
+
return Action(
|
|
114
|
+
kind=ActionKind.WRITE_TEXT,
|
|
115
|
+
path=target,
|
|
116
|
+
status=ActionStatus.NOOP,
|
|
117
|
+
detail=noop_detail,
|
|
118
|
+
payload=payload,
|
|
119
|
+
app=app,
|
|
120
|
+
scope=scope,
|
|
121
|
+
)
|
|
122
|
+
return Action(
|
|
123
|
+
kind=ActionKind.WRITE_TEXT,
|
|
124
|
+
path=target,
|
|
125
|
+
status=ActionStatus.UPDATE,
|
|
126
|
+
detail=update_detail,
|
|
127
|
+
payload=payload,
|
|
128
|
+
app=app,
|
|
129
|
+
scope=scope,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return Action(
|
|
133
|
+
kind=ActionKind.WRITE_TEXT,
|
|
134
|
+
path=target,
|
|
135
|
+
status=ActionStatus.CONFLICT,
|
|
136
|
+
detail=conflict_detail,
|
|
137
|
+
payload=payload,
|
|
138
|
+
app=app,
|
|
139
|
+
scope=scope,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def plan_owned_compiled_text_action(
|
|
144
|
+
*,
|
|
145
|
+
target: Path,
|
|
146
|
+
payload: str,
|
|
147
|
+
managed_paths: set[Path],
|
|
148
|
+
removable_link_paths: set[Path] | None = None,
|
|
149
|
+
managed_root: Path | None = None,
|
|
150
|
+
scope: str,
|
|
151
|
+
app: str,
|
|
152
|
+
create_detail: str,
|
|
153
|
+
noop_detail: str,
|
|
154
|
+
update_detail: str,
|
|
155
|
+
conflict_detail: str = "non-managed path exists",
|
|
156
|
+
) -> Action:
|
|
157
|
+
target_key = target.resolve(strict=False)
|
|
158
|
+
removable = removable_link_paths or set()
|
|
159
|
+
has_symlink_ancestor, is_removable_ancestor = _symlink_ancestor_state(
|
|
160
|
+
target, removable, managed_root
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if target_key in managed_paths or is_removable_ancestor:
|
|
164
|
+
return plan_compiled_text_action(
|
|
165
|
+
target=target,
|
|
166
|
+
payload=payload,
|
|
167
|
+
managed_paths=managed_paths,
|
|
168
|
+
removable_link_paths=removable,
|
|
169
|
+
managed_root=managed_root,
|
|
170
|
+
scope=scope,
|
|
171
|
+
app=app,
|
|
172
|
+
create_detail=create_detail,
|
|
173
|
+
noop_detail=noop_detail,
|
|
174
|
+
update_detail=update_detail,
|
|
175
|
+
conflict_detail=conflict_detail,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if not target.exists() and not target.is_symlink() and not has_symlink_ancestor:
|
|
179
|
+
return Action(
|
|
180
|
+
kind=ActionKind.WRITE_TEXT,
|
|
181
|
+
path=target,
|
|
182
|
+
status=ActionStatus.CREATE,
|
|
183
|
+
detail=create_detail,
|
|
184
|
+
payload=payload,
|
|
185
|
+
app=app,
|
|
186
|
+
scope=scope,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return Action(
|
|
190
|
+
kind=ActionKind.WRITE_TEXT,
|
|
191
|
+
path=target,
|
|
192
|
+
status=ActionStatus.CONFLICT,
|
|
193
|
+
detail=conflict_detail,
|
|
194
|
+
payload=payload,
|
|
195
|
+
app=app,
|
|
196
|
+
scope=scope,
|
|
197
|
+
)
|
{code_agnostic-0.3.8 → code_agnostic-0.3.9}/code_agnostic/apps/common/interfaces/repositories.py
RENAMED
|
@@ -20,16 +20,6 @@ class IAppConfigRepository(ABC):
|
|
|
20
20
|
def config_path(self) -> Path:
|
|
21
21
|
raise NotImplementedError
|
|
22
22
|
|
|
23
|
-
@property
|
|
24
|
-
@abstractmethod
|
|
25
|
-
def skills_dir(self) -> Path:
|
|
26
|
-
raise NotImplementedError
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
@abstractmethod
|
|
30
|
-
def agents_dir(self) -> Path:
|
|
31
|
-
raise NotImplementedError
|
|
32
|
-
|
|
33
23
|
@abstractmethod
|
|
34
24
|
def load_config(self) -> dict[str, Any]:
|
|
35
25
|
raise NotImplementedError
|
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
|
6
6
|
from code_agnostic.apps.app_id import AppId, app_scope
|
|
7
7
|
from code_agnostic.apps.common.compiled_planning import (
|
|
8
8
|
find_replaceable_symlink_ancestor,
|
|
9
|
+
plan_compiled_text_action,
|
|
9
10
|
)
|
|
10
11
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
11
12
|
from code_agnostic.apps.common.interfaces.repositories import IAppConfigRepository
|
|
@@ -17,12 +18,6 @@ from code_agnostic.apps.common.symlink_planning import (
|
|
|
17
18
|
plan_stale_files_group,
|
|
18
19
|
plan_stale_group,
|
|
19
20
|
)
|
|
20
|
-
from code_agnostic.generated_artifacts import (
|
|
21
|
-
ArtifactKind,
|
|
22
|
-
GeneratedArtifact,
|
|
23
|
-
OwnershipPolicy,
|
|
24
|
-
plan_generated_artifact,
|
|
25
|
-
)
|
|
26
21
|
from code_agnostic.models import Action, ActionKind, ActionStatus, SyncPlan
|
|
27
22
|
|
|
28
23
|
|
|
@@ -97,10 +92,7 @@ class IAppConfigService(ABC):
|
|
|
97
92
|
raise NotImplementedError
|
|
98
93
|
|
|
99
94
|
def agent_action_removable_links(self, removable_links: list[Path]) -> list[Path]:
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
def compiled_resource_ownership_policy(self) -> OwnershipPolicy:
|
|
103
|
-
return OwnershipPolicy.OWNED_ONLY
|
|
95
|
+
return []
|
|
104
96
|
|
|
105
97
|
@staticmethod
|
|
106
98
|
def _normalize_managed_group(value: Any) -> dict[str, Any]:
|
|
@@ -127,18 +119,14 @@ class IAppConfigService(ABC):
|
|
|
127
119
|
desired_paths: list[Path] = []
|
|
128
120
|
skipped: list[str] = []
|
|
129
121
|
scheduled_removals: set[Path] = set()
|
|
130
|
-
ownership = self.compiled_resource_ownership_policy()
|
|
131
122
|
|
|
132
123
|
for source in sources:
|
|
133
124
|
target, payload = compile_source(source)
|
|
134
125
|
desired_paths.append(target)
|
|
135
126
|
replaceable_symlink = find_replaceable_symlink_ancestor(target, target_dir)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
if replaceable_symlink is not None and (
|
|
141
|
-
can_replace_symlink and replaceable_symlink not in scheduled_removals
|
|
127
|
+
if (
|
|
128
|
+
replaceable_symlink is not None
|
|
129
|
+
and replaceable_symlink not in scheduled_removals
|
|
142
130
|
):
|
|
143
131
|
scheduled_removals.add(replaceable_symlink)
|
|
144
132
|
removable_link_set.add(replaceable_symlink.resolve(strict=False))
|
|
@@ -152,21 +140,17 @@ class IAppConfigService(ABC):
|
|
|
152
140
|
scope=scope,
|
|
153
141
|
)
|
|
154
142
|
)
|
|
155
|
-
action =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
kind=ArtifactKind.TEXT,
|
|
159
|
-
payload=payload,
|
|
160
|
-
ownership=ownership,
|
|
161
|
-
managed_root=target_dir,
|
|
162
|
-
scope=scope,
|
|
163
|
-
app=app,
|
|
164
|
-
create_detail=create_detail,
|
|
165
|
-
noop_detail=noop_detail,
|
|
166
|
-
update_detail=update_detail,
|
|
167
|
-
),
|
|
143
|
+
action = plan_compiled_text_action(
|
|
144
|
+
target=target,
|
|
145
|
+
payload=payload,
|
|
168
146
|
managed_paths=managed_path_set,
|
|
169
147
|
removable_link_paths=removable_link_set,
|
|
148
|
+
managed_root=target_dir,
|
|
149
|
+
scope=scope,
|
|
150
|
+
app=app,
|
|
151
|
+
create_detail=create_detail,
|
|
152
|
+
noop_detail=noop_detail,
|
|
153
|
+
update_detail=update_detail,
|
|
170
154
|
)
|
|
171
155
|
actions.append(action)
|
|
172
156
|
if action.status == ActionStatus.CONFLICT:
|