For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /concepts/manifest-api-proposal.md.
Quick start

Manifest and pack installation proposal

Status: draft for review. This describes a proposed API, not the currently implemented schema. Approval covers the design; it does not authorize publishing or dependency changes.

Granary distributes source material that consumers own. A pack can contain docs, code, examples, standards, and other packs. One granary.json describes a project that provides material, consumes material, or does both.

The common case

Put the source and optional pack name in the key. Put the requested Git revision in the value.

{
  "name": "holly",
  "packs": {
    "github:thebytefarm/granary/engineering": "v1",
    "github:thebytefarm/granary/testing": "testing/v2",
    "github:vercel/next.js": ["v15.5.0", "v16.0.0"]
  }
}

No mandatory aliases, repeated source fields, empty objects, or output paths. Repositories without Granary manifests remain usable sources.

All repository names and revisions in examples illustrate the proposed syntax; they do not assert that those packs or tags currently exist.

One manifest

Only name is required. Proposed optional root fields are description, version, license, files, and packs.

FieldMeaning
nameProject or default pack name.
descriptionShort explanation of the material.
versionOptional authored release label, not a Git revision resolver.
licenseSPDX expression, UNLICENSED, or a license-file declaration such as SEE LICENSE IN LICENSE.
filesSelection of the project's own content.
packsNamed local pack definitions and source-backed selections. Composition behavior needs the decision below.

License metadata does not replace license text, attribution, or upstream redistribution obligations. There is no generic metadata bucket or speculative tags field.

File selection

Group selection under files. Globs apply to any file type.

{
  "name": "engineering",
  "files": {
    "include": ["README.md", "standards/**", "examples/**"],
    "exclude": ["examples/**/fixtures/**"]
  }
}

Selection paths are relative to their owning source. Exclusions win. Missing exclude means no additional exclusions. An explicitly empty include selects no own files, allowing a composition-only pack.

Selecting files copies bytes; it does not execute source code or install the source repository's dependencies.

Source and pack identity

Proposed GitHub grammar:

github:<owner>/<repo>[/<pack-name>]

The first two segments identify the repository. An optional pack name selects a declared pack, not an arbitrary filesystem directory. A missing named pack is an error.

Local definitions use ordinary names:

{
  "name": "team-standards",
  "packs": {
    "engineering": {
      "files": {
        "include": ["engineering/**"]
      }
    },
    "testing": {
      "files": {
        "include": ["testing/**"]
      }
    }
  }
}

An installation of github:acme/team-standards/engineering selects only engineering and its declared members. It does not include testing or other parent-level selections.

The same grammar needs explicit equivalents for local directories and other Git hosts. Local source keys should require a recognizable prefix such as ./ or ../, rather than competing with ordinary pack names. Those exact locator forms are not finalized here.

Strings, objects, and arrays

ValueMeaning
StringOne installation at a requested Git revision.
ObjectOne installation with explicit revision type, selection, or destination overrides.
Array of strings and objectsMultiple installations of the same source and selected pack.
{
  "name": "holly",
  "packs": {
    "github:vercel/next.js": [
      "v15.5.0",
      {
        "tag": "v16.0.0",
        "files": {
          "include": ["docs/**", "examples/**"],
          "exclude": ["**/fixtures/**"]
        },
        "output": "apps/web/.context/next"
      }
    ],
    "github:thebytefarm/granary/engineering": {
      "branch": "main",
      "output": "contributing/engineering"
    }
  }
}

Expanded Git objects accept at most one of branch, tag, or commit. With none, resolve the remote's default branch. Local-directory selections do not accept Git revision selectors.

The pack is already in the key. Do not repeat it in a pack field. Avoid competing ref and version fields on installation objects.

When narrowing a declared pack with consumer files, filter its declared own-file selection. Do not expand beyond the author's selection or silently filter its child packs.

Versioning: use Git releases

Follow the GitHub Actions model: a requested revision can identify a branch, tag, or exact commit.

Author intentExample request
Repository-wide releasev1.2.0
Independent pack releaseengineering/v1.2.0
Maintained major channelengineering/v1
Development linemain
Immutable sourceFull commit SHA.

Independent pack tags still point at whole repository commits. Granary selects the named pack from that snapshot. There is no registry or history scan to locate a matching manifest version.

Strings resolve exact Git names, not semver ranges. Do not silently add a v prefix. If a branch and tag have the same name, require an explicit selector object rather than guessing.

