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.
This commit is contained in:
Alessandro 2026-06-17 02:37:28 +02:00
parent 1dc719ff9c
commit a24d9a73a0
3 changed files with 28 additions and 1 deletions

View file

@ -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(

View file

@ -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.

View file

@ -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(