code-agnostic 0.3.12__tar.gz → 0.3.14__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.
Files changed (166) hide show
  1. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/PKG-INFO +39 -16
  2. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/README.md +38 -15
  3. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/__init__.py +1 -1
  4. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/__main__.py +2 -0
  5. code_agnostic-0.3.14/code_agnostic/cli/commands/projects.py +78 -0
  6. code_agnostic-0.3.14/code_agnostic/cli/commands/skills.py +236 -0
  7. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/status.py +20 -2
  8. code_agnostic-0.3.14/code_agnostic/core/project_repository.py +10 -0
  9. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/repository.py +90 -0
  10. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/executor.py +122 -41
  11. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/models.py +15 -0
  12. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/planner.py +124 -1
  13. code_agnostic-0.3.14/code_agnostic/project_artifacts.py +39 -0
  14. code_agnostic-0.3.14/code_agnostic/skills/install_sources.py +369 -0
  15. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/status.py +98 -1
  16. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/renderers.py +62 -30
  17. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/tables.py +39 -3
  18. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/PKG-INFO +39 -16
  19. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/SOURCES.txt +7 -0
  20. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/pyproject.toml +1 -1
  21. code_agnostic-0.3.14/tests/test_cli_projects.py +227 -0
  22. code_agnostic-0.3.14/tests/test_cli_skills.py +421 -0
  23. code_agnostic-0.3.14/tests/test_project_config_sync.py +242 -0
  24. code_agnostic-0.3.14/tests/test_skill_install_sources.py +182 -0
  25. code_agnostic-0.3.12/code_agnostic/cli/commands/skills.py +0 -77
  26. code_agnostic-0.3.12/tests/test_cli_skills.py +0 -133
  27. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/LICENSE +0 -0
  28. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/__init__.py +0 -0
  29. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/claude.py +0 -0
  30. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/codex.py +0 -0
  31. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/compilers.py +0 -0
  32. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/models.py +0 -0
  33. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/opencode.py +0 -0
  34. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/parser.py +0 -0
  35. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/__init__.py +0 -0
  36. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/app_id.py +0 -0
  37. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/apps_service.py +0 -0
  38. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/__init__.py +0 -0
  39. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/config_repository.py +0 -0
  40. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/mapper.py +0 -0
  41. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/service.py +0 -0
  42. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/__init__.py +0 -0
  43. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/config_repository.py +0 -0
  44. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/mapper.py +0 -0
  45. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/schema.json +0 -0
  46. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/schema_repository.py +0 -0
  47. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/service.py +0 -0
  48. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/__init__.py +0 -0
  49. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/compiled_planning.py +0 -0
  50. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/framework.py +0 -0
  51. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
  52. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
  53. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
  54. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/service.py +0 -0
  55. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/loader.py +0 -0
  56. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/models.py +0 -0
  57. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/schema.py +0 -0
  58. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/symlink_planning.py +0 -0
  59. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/utils.py +0 -0
  60. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/__init__.py +0 -0
  61. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/config_repository.py +0 -0
  62. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/mapper.py +0 -0
  63. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/schema.json +0 -0
  64. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/schema_repository.py +0 -0
  65. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/service.py +0 -0
  66. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/__init__.py +0 -0
  67. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/config_repository.py +0 -0
  68. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/mapper.py +0 -0
  69. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/schema.json +0 -0
  70. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/schema_repository.py +0 -0
  71. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/service.py +0 -0
  72. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/__init__.py +0 -0
  73. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/aliases.py +0 -0
  74. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/__init__.py +0 -0
  75. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/agents.py +0 -0
  76. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/apply.py +0 -0
  77. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/apps.py +0 -0
  78. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
  79. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/import_.py +0 -0
  80. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/mcp.py +0 -0
  81. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/plan.py +0 -0
  82. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/restore.py +0 -0
  83. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/rules.py +0 -0
  84. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/validate.py +0 -0
  85. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/workspaces.py +0 -0
  86. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/helpers.py +0 -0
  87. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/options.py +0 -0
  88. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/constants.py +0 -0
  89. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/__init__.py +0 -0
  90. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/workspace_repository.py +0 -0
  91. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/errors.py +0 -0
  92. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/generated_artifacts.py +0 -0
  93. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/git_exclude_service.py +0 -0
  94. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/__init__.py +0 -0
  95. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/adapters.py +0 -0
  96. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/filesystem.py +0 -0
  97. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/models.py +0 -0
  98. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/service.py +0 -0
  99. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/lossiness.py +0 -0
  100. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/mcp_service.py +0 -0
  101. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/__init__.py +0 -0
  102. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/compilers.py +0 -0
  103. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/models.py +0 -0
  104. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/parser.py +0 -0
  105. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/repository.py +0 -0
  106. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/__init__.py +0 -0
  107. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/compilers.py +0 -0
  108. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/models.py +0 -0
  109. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/parser.py +0 -0
  110. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/__init__.py +0 -0
  111. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/loaders.py +0 -0
  112. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
  113. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
  114. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
  115. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
  116. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
  117. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/__init__.py +0 -0
  118. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/enums.py +0 -0
  119. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/import_selector.py +0 -0
  120. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/sections.py +0 -0
  121. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/utils.py +0 -0
  122. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/validation.py +0 -0
  123. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/workspace_artifacts.py +0 -0
  124. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/workspaces.py +0 -0
  125. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/dependency_links.txt +0 -0
  126. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/entry_points.txt +0 -0
  127. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/requires.txt +0 -0
  128. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/top_level.txt +0 -0
  129. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/setup.cfg +0 -0
  130. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_agents.py +0 -0
  131. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_aliases.py +0 -0
  132. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_apps.py +0 -0
  133. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_codex.py +0 -0
  134. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_cursor.py +0 -0
  135. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_target.py +0 -0
  136. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apps.py +0 -0
  137. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_explain_lossiness.py +0 -0
  138. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_flags.py +0 -0
  139. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_git_exclude.py +0 -0
  140. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_import.py +0 -0
  141. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_import_interactive.py +0 -0
  142. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_mcp.py +0 -0
  143. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_module_organization.py +0 -0
  144. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_plan.py +0 -0
  145. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_restore.py +0 -0
  146. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_rules.py +0 -0
  147. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_status.py +0 -0
  148. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_validate.py +0 -0
  149. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_workspace_resolution.py +0 -0
  150. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_workspaces.py +0 -0
  151. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_common_mcp_to_dto.py +0 -0
  152. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_common_repository.py +0 -0
  153. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_compiled_planning.py +0 -0
  154. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_dto_to_common_mcp.py +0 -0
  155. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_git_exclude_service.py +0 -0
  156. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_mcp_service.py +0 -0
  157. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_planner_executor.py +0 -0
  158. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_planner_rules.py +0 -0
  159. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_symlink_planning.py +0 -0
  160. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_sync_plan_model.py +0 -0
  161. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_transactional_executor.py +0 -0
  162. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_utils.py +0 -0
  163. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_version.py +0 -0
  164. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_workspace_config_sync.py +0 -0
  165. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_workspace_repo_status.py +0 -0
  166. {code_agnostic-0.3.12 → code_agnostic-0.3.14}/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.12
