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<X.Y.Z> 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
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 <staged>/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
{
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<X.Y.Z>) |
| B | describe == v<X.Y.Z> |
No-op (clean) |
| C | describe matches v<X.Y.Z>-N-g… or ends -dirty |
Warning, no action |
| D | describe is some other tag |
Silent git.checkout(dir, v<X.Y.Z>) |
| E | describe returns nil/err |
Error |
Gitea-slug derivation
Default: sporel-<lib-id>. 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.