Fix project skills folder initialization
Create the .a0proj/skills directory during project metadata setup so Project Settings can open the skills folder for new projects. Repair the folder when loading existing project settings, and cover both creation and repair with regression tests.
Alessandro committed
Jun 17, 2026 at 02:37 UTC
a24d9a73a0e225599d32746c07d38214300d41fe
3 files changed
+28
-1
helpers/projects.py
+5
@@ -12,6 +12,7 @@ PROJECTS_PARENT_DIR = "usr/projects"
12
PROJECT_META_DIR = ".a0proj"
13
PROJECT_INSTRUCTIONS_DIR = "instructions"
14
PROJECT_KNOWLEDGE_DIR = "knowledge"
15
+PROJECT_SKILLS_DIR = "skills"
16
PROJECT_HEADER_FILE = "project.json"
17
PROJECT_MCP_SERVERS_FILE = "mcp_servers.json"
18
PROJECT_AGENTS_MD_FILES = ("AGENTS.md", "Agents.md", "agents.md")
@@ -273,6 +274,7 @@ def load_edit_project_data(name: str) -> EditProjectData:
274
from helpers import git
275
276
data = load_basic_project_data(name)
277
+ create_project_meta_folders(name)
278
additional_instructions = get_additional_instructions_files(name)
279
variables = load_project_variables(name)
280
mcp_servers = load_project_mcp_servers(name)
@@ -639,6 +641,9 @@ def create_project_meta_folders(name: str):
641
# create knowledge folders (plugins create their own subdirs lazily)
642
files.create_dir(get_project_meta(name, PROJECT_KNOWLEDGE_DIR))
643
644
+ # create project skills folder for Project Settings > Skills > Open Folder
645
+ files.create_dir(get_project_meta(name, PROJECT_SKILLS_DIR))
646
+
647
648
def get_knowledge_files_count(name: str):
649
knowledge_folder = files.get_abs_path(
helpers/projects.py.dox.md
+2
-1
@@ -50,13 +50,14 @@
50
- `get_project_agents_md_instruction_file(name: str) -> tuple[str, str] | None`
51
- `_format_project_instruction_files(instruction_files: list[tuple[str, str]]) -> str`
52
- `_normalize_include_agents_md(value: object) -> bool`
53
-- Notable constants/configuration names: `PROJECTS_PARENT_DIR`, `PROJECT_META_DIR`, `PROJECT_INSTRUCTIONS_DIR`, `PROJECT_KNOWLEDGE_DIR`, `PROJECT_HEADER_FILE`, `PROJECT_MCP_SERVERS_FILE`, `PROJECT_AGENTS_MD_FILES`, `DEFAULT_MCP_SERVERS_CONFIG`, `CONTEXT_DATA_KEY_PROJECT`.
53
+- Notable constants/configuration names: `PROJECTS_PARENT_DIR`, `PROJECT_META_DIR`, `PROJECT_INSTRUCTIONS_DIR`, `PROJECT_KNOWLEDGE_DIR`, `PROJECT_SKILLS_DIR`, `PROJECT_HEADER_FILE`, `PROJECT_MCP_SERVERS_FILE`, `PROJECT_AGENTS_MD_FILES`, `DEFAULT_MCP_SERVERS_CONFIG`, `CONTEXT_DATA_KEY_PROJECT`.
54
55
## Runtime Contracts
56
57
- Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
58
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
59
- Per-project MCP server configuration is persisted as `.a0proj/mcp_servers.json`, exposed through `load_edit_project_data(...)`, and saved during project create/clone/update flows.
60
+- Project metadata setup creates and repairs `.a0proj/instructions`, `.a0proj/knowledge`, and `.a0proj/skills` so settings surfaces can open those folders consistently.
61
- Project MCP config uses the same JSON string shape as global MCP settings: an object with `mcpServers`.
62
- Project MCP load/save paths validate project names as simple folder basenames before touching `.a0proj/mcp_servers.json`.
63
- Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, plugin state, settings/state persistence, secret handling.
tests/test_projects.py
+21
@@ -59,6 +59,27 @@ def test_project_mcp_servers_reject_path_names(monkeypatch, tmp_path):
59
raise AssertionError(f"Expected invalid project name: {name!r}")
60
61
62
+def test_project_creation_creates_skills_folder(monkeypatch, tmp_path):
63
+ _prepare_project_tree(monkeypatch, tmp_path)
64
+
65
+ projects.create_project("demo", {"title": "Demo"})
66
+
67
+ assert (tmp_path / "usr" / "projects" / "demo" / ".a0proj" / "skills").is_dir()
68
+
69
+
70
+def test_project_load_repairs_missing_skills_folder(monkeypatch, tmp_path):
71
+ _prepare_project_tree(monkeypatch, tmp_path)
72
+ meta = tmp_path / "usr" / "projects" / "demo" / ".a0proj"
73
+ meta.mkdir(parents=True)
74
+ (meta / "project.json").write_text('{"title": "Demo"}', encoding="utf-8")
75
+
76
+ assert not (meta / "skills").exists()
77
+
78
+ projects.load_edit_project_data("demo")
79
+
80
+ assert (meta / "skills").is_dir()
81
+
82
+
83
def test_project_system_prompt_includes_root_agents_md_with_path(monkeypatch, tmp_path):
84
_prepare_project_tree(monkeypatch, tmp_path)
85
projects.create_project(