3
+ Version: 0.3.14
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
@@ -66,19 +66,19 @@ Today the implementation is still mixed: some assets are compiled and some are s
66
66
 
67
67
  ## Scope model
68
68
 
69
- `code-agnostic` has two managed source scopes today:
69
+ `code-agnostic` has three managed source scopes:
70
70
 
71
71
  - global source config under `~/.config/code-agnostic/`, synced to enabled
72
72
  user-level app config;
73
73
  - workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
74
- propagated into repos inside a registered workspace.
74
+ propagated into repos inside a registered workspace;
75
+ - project source config under `~/.config/code-agnostic/projects/<name>/`,
76
+ synced to exactly one registered project directory.
75
77
 
76
78
  Workspace sync may generate repo-local outputs, but those outputs are not
77
- source. Project-local skill folders that users create directly inside a repo,
78
- such as `.agents/skills` or `.opencode/skills`, are app-native inputs but are
79
- not managed as source by `code-agnostic` yet. First-class project-scoped
80
- installs are planned so a single registered project can have managed local
81
- source config without bypassing the hub.
79
+ source. Project-local skill folders generated inside a repo, such as
80
+ `.agents/skills` or `.opencode/skills`, are also generated outputs; install
81
+ skills into managed project source first, then run `plan` / `apply`.
82
82
 
83
83
  ## Install
84
84
 
@@ -230,21 +230,44 @@ code-agnostic skills list
230
230
  code-agnostic agents list
