# `GgenIgniter.Pack`
[🔗](https://github.com/seanchatmangpt/ggen_igniter/blob/v26.9.8/lib/ggen_igniter/pack.ex#L1)

Resolves the `priv/ggen/<pack-name>/` convention (or an explicit `--pack-dir`)
into sane defaults for `--ontology`/`--query`/`--template`, per the pack
convention design:

    priv/ggen/<pack-name>/
    ├── pack.toml            # optional, not read here
    ├── ontology.ttl          # default --ontology
    ├── gates/*.rq            # default --query source, one query per file
    └── templates/*.{eex,tmpl} # default --template source (single-file case)

Pure helper, no `Igniter` dependency, so both `ggen_igniter.sync` and
`ggen_igniter.doctor` (and tests) can call it directly.

## Marketplace fetch (`fetch_pack!/2`)

Modeled on the real Rust `ggen`'s `ggen pack add <registry>:<id>`
(`crates/ggen-marketplace/src/marketplace/install.rs`): resolve a package
spec, download a real archive over HTTP, verify it, extract it into a local
cache directory so `resolve_dir!/1`-style discovery works against it.

Two real, simple registry sources are implemented -- **their verification
strength is genuinely different, and this moduledoc says so honestly**:

  * `"github:owner/repo"` (optionally `"@ref"`, default `"main"`) -- fetches
    `https://github.com/<owner>/<repo>/archive/refs/heads/<ref>.tar.gz`.
    GitHub's archive endpoint publishes **no checksum** to verify against,
    so this path is **print-only**: the real SHA-256 of the downloaded
    archive is printed so the caller can pin/verify it manually (e.g. against
    a value they've recorded from a prior trusted fetch). This is not
    fail-closed verification -- there is nothing to fail closed against.
  * `"hex:name@version"` (or `"hex:name"` to resolve the latest stable
    version via the Hex API) -- fetches the real Hex package tarball from
    `https://repo.hex.pm/tarballs/<name>-<version>.tar` and compares its
    real SHA-256 against the `checksum` field hex.pm's own API
    (`https://hex.pm/api/packages/<name>/releases/<version>`) publishes for
    that release. This path **is** fail-closed: a mismatch raises before
    anything is extracted.

Neither source is fabricated -- both are real public HTTP endpoints exercised
by the test suite (tagged `:requires_network`, see
`test/ggen_igniter_pack_fetch_test.exs`).

# `default_ontology`

```elixir
@spec default_ontology(String.t()) :: String.t()
```

Default `--ontology` path for `pack_dir`: `<pack_dir>/ontology.ttl`.

# `discover_queries`

```elixir
@spec discover_queries(String.t()) :: [{String.t(), String.t()}]
```

Discovers every `<pack_dir>/gates/*.rq` file, sorted lexically (so the
`NNN_` numeric-prefix convention controls ordering), mapped to
`{name, path}` where `name` is the filename stem with any leading
`^\d+_` digit-prefix stripped: `010_spec.rq` -> `"spec"`, `entities.rq` ->
`"entities"` (no prefix, no change).

# `discover_template`

```elixir
@spec discover_template(String.t(), String.t() | nil) ::
  {:ok, String.t()}
  | {:error, :none}
  | {:error, {:ambiguous, [String.t()]}}
  | {:error, {:stem_not_found, String.t(), [String.t()]}}
```

Discovers the `--template` under `<pack_dir>/templates/`.

With `stem` omitted (or `nil`) -- the plain `--pack NAME` case -- returns
`{:ok, path}` when exactly one `*.eex` or `*.tmpl` file exists,
`{:error, :none}` when there are zero, or `{:error, {:ambiguous, paths}}`
when there is more than one (no guessing which of N templates is "the" one).

With `stem` given -- the `--pack NAME:TEMPLATE_STEM` case (see
`Mix.Tasks.GgenIgniter.Sync`'s moduledoc) -- selects the one template file
whose basename up to its first `.` equals `stem` (`"resource.ex.eex"` ->
stem `"resource"`, `"domain.ex.eex"` -> stem `"domain"`), bypassing the
ambiguity error entirely even when the pack has multiple templates:
`{:ok, path}` on a unique match, `{:error, {:stem_not_found, stem, paths}}`
when no template's stem matches (`paths` lists every template actually
found, for a helpful error), or `{:error, {:ambiguous, paths}}` in the
degenerate case of two templates sharing the same stem with different
extensions (e.g. both `resource.eex` and `resource.tmpl` present).

# `fetch_pack!`

```elixir
@spec fetch_pack!(
  String.t(),
  keyword()
) :: String.t()
```

Fetches a real marketplace pack over HTTP and extracts it into a local
cache directory, returning the extracted pack directory (usable directly
with `resolve_dir!/1` via `pack_dir:`).

`spec` is one of:

  * `"github:owner/repo"` or `"github:owner/repo@ref"` (ref defaults to
    `"main"`) -- see the moduledoc for what verification this gives you
    (print-only SHA-256, no source-supplied checksum to compare against).
  * `"hex:name"` or `"hex:name@version"` (version defaults to the latest
    stable release per the Hex API) -- fail-closed SHA-256 verification
    against hex.pm's own published release checksum.

Options:

  * `:cache_dir` -- override the cache root (default
    `~/.cache/ggen_igniter/packs`). Each pack is extracted to a
    spec-derived subdirectory under this root and is safe to re-fetch
    (previous contents at that path are replaced).

Raises `ArgumentError` for an unrecognized spec, and `RuntimeError` for any
HTTP failure or (hex only) checksum mismatch.

# `resolve_dir!`

```elixir
@spec resolve_dir!(keyword() | map()) :: String.t()
```

Resolves the pack directory from `opts[:pack_dir]` (explicit override) or
`opts[:pack]` (looked up under `priv/ggen/<name>/`). Raises `ArgumentError`
if neither is given.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
