Project manifest

HubBound file

The project-level HubBound file — exact dependency pins plus named artifact and kit releases. Commit it so the team shares the same inputs.

What it is

The HubBound file is hubbound.json in the project directory. It records the exact artifacts and kits a project depends on and, optionally, the local packages that this repository can publish.

For install and upgrade, HubBound finds the nearest project file by walking up from the current directory. Deploy is deliberately different: --path must point to the directory that contains the manifest, so a parent manifest is never selected accidentally.

What it is for

Use dependencies to declare the tooling a project expects. Running hubbound install with no arguments bulk-installs every dependency — similar to bare npm install reading package.json.

Use deployments to describe artifacts and kits that hubbound deploy may publish. Install reads dependencies; deploy reads deployments. Keeping those maps separate prevents a project dependency from becoming a release just because it appears in the same file.

File structure

New files use schema_version 2. hubbound init creates the name and an empty dependencies object; add deployments when the repository publishes artifacts or kits.

{
  "schema_version": 2,
  "name": "my-app",
  "dependencies": {
    "artifacts": {
      "vendor/shared-rule": "1.4.0"
    },
    "kits": {
      "vendor/base-kit": "2.0.0"
    }
  },
  "deployments": {
    "auditor": {
      "kind": "artifact",
      "path": "packages/auditor",
      "tag": "security-auditor",
      "version": "1.2.0",
      "visibility": "public",
      "artifact_type": "SKILL",
      "description": "Security audit skill",
      "content": { "entrypoint": "SKILL.md" },
      "files": ["SKILL.md", "references/**", "scripts/**"]
    },
    "security-kit": {
      "kind": "kit",
      "path": "packages/security-kit",
      "tag": "security-kit",
      "version": "2.0.0",
      "visibility": "public",
      "description": "Security tooling",
      "changelog": "Publish the auditor skill",
      "members": [
        { "deployment": "auditor" },
        { "tag": "vendor/remote-auditor", "version": "1.5.0" }
      ]
    }
  }
}
schema_version Manifest format. New project files use 2; unsupported versions are rejected.
name Project label. Defaults to the directory basename when you run hubbound init. Informational — not used as an install key.
dependencies Optional object containing exact-version artifact and kit pins under dependencies.artifacts and dependencies.kits.
deployments Optional map of local deployment names. deploy requires at least one entry and uses these entries, not dependencies.
deployment name Local selector, not the remote tag: 1–64 characters, starting with a letter or digit, followed by letters, digits, ., _ or -.
deployment.path Relative directory containing the package files, resolved from the manifest directory. Defaults to .; absolute paths, backslashes and parent traversal are rejected.
deployment.tag/version Remote package identity and exact SemVer release. Tags use lowercase path segments such as security-auditor or vendor/security-auditor.
deployment.visibility One of private, org or public.
artifact_type/content/files Artifact-only properties. artifact_type is a non-empty type label (the runtime types are MCP, SKILL, SUBAGENT, HOOK and RULE); content is optional JSON metadata/config; files is a non-empty list of relative file patterns.
kit.changelog/members Kit composition properties. members is exactly a local {deployment} or a remote {tag, version} reference; changelog is optional kit release text and is not sent for artifacts.
  • Project dependency pins must be exact SemVer values such as 1.2.0 — latest is not valid in a v2 project manifest. Profile manifests retain their separate historical latest behavior.
  • Unknown fields, duplicate JSON keys, invalid types, duplicate kit members and local deployment cycles are rejected.
  • HubBound finds the file by walking up from the current directory.

Legacy files and migration

Older manifests with root-level artifacts, kits, or publish fields are accepted as v1 for compatibility and normalized in memory. Mutating operations serialize the canonical v2 shape under dependencies and deployments; hubbound init writes v2 directly.

Do not mix root-level artifacts/kits with a v2 document. In v2, dependencies is the only dependency map and deployments is the only publication map.

Publish with hubbound deploy

A deployment is a release recipe, not an upload directory guessed from the current shell. Select one or more deployment names, or omit names to process every entry in deployments.

For an artifact, deploy snapshots the files declared by files, hashes every byte, asks the authenticated release control plane which digests are missing, uploads those bytes to the presigned URLs, and commits the release. A kit sends its exact artifact composition through the kit release route; local artifact members are prepared before the kit.

$ hubbound auth login

$ hubbound deploy --path . --dry-run --json

$ hubbound deploy auditor --path .

$ hubbound deploy security-kit --path . --allow-dirty

--path <dir> Exact directory containing hubbound.json. Defaults to .; deploy does not walk ancestors to find a manifest.
--allow-dirty Accept a dirty Git worktree and send source.dirty=true. Without it, any tracked, untracked or deleted file stops preflight.
--dry-run Run manifest, Git and file preflight only. It never calls release prepare, presigned PUT or commit.
--json Print the deploy summary as JSON, including state, commit SHA, manifest digest when returned, upload count and idempotency key.
--wait-timeout <duration> Maximum time for a configured public-registry watcher. The default is 5m; it does not make a committed release active by itself.
  • Preflight requires a Git worktree, remote.origin.url, an HTTPS remote without embedded credentials, and a 40- or 64-character hexadecimal HEAD SHA. Query and fragment are removed from the normalized repository URL.
  • Artifact file patterns are relative and support **. Every match must be a regular, non-symlink UTF-8 text file: at most 250 files, 2 MiB per file and 10 MiB total logical size.
  • The snapshot excludes .git, hubbound.json, .env files, backup files and private key/certificate extensions (.pem, .key, .crt), and rejects binary formats such as images, archives, PDFs, executables and fonts.
  • The authenticated control-plane calls use DPoP. The presigned CAS PUT uses the exact headers returned by the backend and must not receive Authorization or DPoP headers.
  • A committed release and its public-registry projection are separate states. In the current checkout the public watcher is not wired, so a public release may be committed while the command reports that publication status is unavailable; verify release_state and publication_state separately.

Compatible commands

These commands read and/or write the project hubbound.json.

$ hubbound init

$ hubbound install artifact jane-a1b2c3/my-hook

$ hubbound install

$ hubbound upgrade all

hubbound init Create hubbound.json in the current directory (fails if it already exists).
hubbound install No args + manifest found → install every pin. With args + local scope → install and record the pin.
hubbound upgrade In local scope, refresh pins in hubbound.json after upgrading (single entity or all).
hubbound deploy Validate and publish entries from deployments; it never publishes dependency pins from dependencies.
--scope local|global Default scope: local if a hubbound.json is found above cwd, otherwise global.

vs profile manifests

Both use hubbound.json, but project manifests and profile manifests have different responsibilities.

  • Project hubbound.json → local/project dependencies and optional deployments; usually committed.
  • profiles/<name>/hubbound.json → global dependency pins under user config; profiles cannot contain deployments.
  • --profile on install always targets a profile file and forces global scope.
  • Bare hubbound install requires a project hubbound.json (or run hubbound init first).