231
231
  ```
232
232
 
233
- Manual skill install today:
233
+ Install copies a skill into managed source first for the chosen scope. Run
234
+ `plan` / `apply` afterward to generate target app files.
234
235
 
235
236
  ```bash
236
- mkdir -p ~/.config/code-agnostic/skills
237
- cp -R ./my-skill ~/.config/code-agnostic/skills/my-skill
237
+ code-agnostic skills install ./my-skill --global
238
+ code-agnostic skills install ./my-skill --workspace myworkspace
239
+ code-agnostic projects add --name myproject --path .
240
+ code-agnostic skills install ./my-skill --project myproject
238
241
  code-agnostic plan
239
242
  code-agnostic apply
240
243
  ```
241
244
 
242
- There is no `skills install` command yet; copy skills into managed source first,
243
- then use the normal `plan` / `apply` workflow.
245
+ Remote GitHub-style sources are also supported:
244
246
 
245
- Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated skills and agents are written under `~/.claude/skills` and `~/.claude/agents`, with workspace copies under repo-local `.claude/skills` and `.claude/agents`.
247
+ ```bash
248
+ code-agnostic skills install owner/repo --global
249
+ code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
250
+ code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
251
+ ```
246
252
 
247
- Project-local skills are not first-class source inputs in `code-agnostic` yet. If a target app discovers repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or user-created `.claude/skills`, treat those as unmanaged app inputs. Workspace sync writes only the exact generated paths recorded in `.sync-state.json`.
253
+ Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
254
+ GitHub tree URL. When a remote source contains multiple skill candidates,
255
+ repeat `--skill` to select the intended skills; otherwise install fails instead
256
+ of guessing. Remote installs still copy the selected skill into the
257
+ `code-agnostic` source of truth before any generated target outputs are written.
258
+
259
+ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
260
+ skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
261
+ inspected with `code-agnostic skills list -w <name>`. Project-local skills live
262
+ under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
263
+ the registered project directory by `plan` / `apply`. Codex generated skill
264
+ outputs are written to `~/.agents/skills`, while Codex agents and config remain
265
+ under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
266
+ skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
267
+ with workspace/project copies under repo-local `.claude/skills` and
268
+ `.claude/agents`.
269
+
270
+ If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
248
271
 
249
272
  Planned convenience command:
250
273
 
@@ -258,7 +281,7 @@ first implementation slice.
258
281
 
259
282
  ### Workspaces
260
283
 
261
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created project-local skill folders remain unmanaged until project-scoped installs are supported.
284
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
262
285
 
263
286
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
264
287
 
@@ -41,19 +41,19 @@ Today the implementation is still mixed: some assets are compiled and some are s
41
41
 
42
42
  ## Scope model
43
43
 
44
- `code-agnostic` has two managed source scopes today:
44
+ `code-agnostic` has three managed source scopes:
45
45
 
46
46
  - global source config under `~/.config/code-agnostic/`, synced to enabled
47
47
  user-level app config;
48
48
  - workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
49
- propagated into repos inside a registered workspace.
49
+ propagated into repos inside a registered workspace;
50
+ - project source config under `~/.config/code-agnostic/projects/<name>/`,
51
+ synced to exactly one registered project directory.
50
52
 
51
53
  Workspace sync may generate repo-local outputs, but those outputs are not
52
- source. Project-local skill folders that users create directly inside a repo,
53
- such as `.agents/skills` or `.opencode/skills`, are app-native inputs but are
54
- not managed as source by `code-agnostic` yet. First-class project-scoped
55
- installs are planned so a single registered project can have managed local
56
- source config without bypassing the hub.
54
+ source. Project-local skill folders generated inside a repo, such as
55
+ `.agents/skills` or `.opencode/skills`, are also generated outputs; install
56
+ skills into managed project source first, then run `plan` / `apply`.
57
57
 
58
58
  ## Install
59
59
 
@@ -205,21 +205,44 @@ code-agnostic skills list
205
205
  code-agnostic agents list
206
206
  ```
207
207
 
208
- Manual skill install today:
208
+ Install copies a skill into managed source first for the chosen scope. Run
209
+ `plan` / `apply` afterward to generate target app files.
209
210
 
