# Story: Git-Based Sources (No Hardcoded Directories) **Phase:** `28_git_based_sources.md` ยท **E2E:** none (no UI/API change; unit + integration cover the git flow) ## Narrative As **the admin (owner)**, I don't want the import sources hard-coded to `~/Homelab` and `~/Deployments`. I want to **specify a list of git repository URLs** and have `import_docs` **clone or pull** each one into a dedicated local location and then **index all the code** in them. - **Given** a list of git URLs and a destination directory - **When** I run `import_docs` - **Then** each repo is cloned (first run) or pulled to `ff` (subsequent runs) into the dedicated directory, and the resulting files are indexed exactly like any other A9-format directory. ## Acceptance criteria 1. `BOR_GIT_SOURCES` (comma-separated git URLs) + `BOR_SOURCES_DIR` (default `~/bor-sources`) settings; `git_source_list` parses the CSV. 2. `scripts/git_sync.py::clone_or_pull(url, dest)` โ€” shallow clone when the dest has no `.git`, `--ff-only` pull otherwise; raises `GitSyncError` (with stderr) on failure and on missing `git`. 3. `scripts.import_docs` resolves `BOR_GIT_SOURCES` โ†’ cloned/pulled local dirs and passes them to `import_sources`; `--source ` overrides (manual mode wins); a failed sync aborts the run (non-zero exit, no partial import). 4. The A9 format filter / hidden-dir skip / exclusion list still apply to the cloned content (the importer's walk is unchanged). 5. `documents.source` = the repo directory name (e.g. `homelab`). 6. `.env.example` + README document both source modes. 7. Unit (git_sync dispatch/errors) + integration (import_docs resolution with mocked git + import) green; `app/` coverage >90%; one `--no-gpg-sign` commit. ## Playwright Mapping Rule No story E2E suite (no `app/` UI/API change). The git flow is covered by unit tests (`tests/unit/test_git_sync.py`) and an integration test (`tests/integration/test_import_docs_git.py`) with `clone_or_pull` and `import_sources` mocked โ€” no network, no `git`, no real embed endpoint.