From a24d9a73a0e225599d32746c07d38214300d41fe Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Wed, 17 Jun 2026 02:37:28 +0200 Subject: [PATCH] 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. --- helpers/projects.py | 5 +++++ helpers/projects.py.dox.md | 3 ++- tests/test_projects.py | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/helpers/projects.py b/helpers/projects.py index 67e855978..561c7ddb6 100644 --- a/helpers/projects.py +++ b/helpers/projects.py @@ -12,6 +12,7 @@ PROJECTS_PARENT_DIR = "usr/projects" PROJECT_META_DIR = ".a0proj" PROJECT_INSTRUCTIONS_DIR = "instructions" PROJECT_KNOWLEDGE_DIR = "knowledge" +PROJECT_SKILLS_DIR = "skills" PROJECT_HEADER_FILE = "project.json" PROJECT_MCP_SERVERS_FILE = "mcp_servers.json" PROJECT_AGENTS_MD_FILES = ("AGENTS.md", "Agents.md", "agents.md") @@ -273,6 +274,7 @@ def load_edit_project_data(name: str) -> EditProjectData: from helpers import git data = load_basic_project_data(name) + create_project_meta_folders(name) additional_instructions = get_additional_instructions_files(name) variables = load_project_variables(name) mcp_servers = load_project_mcp_servers(name) @@ -639,6 +641,9 @@ def create_project_meta_folders(name: str): # create knowledge folders (plugins create their own subdirs lazily) files.create_dir(get_project_meta(name, PROJECT_KNOWLEDGE_DIR)) + # create project skills folder for Project Settings > Skills > Open Folder + files.create_dir(get_project_meta(name, PROJECT_SKILLS_DIR)) + def get_knowledge_files_count(name: str): knowledge_folder = files.get_abs_path( diff --git a/helpers/projects.py.dox.md b/helpers/projects.py.dox.md index bf6a11e58..36d1cc298 100644 --- a/helpers/projects.py.dox.md +++ b/helpers/projects.py.dox.md @@ -50,13 +50,14 @@ - `get_project_agents_md_instruction_file(name: str) -> tuple[str, str] | None` - `_format_project_instruction_files(instruction_files: list[tuple[str, str]]) -> str` - `_normalize_include_agents_md(value: object) -> bool` -- 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`. +- 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`. ## Runtime Contracts - Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together. - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change. - 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. +- Project metadata setup creates and repairs `.a0proj/instructions`, `.a0proj/knowledge`, and `.a0proj/skills` so settings surfaces can open those folders consistently. - Project MCP config uses the same JSON string shape as global MCP settings: an object with `mcpServers`. - Project MCP load/save paths validate project names as simple folder basenames before touching `.a0proj/mcp_servers.json`. - Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, plugin state, settings/state persistence, secret handling. diff --git a/tests/test_projects.py b/tests/test_projects.py index 0dc0b4ac5..8dac0d2c1 100644 --- a/tests/test_projects.py +++ b/tests/test_projects.py @@ -59,6 +59,27 @@ def test_project_mcp_servers_reject_path_names(monkeypatch, tmp_path): raise AssertionError(f"Expected invalid project name: {name!r}") +def test_project_creation_creates_skills_folder(monkeypatch, tmp_path): + _prepare_project_tree(monkeypatch, tmp_path) + + projects.create_project("demo", {"title": "Demo"}) + + assert (tmp_path / "usr" / "projects" / "demo" / ".a0proj" / "skills").is_dir() + + +def test_project_load_repairs_missing_skills_folder(monkeypatch, tmp_path): + _prepare_project_tree(monkeypatch, tmp_path) + meta = tmp_path / "usr" / "projects" / "demo" / ".a0proj" + meta.mkdir(parents=True) + (meta / "project.json").write_text('{"title": "Demo"}', encoding="utf-8") + + assert not (meta / "skills").exists() + + projects.load_edit_project_data("demo") + + assert (meta / "skills").is_dir() + + def test_project_system_prompt_includes_root_agents_md_with_path(monkeypatch, tmp_path): _prepare_project_tree(monkeypatch, tmp_path) projects.create_project(