| 1 | # projects.py DOX |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | - Own the `projects.py` helper module. |
| 6 | - This module owns project metadata, workspace creation, Git status, and project-scoped settings including per-project MCP server config. |
| 7 | - Keep this file-level DOX profile synchronized with `projects.py` because this directory is intentionally flat. |
| 8 | |
| 9 | ## Ownership |
| 10 | |
| 11 | - `projects.py` owns the runtime implementation. |
| 12 | - `projects.py.dox.md` owns durable notes about responsibilities, contracts, side effects, and verification for that implementation. |
| 13 | - Classes: |
| 14 | - `FileStructureInjectionSettings` (`TypedDict`) |
| 15 | - `SubAgentSettings` (`TypedDict`) |
| 16 | - `BasicProjectData` (`TypedDict`) |
| 17 | - `GitStatusData` (`TypedDict`) |
| 18 | - `EditProjectData` (`BasicProjectData`) |
| 19 | - Top-level functions: |
| 20 | - `get_projects_parent_folder()` |
| 21 | - `get_project_folder(name: str)` |
| 22 | - `get_project_meta(name: str, *sub_dirs)` |
| 23 | - `validate_project_name(name: str | None) -> str` |
| 24 | - `delete_project(name: str)` |
| 25 | - `create_project(name: str, data: BasicProjectData)` |
| 26 | - `clone_git_project(name: str, git_url: str, git_token: str, data: BasicProjectData)`: Clone a git repository as a new A0 project. Token is used only for cloning via http header. |
| 27 | - `load_project_header(name: str)` |
| 28 | - `_default_file_structure_settings()` |
| 29 | - `_normalizeBasicData(data: BasicProjectData) -> BasicProjectData` |
| 30 | - `_normalizeEditData(data: EditProjectData) -> EditProjectData` |
| 31 | - `_edit_data_to_basic_data(data: EditProjectData)` |
| 32 | - `_basic_data_to_edit_data(data: BasicProjectData) -> EditProjectData` |
| 33 | - `update_project(name: str, data: EditProjectData)` |
| 34 | - `load_basic_project_data(name: str) -> BasicProjectData` |
| 35 | - `load_edit_project_data(name: str) -> EditProjectData` |
| 36 | - `save_project_header(name: str, data: BasicProjectData)` |
| 37 | - `load_project_extended_data(name: str) -> ProjectExtendedData` |
| 38 | - `save_project_extended_data(name: str, project_data: ProjectExtendedData)` |
| 39 | - `_project_extended_data_for_save(data: object) -> ProjectExtendedData` |
| 40 | - `_merge_project_extended_data(data: EditProjectData, extended_data: object) -> None` |
| 41 | - `load_project_mcp_servers(name: str) -> str` |
| 42 | - `save_project_mcp_servers(name: str, mcp_servers: str)` |
| 43 | - `get_active_projects_list()` |
| 44 | - `_get_projects_list(parent_dir)` |
| 45 | - `reconcile_agent_profile(context: AgentContext, project_name: str | None) -> bool` |
| 46 | - `reconcile_agent_profiles(project_name: str | None, *, all_scopes: bool=...) -> None` |
| 47 | - `activate_project(context_id: str, name: str, mark_dirty: bool=...)` |
| 48 | - `deactivate_project(context_id: str, mark_dirty: bool=...)` |
| 49 | - `reactivate_project_in_chats(name: str)` |
| 50 | - `deactivate_project_in_chats(name: str)` |
| 51 | - `build_system_prompt_vars(name: str)` |
| 52 | - `get_agents_md_chain(root: str, target: str) -> list[tuple[str, str]]` |
| 53 | - `build_agents_md_protocol(name: str, target: str | None=...) -> str` |
| 54 | - `get_additional_instructions_files(name: str)` |
| 55 | - `get_project_instruction_files(name: str, include_agents_md: bool=...) -> list[tuple[str, str]]` |
| 56 | - `get_project_agents_md_instruction_file(name: str) -> tuple[str, str] | None` |
| 57 | - `_format_project_instruction_files(instruction_files: list[tuple[str, str]]) -> str` |
| 58 | - `_normalize_include_agents_md(value: object) -> bool` |
| 59 | - `load_project_subagents(name: str) -> dict[str, SubAgentSettings]` |
| 60 | - `save_project_subagents(name: str, subagents_data: dict[str, SubAgentSettings])` |
| 61 | - `set_project_subagent_enabled(name: str, profile_id: str, enabled: bool) -> None` |
| 62 | - `_normalize_subagents(subagents_data: dict[str, SubAgentSettings], project_name: str=...) -> dict[str, SubAgentSettings]` |
| 63 | - 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`. |
| 64 | |
| 65 | ## Runtime Contracts |
| 66 | |
| 67 | - Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together. |
| 68 | - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change. |
| 69 | - 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. |
| 70 | - Additional project-edit payload sections are delegated through extensible `load_project_extended_data(...)` and `save_project_extended_data(...)`; the project helper must stay storage-agnostic and plugin-specific config rules belong to the owning plugin. |
| 71 | - Project extension data may add named top-level sections such as `llm`, but it must not overwrite core project fields owned by `EditProjectData`. |
| 72 | - Project extension save payloads exclude core project fields and transient inputs such as `git_token`; plugins needing core metadata should load it by project name. |
| 73 | - Project metadata setup creates and repairs `.a0proj/instructions`, `.a0proj/knowledge`, and `.a0proj/skills` so settings surfaces can open those folders consistently. |
| 74 | - AGENTS.md discovery is a linear root-to-target chain walk with `AGENTS.override.md` precedence; sibling directories are not scanned. |
| 75 | - Active-project AGENTS.md protocol guidance excludes the exact project root AGENTS.md because `build_system_prompt_vars(...)` already loads it into project instructions; prose for that protocol block lives in `prompts/agent.protocol.projects.agents_md.md`. |
| 76 | - Project MCP config uses the same JSON string shape as global MCP settings: an object with `mcpServers`. |
| 77 | - Project MCP load/save paths validate project names as simple folder basenames before touching `.a0proj/mcp_servers.json`. |
| 78 | - Activating or deactivating a project preserves the active chat profile when it |
| 79 | is available in the destination scope; otherwise it replaces it with the |
| 80 | configured default profile, then `agent0`, then the first available profile. |
| 81 | - Per-project profile availability is persisted sparsely in `.a0proj/agents.json`; |
| 82 | entries matching the profile definition's scoped default are omitted. The |
| 83 | helper retains the established tolerant load contract for read-only settings. |
| 84 | Profile-scoped mutations re-read the file strictly, preserve unrelated |
| 85 | entries, refuse malformed data, and write through `helpers.files`. General |
| 86 | project edit payloads neither expose nor mutate profile availability; legacy |
| 87 | `subagents` input is ignored. |
| 88 | - Profile reconciliation treats `None` as the Global scope. Callers must pass |
| 89 | `all_scopes=True` to check every loaded chat after a Global availability |
| 90 | change. Each pass resolves the available profile catalog once per encountered |
| 91 | scope; only chats whose active profile actually changes are persisted and |
| 92 | marked dirty. Context creation uses the same reconciliation after resolving |
| 93 | its scope, so a disabled configured profile cannot become invisibly active. |
| 94 | - Project updates and deletion refresh only chats assigned to that project and |
| 95 | persist each affected chat once; unrelated chats are never rewritten. |
| 96 | - Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, plugin state, settings/state persistence, secret handling. |
| 97 | - Imported dependency areas include: `helpers`, `helpers.print_style`, `os`, |
| 98 | `typing`. |
| 99 | |
| 100 | ## Key Concepts |
| 101 | |
| 102 | - Important called helpers/classes observed in the source: `files.get_abs_path`, `files.delete_dir`, `deactivate_project_in_chats`, `files.create_dir_safe`, `create_project_meta_folders`, `_normalizeBasicData`, `save_project_header`, `save_project_mcp_servers`, `load_project_mcp_servers`, `save_project_extended_data`, `load_project_extended_data`, `_project_extended_data_for_save`, `_merge_project_extended_data`, `_PROJECT_CORE_EDIT_KEYS`, `_PROJECT_TRANSIENT_INPUT_KEYS`, `extension.extensible`, `files.basename`, `dirty_json.parse`, `FileStructureInjectionSettings`, `cast`, `_normalizeEditData`, `load_edit_project_data`, `_edit_data_to_basic_data`, `save_project_variables`, `save_project_secrets`, `save_project_subagents`, `reactivate_project_in_chats`, `load_basic_project_data`. |
| 103 | - Keep request/response, tool, or helper semantics documented here at the same time as source changes. |
| 104 | |
| 105 | ## Work Guidance |
| 106 | |
| 107 | - Preserve public helper APIs used by core code and plugins unless every caller is updated. |
| 108 | - Keep path, auth, secret, persistence, network, and subprocess behavior explicit and bounded. |
| 109 | - Prefer adding cohesive helper functions here only when behavior is reused across modules. |
| 110 | |
| 111 | ## Verification |
| 112 | |
| 113 | - Run targeted tests for changed helper behavior; run security regressions for auth, filesystem, WebSocket, tunnel, upload, or secret-handling helpers. |
| 114 | - Related tests observed by source search: |
| 115 | - `tests/test_model_config_project_presets.py` |
| 116 | - `tests/test_office_document_store.py` |
| 117 | - `tests/test_plugin_activation_ui.py` |
| 118 | - `tests/test_projects.py` |
| 119 | - `tests/test_skills_runtime.py` |
| 120 | - `tests/test_task_scheduler_timezone.py` |
| 121 | - `tests/test_time_travel.py` |
| 122 | - `tests/test_tool_action_contracts.py` |
| 123 | |
| 124 | ## Child DOX Index |
| 125 | |
| 126 | No child DOX files. |