diff --git a/.github/ISSUE_TEMPLATE/bundle_submission.yml b/.github/ISSUE_TEMPLATE/bundle_submission.yml index 0595b8c60b..5b4410f8be 100644 --- a/.github/ISSUE_TEMPLATE/bundle_submission.yml +++ b/.github/ISSUE_TEMPLATE/bundle_submission.yml @@ -9,7 +9,7 @@ body: Thanks for contributing a bundle! This template captures metadata for maintainers to validate formatting, links, component resolution, and installation evidence. Maintainers do not audit, endorse, or support bundle code or installed components. **Before submitting:** - - Review the [Bundles reference](https://github.com/github/spec-kit/blob/main/docs/reference/bundles.md) + - Review the [Bundle publishing guide](https://github.github.io/spec-kit/bundles/publishing.html) - Ensure your bundle has a valid `bundle.yml` manifest - Create a GitHub release with a versioned bundle artifact - Test installation from a downloaded artifact: `specify bundle install ./your-bundle-1.0.0.zip` diff --git a/README.md b/README.md index 3b8ac0cdc0..987496b4fb 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Spec Kit combines ready-to-use processes with composable mechanisms: | Automate prompts, commands, gates, and shell tasks | [Workflow](https://github.github.io/spec-kit/workflows/overview.html) | | Modify an installed workflow without forking it | [Workflow overlay](https://github.github.io/spec-kit/workflows/overlays.html) | | Add reusable executable behavior to workflows | [Workflow step](https://github.github.io/spec-kit/workflows/developing.html#custom-step-packages) | -| Install a curated setup for a role or team | [Bundle](https://github.github.io/spec-kit/reference/bundles.html) | +| Install a curated setup for a role or team | [Bundle](https://github.github.io/spec-kit/bundles/overview.html) | | Discover community components | [Catalog](https://github.github.io/spec-kit/reference/catalogs.html) | | Host a private catalog of approved components | [Catalog](https://github.github.io/spec-kit/reference/catalogs.html) | | Access private catalogs and component downloads | [Authentication](https://github.github.io/spec-kit/reference/authentication.html) | diff --git a/bundles/assess/README.md b/bundles/assess/README.md index 4aae8459f6..790677b880 100644 --- a/bundles/assess/README.md +++ b/bundles/assess/README.md @@ -38,3 +38,8 @@ specify bundle remove assess ``` Removing the bundle uninstalls the workflow and the extension it contributed, unless they are still depended on by another installed bundle (FR-022). Components you installed independently are not attributed to this bundle and survive removal. + +## See also + +- [Bundle overview](https://github.github.io/spec-kit/bundles/overview.html) +- [Bundle CLI reference](https://github.github.io/spec-kit/bundles/cli.html) diff --git a/bundles/bugfix/README.md b/bundles/bugfix/README.md index f30a952bb7..9315568abc 100644 --- a/bundles/bugfix/README.md +++ b/bundles/bugfix/README.md @@ -36,3 +36,8 @@ specify bundle remove bugfix ``` Removing the bundle uninstalls the workflow and the extension it contributed, unless they are still depended on by another installed bundle (FR-022). Components you installed independently are not attributed to this bundle and survive removal. + +## See also + +- [Bundle overview](https://github.github.io/spec-kit/bundles/overview.html) +- [Bundle CLI reference](https://github.github.io/spec-kit/bundles/cli.html) diff --git a/docs/README.md b/docs/README.md index 8df9caa8c0..013235540d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -54,6 +54,12 @@ To build the documentation locally: - `workflows/overlays.md` - Workflow composition through overlays and slots - `workflows/catalogs.md` - Workflow and step catalogs, releases, and trust - `workflows/publishing.md` - Workflow publishing and catalog submission guide +- `bundles/overview.md` - Bundle user guide and first-party setups +- `bundles/cli.md` - Bundle management CLI reference +- `bundles/developing.md` - Bundle authoring and installation testing guide +- `bundles/api.md` - Manifest, reference expansion, and installed-record contracts +- `bundles/catalogs.md` - Bundle catalogs, policies, and release history +- `bundles/publishing.md` - Bundle publishing and catalog submission guide - `community/extensions.md` - Community extension listing - `integrations/index.md` - Integration directory and shared setup guidance - `integrations/cli.md` - Shared integration management CLI reference diff --git a/docs/bundles/api.md b/docs/bundles/api.md new file mode 100644 index 0000000000..2fd43ca27e --- /dev/null +++ b/docs/bundles/api.md @@ -0,0 +1,236 @@ +# Bundle API reference + +This page defines the declarative bundle manifest, reference expansion, and +installed-record contracts. Bundles compose existing components; they do not +provide a separate execution API. + +## Manifest + +The UTF-8 `bundle.yml` file is a YAML mapping. Bundle ZIPs must contain it at +the archive root. The developing guide provides complete examples. + +| Field | Requirement | +| --- | --- | +| `schema_version` | Required; `"1.0"` or `"1.1"` | +| `bundle` | Required metadata mapping | +| `requires.speckit_version` | Required nonempty Spec Kit version constraint | +| `requires.tools` | Optional list of tool names; defaults to empty | +| `requires.mcp` | Optional list of MCP server names; defaults to empty | +| `integration` | Optional mapping with a nonempty `id` | +| `provides` | Optional component/reference mapping | +| `tags` | Optional list of strings | + +An absent or null `requires`/`provides` mapping is treated as empty, although +the required Spec Kit constraint must still be supplied. A present non-mapping +value is invalid. String-list fields cannot be bare strings or contain +non-string members. + +### Metadata + +All fields in `bundle` are required and nonempty: + +| Field | Meaning | +| --- | --- | +| `id` | Filesystem-safe bundle identifier | +| `name` | Human-readable name | +| `version` | Exact semantic version | +| `role` | Role, team, or persona served | +| `description` | Description of the setup | +| `author` | Author or organization | +| `license` | License identifier | + +IDs match `^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$`: start and end with a +lowercase letter or digit, with lowercase letters, digits, dots, underscores, +and hyphens between. Path separators and traversal IDs are invalid. + +Bundle and component versions use full `MAJOR.MINOR.PATCH` SemVer, optionally +with prerelease/build metadata and a leading `v` or `V`. Component ranges and +`latest` are not accepted. The Spec Kit requirement is instead a constraint, +such as `">=0.9.0,<2.0.0"`; it is evaluated during installation/update, not +against the running version by `bundle validate`. + +`requires.tools` and `requires.mcp` generate requirement warnings. They do not +install tools, provision MCP servers, or verify those dependencies are present. + +### Integration + +```yaml +integration: + id: copilot +``` + +Omitting `integration` or setting it to null makes a bundle +integration-agnostic. A mapping without a nonempty ID is invalid. + +The root bundle must agree with the project's active integration. It does not +switch integrations. Referenced bundle integration differences or an +indeterminate effective integration are warnings rather than additional +integration gates. + +### Component references + +`provides.extensions`, `provides.presets`, `provides.steps`, and +`provides.workflows` are lists of mappings. Omitted or null lists are empty. +Each `(kind, id)` may appear only once in a manifest, even with identical pins. +The same ID in different component kinds is distinct. + +| Field | Extensions | Presets | Steps | Workflows | +| --- | --- | --- | --- | --- | +| `id` | Required | Required | Required | Required | +| `version` | Required | Required | Optional | Required | +| `source` | Optional catalog name | Optional catalog name | Optional catalog name | Optional catalog name | +| `priority` | Optional; default `10` | Required integer, at least `1` | Not used | Not used | +| `strategy` | Not used | Required | Not used | Not used | + +Explicit extension priority must also be at least `1`. Preset strategies are +`replace`, `prepend`, `append`, and `wrap`; their effects are defined by the +[preset composition contract](../presets/api.md). + +`source` asserts the expected winning component catalog. It does not select a +lower-priority catalog, bypass a discovery-only policy, accept a direct +artifact URL, or refer to bundle-local payloads. Explicit sources are checked +even for already installed components, require network access, and reject +ambiguous catalog names. + +Without `source`, packaged extensions and presets take precedence over their +catalogs. Packaged workflows may also be used. The CLI reference explains +stable packaged patch compatibility and catalog pin enforcement. + +### Bundle references + +A nonempty `provides.bundles` list requires schema `"1.1"`. An empty or null +list has no references and is also accepted under schema `"1.0"`. + +```yaml +provides: + bundles: + - id: qa-pack + version: 2.0.0 + source: team-catalog +``` + +| Field | Requirement | +| --- | --- | +| `id` | Required safe slug; unique in this manifest and different from the parent ID | +| `version` | Required exact SemVer | +| `source` | Optional expected winning bundle catalog ID, not a URL | + +References are resolved at command time through the same effective bundle +catalog stack as the parent. An explicit source is an assertion, not a +precedence override. A mismatch fails without falling through to another +catalog. + +## Reference expansion + +The result is a single flat component plan, not nested installations. + +### Ordering and conflict rules + +References are walked depth-first in declaration order, followed by the +declaring bundle's own components. The final plan preserves component grouping: +extensions, presets, steps, then workflows. + +Each component is installed at most once per `(kind, id)`. Repeated declarations +agree when versions and sources match; omitted and explicit sources differ. +Extensions and presets must also agree on priority, and presets on strategy. +An omitted extension priority equals its default of `10`. + +Conflicting sibling references fail unless the declaring bundle resolves them +with its own component declaration. At every level, a bundle's own declaration +wins over its references, takes its own declaration position, and emits an +override warning with the replaced declarations' origin chains. + +One bundle ID can have only one version in an expansion. A second version is +an error, not another installation. Choose compatible referencing releases to +resolve that conflict. + +### Limits and gates + +- Cycles are rejected with their reference chain. +- At most `10` levels of reference nesting are allowed. +- At most `50` distinct referenced bundle IDs are allowed, excluding the root. +- Install and update enforce the Spec Kit version requirement for every bundle + in the graph. +- The root is the only integration gate. Referenced integration differences + generate warnings. +- Tools and MCP requirements are unioned across the graph for warnings. + +`info` can expand discovery-only references for inspection. Install, update, +and online validate reject them as non-installable. `validate --offline` does +not expand references and warns once per declared reference. `build` performs +structural checks only; it neither resolves nor embeds referenced bundles. + +Referenced manifests require network-backed catalog downloads. Local/offline +reference resolution and local `bundle info` previews are not supported. +First-party bundle catalogs keep no release history, so a first-party reference +can become unavailable when the catalog advances. Broader schema compatibility +negotiation is not implemented. + +## Installed records and ownership + +The project stores provenance in `.specify/bundle-records.json`: + +```json +{ + "schema_version": "1.0", + "updated_at": "2026-01-01T00:00:00Z", + "bundles": [ + { + "bundle_id": "team-setup", + "version": "1.0.0", + "installed_at": "2026-01-01T00:00:00Z", + "required_components": [ + {"kind": "extensions", "id": "bug", "version": "1.0.0"} + ], + "contributed_components": [ + {"kind": "extensions", "id": "bug", "version": "1.0.0"} + ], + "installed_versions": { + "extensions:bug": "1.0.1" + } + } + ] +} +``` + +| Record field | Meaning | +| --- | --- | +| `bundle_id`, `version` | Installed root bundle identity | +| `installed_at` | Original installation time, preserved across refresh/update | +| `required_components` | Every required component in the expanded plan, including independently supplied components | +| `contributed_components` | Components attributed to the bundle; always a subset of its requirements | +| `installed_versions` | Optional known actual versions keyed by plural `kind:id`, distinct from requested pins | +| `bundle_references` | Optional list of direct/transitive bundle references, each with `id`, `version`, and resolved catalog `source` | + +Record component entries use plural `kind` names and contain `id`, `version`, +and any declared `source`, `priority`, and `strategy`. For a reference-bearing +bundle, only the parent has an installed record; child references are recorded +once, not installed as separate bundle records. + +Required pins participate in cross-bundle conflict checks even when the +component was independently installed. Compatible independently installed +components are not adopted, refreshed, or uninstalled by the bundle. Components +already owned by other bundles can be shared. + +Shared requirements must agree on version and source, extension/preset +priority, and preset strategy. A bundle update cannot replace a pin another +bundle requires. When removing a bundle or dropping a component during refresh, +components another bundle still needs are retained; ownership transfers to a +remaining requiring bundle if necessary. + +Older records without `required_components` retain their known contributed +pins. A successful reinstall/refresh records the full requirements. Missing +records are treated as no bundles, but malformed records and incompatible +schema major versions are errors, not an empty installation. + +Record writes happen after successful component operations. They do not make +installation, update, refresh, or removal globally transactional; see the CLI +reference for failure and rollback boundaries. + +## See also + +- [Developing bundles](developing.md) +- [CLI output and lifecycle behavior](cli.md) +- [Catalog entry and release contracts](catalogs.md) +- [Publishing bundles](publishing.md) +- [Bundle overview](overview.md) diff --git a/docs/bundles/catalogs.md b/docs/bundles/catalogs.md new file mode 100644 index 0000000000..5066d09a35 --- /dev/null +++ b/docs/bundles/catalogs.md @@ -0,0 +1,217 @@ +# Bundle catalogs + +Bundle catalogs locate bundle manifests and describe the setups available for +discovery. They do not supply the extension, preset, workflow, or step catalogs +needed by those manifests. Register and vet those component catalogs +separately. + +## Trust and installation policy + +> [!IMPORTANT] +> Vet both a bundle source and its component catalogs before installation. +> A project can supply bundle and install-allowed component catalogs under +> `.specify/`; their presence is not evidence that anyone reviewed them. + +Inspect all applicable sources, including those you did not add yourself: + +```bash +specify bundle catalog list +specify extension catalog list +specify preset catalog list +specify workflow catalog list +specify workflow step catalog list +``` + +Treat unfamiliar sources as unvetted until you have reviewed them. For workflow +components, inspect shell `run` and `env` fields and referenced code, including +active overlays. Even `strict: true` steps can change executable lookup or +runtime loading through environment bindings; see +[reviewing shell execution](../workflows/steps.md#reviewing-shell-execution). + +| Policy | Effect | +| --- | --- | +| `install-allowed` | Permits installation/update from this source | +| `discovery-only` | Permits search and inspection, but refuses installation/update | + +The `verified` flag is catalog metadata, not an installation policy or a +security guarantee. Community entries are unverified, and maintainers do not +review, audit, endorse, or support their code. Bundle installation does not +automatically register or trust companion catalogs. + +## Built-in sources + +| Source ID | URL | Priority | Policy | +| --- | --- | --- | --- | +| `default` | `builtin://default` | `1` | `install-allowed` | +| `community` | `builtin://community` | `20` | `discovery-only` | + +The default catalog is `bundles/catalog.json`; community submissions appear +in `bundles/catalog.community.json`. Both are fetched from the repository +online. Offline discovery uses the packaged wheel snapshots. Transient +built-in catalog availability failures warn and fall back to those snapshots; +malformed content or security validation failures do not silently fall back. + +The bundle manifest's `download_url` still requires network access. A packaged +catalog snapshot is not a local payload store. + +## Configure sources + +From an initialized project, after vetting the source: + +```bash +specify bundle catalog add https://example.com/bundles.json \ + --id team --priority 10 --policy install-allowed +specify bundle catalog list +specify bundle catalog remove team +``` + +The CLI writes `.specify/bundle-catalogs.yml`: + +```yaml +schema_version: "1.0" +catalogs: + - id: team + url: https://example.com/bundles.json + priority: 10 + install_policy: install-allowed +``` + +User configuration uses the same shape at `~/.specify/bundle-catalogs.yml`. +CLI add/remove operations write project scope only. Both readers accept a +missing schema version for compatibility and reject an unsupported major +version. + +Source URLs can be HTTPS, HTTP on localhost, `file://`, local paths, or known +`builtin://` URLs. Added local paths are stored as absolute paths. This +flexibility applies to the catalog file, not its bundle download URLs. + +### Precedence + +Two steps determine the effective stack: + +1. Merge sources by ID: a project-scoped source replaces the same ID in user + scope, and user scope replaces the same ID in built-in scope. +2. Sort surviving sources by numeric priority, then source ID. Lower priority + numbers win. + +Scope does not outrank priority for distinct IDs. An added source defaults to +priority `10`, ahead of the community source at `20`, but behind the default +source at `1`. Override a built-in source by its ID or choose an appropriate +priority deliberately. + +The first source containing a bundle ID wins. A missing historical release, +source mismatch, or discovery-only policy does not cause fallback to a +lower-priority catalog. `source` in a bundle reference asserts this winner; it +does not select a source. + +Re-adding an existing URL with matching ID/settings is a no-op. Conflicting +URL/ID settings fail. Built-in sources cannot be removed, but a project +override of a built-in ID can be removed to restore the lower-scope source. + +## Catalog payload + +Catalog files are JSON objects with a `bundles` mapping keyed by bundle ID: + +```json +{ + "schema_version": "1.0", + "bundles": { + "team-setup": { + "id": "team-setup", + "name": "Team setup", + "version": "1.2.0", + "role": "developer", + "description": "A curated team setup", + "author": "Example team", + "license": "MIT", + "repository": "https://github.com/example/team-setup", + "download_url": "https://github.com/example/team-setup/releases/download/v1.2.0/team-setup-1.2.0.zip", + "requires": {"speckit_version": ">=0.9.0"}, + "provides": {"extensions": 1, "presets": 0, "steps": 0, "workflows": 1}, + "tags": ["developer", "bugfix"], + "verified": false + } + } +} +``` + +The entry's `id` must match its enclosing key. `requires` and `provides`, when +present and non-null, must be mappings; `tags` must be a list of strings; +`verified` must be a real boolean. `provides` describes component counts, not +the manifest's component lists. Catalog schema versions with an unsupported +major version and duplicate JSON keys are rejected. + +Use a `sha256` digest to verify artifact bytes. Community submission automation +records the digest it computes from the actual archive; a submitted checksum +mismatch fails validation. The generic current-entry reader still accepts +entries without a digest. Historical releases require their own digest. + +Catalog bundle download URLs must use HTTPS, or HTTP on localhost, and resolve +to a bundle manifest or ZIP containing root-level `bundle.yml`. The fetched +manifest's ID and version must match the selected catalog entry. Local paths +and `file://` are not accepted as bundle download URLs; pass local bundle +sources directly to `bundle install`. + +Community submissions have a narrower publishing contract: a versioned, +tag-pinned GitHub release ZIP asset. See the publishing guide. + +## Release history + +An entry can add approved historical releases alongside its current release: + +```json +{ + "id": "team-setup", + "version": "1.2.0", + "download_url": "https://example.com/team-setup-1.2.0.zip", + "releases": { + "1.1.0": { + "download_url": "https://example.com/team-setup-1.1.0.zip", + "sha256": "<64-character SHA-256 digest>" + } + } +} +``` + +This is an entry fragment, not a complete catalog. Replace the digest +placeholder before use. Each historical record needs its own HTTPS (or +localhost HTTP) `download_url` and 64-character hexadecimal SHA-256 digest. +A digest may have a case-insensitive `sha256:` prefix and surrounding +whitespace; other algorithm prefixes are rejected. + +History keys must be strict SemVer values that are PEP 440-comparable. They +cannot duplicate the current version or each other, including equivalent +spellings such as `1.0.0-rc.1` and `1.0.0-rc1`. A historical record cannot +contain `id`, `version`, or `releases`. + +Shared metadata such as name, description, and author is inherited unless +overridden. Release-specific `requires`, `provides`, and `verified` are not +inherited: supply them on each historical record, or that release has no +requirements, no provided counts, and `community` trust. + +```bash +specify bundle info team-setup --versions +specify bundle install team-setup --version 1.1.0 +``` + +Equivalent version spellings such as `v1.1.0` and `1.1.0` select the same +release; the catalog key spelling is display-only. Exact bundle release +selection never changes component pins in the fetched manifest. +`bundle update` always selects the current release. + +History is validated when the catalog source loads. Malformed history fails +any operation that consults that source. An ID lookup can leave malformed +lower-precedence sources unobserved after finding a winner; search loads all +sources, so even shadowed malformed entries fail search. + +Existing single-release catalogs remain valid. First-party and community +bundle catalogs currently advertise one release per bundle, not historical +retention. + +## See also + +- [Bundle CLI reference](cli.md) +- [Manifest and source assertions](api.md) +- [Publishing and community submission](publishing.md) +- [Community bundles](../community/bundles.md) +- [Shared catalog concepts](../reference/catalogs.md) diff --git a/docs/bundles/cli.md b/docs/bundles/cli.md new file mode 100644 index 0000000000..a1dc933fe6 --- /dev/null +++ b/docs/bundles/cli.md @@ -0,0 +1,359 @@ +# Bundle CLI reference + +The `specify bundle` command group discovers, installs, and authors bundles. +Use `specify bundle --help` for command help. Bundle catalog commands +have their own nested help: `specify bundle catalog --help`. + +## Project requirements + +| Commands | Project requirement | +| --- | --- | +| `search`, `info` | Work without an initialized project; use user and built-in catalogs, plus any applicable project-scoped configuration | +| `install`, `add`, `init` | Initialize a project on demand | +| `list`, `update`, `remove`, `catalog list/add/remove` | Require an initialized project | +| `validate`, `build` | Work on a local bundle source without initializing a project | + +`search`, `info`, and `list` support `--json`; other bundle commands do not. +Successful JSON output shapes are described with each command below. Do not +assume all bundle failures emit a JSON envelope: ordinary bundler errors are +reported as errors with a nonzero exit status. + +## Search available bundles + +```bash +specify bundle search [query] +``` + +| Option | Description | +| --- | --- | +| `--offline` | Do not access the network | +| `--json` | Emit JSON to stdout | + +Searches all active catalogs. The optional query matches substrings in the +winning entry's ID, name, role, tags, or description. Without a query, lists +every available bundle. Each ID appears once, from its highest-precedence +source, and results are sorted by ID. + +Text output shows version, role, source, and trust. JSON is an array of entries +with `id`, `name`, `version`, `role`, `description`, `source`, `install_policy`, +`verified`, and `trust`. Trust is `verified` for entries marked verified and +`community` otherwise; it does not change installation policy. + +## Inspect a bundle + +```bash +specify bundle info +specify bundle info --versions +``` + +| Option | Description | +| --- | --- | +| `--offline` | Do not access the network | +| `--json` | Emit JSON to stdout | +| `--versions` | List current and historical catalog versions without downloading a manifest | + +Without `--versions`, downloads the current manifest and shows the fully +expanded component set, including versions, preset priority and strategy, +integration, source policy, trust, and informational overlaps with installed +bundles. A required component may already be installed; this preview is not a +promise that every component will be newly added or that all installation +gates have passed. Local bundle paths are not supported by `info`. + +JSON includes `id`, `name`, `version`, `role`, `description`, `author`, +`license`, `source`, `install_policy`, `provides`, `requires`, `verified`, +`trust`, `integration`, `components`, and `overlaps`. Component entries contain +`kind`, `id`, `version`, and any declared `priority` or `strategy`. + +For bundles with references, JSON also includes: + +- `references`: each referenced bundle's `id`, `version`, resolved `source`, + `install_policy`, `via`, `verified`, and `trust`. +- Component `source`, when declared; flat bundles omit this field. +- Component `origin`, when contributed by a referenced bundle, using the + shortest origin chain. +- Component `overrides`, when replacing declarations. Each item contains the + replaced `version`, `origin`, and any `source`, `priority`, or `strategy`. + Every distinct declaring bundle is represented, using its shortest route. + An overridden parent declaration has an empty origin. + +Text output includes a References summary, origin chains, overrides, and +discovery-only notices. Referenced bundles retain their own trust level; they +do not inherit the parent's. + +`--versions --json` instead emits an object with `id`, `versions`, `current`, +`source`, and `install_policy`. Versions list the current release first, +followed by historical releases newest first. + +If a manifest cannot be downloaded or expanded, `info` fails rather than +substituting catalog component counts for a verified expansion. + +## Install a bundle + +```bash +specify bundle install +specify bundle add +``` + +`add` accepts the same arguments and options as `install`. + +| Option | Description | +| --- | --- | +| `--integration ` | Select an integration for initialization, or confirm one when the active integration cannot be determined | +| `--offline` | Do not access the network | +| `--refresh` | Reapply owned components from the supplied source | +| `--version ` | Select an exact current or historical catalog release | + +The argument can be a catalog ID, local bundle directory, manifest file, or +built ZIP with `bundle.yml` at its root. Local sources bypass the bundle catalog +unless they declare bundle references, which resolve through the catalog stack. +An archive URL is not a direct install argument: download it, vet it, and pass +its local path, or use an install-allowed catalog. + +### Initialization and integration + +If no project exists, installation first resolves the manifest and hard +compatibility gates, then initializes the project. Integration selection uses +the explicit option, the bundle's declared integration, then the CLI's default. + +In an existing project, the recorded active integration is authoritative. +`--integration` does not switch it or bypass a bundle integration mismatch. +When a pinned bundle's active project integration cannot be determined because +`.specify/integration.json` is missing or unreadable, confirm the target with +`--integration` or repair the project. + +Integration-agnostic bundles inherit the active integration. The root bundle +is the integration gate; differing or indeterminate integration targets in +referenced bundles generate warnings. + +### Reinstallation and refresh + +An ordinary install skips compatible installed components and preserves their +local edits. A different or unknown installed version cannot satisfy a pin, +except for the packaged patch rule below. Independently installed components +are never adopted or replaced. + +An ordinary reinstall rejects changes to the recorded bundle version or owned +component metadata, including removing an owned component, even if a local +manifest keeps the same bundle version. Reordering unchanged components or +adding components does not require refresh. + +To apply a revised local source: + +```bash +specify bundle install ./new-release/bundle.yml --refresh +``` + +Refresh reapplies bundle-owned components through their primitive update paths, +preserving primitive-level overrides such as preset priority. It can repair +version drift owned exclusively by this bundle. It removes omitted owned +components unless another bundle still needs them, leaves independently +installed components untouched, and reports refreshed and removed counts. + +All bundles requiring a shared component must agree on its pin, source, and +relevant settings. An unpinned refresh cannot replace a version another bundle +requires at a pin; sharing a matching installed version without refreshing is +still allowed. See the API reference for requirement comparison rules. + +The bundle record advances only after successful installation. On component +failure, newly installed components are removed on a best-effort basis. +Rollback removal errors are swallowed, so partial disk state may remain. +Previously installed components modified or removed during refresh are not +restored. Initialization is not rolled back either. + +### Packaged patch compatibility + +For extensions, presets, and workflows shipped with Spec Kit, a newer stable +patch within the pinned major/minor version is accepted with a warning when +no component `source` is declared. For example, packaged `bug` version `1.0.1` +can satisfy a bundle's `1.0.0` pin. Validation uses the same warning. + +Downgrades, major/minor differences, and differing prerelease versions are not +covered by this exception. Explicit sources and catalog-resolved releases +retain exact-pin enforcement. A patch release is not a guarantee of unchanged +behavior. + +Requested pins remain in `required_components` and `contributed_components`. +Known actual versions are recorded separately under `installed_versions` and +shown by `bundle list --json`. + +### Exact catalog releases + +`--version` selects a bundle release advertised by the winning catalog. It +does not override component pins inside that release. It cannot be blank, +used with a local source, or combined with `--refresh`. + +```bash +specify bundle install team-setup --version 1.1.0 +``` + +A missing version does not fall through to a lower-priority source. +Discovery-only sources remain non-installable, including historical releases. +See the catalog reference for release history requirements. + +### Offline limits + +A local manifest, directory, or ZIP can install packaged components with +`--offline`. Local bundle assets are not a source of component payloads. +Components resolved through catalogs need network access for installation or +refresh, even when already installed and being refreshed. + +Catalog bundle manifests and referenced bundle manifests require network +downloads. A local root can identify the first unresolved bundle reference in +an offline error; a catalog root can fail earlier while fetching its catalog +or root manifest. Packaged catalog snapshots enable discovery, not offline +catalog-ID installation. + +Step payloads resolve exclusively through step catalogs. Bundle-local +`steps//` directories and relative step `source` values are not resolved +as payloads, so bundling a step directory does not make it installable offline. + +## Update bundles + +```bash +specify bundle update +specify bundle update --all +``` + +| Option | Description | +| --- | --- | +| `--all` | Update every installed bundle | +| `--integration ` | Confirm an integration only when the project's active integration cannot be determined | +| `--offline` | Do not access the network | + +Requires an installed bundle and an install-allowed source. Re-resolves the +parent's current catalog release and its pinned bundle references, then +refreshes owned components using the same lifecycle and patch-compatibility +rules as `install --refresh`. `update` does not retain a historical +`install --version` selection. + +Each component pin selects the exact release from its winning catalog; the +bundler does not guess an old download URL or try a lower-priority catalog. +Legacy extension and preset entries advertising no version cannot enforce +their pins or verify the archive's version. Publish versioned entries to make +those pins enforceable. An unpinned step selects the current catalog release. + +Without an explicit `source`, bundled extensions and presets take precedence +over catalog releases. An incompatible bundled pin is rejected even if a +matching catalog release exists. A bundled workflow uses the packaged version +when compatible, or its catalog release when appropriate and online. + +`--all` processes bundles sequentially and stops on failure; earlier successful +updates are not rolled back. + +## Remove a bundle + +```bash +specify bundle remove +``` + +Uninstalls only contributed components that no other bundle still needs. +Retained components without a remaining owner transfer to a remaining requiring +bundle so later removal can clean them up. Independently installed components +are never adopted. + +Removal is not transactional. A component failure can leave a partially +uninstalled project while the bundle record remains unchanged; the command +reports the failure and recovery context. + +## List installed bundles + +```bash +specify bundle list +``` + +| Option | Description | +| --- | --- | +| `--json` | Emit JSON to stdout | + +Shows installed bundle IDs, versions, required component counts, and original +installation timestamps. Counts include components supplied independently. +JSON is an array of installed records using the API reference's record shape, +including optional `installed_versions` and `bundle_references`. + +## Initialize a project with a bundle + +```bash +specify bundle init [bundle_id] +``` + +| Option | Description | +| --- | --- | +| `--integration ` | Integration override for initialization | +| `--offline` | Do not access the network | + +Ensures a project exists, then optionally installs a bundle. Repeating an +unchanged installation skips existing components and preserves edits. It +follows ordinary install rules, not refresh rules: use `update` or +`install --refresh` for recorded version or owned-component changes. + +## Validate a bundle + +```bash +specify bundle validate --path ./team-setup +``` + +| Option | Description | +| --- | --- | +| `--path ` | Bundle directory or manifest; defaults to the current directory | +| `--offline` | Check against packaged/installed components without network access | + +Checks manifest structure and component resolution. Online validation expands +bundle references and checks cycles, versions, sources, conflicts, and install +policies across the expanded plan. Explicit component sources are checked +against the winning catalog instead of local components. + +Missing releases, mismatched or ambiguous sources, discovery-only sources, +and malformed catalog metadata fail validation. Unverifiable component +references, including unreachable catalogs, produce warnings; an unreachable +higher-priority catalog cannot be settled by a lower-priority match. +Bundle-reference fetch failures fail online validation. + +Offline validation warns once per declared bundle reference that it was not +expanded. A successful result with warnings is not proof of an installable +full graph. Validation does not check the running CLI against +`requires.speckit_version`; install/update enforce that gate. + +## Build a bundle artifact + +```bash +specify bundle build --path ./team-setup --output ./dist +``` + +| Option | Description | +| --- | --- | +| `--path ` | Bundle directory; defaults to the current directory | +| `--output ` | Artifact destination; defaults to the bundle directory | + +Produces `-.zip` with the manifest, README, and local assets. +Requires regular, non-symlinked `bundle.yml` and `README.md` files and a +structurally valid manifest. Build does not resolve components or download +referenced bundles; it is not a substitute for online validation. + +## Manage catalog sources + +```bash +specify bundle catalog list +specify bundle catalog add +specify bundle catalog remove +``` + +`list` prints the active stack with IDs, URLs, priorities, scopes, and policies. +`add` and `remove` write project-scoped configuration. + +| `catalog add` option | Description | +| --- | --- | +| `--policy ` | `install-allowed` (default) or `discovery-only` | +| `--priority ` | Lower numbers have higher precedence; defaults to `10` | +| `--id ` | Explicit source ID; otherwise derived from the URL or path | + +Re-adding the same source with matching settings is a no-op; conflicting +settings fail. Built-in sources cannot be deleted. Removing a project override +of a built-in ID restores the lower-scope source. + +## See also + +- [Bundle overview](overview.md) +- [Manifest, expansion, and record contracts](api.md) +- [Catalog configuration and release history](catalogs.md) +- [Developing bundles](developing.md) +- [Publishing bundles](publishing.md) diff --git a/docs/bundles/developing.md b/docs/bundles/developing.md new file mode 100644 index 0000000000..92214ae7a9 --- /dev/null +++ b/docs/bundles/developing.md @@ -0,0 +1,181 @@ +# Developing bundles + +A bundle distributes an existing component stack through a manifest and usage +documentation. Start with the role or team outcome, select components that +provide it, and test their combined installation. A bundle does not replace +extension, preset, workflow, or step authoring. + +## Create a bundle directory + +Keep the manifest and README at the directory root: + +```text +team-setup/ + bundle.yml + README.md + LICENSE +``` + +This minimal example uses the packaged bug extension and workflow: + +```yaml +schema_version: "1.0" +bundle: + id: team-setup + name: Team setup + version: 1.0.0 + role: developer + description: A bug-fixing setup for the team + author: Example team + license: MIT +requires: + speckit_version: ">=0.9.0" +provides: + extensions: + - id: bug + version: 1.0.2 + workflows: + - id: bugfix + version: 1.0.0 +tags: [developer, bugfix] +``` + +Choose pins from releases you have tested. Compatible newer stable packaged +patches may satisfy these pins with warnings; explicit component catalog +sources retain exact-pin enforcement. + +The README should explain the intended role, installed components, integration +requirements, catalogs users need to register, installation commands, and how +to use the installed setup. For community publication, include a license file. + +## Select components and their sources + +Extension, preset, and workflow references require exact versions. Steps may +omit a version, in which case installation selects the current step catalog +release. Pin steps too when reproducibility matters. + +Presets must declare an integer priority of at least `1` and a composition +strategy. Extensions can also declare priority; their default is `10`. +Use the API reference for the complete field contract. + +An optional component `source` names the expected winning component catalog. +It is not a URL, a directory, or a mechanism to change catalog precedence. +Before selecting sources, inspect the effective catalogs: + +```bash +specify extension catalog list +specify preset catalog list +specify workflow catalog list +specify workflow step catalog list +``` + +Document every non-default catalog needed by the bundle, with its appropriate +`catalog add` command. Catalogs remain separate: adding a bundle catalog does +not add component catalogs. Users must vet them before granting installation. +Use unique component catalog names; an explicit source that matches multiple +active catalogs is ambiguous and rejected. + +Local bundle assets are packaged for distribution but are not automatically +installed as component payloads. In particular, copying a custom step under +`steps//` does not make it resolvable. Publish it through an accessible step +catalog and document that dependency. + +## Compose existing bundles + +Use schema `1.1` and `provides.bundles` to reference exact releases of other +bundles. For example, after checking the release is available in your catalog: + +```yaml +schema_version: "1.1" +bundle: + id: team-setup + name: Team setup + version: 1.0.0 + role: developer + description: A composed team setup + author: Example team + license: MIT +requires: + speckit_version: ">=0.9.0" +provides: + bundles: + - id: bugfix + version: 1.0.2 + source: default +``` + +References resolve through the effective bundle catalog, including when the +parent is a local manifest. They are not local path references and are not +embedded by `build`. The parent's own component declarations can override +referenced declarations; incompatible sibling declarations need an explicit +parent resolution. The API reference defines ordering, deduplication, and +limits. + +First-party bundle catalogs currently keep no historical releases. An exact +reference can stop resolving when the catalog advances to its next release. +For long-lived composition, use a catalog that preserves the required history. + +## Validate and build + +From the repository or directory containing your bundle: + +```bash +specify bundle validate --path ./team-setup +specify bundle build --path ./team-setup --output ./dist +``` + +Read warnings as well as errors. Online validation checks component +resolution and expands references, but it does not enforce the running CLI's +Spec Kit version gate. Offline validation cannot confirm network-only +components or expand bundle references. + +Build requires a regular `README.md` and `bundle.yml` and checks manifest +structure. It does not confirm that references resolve or that installation +succeeds. The resulting ZIP contains root-level files and local assets, not +downloaded component payloads or referenced bundles. + +Build output is reproducible for identical inputs: files are sorted by their +POSIX archive paths, timestamps are fixed, and permissions normalize to +executable or non-executable modes. Symlinks, `.git`, `__pycache__`, `.DS_Store`, +previous versioned build artifacts for this bundle, and a nested output +directory are excluded. + +## Test installation and changes + +Use a clean project with the intended integration. Register any vetted +dependency catalogs, then install the exact artifact users will receive: + +```bash +specify init --here --integration copilot +specify bundle install /path/to/dist/team-setup-1.0.0.zip +specify bundle list --json +``` + +Inspect delivered commands and run the installed workflows or other intended +capabilities. Repeat the installation to confirm that unchanged components +and local edits are preserved. Test removal while independent or other-bundle +components remain installed. + +For local development, ordinary reinstall is not an update operation. Apply +revised versions, changed owned-component settings, or removed components with: + +```bash +specify bundle install /path/to/team-setup/bundle.yml --refresh +``` + +Refresh can overwrite owned components through their update paths and is not +fully rolled back on failure. Use a disposable project for this test. +Test both valid installs and relevant rejected cases, such as unavailable pins, +integration mismatches, and conflicts with another bundle's requirements. + +If distributing through a catalog, test ID installation from that +install-allowed catalog too. A successful direct-artifact install does not +verify catalog precedence, download metadata, or historical release selection. + +## See also + +- [Bundle overview](overview.md) +- [Manifest and bundle-reference API](api.md) +- [CLI reference](cli.md) +- [Catalog configuration](catalogs.md) +- [Publishing bundles](publishing.md) diff --git a/docs/bundles/overview.md b/docs/bundles/overview.md new file mode 100644 index 0000000000..3475d62ae7 --- /dev/null +++ b/docs/bundles/overview.md @@ -0,0 +1,98 @@ +# Bundles + +Bundles compose existing Spec Kit extensions, presets, workflows, and workflow +step types into one versioned, installable setup for a role or team. A bundle +declares the components to install in `bundle.yml`; it does not add runtime +behavior of its own. Each component is installed through its own machinery. + +Use an extension to add capabilities, a preset to customize existing +capabilities, and a workflow to automate a sequence. Use a bundle when those +pieces should be distributed and installed together. + +> [!IMPORTANT] +> Vet third-party bundles and the components they install before using them. +> Community catalogs are discovery channels, not security endorsements. +> Maintainers check submission metadata for completeness and format; they do +> not review, audit, endorse, or support community bundle code or its components. + +## Quick start + +Discover bundles and inspect the components a catalog bundle declares: + +```bash +specify bundle search +specify bundle info bugfix +``` + +Install a first-party bundle, inspect the installed record, and remove it: + +```bash +specify bundle install bugfix +specify bundle list +specify bundle remove bugfix +``` + +`add` is an alias for `install`. Installation initializes a Spec Kit project +if needed; use `--integration copilot`, for example, to select the integration +for a new project. It does not switch an existing project's active integration. + +For a bundle you have authored or vetted, pass a local directory, manifest, or +built artifact: + +```bash +specify bundle install ./team-setup/bundle.yml +specify bundle install ./team-setup-1.0.0.zip +``` + +The local source supplies the manifest, not an embedded component repository. +Its components still need to resolve from packaged assets, compatible installed +components, or their own catalogs. A community search result does not itself +permit installation by ID. + +## First-party bundles + +Spec Kit maintains the first-party catalog in `bundles/catalog.json`, exposed +through the install-allowed `builtin://default` source. Its entries are marked +`verified: true`. + +| Bundle | Components | Use case | +| --- | --- | --- | +| `bugfix` | `bug` extension and `bugfix` workflow | Guided assess, assessment gate, fix, and test | +| `assess` | `assess` extension and `assess` workflow | Idea triage before Spec-Driven Development | + +```bash +specify bundle add assess +``` + +Installing a workflow does not run it. Follow the +[bug-fixing guide](../guides/bugfix.md) or +[idea assessment guide](../guides/assessment.md) to use the installed setup. + +Built-in catalogs are fetched online and use packaged snapshots offline. +Catalog-discovered bundle manifests still require a network download, even for +first-party entries. A local manifest can install packaged components with +`--offline`; see the CLI reference for the exact limits. + +## Lifecycle and ownership + +Bundles record both required components and the components they contributed. +A compatible component installed independently can satisfy a requirement +without becoming bundle-owned. Removing the bundle leaves it in place. +Components shared with other bundles remain installed while still needed. + +Repeating an unchanged install preserves existing components and local edits. +Use `bundle update ` for a catalog release, or +`bundle install --refresh` for a revised local source, to reapply owned +components. Updates are not a global transaction: changes to components that +were already installed are not rolled back after a later failure. + +## See also + +- [CLI reference](cli.md) +- [Developing bundles](developing.md) +- [Manifest and composition API](api.md) +- [Catalogs](catalogs.md) +- [Publishing bundles](publishing.md) +- [Community bundles](../community/bundles.md) +- [Community walkthroughs](../community/walkthroughs.md) +- [Choosing customization building blocks](../guides/customization.md) diff --git a/docs/bundles/publishing.md b/docs/bundles/publishing.md new file mode 100644 index 0000000000..948a8afb6a --- /dev/null +++ b/docs/bundles/publishing.md @@ -0,0 +1,152 @@ +# Publishing bundles + +Publish a versioned bundle artifact, test its distribution path, then submit +metadata through a Bundle Submission issue. Use the same issue flow for new +entries, version updates, and repairs. Do not hand-edit the community catalog +or its documentation table as a substitute for submission. + +Community publication is not code certification. Maintainers check metadata, +links, artifact consistency, and submitted installation evidence; they do not +review, audit, endorse, or support bundle code or its installed components. +Users must vet those components before installation. + +## Prepare a release + +Provide a public GitHub repository containing `bundle.yml`, `README.md`, and a +license file (`LICENSE`, `LICENSE.md`, or `LICENSE.txt`). The README must explain +the intended role, installed components, required catalogs, and installation +and usage steps. + +Use a safe bundle ID matching the manifest contract. Community submission +versions must be numeric `X.Y.Z`, without a `v` prefix. This is narrower than +the manifest parser's general SemVer support. Choose two to five lowercase tags. + +Validate online and build the versioned artifact: + +```bash +specify bundle validate --path ./team-setup +specify bundle build --path ./team-setup --output ./dist +``` + +Resolve warnings and confirm the intended component releases are available. +Build performs structural checks only; a built ZIP is not installation +evidence. Include the manifest and README as regular files, not symlinks. + +Create a tag and GitHub release, then attach the generated +`team-setup-1.0.0.zip`. The community Download URL must belong to the submitted +repository and follow this form: + +```text +https://github.com///releases/download//-.zip +``` + +The tag must match the submitted version: `v1.0.0`, `1.0.0`, or a scoped tag +with the matching suffix, such as `team-setup-v1.0.0`. Floating +`releases/latest/` URLs and source archives are not the community bundle asset +contract. The release and exact attached ZIP asset must exist. + +## Test the distribution path + +Test in a clean Spec Kit project with the intended integration and every +required vetted component catalog registered. Installing the release artifact +must work without relying on components left over in the author's project. + +For example, after vetting the sources: + +```bash +specify init --here --integration copilot +specify preset catalog add https://example.com/presets.json \ + --name team --install-allowed +specify extension catalog add https://example.com/extensions.json \ + --name team --install-allowed +curl -L -o team-setup-1.0.0.zip \ + https://github.com/example/team-setup/releases/download/v1.0.0/team-setup-1.0.0.zip +specify bundle install ./team-setup-1.0.0.zip +specify bundle list --json +``` + +Use only the catalog commands your bundle actually needs, including workflow +and step catalogs where applicable. If every component resolves from packaged +or default sources, report `None` for required component catalogs. + +If proposing a bundle catalog entry, test ID installation from an +install-allowed catalog in a separate clean project: + +```bash +specify bundle catalog add https://example.com/bundles.json \ + --id team --policy install-allowed +specify bundle install team-setup +``` + +Register required component catalogs in that project too. The bundle catalog +locates the manifest; it does not register companion catalogs. The built-in +community catalog remains discovery-only after publication. + +Describe validation, building, artifact installation, catalog-ID installation +when applicable, actual use of installed commands/workflows, and removal. +Include the tested Spec Kit version, operating system, integration, and any +dependency catalogs in the submitted testing details. + +## Submit metadata + +Open a +[Bundle Submission issue](https://github.com/github/spec-kit/issues/new?template=bundle_submission.yml). +Supply the ID, name, version, role/team, description, author, repository URL, +Download URL, Documentation URL, license, required Spec Kit version, optional +integration target, component summary, required catalogs, tags, key features, +testing details, example usage, and proposed catalog entry. + +Complete every required testing and submission checkbox. The manifest, +README, issue metadata, catalog proposal, and test evidence must agree. +Required companion catalogs must have HTTPS URLs and documented setup +commands; submission automation does not register them. + +An optional submitted checksum can be supplied as `sha256` in the proposed +catalog entry or beneath an appended `### SHA-256` heading. It must contain +exactly 64 hexadecimal characters. If both locations are supplied, they must +agree after trimming whitespace and normalizing case. A malformed or +mismatching checksum fails validation; it is not silently replaced. + +## What happens after submission + +1. The issue form applies the `triage-must-have` intake verdict. That does not + start catalog validation. +2. A maintainer reviews the issue during triage and separately applies + `bundle-submission` to start the automated workflow. Contributors do not + need to apply or request that label. +3. Automation validates fields, repository/documentation availability, + manifest consistency, license availability, release pinning, the actual + artifact download and optional checksum, catalog shape, required catalogs, + and testing attestations. It does not execute downloaded content or audit + the code. +4. On success, automation proposes a draft pull request updating + `bundles/catalog.community.json` and `docs/community/bundles.md`. It records + the actual downloaded archive's SHA-256 and keeps `verified: false`. +5. A maintainer reviews the catalog changes and merges them when approved. + The entry becomes discoverable with `specify bundle search`. + +Confirmed submission defects receive `validation-failed` with correction +details. Permission, network, or tooling blockers are reported as blocked, +not as passed checks or necessarily as defects in the bundle. A checksum +mismatch or observed missing artifact is a submission failure. + +## Updates and repairs + +Publish a new immutable tagged release, update manifest and README references, +and retest the exact artifact and applicable catalog install path. File another +Bundle Submission issue and identify the existing entry being updated. +Versions must advance unless the issue explicitly explains a metadata-only +correction at the same version. + +For a private catalog you operate, follow the catalog reference for current +and historical release records. Do not assume the community submission flow +retains old releases or changes the built-in source to install-allowed. + +## See also + +- [Developing and testing bundles](developing.md) +- [Manifest API](api.md) +- [Catalog entries and release history](catalogs.md) +- [CLI reference](cli.md) +- [Community bundle listing](../community/bundles.md) +- [Bundle overview](overview.md) diff --git a/docs/community/bundles.md b/docs/community/bundles.md index b0767e69c9..2638be1957 100644 --- a/docs/community/bundles.md +++ b/docs/community/bundles.md @@ -5,7 +5,7 @@ Bundles compose existing Spec Kit components — extensions, presets, workflows, and steps — into a single role or team stack. They are useful when a user should be able to install a tested set of components together instead of following several separate install commands. -Accepted community bundle entries are published in [`bundles/catalog.community.json`](https://github.com/github/spec-kit/blob/main/bundles/catalog.community.json) and listed below. The built-in community source is discovery-only: `specify bundle search` and `specify bundle info` can inspect entries, but installing by ID requires explicitly adding an install-allowed catalog. Explicit catalogs use a higher default precedence than the built-in community source. To submit a bundle for review, file a [Bundle Submission](https://github.com/github/spec-kit/issues/new?template=bundle_submission.yml) issue. +Accepted community bundle entries are published in [`bundles/catalog.community.json`](https://github.com/github/spec-kit/blob/main/bundles/catalog.community.json) and listed below. The built-in community source is discovery-only: `specify bundle search` and `specify bundle info` can inspect entries, but installing by ID requires explicitly adding a vetted, install-allowed catalog. Explicit catalogs use a higher default precedence than the built-in community source. To submit a bundle for metadata validation, follow the [publishing guide](../bundles/publishing.md) and file a [Bundle Submission](https://github.com/github/spec-kit/issues/new?template=bundle_submission.yml) issue. | Bundle | Purpose | Role or team | Provides | Required catalogs | URL | |--------|---------|--------------|----------|-------------------|-----| @@ -13,47 +13,10 @@ Accepted community bundle entries are published in [`bundles/catalog.community.j | SicarioSpec Security & Governance Bundle | Secure-by-default governance bundle for GitHub Spec Kit. Enforces data classification, threat modeling, and code-owned verification gates. | `security-engineer` | 1 extension, 11 presets | Documented | [sicario-spec](https://github.com/dfirs1car1o/sicario-spec) | | SpecAssay | Durable-ID promotion for stock Spec Kit: templates, Gate 2 refusal, and trace-manifest emission. | `developer` | 1 extension, 1 preset | Documented | [specassay](https://github.com/rdryfoos/specassay) | -## What to Submit +## See also -A bundle submission should include: - -- A public repository with a valid `bundle.yml` manifest. -- A versioned GitHub release with a bundle artifact created by `specify bundle build`. -- Documentation that explains the intended role, installed components, required catalogs, and expected workflow. -- A proposed catalog entry with bundle metadata and component counts. -- Test evidence from a clean Spec Kit project. - -## Component Resolution - -A bundle catalog entry describes where to download the bundle artifact, but the bundle's component references still need to resolve when a user installs it. References can resolve from bundled components, already installed components, or active extension, preset, workflow, and step catalogs. - -If your bundle depends on components that are not available from the default Spec Kit catalogs, include the required catalog URLs in the submission and in your README. Test the full install path from a clean project with those catalogs added before submitting. - -For example: - -```bash -specify preset catalog add https://example.com/presets.json --name example-bundle --install-allowed -specify extension catalog add https://example.com/extensions.json --name example-bundle --install-allowed -curl -L -o example-bundle-1.0.0.zip https://example.com/example-bundle-1.0.0.zip -specify bundle install ./example-bundle-1.0.0.zip - -# Or install by id from an install-allowed bundle catalog. -specify bundle catalog add https://example.com/bundles.json --id example-bundle-catalog --policy install-allowed -specify bundle install example-bundle -``` - -## Review Scope - -Maintainers check that: - -- The submission fields are complete and correctly formatted. -- The release artifact and documentation URLs are reachable. -- The repository contains a `bundle.yml` manifest. -- The submission clearly identifies any required component catalogs. -- The proposed catalog entry uses the expected bundle catalog entry shape. - -Maintainers do not audit the behavior of installed extensions, presets, workflows, steps, or scripts. Users should review those components before installing a community bundle. - -## Updating a Bundle - -To update a submitted bundle, file another [Bundle Submission](https://github.com/github/spec-kit/issues/new?template=bundle_submission.yml) issue with the new version, download URL, changed component list, and updated test evidence. Mention that the issue updates an existing bundle entry. +- [Bundle overview](../bundles/overview.md) +- [Installing and inspecting bundles](../bundles/cli.md) +- [Catalog policies and component-source vetting](../bundles/catalogs.md) +- [Developing bundles](../bundles/developing.md) +- [Publishing, updating, and submission review scope](../bundles/publishing.md) diff --git a/docs/community/overview.md b/docs/community/overview.md index f46a6ca2e0..15528f70ad 100644 --- a/docs/community/overview.md +++ b/docs/community/overview.md @@ -52,6 +52,6 @@ Community projects that extend, visualize, or build on Spec Kit — including VS To publish your own work, follow the [Extension Publishing Guide](../extensions/publishing.md), the [Presets Publishing Guide](../presets/publishing.md), -the [Community Bundles guide](bundles.md), or the +the [Bundle Publishing Guide](../bundles/publishing.md), or the [Community Workflow Step Types guide](workflow-steps.md). For help choosing a component type, see [Customize Spec Kit](../guides/customization.md). diff --git a/docs/docfx.json b/docs/docfx.json index 09f1b36154..4e29a98a88 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -6,6 +6,7 @@ "*.md", "toc.yml", "community/*.md", + "bundles/*.md", "concepts/*.md", "extensions/*.md", "guides/*.md", diff --git a/docs/extensions/catalogs.md b/docs/extensions/catalogs.md index 4278ebaa40..16dbda11ae 100644 --- a/docs/extensions/catalogs.md +++ b/docs/extensions/catalogs.md @@ -326,7 +326,7 @@ Catalog-backed bundle extension pins also select historical releases from the winning source, using that release's own archive and digest and checking the downloaded manifest's ID and version. Bundled extensions follow the packaged version policy: a newer compatible packaged patch can be used with a warning. -See the [bundle reference](../reference/bundles.md) for bundle installation. +See the [bundle CLI reference](../bundles/cli.md#install-a-bundle) for bundle installation. ## See also diff --git a/docs/guides/customization.md b/docs/guides/customization.md index e23332b697..dc320f1b7f 100644 --- a/docs/guides/customization.md +++ b/docs/guides/customization.md @@ -108,10 +108,10 @@ specify bundle install ``` Inspect `info` before installing to see the resolved component set. See the -[bundle reference](../reference/bundles.md) for updates, removal, catalog policies, -offline limitations, validation, and publishing, and the -[example manifests](https://github.com/github/spec-kit/tree/main/examples/bundles) -for product manager, business analyst, security researcher, and developer setups. +[bundle overview](../bundles/overview.md) for available first-party setups and +the [CLI reference](../bundles/cli.md) for updates, removal, and offline limits. +For authoring, start with [developing bundles](../bundles/developing.md) and +[publishing bundles](../bundles/publishing.md). ## Share your customizations diff --git a/docs/index.md b/docs/index.md index db7092f156..84110be542 100644 --- a/docs/index.md +++ b/docs/index.md @@ -79,7 +79,7 @@ Including entirely different processes: Presets →   Extensions →   Workflows →   -Bundles → +Bundles → diff --git a/docs/install/air-gapped.md b/docs/install/air-gapped.md index fc92013d2b..f824e9d8a4 100644 --- a/docs/install/air-gapped.md +++ b/docs/install/air-gapped.md @@ -69,7 +69,7 @@ bundle manifest, not the component payloads it names. Before disconnecting, install matching non-bundled components separately. Components with an explicit catalog `source` still require network verification even when already installed, and workflow step payloads currently resolve only through step catalogs. Review -the [bundle offline limits](../reference/bundles.md#install-a-bundle) before +the [bundle offline limits](../bundles/cli.md#offline-limits) before depending on `specify bundle install --offline`. See the [Catalogs reference](../reference/catalogs.md) for catalog types, trust diff --git a/docs/reference/bundles.md b/docs/reference/bundles.md deleted file mode 100644 index 252da7cf31..0000000000 --- a/docs/reference/bundles.md +++ /dev/null @@ -1,375 +0,0 @@ -# Bundles - -Bundles compose existing Spec Kit components — extensions, presets, workflows, and steps — into a single, versioned, installable unit. Where extensions and presets are primitives, a bundle is a curated stack that declares everything a team or role needs and installs it in one step through each component's own machinery. Bundles add no new runtime behavior of their own: they are a distribution and composition layer over the primitives you already use. - -A bundle is described by a `bundle.yml` manifest and is discovered through the same catalog stack as other components. Installing a bundle resolves its declared components against pinned versions, checks active-integration and shared-component-version conflicts, and applies each component idempotently with full provenance tracking so it can be cleanly removed or refreshed later. - -For a concrete starting point, see the -[example bundle manifests](https://github.com/github/spec-kit/tree/main/examples/bundles) -for product managers, business analysts, security researchers, and developers. -These demonstrate packaging a role-based setup, not filled generated feature -specs; for end-to-end usage examples, see -[community walkthroughs](../community/walkthroughs.md). - -## First-party Bundles - -Spec Kit ships a first-party bundle catalog in `bundles/catalog.json`. These bundles are curated, marked `verified: true`, and resolve through the built-in `builtin://default` catalog source. - -| Bundle | Role | Components | Use case | -| --------- | ----------- | ----------------------------------------------------- | --------------------------------------- | -| `bugfix` | `developer` | `bug` extension + `bugfix` workflow | Guided assess → gate → fix → test | -| `assess` | `developer` | `assess` extension + `assess` workflow | Idea triage before Spec-Driven Development | - -Install a first-party bundle the same way you install any bundle (`add` is an alias for `install`): - -```bash -specify bundle install bugfix -specify bundle add assess -``` - -The first-party catalog is fetched from the repository online and falls back to the packaged wheel snapshot offline so discovery works without network access. A local bundle manifest can install bundled extensions and workflows with `--offline`. Catalog-discovered bundle manifests still resolve from their `download_url`, so `specify bundle add ` requires network today; fully offline catalog installation is tracked as follow-up work. - -## Search Available Bundles - -```bash -specify bundle search [query] -``` - -| Option | Description | -| ----------- | ---------------------------- | -| `--offline` | Do not access the network | -| `--json` | Emit machine-readable JSON | - -Searches all active catalogs for bundles matching the query. Without a query, lists every available bundle with its version, role, source, and a trust indicator (`verified` for org-curated catalog entries, `community` otherwise) so you can judge trust before installing. - -## Bundle Info - -```bash -specify bundle info -specify bundle info --versions -``` - -| Option | Description | -| ------------ | --------------------------------- | -| `--offline` | Do not access the network | -| `--json` | Emit machine-readable JSON | -| `--versions` | List the current and historical catalog releases | - -Shows full metadata for a bundle along with the **fully expanded component set** it installs — every extension, preset, step, and workflow with its pinned version, plus preset priority and strategy. The output also includes a trust indicator (`verified` vs `community`) so you can judge trust before installing. This preview is the same plan `install` applies, so you can see exactly what will be added before committing. For bundles with references, text output includes a `References` summary, with each referenced bundle's own trust level (`verified` or `community`), and marks referenced components with their shortest origin chain; overridden components show the overridden version and origin. JSON adds `references` entries with `id`, `version`, resolved `source`, `install_policy`, `via`, and the referenced bundle's own `verified` and `trust`; for bundles with references, component entries also include `source` when the manifest declares one (flat bundles omit it), `origin` (the chain of bundles that contributed the component) when it comes from a referenced bundle, and `overrides` whenever the entry replaced other declarations, including a parent's own declaration, which has no `origin`. Each `overrides` item carries the replaced `version` and `origin`, plus any `source`, `priority`, or `strategy`; an override entry names every distinct bundle that declared the replaced value (keeping the shortest route to each), not only the first route. Discovery-only references are marked `discovery-only, not installable`. Foreseeable overlaps and version conflicts with components already required by installed bundles are surfaced here as well; a bundle can require a component without having installed it. - -Use `specify bundle info --versions` to inspect the catalog history without downloading a bundle manifest. With `--json`, it emits the bundle ID, available versions, current version, source, and install policy. - -## Install a Bundle - -```bash -specify bundle install -``` - -| Option | Description | -| ---------------- | ------------------------------------------------------------------ | -| `--integration` | Override the integration used when initializing/installing | -| `--offline` | Do not access the network | -| `--refresh` | Refresh owned components from the supplied bundle source | -| `--version` | Install an exact historical or current catalog release | - -Installs a bundle's full component set through each primitive's machinery. The argument may be a catalog bundle id, or a local path to a built `.zip` artifact, a bundle directory, or a `bundle.yml` file; local sources install directly without consulting the catalog stack, unless the manifest declares `provides.bundles`. A local parent with bundle references resolves those children through the catalog stack at command time, so it needs catalog and network access (see [Bundle references](#bundle-references)). - -If the current directory is not yet a Spec Kit project, `install` initializes one first so a fresh checkout reaches a working state in a single command. `--integration` selects the integration when initializing a new project, and confirms the target when a bundle pins a specific integration but the project's active integration can't be determined (missing or unreadable `.specify/integration.json`). It does **not** override an already-initialized project's active integration: if a bundle targets a different integration than the project's, install aborts with no changes. Integration-agnostic bundles inherit the project's active integration. Without `--refresh`, installation is idempotent — already-installed components matching their pins are skipped, as are bundle-owned compatible packaged patch releases with a warning (see below). Other differing or missing installed versions cannot satisfy a pin. Only `--refresh` can repair a drifted version owned exclusively by this bundle; independently installed components are skipped and never adopted or replaced. On failure, no provenance record is written (a failed install records nothing), and the components installed during that run are removed on a best-effort basis — removal errors are swallowed, so partial on-disk state may remain. - -Installed-bundle records store required component pins separately from contributed components. A compatible independently installed component satisfies a bundle's requirement but remains independently owned: its pin still blocks a later bundle from installing an incompatible version, while removing the bundle does not uninstall it. An unpinned install or refresh cannot replace a component another bundle requires at a pinned version; sharing an already installed matching version without refreshing remains allowed. Shared bundles must also agree on component source and, for presets, priority and strategy before installing or refreshing the shared component. Older records without the requirement list retain their known contributed pins; reinstall or refresh the bundle to record its full requirements. - -A normal install rejects a change to an already-recorded bundle's version or owned component metadata (version, source, preset priority, or strategy), including removal of an owned component. This applies even if a local manifest keeps the same bundle version. Reordering unchanged components or adding new components does not require refresh. To apply changes to a local bundle without adding it to a catalog, pass the revised source with `--refresh`: - -```bash -specify bundle install ./new-release/bundle.yml --refresh -``` - -The source may also be a bundle directory or `.zip` artifact. Refresh uses the same primitive update path as `bundle update`, re-applies components owned by a bundle, and removes previously owned components omitted from the new manifest unless another bundle still needs them. Components installed independently remain untouched and are not adopted. The success summary includes refreshed and removed counts. The bundle record advances only after the operation succeeds; as with `bundle update`, already-installed components modified during a failed refresh are not rolled back. - -A local bundle source supplies the manifest, not its component payloads. Components resolved through catalogs still require network access to refresh, even when already installed. Add `--offline` only when the components being installed or refreshed ship with Spec Kit; otherwise the command reports which component needs network access. Re-run without `--offline` to fetch that component through its catalog. - -**Packaged patch compatibility.** When an extension, preset, or workflow shipped -with the CLI is a newer stable patch release within the pinned major/minor -version, installation and refresh warn and use that packaged release. For -example, a bundle pinning `bug` to `1.0.0` can use packaged `bug` `1.0.1` without -editing its manifest. Validation reports the same warning. Downgrades, -major/minor mismatches, and differing prerelease versions remain errors; an -explicit component `source` also retains exact-pin enforcement. Catalog-resolved -components still require exact matches. This exception improves compatibility -across CLI upgrades, but a patch release is not a guarantee of unchanged behavior. - -Bundle records retain requested versions in `required_components` and -`contributed_components` and -store actual known installed versions under `installed_versions`, keyed by -`kind:id` (for example, `"extensions:bug": "1.0.1"`). These are also visible in -`specify bundle list --json`. Component version ranges, `latest`, and omitted -extension versions are not supported. - -> **Step payloads resolve through the step catalog only.** A bundle's `provides.steps` entries still resolve exclusively through the active step catalogs. Bundle-local `steps//` payloads and relative `provides.steps[].source` overrides are **not** resolved in this release, so a step declared that way cannot be installed offline. To ship a step with a bundle today, publish it to a step catalog the bundle's users can reach. - -## Bundle References - -A bundle can compose other bundles with exact, pinned references in -`provides.bundles`. A non-empty reference list requires manifest schema `1.1`; -empty or null lists are treated as no references. Each reference has a bundle -ID, an exact SemVer `version`, and an optional `source` naming the expected -winning bundle catalog (not a URL). Reference IDs are safe slugs, must be -unique within the manifest, and cannot name the parent bundle: - -```yaml -schema_version: "1.1" -bundle: - id: team - name: Team setup - version: 1.0.0 - role: developer - description: A team bundle - author: Example team - license: MIT -requires: - speckit_version: ">=0.1.0" -provides: - bundles: - - id: bugfix - version: 1.0.1 - source: default - - id: qa-pack - version: 2.0.0 -``` - -References resolve at command time through the effective bundle catalog stack. -The stack's winning entry is selected for each exact pin; an explicit `source` -is an assertion that this is the winning catalog, not a request to change -precedence; a mismatch is an error and does not fall through to another -catalog. A referenced bundle from a discovery-only source can be inspected -by `info`, but `install`, `update`, and online `validate` reject it as -non-installable. Every bundle ID may have only one version in an expansion; -cycles and a second version for the same ID are errors. Resolve a second -version by choosing releases of the referencing bundles that agree on it. - -The expansion is one flat component plan. Referenced bundles are walked -recursively in declaration order (depth-first), followed by the declaring -bundle's own components, while preserving the existing extension, preset, step, -and workflow grouping. A component is installed at most once per `kind:id`. -Copies agree when their version and `source` match (omitted and set `source` -values differ); extension and preset copies must also have the same `priority`, -and preset copies must also have the same `strategy`. Extensions may omit -`priority`; an omitted value means the default, `10`, so it agrees with an -explicit `10`. Presets must declare an integer `priority`. -Other differences between sibling references are conflicts that must be fixed -in the parent. At every level, a bundle's own declaration wins over its -references, takes the declaration's position, and emits a warning describing -the override and its origin chain. - -Expansion allows at most 10 levels of reference nesting and 50 distinct -referenced bundle IDs, excluding the root. A cycle is rejected with its -reference chain. At install and update time, the Spec Kit version requirement -is checked for every bundle in the graph, and an incompatible requirement is -a hard error, while the root bundle remains the only integration gate: a -referenced bundle's differing or unknown integration is a warning. -`requires.tools` and `requires.mcp` are unioned across the graph -and rendered through the existing tool and MCP warnings. - -The installed parent record contains the expanded component set and an -optional `bundle_references` JSON key listing each direct and transitive -reference once with its resolved catalog source. There are no child-bundle -records. `bundle update` follows the parent's current manifest and its pinned -references; remove and refresh retain the existing ownership and component -lifecycle rules. - -Referenced manifests require network access. For a local root manifest, -`install` can identify the first reference and fail offline with that reference -named. A catalog-root `install`, `info`, or `update` can instead fail earlier -while fetching its catalog or root manifest, before any reference can be known. -`validate --offline` warns once per declared reference that it was not expanded. -`bundle build` performs only the structural checks for references and does not -resolve or embed them. - -**Known limitations.** - -- Offline and local resolution of referenced bundles is not supported; referenced - manifests need network-backed catalog downloads. -- First-party bundle catalogs keep no release history, so an exact pin to a - first-party bundle can break when that bundle publishes its next release. -- A local bundle with references cannot be previewed with `bundle info`; local - preview is deferred. -- Broader schema versioning and CLI compatibility checks/ranges are deferred. - -## Catalog Release History - -A catalog entry can advertise a current release and approved historical releases. Older single-version catalogs continue to work unchanged. - -```json -{ - "id": "team-setup", - "version": "1.2.0", - "download_url": "https://example.com/team-setup-1.2.0.zip", - "sha256": "", - "releases": { - "1.1.0": { - "download_url": "https://example.com/team-setup-1.1.0.zip", - "sha256": "" - } - } -} -``` - -Each historical release needs its own `download_url` and a 64-character SHA-256 digest; as with current releases, a digest may use a case-insensitive `sha256:` prefix and surrounding whitespace, and other algorithm prefixes are rejected. A release's `requires`, `provides`, and `verified` fields are not inherited from the current release: put them in that release's record, or the release has no requirements, no provided counts, and is shown as `community`. Other shared metadata, such as the name, description, and author, is inherited, and a release record may override it. A release record cannot contain `id`, `version`, or `releases`. - -`specify bundle install team-setup --version 1.1.0` selects the bundle release, downloads that release's artifact, verifies its digest, and checks that its manifest declares the catalog ID and the same version as the selected release. Equivalent spellings such as `v1.1.0` and `1.1.0` match; the catalog key spelling is display-only. It never overrides component pins inside `bundle.yml`. `--version` is catalog-only, so it cannot be blank, used with a local path, or combined with `--refresh`. - -Only the highest-precedence catalog that lists a bundle is consulted; a missing version does not fall through to a lower-priority catalog. Discovery-only sources remain non-installable, including exact historical releases. Duplicate JSON keys anywhere in a catalog are rejected while it is read. Historical `download_url` values must use HTTPS (or HTTP on localhost), and release keys must be strict SemVer values that are PEP 440-comparable. Version keys must be distinct, including PEP 440-equivalent spellings such as `1.0.0-rc.1` and `1.0.0-rc1`, and cannot repeat the current version. `bundle update` always selects the current release. The first-party and community catalogs currently advertise one release per bundle. - -Release history is validated when its catalog source loads. A malformed `releases` mapping makes `install`, `info`, `update`, and `search` fail while that source is consulted. Resolution stops after the first source that lists the bundle, so a malformed lower-precedence source remains unobserved when a higher-precedence source supplies that ID; `search` loads every source, so a shadowed malformed entry still causes it to fail. - -## Update Bundles - -```bash -specify bundle update [] -``` - -| Option | Description | -| ---------------- | --------------------------------------------------------------------------------------------------------------------- | -| `--all` | Update every installed bundle | -| `--integration` | Override the integration used when refreshing components; applied only when the project's active integration can't be determined | -| `--offline` | Do not access the network | - -Re-resolves a bundle and **refreshes** its components through each primitive's update path, bringing already-installed components up to the bundle's newly pinned versions (or compatible newer packaged patches, with a warning) while preserving primitive-level overrides (such as preset priority). Provide a bundle id, or use `--all` to update everything installed. - -**Pinned catalog releases.** Extensions, presets, workflows, and steps with version pins select that exact release from the highest-priority active catalog entry. Historical releases must be advertised under `releases` with their own artifact URL and SHA-256 digest; the bundler never guesses an old URL from the current one or falls through to another catalog. Legacy extension and preset entries that advertise no version are an exception: the bundler uses the winning entry as-is and cannot enforce its pin or verify the installed archive's version. Publish a versioned entry to make that pin enforceable. The primitive installer uses the selected workflow or step record without re-reading the catalog, and verifies downloaded archive or workflow/step metadata against that release. A pinned workflow that ships with Spec Kit uses its bundled copy when the version matches, and the catalog release when it differs and network access is allowed. A step without a pin installs the current catalog release. - -Without an explicit `source`, bundled extensions and presets take precedence over catalog releases. Validation rejects an incompatible pin even when a matching catalog release exists; compatible newer stable packaged patches produce a warning instead. Specify the winning catalog as `source` to opt into its exact release. - -> **One installed version per component ID.** Bundles sharing a component must agree on its pinned version. A different or unknown pin from another bundle is rejected before installation, including during `bundle update`; refreshing one bundle cannot replace a version required by another. - -A bundle may list a component ID only once per kind; duplicate references in the same manifest are invalid, even if their pins agree. - -An optional `source` in a `provides.` reference names the expected winning component catalog (as displayed by `specify catalog list`, or `specify workflow step catalog list` for steps). It is **not** an artifact URL and does not override catalog priority or install policy. When specified, the component is verified against that catalog even if already installed, rather than resolved from a Spec Kit-bundled copy; a different winning catalog or a discovery-only source prevents installation. If multiple active catalogs share the named source, bundle validation and installation reject it as ambiguous; give each catalog a unique name. Verifying an explicit source requires network access. Direct primitive `--from` URLs are a separate, explicitly requested route. - -## Remove a Bundle - -```bash -specify bundle remove -``` - -Uninstalls only the components this bundle contributed, leaving any component that another installed bundle still needs in place (no collateral removals). If no other bundle owns a retained component, its contribution is transferred to a remaining requiring bundle so removing that bundle later can clean it up. Independently installed components are never adopted. - -## List Installed Bundles - -```bash -specify bundle list -``` - -| Option | Description | -| -------- | ---------------------------- | -| `--json` | Emit machine-readable JSON | - -Lists the bundles installed in the project with their versions, required component counts (including components supplied independently), and install timestamps. With `--json`, a reference-bearing record also includes `bundle_references`, containing each resolved direct or transitive reference and its winning catalog source. - -## Initialize a Project with a Bundle - -```bash -specify bundle init [] -``` - -| Option | Description | -| ---------------- | ---------------------------------------- | -| `--integration` | Integration override | -| `--offline` | Do not access the network | - -Ensures the current directory is a Spec Kit project (initializing it idempotently if needed), then optionally installs the given bundle. Useful as an explicit one-step bootstrap for a new checkout. - -Repeating `bundle init` skips components already installed by the bundle and preserves their local edits. It follows the same rules as a normal [`bundle install`](#install-a-bundle): components newly added to the manifest are installed and recorded, but it fails without changing the installed bundle if the manifest changes the recorded bundle version, changes an owned component's version, source, preset priority, or strategy, or removes an owned component. To refresh components or apply such changes, use `bundle update ` for a catalog bundle or `bundle install --refresh` for a local source. - -## Validate a Bundle - -```bash -specify bundle validate -``` - -| Option | Description | -| ------------ | ------------------------------------------------------------------- | -| `--path` | Bundle directory or `bundle.yml` (default: current directory) | -| `--offline` | Verify references against bundled/installed components only | - -Reports whether a `bundle.yml` is well-formed and whether every declared component reference resolves at its pinned version. For a reference-bearing manifest, online validation expands the complete reference graph and checks every component in the expanded plan, including structural, cycle, version, source, conflict, and install-policy checks. References are checked against matching bundled or installed components and — when online — the exact release in the winning install-allowed catalog. An explicit `source` is verified against the winning catalog instead of resolving locally. Missing releases, mismatched sources, discovery-only sources, and malformed catalog metadata fail validation; component references that cannot be checked offline or because a catalog is unreachable produce warnings. Bundle-reference fetch failures (such as an unreachable bundle catalog or a failed child-manifest download) fail online validation, while `--offline` keeps the per-reference warning. In `--offline` mode, each declared bundle reference produces a warning that it was not expanded. If a higher-priority catalog is unreachable before a match is found, the lookup remains unverified; a lower-priority match cannot settle it. Validation does not evaluate `requires.speckit_version` against the running CLI; `install` and `update` enforce that runtime compatibility gate. As with installation, legacy extension and preset entries advertising no version are accepted without verifying their pins. - -## Build a Bundle Artifact - -```bash -specify bundle build -``` - -| Option | Description | -| ----------- | ------------------------------------------------------- | -| `--path` | Bundle directory (default: current directory) | -| `--output` | Output directory for the artifact | - -Produces a single versioned, distributable `.zip` artifact from a bundle directory. The artifact embeds the manifest and can be installed directly with `specify bundle install `. - -## Publish a Bundle - -Bundle authors validate and package bundles locally, then host the generated artifact and catalog metadata where users can access it. A bundle catalog entry points at the bundle artifact, but the components declared inside `bundle.yml` still resolve through bundled components, installed components, or active extension, preset, workflow, and step catalogs. - -If your bundle references components from non-default catalogs, document those catalog URLs and test the install path from a clean project with those catalogs added. Community bundle submissions should include that dependency-resolution evidence in the [Bundle Submission](https://github.com/github/spec-kit/issues/new?template=bundle_submission.yml) issue. - -### What Happens After You Submit - -1. GitHub applies the `triage-must-have` verdict when the issue is opened through the Bundle Submission form. Bundle submissions join extension and preset submissions in using this intake automation; the manual triage rubric for other issues remains unchanged. -2. A maintainer reviews the issue during issue triage and applies the separate `bundle-submission` label, which starts the automated catalog validation. On this public repository, contributors cannot apply that label themselves, so there is nothing to label or re-request — the issue simply waits in triage. -3. The automated workflow validates the submission and, when validation passes, updates `bundles/catalog.community.json` and `docs/community/bundles.md` in a draft pull request -4. A maintainer reviews the generated pull request and merges it when approved -5. Your bundle becomes discoverable via `specify bundle search` - -## Manage Catalog Sources - -Bundles are discovered through a priority-ordered stack of catalog sources (project, user, and built-in scopes). The built-in sources are: - -- `builtin://default` — first-party bundles shipped in `bundles/catalog.json` (`bugfix`, `assess`, ...), install-allowed. -- `builtin://community` — community submissions in `bundles/catalog.community.json`, discovery-only. - -Each source has an install policy. `install-allowed` sources can be installed -from; `discovery-only` sources appear in `search` and `info` but refuse -installation. Inspect the active stack before installing a bundle from a -non-default source. - -> **Vet both the bundle source and its component catalogs.** A project can supply its own bundle catalog and `install-allowed` component catalogs (extension, preset, workflow, and step) under `.specify/`; that project configuration existing is not evidence anything in it was reviewed. Before installing a bundle from an unfamiliar project, run `specify bundle catalog list` — and the equivalent `extension`/`preset`/`workflow catalog list` and `specify workflow step catalog list` commands for the components it pulls in — and treat any source you didn't add yourself as unvetted until you've reviewed it. - -For workflow components, inspect shell `run` and `env` fields and their -referenced code, including changes introduced by active overlays. This applies -even to `strict: true` steps: environment bindings can change executable lookup -or runtime loading. See [Reviewing shell execution](../workflows/steps.md#reviewing-shell-execution). - -### List the Catalog Stack - -```bash -specify bundle catalog list -``` - -Prints the active, priority-ordered catalog stack with each source's scope and install policy. - -### Add a Catalog Source - -```bash -specify bundle catalog add -``` - -| Option | Description | -| ------------- | ------------------------------------------------------- | -| `--policy` | `install-allowed` or `discovery-only` | -| `--priority` | Source priority (lower = higher precedence; default 10) | -| `--id` | Explicit source id | - -Registers a project-scoped catalog source and persists it. - -Re-adding the same source with the same ID, URL, policy, and priority succeeds without changing the configuration; different settings are rejected. - -### Remove a Catalog Source - -```bash -specify bundle catalog remove -``` - -Removes a project-scoped catalog source. Built-in default sources cannot be deleted. - -> **Note:** `search` and `info` work anywhere — with no project they fall back to the built-in/user catalog stack. The remaining state-changing commands (`list`, `update`, `remove`, `catalog`) require a project already initialized with `specify init`. `install` and `init` will initialize a project on demand when run in an uninitialized directory. diff --git a/docs/reference/catalogs.md b/docs/reference/catalogs.md index 6f6b0d5f5f..a5a977429c 100644 --- a/docs/reference/catalogs.md +++ b/docs/reference/catalogs.md @@ -58,7 +58,7 @@ project: | Presets | `specify preset catalog list` | [Preset catalogs](../presets/catalogs.md) | | Workflows | `specify workflow catalog list` | [Workflow catalogs](../workflows/catalogs.md) | | Workflow steps | `specify workflow step catalog list` | [Workflow catalogs](../workflows/catalogs.md) | -| Bundles | `specify bundle catalog list` | [Bundles](bundles.md#manage-catalog-sources) | +| Bundles | `specify bundle catalog list` | [Bundle catalogs](../bundles/catalogs.md) | Each command group also provides `catalog add` and `catalog remove`. Options, identifiers, install policies, and configuration filenames differ by component diff --git a/docs/reference/overview.md b/docs/reference/overview.md index 136ef895ed..8da6a33daa 100644 --- a/docs/reference/overview.md +++ b/docs/reference/overview.md @@ -64,7 +64,9 @@ Workflows automate multi-step Spec-Driven Development processes into repeatable Bundles compose existing extensions, presets, workflows, and steps into a single, versioned, installable unit. Rather than adding new behavior, a bundle curates a stack of primitives — everything a team or role needs — and installs it in one step through each component's own machinery, with version pinning, conflict checks, and provenance tracking for clean updates and removal. -[Bundles reference →](bundles.md) +[Bundles guide →](../bundles/overview.md) + +[Bundle management reference →](../bundles/cli.md) ## Agentic Commands diff --git a/docs/toc.yml b/docs/toc.yml index ea87352397..8ce34f78a4 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -192,6 +192,23 @@ - name: Publishing href: workflows/publishing.md +# Bundles +- name: Bundles + href: bundles/overview.md + items: + - name: Overview + href: bundles/overview.md + - name: CLI Reference + href: bundles/cli.md + - name: Developing + href: bundles/developing.md + - name: API Reference + href: bundles/api.md + - name: Catalogs + href: bundles/catalogs.md + - name: Publishing + href: bundles/publishing.md + # Reference - name: Reference items: @@ -207,8 +224,6 @@ href: reference/catalogs.md - name: Artifacts href: reference/artifacts.md - - name: Bundles - href: reference/bundles.md - name: Agentic SDD href: reference/agentic-sdd.md - name: Agentic Bug Fix diff --git a/docs/workflows/developing.md b/docs/workflows/developing.md index 4b44efe8a5..242b0f1d6a 100644 --- a/docs/workflows/developing.md +++ b/docs/workflows/developing.md @@ -211,7 +211,7 @@ Use `specify workflow step add my-step --dev ./my-step` for local iteration and A bundle's `provides.steps` still resolves only through the active step catalogs. Bundle-local `steps//` payloads and relative `provides.steps[].source` overrides are **not** resolved in this release, so -such steps are not installable offline. See the [Bundles reference](../reference/bundles.md). +such steps are not installable offline. See the [bundle offline limits](../bundles/cli.md#offline-limits). ## Authoring practices diff --git a/examples/bundles/business-analyst/README.md b/examples/bundles/business-analyst/README.md deleted file mode 100644 index 9f63464640..0000000000 --- a/examples/bundles/business-analyst/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Business Analyst bundle - -A role bundle for business analysts working in a Spec-Driven Development flow: -requirements elicitation, traceability, and acceptance criteria. - -## What it installs - -- **Extension** `agent-context` — keeps the agent context file in sync. -- **Preset** `requirements-elicitation` (priority 10, append) — elicitation and - analysis command set. -- **Steps** `capture-requirements`, `trace-acceptance-criteria`. -- **Workflow** `requirements-to-spec` — turns captured requirements into a spec. - -This bundle is **integration-agnostic**: it inherits the project's active -integration. - -## Usage - -```bash -specify bundle validate --path examples/bundles/business-analyst -specify bundle build --path examples/bundles/business-analyst --output dist/ -``` diff --git a/examples/bundles/business-analyst/bundle.yml b/examples/bundles/business-analyst/bundle.yml deleted file mode 100644 index 58bd95bd5e..0000000000 --- a/examples/bundles/business-analyst/bundle.yml +++ /dev/null @@ -1,33 +0,0 @@ -schema_version: "1.0" - -bundle: - id: "business-analyst" - name: "Business Analyst" - version: "1.0.0" - role: "business-analyst" - description: "Spec-Driven Development setup for business analysts: requirements elicitation, traceability, and acceptance criteria." - author: "spec-kit-examples" - license: "MIT" - -requires: - speckit_version: ">=0.9.0" - tools: [] - mcp: [] - -provides: - extensions: - - id: "agent-context" - version: "1.0.2" - presets: - - id: "requirements-elicitation" - version: "1.0.0" - priority: 10 - strategy: "append" - steps: - - id: "capture-requirements" - - id: "trace-acceptance-criteria" - workflows: - - id: "requirements-to-spec" - version: "1.0.0" - -tags: ["requirements", "traceability", "analysis"] diff --git a/examples/bundles/developer/README.md b/examples/bundles/developer/README.md deleted file mode 100644 index c56d5071ca..0000000000 --- a/examples/bundles/developer/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Developer bundle - -A role bundle for developers practicing Spec-Driven Development: implementation -planning, task breakdown, and code review. - -## What it installs - -- **Extension** `agent-context` — keeps the agent context file in sync. -- **Preset** `implementation-planning` (priority 10, append) — implementation - planning command set. -- **Steps** `plan-implementation`, `break-down-tasks`. -- **Workflow** `spec-to-implementation` — drives a spec through to code. - -This bundle is **integration-agnostic**: it inherits the project's active -integration. - -## Usage - -```bash -specify bundle validate --path examples/bundles/developer -specify bundle build --path examples/bundles/developer --output dist/ -``` diff --git a/examples/bundles/developer/bundle.yml b/examples/bundles/developer/bundle.yml deleted file mode 100644 index 95864f3e25..0000000000 --- a/examples/bundles/developer/bundle.yml +++ /dev/null @@ -1,33 +0,0 @@ -schema_version: "1.0" - -bundle: - id: "developer" - name: "Developer" - version: "1.0.0" - role: "developer" - description: "Spec-Driven Development setup for developers: implementation planning, task breakdown, and code review." - author: "spec-kit-examples" - license: "MIT" - -requires: - speckit_version: ">=0.9.0" - tools: [] - mcp: [] - -provides: - extensions: - - id: "agent-context" - version: "1.0.2" - presets: - - id: "implementation-planning" - version: "1.0.0" - priority: 10 - strategy: "append" - steps: - - id: "plan-implementation" - - id: "break-down-tasks" - workflows: - - id: "spec-to-implementation" - version: "1.0.0" - -tags: ["development", "implementation", "code-review"] diff --git a/examples/bundles/product-manager/README.md b/examples/bundles/product-manager/README.md deleted file mode 100644 index c7b5c8b9f8..0000000000 --- a/examples/bundles/product-manager/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Product Manager bundle - -A role bundle that prepares a Spec Kit project for product managers driving -Spec-Driven Development: discovery, specification, and roadmap planning. - -## What it installs - -- **Extension** `agent-context` — keeps the agent context file in sync. -- **Preset** `product-discovery` (priority 10, append) — discovery-oriented - command set. -- **Steps** `draft-spec`, `review-spec` — specification authoring steps. -- **Workflow** `spec-to-roadmap` — turns an approved spec into a roadmap. - -This bundle is **integration-agnostic**: it inherits whatever integration the -project already uses (e.g. `copilot`, `claude`). - -## Usage - -```bash -specify bundle validate --path examples/bundles/product-manager -specify bundle build --path examples/bundles/product-manager --output dist/ -``` diff --git a/examples/bundles/product-manager/bundle.yml b/examples/bundles/product-manager/bundle.yml deleted file mode 100644 index b2e88c72c4..0000000000 --- a/examples/bundles/product-manager/bundle.yml +++ /dev/null @@ -1,35 +0,0 @@ -schema_version: "1.0" - -bundle: - id: "product-manager" - name: "Product Manager" - version: "1.0.0" - role: "product-manager" - description: "Spec-Driven Development setup for product managers: discovery, specification, and roadmap workflows." - author: "spec-kit-examples" - license: "MIT" - -requires: - speckit_version: ">=0.9.0" - tools: [] - mcp: [] - -# Agnostic bundle: inherits the project's active integration. - -provides: - extensions: - - id: "agent-context" - version: "1.0.2" - presets: - - id: "product-discovery" - version: "1.0.0" - priority: 10 - strategy: "append" - steps: - - id: "draft-spec" - - id: "review-spec" - workflows: - - id: "spec-to-roadmap" - version: "1.0.0" - -tags: ["product", "discovery", "roadmap"] diff --git a/examples/bundles/security-researcher/README.md b/examples/bundles/security-researcher/README.md deleted file mode 100644 index 417cf5aced..0000000000 --- a/examples/bundles/security-researcher/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Security Researcher bundle - -A role bundle for security researchers practicing Spec-Driven Development: -threat modeling, security review, and compliance. - -## What it installs - -- **Extension** `agent-context` — keeps the agent context file in sync. -- **Preset** `security-compliance` (priority 5, append) — security and - compliance command set; presets apply in ascending priority order, so this - low number (5) places it ahead of higher-numbered presets in the stack. -- **Steps** `threat-model`, `security-review`. -- **Workflow** `secure-sdd` — a security-first SDD workflow. - -This bundle is **integration-agnostic**: it inherits the project's active -integration. - -## Usage - -```bash -specify bundle validate --path examples/bundles/security-researcher -specify bundle build --path examples/bundles/security-researcher --output dist/ -``` diff --git a/examples/bundles/security-researcher/bundle.yml b/examples/bundles/security-researcher/bundle.yml deleted file mode 100644 index 5c6acceed9..0000000000 --- a/examples/bundles/security-researcher/bundle.yml +++ /dev/null @@ -1,33 +0,0 @@ -schema_version: "1.0" - -bundle: - id: "security-researcher" - name: "Security Researcher" - version: "1.0.0" - role: "security-researcher" - description: "Spec-Driven Development setup for security researchers: threat modeling, security review, and compliance checks." - author: "spec-kit-examples" - license: "MIT" - -requires: - speckit_version: ">=0.9.0" - tools: [] - mcp: [] - -provides: - extensions: - - id: "agent-context" - version: "1.0.2" - presets: - - id: "security-compliance" - version: "1.0.0" - priority: 5 - strategy: "append" - steps: - - id: "threat-model" - - id: "security-review" - workflows: - - id: "secure-sdd" - version: "1.0.0" - -tags: ["security", "compliance", "threat-modeling"] diff --git a/newsletters/2026-June.md b/newsletters/2026-June.md index acf58e408d..7e1d7ed568 100644 --- a/newsletters/2026-June.md +++ b/newsletters/2026-June.md @@ -40,7 +40,7 @@ A bundle is described by a **`bundle.yml` manifest**: metadata (`id`, `name`, `v The subcommand surface is a full package-manager experience: `search` and `info` (which previews the **fully expanded component set** with pinned versions and a `verified`-vs-`community` trust indicator before you install), `install`, `update` (`--all`), `remove`, `list`, `init`, `validate`, `build` (produces a single versioned `.zip` artifact), `publish`, and `catalog` management (`list`/`add`/`remove` sources). Installs are **idempotent with full provenance tracking**, so a bundle can be cleanly removed or refreshed later; `remove` uninstalls only the components a bundle contributed, leaving anything another installed bundle still needs in place. If run in a directory that isn't yet a Spec Kit project, `install` and `init` **bootstrap one first**, so a fresh checkout reaches a working state in a single command. The only cross-bundle conflict point checked at install time is the active integration. -Bundles are discovered through the same priority-ordered catalog stack (project, user, and built-in scopes) as every other component, and by the end of the month they had become a **fourth community-submittable artifact type** alongside extensions, presets, and workflows, via a dedicated submission path (#3162). Bundles are the project's answer to the "how do I distribute a whole role setup?" question — the composability story that ties the entire catalog together. [\[github.com\]](https://github.com/github/spec-kit/blob/main/docs/reference/bundles.md) +Bundles are discovered through the same priority-ordered catalog stack (project, user, and built-in scopes) as every other component, and by the end of the month they had become a **fourth community-submittable artifact type** alongside extensions, presets, and workflows, via a dedicated submission path (#3162). Bundles are the project's answer to the "how do I distribute a whole role setup?" question — the composability story that ties the entire catalog together. [\[Bundle documentation\]](https://github.github.io/spec-kit/bundles/overview.html) ### The Workflow Engine Matures @@ -148,7 +148,7 @@ The "which SDD tool?" comparison became June's dominant content genre, almost al Areas under discussion or in progress for future development: - **The convergence loop** — `/speckit.converge` (#3001) is the core's direct answer to the drift-and-verification concern raised in nearly every review. Expect the append-only convergence model to deepen, and the community drift/verification extensions (Golden Demo, Spec Trace, Coding Standards Drift Control) to keep feeding requirements upstream. [\[github.com\]](https://github.com/github/spec-kit/blob/main/docs/quickstart.md) -- **The bundle subsystem** — `specify bundle` (#3070) establishes role-based distribution as a first-class primitive. With a community submission path now open (#3162) and four example roles shipped, curation, trust signals (`verified` vs `community`), and version-pin enforcement become the next areas to mature. [\[github.com\]](https://github.com/github/spec-kit/blob/main/docs/reference/bundles.md) +- **The bundle subsystem** — `specify bundle` (#3070) establishes role-based distribution as a first-class primitive. With a community submission path now open (#3162) and four example roles shipped, curation, trust signals (`verified` vs `community`), and version-pin enforcement become the next areas to mature. [\[Bundle documentation\]](https://github.github.io/spec-kit/bundles/overview.html) - **A programmable workflow platform** — with the step catalog, JSON output, and `from_json` filter, workflows are now community-extensible and scriptable. The open question is discoverability and pull: the step catalog is new, and adoption will show whether standalone workflow authoring becomes a real ecosystem or stays a power-user niche. [\[github.com\]](https://github.com/github/spec-kit/releases) - **PyPI publishing** — a publishing workflow and README metadata landed (#2915, closing #2623), but official PyPI distribution is not yet the recommended install path; `uv tool install` and git remain canonical. Completing and hardening this reduces friction for restricted/air-gapped environments. [\[github.com\]](https://github.com/github/spec-kit/releases) - **CLI architecture cleanup** — the `__init__.py` decomposition reached 7/8 (extensions/_commands.py, #3014), with one part remaining. The payoff is contributor onboarding and test isolation. [\[github.com\]](https://github.com/github/spec-kit/releases) diff --git a/tests/contract/test_bundled_extension_versions.py b/tests/contract/test_bundled_extension_versions.py index f86f0afc72..0b4ba921cc 100644 --- a/tests/contract/test_bundled_extension_versions.py +++ b/tests/contract/test_bundled_extension_versions.py @@ -25,7 +25,7 @@ REPO_ROOT = Path(__file__).parents[2] EXTENSIONS_ROOT = REPO_ROOT / "extensions" -EXAMPLE_BUNDLES_ROOT = REPO_ROOT / "examples" / "bundles" +BUNDLES_ROOT = REPO_ROOT / "bundles" def _catalog_entries() -> dict[str, dict]: @@ -84,37 +84,78 @@ def test_catalog_version_is_valid_pep440(ext_id: str): ) -def _example_bundle_extension_pins() -> list[tuple[str, str, str]]: - """(bundle name, extension id, pinned version) for every example bundle.""" +def _firstparty_bundle_component_pins() -> list[tuple[str, str, str, str]]: + """Pinned components from the published first-party bundle sources.""" pins = [] - for manifest_path in sorted(EXAMPLE_BUNDLES_ROOT.glob("*/bundle.yml")): + for manifest_path in sorted(BUNDLES_ROOT.glob("*/bundle.yml")): data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) - for entry in (data.get("provides") or {}).get("extensions") or []: - pins.append((manifest_path.parent.name, entry["id"], str(entry.get("version")))) + for kind, entries in (data.get("provides") or {}).items(): + if kind == "bundles": + continue + for entry in entries or []: + if kind == "steps" and not entry.get("version"): + continue + pins.append( + (manifest_path.parent.name, kind, entry["id"], str(entry.get("version"))) + ) return pins -def test_example_bundles_pin_extensions(): - assert _example_bundle_extension_pins(), "expected at least one extension pin in examples/bundles" +def test_firstparty_bundles_pin_components(): + assert _firstparty_bundle_component_pins(), "expected component pins in bundles/" + + +@pytest.mark.parametrize("optional_version", [{}, {"version": None}, {"version": ""}]) +def test_firstparty_bundle_pins_skip_unpinned_steps(tmp_path, monkeypatch, optional_version): + from specify_cli.bundles.manifest import BundleManifest + from tests.specify_cli.bundles.helpers import valid_manifest_dict, write_manifest + + data = valid_manifest_dict() + data["provides"]["steps"] = [ + {"id": "unpinned", **optional_version}, + {"id": "pinned", "version": "1.3.0"}, + ] + assert BundleManifest.from_dict(data).structural_errors() == [] + write_manifest(tmp_path / "demo-bundle", data) + monkeypatch.setitem(globals(), "BUNDLES_ROOT", tmp_path) + + assert _firstparty_bundle_component_pins() == [ + ("demo-bundle", "extensions", "ext-a", "1.0.0"), + ("demo-bundle", "presets", "preset-a", "2.0.0"), + ("demo-bundle", "steps", "pinned", "1.3.0"), + ("demo-bundle", "workflows", "wf-a", "0.3.0"), + ] + + +@pytest.mark.parametrize("kind", ["extensions", "presets", "steps", "workflows"]) +def test_firstparty_bundle_pins_retain_invalid_versions(tmp_path, monkeypatch, kind): + from tests.specify_cli.bundles.helpers import valid_manifest_dict, write_manifest + + data = valid_manifest_dict(provides={kind: [{"id": "invalid", "version": "bad"}]}) + write_manifest(tmp_path / "demo-bundle", data) + monkeypatch.setitem(globals(), "BUNDLES_ROOT", tmp_path) + + pins = _firstparty_bundle_component_pins() + assert pins == [("demo-bundle", kind, "invalid", "bad")] + with pytest.raises(InvalidVersion): + Version(pins[0][3]) @pytest.mark.parametrize( - "bundle_name,ext_id,pinned", - _example_bundle_extension_pins(), + "bundle_name,kind,component_id,pinned", + _firstparty_bundle_component_pins(), ids=lambda v: v if isinstance(v, str) else str(v), ) -def test_example_bundle_pins_match_bundled_manifest(bundle_name: str, ext_id: str, pinned: str): - """``BundleExtensionPrimitive`` refuses to install when the resolved - extension version differs from the bundle's pin, so an example bundle that - still pins a bundled extension's previous version fails for every user who - installs it. Test fixtures derive their pins from the bundled manifest and - therefore cannot catch this; the shipped examples must be checked directly.""" - manifest_path = EXTENSIONS_ROOT / ext_id / "extension.yml" - if not manifest_path.is_file(): - pytest.skip(f"'{ext_id}' is not a bundled extension; pin resolves via the catalog at install time") - manifest_version = _manifest_version(ext_id) +def test_firstparty_bundle_pins_match_bundled_manifest( + bundle_name: str, kind: str, component_id: str, pinned: str +): + """Check real first-party pins rather than synthetic or illustrative bundles.""" + manifest_path = REPO_ROOT / kind / component_id / f"{kind[:-1]}.yml" + assert manifest_path.is_file(), f"first-party component manifest missing: {manifest_path}" + data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + manifest_version = str(data[kind[:-1]]["version"]) assert Version(pinned) == Version(manifest_version), ( - f"examples/bundles/{bundle_name}/bundle.yml pins '{ext_id}' to {pinned} but " + f"bundles/{bundle_name}/bundle.yml pins {kind}:{component_id} to {pinned} but " f"{manifest_path.relative_to(REPO_ROOT)} declares {manifest_version}; the bundler " - f"enforces exact pins, so installing this example would fail" + f"requires compatible pins for installation" ) diff --git a/tests/test_catalog_trust_docs.py b/tests/test_catalog_trust_docs.py index 89664abbb8..d1aeae5c33 100644 --- a/tests/test_catalog_trust_docs.py +++ b/tests/test_catalog_trust_docs.py @@ -25,7 +25,7 @@ def test_preset_catalog_docs_warn_about_vetting_install_allowed(): def test_bundle_catalog_docs_cover_bundle_source_and_component_catalogs(): - text = (REPO_ROOT / "docs" / "reference" / "bundles.md").read_text(encoding="utf-8") + text = (REPO_ROOT / "docs" / "bundles" / "catalogs.md").read_text(encoding="utf-8") assert "component catalogs" in text.lower() assert "vet" in text.lower() assert "specify workflow step catalog list" in text