The manifest records the requested revision. The lock records the resolved commit, selected pack, file selection, integrity, composition edges, and destinations.

  • Install restores the locked selection without advancing refs.
  • Update resolves the requested refs again and prepares an explicit review.
  • Moving from v1 to v2 changes the request. Granary does not infer that upgrade.
  • Accepted local customizations remain separate from upstream content hashes.

Packs inside packs

Explicitly included packs should install with their parent. Discovering a nested manifest on disk should not create membership.

For example, a standalone composition pack could declare:

{
  "name": "recommended-typescript",
  "files": {
    "include": []
  },
  "packs": {
    "github:thebytefarm/granary/engineering": "v1",
    "github:thebytefarm/granary/documentation": "v1",
    "github:thebytefarm/granary/typescript-functional": "v1"
  }
}

Installing it installs those three members, including their own explicitly declared members. Nested composition extends the current flat-preset implementation.

The membership decision

The remaining tension is whether a root packs map is both a catalog and a composition. The examples above deliberately expose that choice rather than hiding a second meaning in the resolver.

:::question Should installing a repository's default pack include every entry declared in its root packs map?

The simplest consistent rule is that a pack contains its own files and its declared child packs. Named children remain individually selectable. That makes the root both a catalog and an aggregate; it means installing a catalog root installs all its entries.

  • Yes: packs always declares members; the default/root pack includes all of them.
  • No: separate available pack definitions from an explicit membership list.

Recommended: Yes: packs always declares members; the default/root pack includes all of them. :::

Under the recommended rule, selecting a named child never pulls in its siblings. A catalog maintainer who wants a narrower recommended set defines that set as its own named pack.

Graph resolution rejects cycles and reports the reference chain. Identical selections may share fetches, but different revisions remain distinct. Array position and discovery order do not define identity or overwrite precedence.

Sibling pack composition also needs a same-snapshot reference form. Prefer a local named-pack reference over repeating a remote URL and resolving a moving branch again. Its exact syntax remains open; do not treat a filesystem directory and a named sibling as interchangeable.

Default content

For a source with a Granary manifest, selecting its default or named pack uses that pack's explicit contract. For a repository without a manifest, select source files directly. Explicit consumer files narrows either selection.

This supersedes the earlier suggestion that discovering a manifest should never affect default selection. The manifest defines the source's authored default; the lock makes changes to that definition reviewable.

:::question What should an arbitrary Git repository include when the consumer supplies no files selection?

Requiring a manifest defeats arbitrary-repository installation. Guessing docs directories is unreliable. A complete tracked-file snapshot is predictable but can be large.

  • All tracked files, with a size warning before the first unlocked installation.
  • Require an explicit file selection for repositories without a manifest.

Recommended: All tracked files, with a size warning before the first unlocked installation. :::

Git metadata is never payload. Submodules, Git LFS content, symlinks, and untracked local files need explicit source-adapter rules before implementation. A local directory does not automatically have Git's tracked-file boundary.

Destinations and ownership

Omitted output installs beneath .context/. The generated layout needs a collision-safe source identity, selected pack, and requested revision. Do not use only <repo>/<ref>: owners, pack names, and slash-containing refs can collide.

Exact default path encoding remains open. It should remain stable while a tracked branch advances, rather than moving accepted edits into a new commit-named directory on every update.

An explicit output adopts material into a consumer-chosen directory, including docs/, contributing/, and source directories. Installed files belong to the consumer in both modes. .context/ is a default location, not a prohibition on local edits.

Before writing, reject overlapping file destinations across installations. Never silently use array order or composition depth to choose a winner. Existing unrelated consumer files remain untouched.

:::question Should a parent's explicit output relocate its included child packs?

A composition has multiple independently tracked members. Flattening them into one directory causes collisions; ignoring the parent's destination makes adopting a whole set awkward.

  • Yes: treat it as a destination root and namespace child packs beneath it.
  • No: output affects only the selected pack's own files; configure child destinations separately.

Recommended: Yes: treat it as a destination root and namespace child packs beneath it. :::

The exact namespace and precedence between parent and authored child outputs need to be settled before schema implementation. Upstream destinations must not be able to escape the consumer-selected root.

Scope after design review

Implementation requires coordinated schema, discovery, resolver, lock, installation, update-review, CLI, documentation, and test changes. Existing provenance and consumer customization protections remain intact.

This proposal does not add executable transforms, automatic code execution, semver range resolution, a registry, or an agentic linter. Inline standards enforcement formatting remains a separate agreed documentation change.

Review approval settles the selected design decisions. It does not make these examples valid in the current CLI.

References