210
211
  ```bash
211
- mkdir -p ~/.config/code-agnostic/skills
212
- cp -R ./my-skill ~/.config/code-agnostic/skills/my-skill
212
+ code-agnostic skills install ./my-skill --global
213
+ code-agnostic skills install ./my-skill --workspace myworkspace
214
+ code-agnostic projects add --name myproject --path .
215
+ code-agnostic skills install ./my-skill --project myproject
213
216
  code-agnostic plan
214
217
  code-agnostic apply
215
218
  ```
216
219
 
217
- There is no `skills install` command yet; copy skills into managed source first,
218
- then use the normal `plan` / `apply` workflow.
220
+ Remote GitHub-style sources are also supported:
219
221
 
220
- Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated skills and agents are written under `~/.claude/skills` and `~/.claude/agents`, with workspace copies under repo-local `.claude/skills` and `.claude/agents`.
222
+ ```bash
223
+ code-agnostic skills install owner/repo --global
224
+ code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
225
+ code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
226
+ ```
221
227
 
222
- Project-local skills are not first-class source inputs in `code-agnostic` yet. If a target app discovers repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or user-created `.claude/skills`, treat those as unmanaged app inputs. Workspace sync writes only the exact generated paths recorded in `.sync-state.json`.
228
+ Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
229
+ GitHub tree URL. When a remote source contains multiple skill candidates,
230
+ repeat `--skill` to select the intended skills; otherwise install fails instead
231
+ of guessing. Remote installs still copy the selected skill into the
232
+ `code-agnostic` source of truth before any generated target outputs are written.
233
+
234
+ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
235
+ skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
236
+ inspected with `code-agnostic skills list -w <name>`. Project-local skills live
237
+ under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
238
+ the registered project directory by `plan` / `apply`. Codex generated skill
239
+ outputs are written to `~/.agents/skills`, while Codex agents and config remain
240
+ under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
241
+ skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
242
+ with workspace/project copies under repo-local `.claude/skills` and
243
+ `.claude/agents`.
244
+
245
+ If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
223
246
 
224
247
  Planned convenience command:
225
248
 
@@ -233,7 +256,7 @@ first implementation slice.
233
256
 
234
257
  ### Workspaces
235
258
 
236
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created project-local skill folders remain unmanaged until project-scoped installs are supported.
259
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs write project-root `opencode.json` files that reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
237
260
 
238
261
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
239
262
 
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.3.12"
3
+ __version__ = "0.3.14"
@@ -10,6 +10,7 @@ from code_agnostic.cli.commands.explain_lossiness import explain_lossiness
10
10
  from code_agnostic.cli.commands.import_ import import_group
11
11
  from code_agnostic.cli.commands.mcp import mcp
12
12
  from code_agnostic.cli.commands.plan import plan
13
+ from code_agnostic.cli.commands.projects import projects
13
14
  from code_agnostic.cli.commands.restore import restore
14
15
  from code_agnostic.cli.commands.rules import rules
15
16
  from code_agnostic.cli.commands.skills import skills
@@ -40,6 +41,7 @@ cli.add_command(explain_lossiness)
40
41
  # Register command groups
41
42
  cli.add_command(apps)
42
43
  cli.add_command(workspaces)
44
+ cli.add_command(projects)
43
45
  cli.add_command(rules)
44
46
  cli.add_command(skills)
45
47
  cli.add_command(agents_group)
