# lib-management.dep-fetcher Pure-Lua manifest-walker + dep-closure resolver. Reads a module's `manifest.module`, walks the transitive lib-dep tree, detects pin conflicts, then uses `lib-core.git` to ensure every lib in the closure is at the pinned `v` tag locally — cloning missing libs, checking out wrong-tag libs, surfacing dirty libs as warnings. Slice 5 of the dep-fetcher plan (`sporel-meta/docs/superpowers/plans/2026-05-30-dep-fetcher-implementation.md`). ## API ```lua local depf = require("lib-management.dep-fetcher") -- Primary entry point local result = depf.ensure_for_module_at(manifest_path, opts) -- Convenience wrapper for install-layout resolution (slice 6: switches -- to engine.install_root() once that binding lands). local result = depf.ensure_for_module(module_id, opts) ``` ### `opts` | Field | Type | Notes | |---|---|---| | `install_root` | string | Root of `/libs/`. Falls back to env `SPOREL_INSTALL_ROOT`. Slice 6 will add `engine.install_root()` and drop this stop-gap. | | `check_only` | bool | Skip all git operations. Closure walk + conflict detection only — used by unit tests and by the launcher's dep-diagnose surface. | | `gitea_base` | string | Override base URL (default `https://git.davoryn.de/sporel`). Slice 6 will source from `engine.json` via `engine.config(key)`. | ### `result` ```lua { ok : bool, -- false if conflict or error conflicts : { {lib_id, pins=[{source,version}]}, ... }, warnings : { {lib_id, kind="dirty", describe}, ... }, errors : { {lib_id, kind, message}, ... }, closure : { {source, lib_id, version}, ... }, } ``` `errors[].kind ∈ { "config", "manifest-read", "net-fail", "describe-fail", "checkout-fail" }`. ## State-check cases Per spec `sporel-meta/docs/superpowers/specs/2026-05-30-dep-fetcher-architecture.md` §5: | Case | Condition | Action | |---|---|---| | A | `is_repo == false` | `git.clone(url, dir, v)` | | B | `describe == v` | No-op (clean) | | C | `describe` matches `v-N-g…` or ends `-dirty` | Warning, no action | | D | `describe` is some other tag | Silent `git.checkout(dir, v)` | | E | `describe` returns nil/err | Error | ## Gitea-slug derivation Default: `sporel-`. Exception for libs whose ID ends in `.git` (currently only `lib-core.git`): the Gitea repo name pattern `*.git` is reserved by the protocol, so the slug appends `-lib`: ``` lib-core.maps → sporel-lib-core.maps lib-management.input → sporel-lib-management.input lib-core.git → sporel-lib-core.git-lib (exception) ``` This rule is encoded as a generic predicate (`_gitea_slug_for`) so any future `.git`-suffixed library uses the same translation without code change. ## Visibility model (slice 5 status) Only the **Vagrant transitive closure** is public. Everything else is private (paid content, Steam-backed Gitea-SSO planned). Slice 5 intentionally does not plumb credentials; if a private repo is in the closure and not locally present, the clone returns `errors[]` with `kind="net-fail"`. That is the documented limit of this slice. ## JSON parser Bundled inline (see `init.lua::json_decode`). The engine has no generic `engine.json.decode` binding yet — `engine.asset.load_json` is sandboxed to the asset-tree and cannot read fixture/install paths. A future slice may extract this parser into a shared utility lib; until then it lives here.