Files
headquarter/openspec/tasks/config-profile-multi-repo-mounts.md
Alex Blank 0e6521e433 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
2026-05-28 23:35:22 +02:00

4.4 KiB

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

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.