@@ -0,0 +1,78 @@
1
+ """Projects group commands."""
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+
7
+ from code_agnostic.core.project_repository import ProjectConfigRepository
8
+ from code_agnostic.core.repository import CoreRepository
9
+ from code_agnostic.errors import SyncAppError
10
+
11
+
12
+ @click.group(help="Manage project roots for project-local source config.")
13
+ def projects() -> None:
14
+ pass
15
+
16
+
17
+ @projects.command("add", help="Add a project by name and path.")
18
+ @click.option("--name", required=True, help="Project name.")
19
+ @click.option(
20
+ "--path",
21
+ required=True,
22
+ type=click.Path(path_type=Path),
23
+ help="Project root path.",
24
+ )
25
+ @click.pass_obj
26
+ def projects_add(obj: dict[str, str], name: str, path: Path) -> None:
27
+ core = CoreRepository()
28
+ try:
29
+ core.add_project(name, path)
30
+ except (ValueError, SyncAppError) as exc:
31
+ raise click.ClickException(str(exc))
32
+ click.echo(f"Project added: {name.strip()}")
33
+ click.echo(str(path.expanduser().resolve()))
34
+
35
+
36
+ @projects.command("remove", help="Unregister a project by name.")
37
+ @click.option("--name", required=True, help="Project name to unregister.")
38
+ @click.pass_obj
39
+ def projects_remove(obj: dict[str, str], name: str) -> None:
40
+ core = CoreRepository()
41
+ try:
42
+ removed = core.remove_project(name)
43
+ except SyncAppError as exc:
44
+ raise click.ClickException(str(exc))
45
+ if not removed:
46
+ raise click.ClickException(f"Project not found: {name}")
47
+ click.echo(f"Project unregistered: {name}")
48
+
49
+
50
+ @projects.command("list", help="List configured projects.")
51
+ @click.pass_obj
52
+ def projects_list(obj: dict[str, str]) -> None:
53
+ core = CoreRepository()
54
+ try:
55
+ items = core.load_projects()
56
+ except SyncAppError as exc:
57
+ raise click.ClickException(str(exc))
58
+
59
+ if not items:
60
+ click.echo("No projects configured.")
61
+ click.echo("code-agnostic projects add --name <name> --path <path>")
62
+ return
63
+
64
+ for item in items:
65
+ project_source = ProjectConfigRepository(
66
+ root=core.project_config_dir(item["name"])
67
+ )
68
+ markers = []
69
+ if project_source.has_mcp():
70
+ markers.append("mcp")
71
+ if project_source.has_rules():
72
+ markers.append("rules")
73
+ if project_source.has_skills():
74
+ markers.append("skills")
75
+ if project_source.has_agents():
76
+ markers.append("agents")
77
+ suffix = f" [{', '.join(markers)}]" if markers else ""
78
+ click.echo(f"{item['name']}: {item['path']}{suffix}")
@@ -0,0 +1,236 @@
1
+ """Skills group commands."""
2
+
3
+ from pathlib import Path
4
+ import shutil
5
+
6
+ import click
7
+ from rich.console import Console
8
+
9
+ from code_agnostic.cli.helpers import validate_resource_name, workspace_config_root
10
+ from code_agnostic.cli.options import workspace_option
11
+ from code_agnostic.core.repository import CoreRepository
12
+ from code_agnostic.errors import SyncAppError
13
+ from code_agnostic.skills.install_sources import (
14
+ SkillInstallSourceError,
15
+ cleanup_skill_install_resolution,
16
+ resolve_skill_install_source,
17
+ )
18
+ from code_agnostic.tui import SyncConsoleUI
19
+ from code_agnostic.utils import compact_home_path
20
+
21
+
22
+ def _entries_containing_cwd(
23
+ entries: list[dict[str, str]], cwd: Path
24
+ ) -> list[dict[str, str]]:
25
+ matches = []
26
+ for entry in entries:
27
+ root = Path(entry["path"]).expanduser().resolve()
28
+ if cwd == root or cwd.is_relative_to(root):
29
+ matches.append(entry)
30
+ return matches
31
+
32
+
33
+ def _project_entries_by_name(core: CoreRepository) -> dict[str, dict[str, str]]:
34
+ return {item["name"]: item for item in core.load_projects()}
35
+
36
+
37
+ def _install_root(
38
+ core: CoreRepository,
39
+ *,
40
+ global_scope: bool,
41
+ workspace: str | None,
42
+ project: str | None,
43
+ ) -> tuple[Path, str, str | None]:
44
+ explicit_count = sum(
45
+ [global_scope, workspace is not None, project is not None],
46
+ )
47
+ if explicit_count > 1:
48
+ raise click.ClickException(
49
+ "Choose only one scope: --global, --workspace, or --project."
50
+ )
51
+
52
+ try:
53
+ if global_scope:
54
+ return core.root, "global", None
55
+ if workspace is not None:
56
+ return (
57
+ workspace_config_root(core, workspace),
58
+ f"workspace:{workspace}",
59
+ None,
60
+ )
61
+ if project is not None:
62
+ projects = _project_entries_by_name(core)
63
+ if project not in projects:
64
+ raise click.ClickException(f"Project not found: {project}")
65
+ return core.project_config_dir(project), f"project:{project}", None
66
+
67
+ cwd = Path.cwd().resolve()
68
+ project_matches = _entries_containing_cwd(core.load_projects(), cwd)
69
+ if len(project_matches) == 1:
70
+ name = project_matches[0]["name"]
71
+ return core.project_config_dir(name), f"project:{name}", None
72
+
73
+ workspace_matches = _entries_containing_cwd(core.load_workspaces(), cwd)
74
+ if len(project_matches) == 0 and len(workspace_matches) == 1:
75
+ name = workspace_matches[0]["name"]
76
+ return core.workspace_config_dir(name), f"workspace:{name}", None
77
+ except SyncAppError as exc:
78
+ raise click.ClickException(str(exc)) from exc
79
+
80
+ raise click.ClickException(
81
+ "No unique project/workspace scope detected. Use --global, --project, "
82
+ "or --workspace."
83
+ )
84
+
85
+
86
+ def _preflight_skill_destinations(
87
+ source_dirs: tuple[Path, ...], root: Path, scope: str
88
+ ) -> list[tuple[str, Path, Path]]:
89
+ installs: list[tuple[str, Path, Path]] = []
90
+ names = _validate_skill_source_names(source_dirs)
91
+ for name, source_dir in zip(names, source_dirs, strict=True):
92
+ destination = root / "skills" / name
93
+ if destination.exists():
94
+ raise click.ClickException(f"Skill already exists: {scope}:{name}")
95
+ installs.append((name, source_dir, destination))
96
+ return installs
97
+
98
+
99
+ def _validate_skill_source_names(source_dirs: tuple[Path, ...]) -> list[str]:
100
+ names: list[str] = []
101
+ seen_names: set[str] = set()
102
+ for source_dir in source_dirs:
103
+ name = source_dir.name
104
+ validate_resource_name(name, "skill")
105
+ if name in seen_names:
106
+ raise click.ClickException(f"Duplicate skill name in source: {name}")
107
+ seen_names.add(name)
108
+ names.append(name)
109
+ return names
110
+
111
+
112
+ @click.group(
113
+ help=(
114
+ "Manage source skill definitions. Commands use global source by default; "
115
+ "pass -w/--workspace for workspace source."
116
+ )
117
+ )
118
+ def skills() -> None:
119
+ pass
120
+
121
+
122
+ @skills.command("install", help="Install a skill source into source config.")
123
+ @click.argument("source")
124
+ @click.option(
125
+ "--skill",
126
+ "skill_selectors",
127
+ multiple=True,
128
+ help="Select a skill by name or source path; repeat to install multiple.",
129
+ )
130
+ @click.option("--global", "global_scope", is_flag=True, help="Install globally.")
131
+ @workspace_option()
132
+ @click.option("--project", help="Install into a registered project source.")
133
+ @click.pass_obj
134
+ def skills_install(
135
+ obj: dict[str, str],
136
+ source: str,
137
+ skill_selectors: tuple[str, ...],
138
+ global_scope: bool,
139
+ workspace: str | None,
140
+ project: str | None,
141
+ ) -> None:
142
+ resolution = None
143
+ source_path = Path(source).expanduser()
144
+ if source_path.exists():
145
+ try:
146
+ resolution = resolve_skill_install_source(
147
+ source,
148
+ skill_selectors=skill_selectors,
149
+ )
150
+ _validate_skill_source_names(resolution.skill_dirs)
151
+ except SkillInstallSourceError as exc:
152
+ raise click.ClickException(str(exc)) from exc
153
+
154
+ core = CoreRepository()
155
+ root, scope, scope_note = _install_root(
156
+ core,
157
+ global_scope=global_scope,
158
+ workspace=workspace,
159
+ project=project,
160
+ )
161
+ try:
162
+ if resolution is None:
163
+ resolution = resolve_skill_install_source(
164
+ source,
165
+ skill_selectors=skill_selectors,
166
+ )
167
+ installs = _preflight_skill_destinations(resolution.skill_dirs, root, scope)
168
+ for _name, _source_dir, destination in installs:
169
+ destination.parent.mkdir(parents=True, exist_ok=True)
170
+ for name, source_dir, destination in installs:
171
+ shutil.copytree(source_dir, destination)
172
+ if scope_note is not None:
173
+ click.echo(scope_note)
174
+ scope_note = None
175
+ click.echo(f"Installed {scope} skill: {name}")
176
+ click.echo(f"Source: {compact_home_path(source_dir)}")
177
+ click.echo(f"Destination: {compact_home_path(destination)}")
178
+ except SkillInstallSourceError as exc:
179
+ raise click.ClickException(str(exc)) from exc
180
+ finally:
181
+ if resolution is not None:
182
+ cleanup_skill_install_resolution(resolution)
183
+
184
+
185
+ @skills.command("list", help="List global skills, or workspace skills with -w.")
186
+ @workspace_option()
187
+ @click.pass_obj
188
+ def skills_list(obj: dict[str, str], workspace: str | None) -> None:
189
+ ui = SyncConsoleUI(Console())
190
+ core = CoreRepository()
191
+ root = workspace_config_root(core, workspace)
192
+ skill_sources = CoreRepository(root).list_skill_sources()
193
+ scope = f"workspace:{workspace}" if workspace else "global"
194
+ rows = [
195
+ [
196
+ source.name,
197
+ scope,
198
+ "bundle" if (source / "meta.yaml").exists() else "legacy",
199
+ compact_home_path(source),
200
+ ]
201
+ for source in skill_sources
202
+ ]
203
+ skill_dir = compact_home_path(root / "skills")
204
+ scope_message = (
205
+ f"No workspace skills configured for {workspace}"
206
+ if workspace
207
+ else "No global skills configured"
208
+ )
209
+ empty_message = (
210
+ f"{scope_message} in {skill_dir}.\n"
211
+ f"- Copy a skill into {skill_dir}/<name>\n"
212
+ "- code-agnostic plan\n"
213
+ "- code-agnostic apply"
214
+ )
215
+ ui.render_list(
216
+ "skills",
217
+ ["Skill", "Scope", "Format", "Source"],
218
+ rows,
219
+ empty_message,
220
+ )
221
+
222
+
223
+ @skills.command("remove", help="Remove a global skill, or a workspace skill with -w.")
224
+ @click.option("--name", required=True, help="Skill name to remove.")
225
+ @workspace_option()
226
+ @click.pass_obj
227
+ def skills_remove(obj: dict[str, str], name: str, workspace: str | None) -> None:
228
+ validate_resource_name(name, "skill")
229
+ core = CoreRepository()
230
+ root = workspace_config_root(core, workspace)
231
+ skill_dir = root / "skills" / name
232
+ if not skill_dir.exists():
233
+ raise click.ClickException(f"Skill not found: {name}")
234
+ shutil.rmtree(skill_dir)
235
+ scope = f"workspace:{workspace}" if workspace else "global"
236
+ click.echo(f"Removed {scope} skill: {name}")
@@ -11,6 +11,8 @@ from code_agnostic.errors import SyncAppError
11
11
  from code_agnostic.models import (
12
12
  EditorStatusRow,
13
13
  EditorSyncStatus,
14
+ ProjectStatusRow,
15
+ ProjectSyncStatus,
14
16
  WorkspaceStatusRow,
15
17
  WorkspaceSyncStatus,
16
18
  )
