feat: config profile multi-repo mounts
- Add mappings array support to git_mount entries - Clone repository once per git_mount entry, mount multiple subdirectories - Normalize legacy source_path+target_path to mappings on read - Update _merge_git_mounts to dedup by (remote_url, branch) and concatenate mappings - Add _normalize_git_mount, _clone_git_repo, _resolve_git_mount_mappings helpers - Update GitMountItem Pydantic model with GitMountMapping and model_validator - Update frontend GitMountEditor component with mappings UI - Auto-convert legacy git mount entries to mappings format on load - Add 15 backend unit tests for normalization, resolution, and glob expansion - Update existing config profile resolver tests for new merge behavior Quality gates: pytest 167 passed, frontend typecheck clean Addresses: config-profile-multi-repo-mounts
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# Tasks: Config Profile Multi-Repo Mounts
|
||||
|
||||
## T1: Backend — Refactor git mount resolution for mappings
|
||||
|
||||
### T1.1: Normalize legacy git mount format
|
||||
**File**: `apps/api/src/api/tool_instances.py`
|
||||
- Add `_normalize_git_mount(entry: dict) -> dict` helper
|
||||
- Converts `{"source_path": "...", "target_path": "..."}` to `{"mappings": [{...}]}`
|
||||
- Call normalization at the start of `_resolve_single_git_mount`
|
||||
|
||||
### T1.2: Extract clone logic
|
||||
**File**: `apps/api/src/api/tool_instances.py`
|
||||
- Create `_clone_git_repo(remote_url, branch, clone_parent) -> str` function
|
||||
- Move clone/pull logic from `_resolve_single_git_mount` into it
|
||||
- Returns `repo_path` (path to `repo-clone`)
|
||||
|
||||
### T1.3: Resolve mappings from cloned repo
|
||||
**File**: `apps/api/src/api/tool_instances.py`
|
||||
- Create `_resolve_git_mount_mappings(repo_path, mappings, working_directory) -> list[dict]`
|
||||
- Iterates over mappings, expands globs, resolves targets
|
||||
- Returns flat list of volume mount dicts
|
||||
|
||||
### T1.4: Wire it together
|
||||
**File**: `apps/api/src/api/tool_instances.py`
|
||||
- Update `_resolve_single_git_mount` to:
|
||||
1. Normalize entry
|
||||
2. Clone repo once
|
||||
3. Resolve all mappings
|
||||
4. Return flat volume mounts
|
||||
|
||||
### T1.5: Update merge logic
|
||||
**File**: `apps/api/src/services/config_profile_resolver.py`
|
||||
- Change `_merge_git_mounts` merge key from `(remote_url, target_path)` to `(remote_url, branch)`
|
||||
- When same repo+branch: concatenate `mappings` arrays
|
||||
- When different: append as separate entry
|
||||
|
||||
### T1.6: Add validation
|
||||
**File**: `apps/api/src/api/config_profiles.py`
|
||||
- Validate `git_mounts` on create/update:
|
||||
- `remote_url` required
|
||||
- Either `mappings` (non-empty array) OR (`source_path` + `target_path`)
|
||||
- Each mapping has `source_path` and `target_path`
|
||||
|
||||
### T1.7: Unit tests
|
||||
**File**: `apps/api/tests/unit/test_git_mounts.py` (new)
|
||||
- Test normalization: legacy → mappings
|
||||
- Test single clone with 3 mappings → 3 volume mounts
|
||||
- Test glob expansion within mapping
|
||||
- Test relative target resolution
|
||||
|
||||
**File**: `apps/api/tests/unit/test_config_profile_resolver.py`
|
||||
- Update merge tests for new dedup key
|
||||
|
||||
---
|
||||
|
||||
## T2: Frontend — Git mount mappings editor
|
||||
|
||||
### T2.1: Update types
|
||||
**File**: `apps/web/src/api/config_profiles.ts`
|
||||
- Add `GitMountMapping` interface
|
||||
- Update `GitMountEntry` to have `mappings: GitMountMapping[]`
|
||||
- Keep optional `source_path`/`target_path` for backward compat
|
||||
|
||||
### T2.2: Auto-convert legacy entries on load
|
||||
**File**: `apps/web/src/components/config-profile-editor.tsx` or new `GitMountEditor.tsx`
|
||||
- On loading a profile, normalize any git_mount entries that lack `mappings`
|
||||
|
||||
### T2.3: Build mappings UI
|
||||
**File**: `apps/web/src/components/GitMountEditor.tsx` (new)
|
||||
- Render table/list of mappings per git mount entry
|
||||
- "Add mapping" button appends empty row
|
||||
- "Remove" button deletes a mapping row
|
||||
- Source path and target path inputs
|
||||
|
||||
### T2.4: Integrate into ConfigProfileEditor
|
||||
**File**: `apps/web/src/components/config-profile-editor.tsx`
|
||||
- Replace existing git_mounts flat form with GitMountEditor component
|
||||
- Ensure save sends correct JSON shape
|
||||
|
||||
### T2.5: Frontend tests
|
||||
**File**: `apps/web/src/components/GitMountEditor.test.tsx` (new)
|
||||
- Render with 2 mappings
|
||||
- Add mapping increases count
|
||||
- Remove mapping decreases count
|
||||
- Legacy entry auto-converts
|
||||
|
||||
---
|
||||
|
||||
## T3: Integration & Verification
|
||||
|
||||
### T3.1: Integration test
|
||||
**File**: `apps/api/tests/integration/test_config_profiles_git_mounts.py` (new)
|
||||
- Create profile with 2 mappings from same repo
|
||||
- Start instance
|
||||
- Verify single clone directory exists
|
||||
- Verify 2 bind mounts in compose file
|
||||
|
||||
### T3.2: Manual verification
|
||||
- Create a Config Profile with a monorepo git mount + 3 mappings
|
||||
- Create and start a pi-agent instance with the profile
|
||||
- Verify all 3 directories are mounted correctly
|
||||
- Verify legacy profile (single mapping) still works
|
||||
|
||||
### T3.3: Typecheck & tests
|
||||
```bash
|
||||
cd apps/web && npm run typecheck
|
||||
cd apps/api && pytest tests/unit/test_git_mounts.py tests/unit/test_config_profile_resolver.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estimation
|
||||
|
||||
| Task | Effort | Files |
|
||||
|------|--------|-------|
|
||||
| T1.1-T1.4 | 2h | 1 |
|
||||
| T1.5 | 1h | 1 |
|
||||
| T1.6 | 1h | 1 |
|
||||
| T1.7 | 2h | 2 |
|
||||
| T2.1-T2.4 | 3h | 3 |
|
||||
| T2.5 | 1h | 1 |
|
||||
| T3.1-T3.3 | 2h | 2 |
|
||||
| **Total** | **12h** | **11** |
|
||||
|
||||
## PR Strategy
|
||||
|
||||
**Single PR** (~400 lines estimated, within review budget):
|
||||
- Backend changes (T1)
|
||||
- Frontend changes (T2)
|
||||
- Tests (T1.7, T2.5, T3.1)
|
||||
|
||||
All changes are tightly coupled (backend schema + frontend UI + tests) so a single PR is appropriate.
|
||||
Reference in New Issue
Block a user