@@ -70,12 +72,28 @@ def status(obj: dict[str, str], app: str, verbose: bool) -> None:
70
72
  repos=[],
71
73
  )
72
74
  ]
75
+ try:
76
+ project_rows = status_service.build_project_status(
77
+ core, app_services=enabled_services
78
+ )
79
+ except SyncAppError as exc:
80
+ project_rows = [
81
+ ProjectStatusRow(
82
+ name="projects",
83
+ path=str(core.projects_path),
84
+ status=ProjectSyncStatus.ERROR,
85
+ detail=str(exc),
86
+ )
87
+ ]
73
88
  ui.render_status(
74
89
  editor_rows,
75
90
  workspace_rows,
91
+ project_rows,
76
92
  )
77
93
 
78
- if any(row.status == EditorSyncStatus.ERROR for row in editor_rows) or any(
79
- row.status == WorkspaceSyncStatus.ERROR for row in workspace_rows
94
+ if (
95
+ any(row.status == EditorSyncStatus.ERROR for row in editor_rows)
96
+ or any(row.status == WorkspaceSyncStatus.ERROR for row in workspace_rows)
97
+ or any(row.status == ProjectSyncStatus.ERROR for row in project_rows)
80
98
  ):
81
99
  raise click.exceptions.Exit(1)
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
4
+
5
+
6
+ class ProjectConfigRepository(WorkspaceConfigRepository):
7
+ """Source repository for project-level config."""
8
+
9
+ def __init__(self, root: Path) -> None:
10
+ super().__init__(root)