feat: add specify bundle command for role-based project setup (dogfooding, scrub generated files before merge)#3070
Conversation
Scaffold Spec Kit (--integration copilot) and run the full SDD workflow against the `specify bundle` subcommand feature: - spec.md (4 user stories, 31 FRs, 8 success criteria) + clarifications - plan.md, research.md, data-model.md, contracts/, quickstart.md - tasks.md (43 dependency-ordered tasks, organized by user story) - Spec Kit Constitution v1.0.0 (code quality, testing, UX, performance, dependency/security principles) derived from deep codebase analysis - plan Constitution Check + tasks grounded against the ratified principles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the Spec Kit Bundler as a `specify bundle ...` subcommand group that calls existing primitive machinery in-process with zero new dependencies, per the v1.0.0 constitution (Principles I-V). Adds the `specify_cli.bundler` package (models, services, lib helpers) and the `commands/bundle` Typer group wiring search, info, list, install, update, remove, validate, build, init, and catalog list/add/remove (with --json and --offline). Includes manifest/catalog schemas, version + integration-clash gating, discovery-only refusal, idempotent install with atomic rollback, non-collateral removal, and offline-first catalog resolution. Ships an 82-test suite (contract/unit/integration), four sample role bundles (product-manager, business-analyst, security-researcher, developer), README "Bundles" docs, and an AGENTS.md pitfall on the test-venv gotcha. Marks tasks T001-T043 complete and records follow-ups T044 (live in-process primitive dispatch) and T045 (install from a local artifact path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….venv Add a "Running the full test suite" subsection under Automated checks covering `uv pip install -e ".[test]"` + `.venv/bin/python -m pytest`, with the shared/global editable-install contamination caveat that mirrors the AGENTS.md pitfall. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t install Closes the two follow-ups left after the initial bundler landing. T044 — DefaultPrimitiveInstaller now performs real installs through existing machinery instead of raising "use the primitive command" errors: - presets/extensions install via their reusable managers (install_from_directory / install_from_zip); bundled assets install fully offline, catalog assets are fetched only when the network is allowed. - workflows/steps delegate to the existing `workflow add` / `workflow step add` command callables in-process (project root as cwd), avoiding any duplicated download/validation logic (Principle I). - `--offline` is threaded through DefaultPrimitiveInstaller(allow_network=…) so network-only kinds refuse with an actionable message rather than silently reaching out. T045 — `specify bundle install` now accepts a local path (a built .zip artifact, a bundle directory, or a bundle.yml) and installs directly without consulting the catalog stack; bundle-ids still resolve via the stack. Adds 13 tests (routing, offline gating, local-source resolution, and an end-to-end offline build → install → list → remove of the bundled agent-context extension). Bundler suite: 95 passing; ruff clean. Marks T044 and T045 complete in tasks.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ran the converge command: assessed the codebase against spec.md, plan.md, tasks.md, and the v1.0.0 constitution. Appended 7 traceable gap-closure tasks (T046–T052) as a new "Phase 8: Convergence" section. Append-only — no existing tasks were modified and no application code was changed. Findings: 1 CRITICAL (Constitution III — bundle group undocumented under docs/reference/), 3 HIGH (FR-005/SC-007 validate references; FR-009/SC-002 info expansion; FR-012 install-time init), 3 MEDIUM (FR-013 integration precedence; FR-020 surface overlaps; FR-028 update refresh). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Close the gaps the converge command found between the bundler spec/plan/ constitution and the code: - T046: add docs/reference/bundles.md documenting the full `specify bundle` command group; link it from docs/reference/overview.md (Constitution III). - T047: wire a reference checker into `bundle validate` (services/references.py); online runs fail and name unresolved component references, offline runs warn. - T048: expand `bundle info` to enumerate the full component set (versions, preset priority/strategy) plus the bundle integration — info == install. - T049/T050: `bundle install`/`bundle init` now scaffold an uninitialized project via the existing `specify init` machinery, choosing the integration by precedence (override → bundle-declared → Copilot + OS default script type). - T051: surface foreseeable component overlaps during info and install. - T052: `bundle update` refreshes already-installed components via a new refresh path in install_bundle, preserving primitive-level overrides. Adds unit/contract/integration coverage (107 tests pass). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-run of converge after Phase 8. The seven Phase 8 tasks are verified closed. One residual partial gap remains: the `verified`/trust indicator (FR-010, FR-027) is exposed only in `bundle info --json`, absent from `bundle search` (the primary discovery surface) and `bundle info` text. Appended as a single new task for implement to complete. Append-only; no code changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`bundle search` (text + JSON) and `bundle info` (text + JSON) now expose each catalog entry's verification/trust level (verified vs community), so users can judge a bundle's trust before installing, per FR-010 / FR-027. Previously `verified` was only present in `bundle info --json`. Adds contract coverage; 108 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scaffold Spec Kit (--integration copilot) and run the full SDD workflow against the `specify bundle` subcommand feature: - spec.md (4 user stories, 31 FRs, 8 success criteria) + clarifications - plan.md, research.md, data-model.md, contracts/, quickstart.md - tasks.md (43 dependency-ordered tasks, organized by user story) - Spec Kit Constitution v1.0.0 (code quality, testing, UX, performance, dependency/security principles) derived from deep codebase analysis - plan Constitution Check + tasks grounded against the ratified principles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the Spec Kit Bundler as a `specify bundle ...` subcommand group that calls existing primitive machinery in-process with zero new dependencies, per the v1.0.0 constitution (Principles I-V). Adds the `specify_cli.bundler` package (models, services, lib helpers) and the `commands/bundle` Typer group wiring search, info, list, install, update, remove, validate, build, init, and catalog list/add/remove (with --json and --offline). Includes manifest/catalog schemas, version + integration-clash gating, discovery-only refusal, idempotent install with atomic rollback, non-collateral removal, and offline-first catalog resolution. Ships an 82-test suite (contract/unit/integration), four sample role bundles (product-manager, business-analyst, security-researcher, developer), README "Bundles" docs, and an AGENTS.md pitfall on the test-venv gotcha. Marks tasks T001-T043 complete and records follow-ups T044 (live in-process primitive dispatch) and T045 (install from a local artifact path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….venv Add a "Running the full test suite" subsection under Automated checks covering `uv pip install -e ".[test]"` + `.venv/bin/python -m pytest`, with the shared/global editable-install contamination caveat that mirrors the AGENTS.md pitfall. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t install Closes the two follow-ups left after the initial bundler landing. T044 — DefaultPrimitiveInstaller now performs real installs through existing machinery instead of raising "use the primitive command" errors: - presets/extensions install via their reusable managers (install_from_directory / install_from_zip); bundled assets install fully offline, catalog assets are fetched only when the network is allowed. - workflows/steps delegate to the existing `workflow add` / `workflow step add` command callables in-process (project root as cwd), avoiding any duplicated download/validation logic (Principle I). - `--offline` is threaded through DefaultPrimitiveInstaller(allow_network=…) so network-only kinds refuse with an actionable message rather than silently reaching out. T045 — `specify bundle install` now accepts a local path (a built .zip artifact, a bundle directory, or a bundle.yml) and installs directly without consulting the catalog stack; bundle-ids still resolve via the stack. Adds 13 tests (routing, offline gating, local-source resolution, and an end-to-end offline build → install → list → remove of the bundled agent-context extension). Bundler suite: 95 passing; ruff clean. Marks T044 and T045 complete in tasks.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ran the converge command: assessed the codebase against spec.md, plan.md, tasks.md, and the v1.0.0 constitution. Appended 7 traceable gap-closure tasks (T046–T052) as a new "Phase 8: Convergence" section. Append-only — no existing tasks were modified and no application code was changed. Findings: 1 CRITICAL (Constitution III — bundle group undocumented under docs/reference/), 3 HIGH (FR-005/SC-007 validate references; FR-009/SC-002 info expansion; FR-012 install-time init), 3 MEDIUM (FR-013 integration precedence; FR-020 surface overlaps; FR-028 update refresh). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Close the gaps the converge command found between the bundler spec/plan/ constitution and the code: - T046: add docs/reference/bundles.md documenting the full `specify bundle` command group; link it from docs/reference/overview.md (Constitution III). - T047: wire a reference checker into `bundle validate` (services/references.py); online runs fail and name unresolved component references, offline runs warn. - T048: expand `bundle info` to enumerate the full component set (versions, preset priority/strategy) plus the bundle integration — info == install. - T049/T050: `bundle install`/`bundle init` now scaffold an uninitialized project via the existing `specify init` machinery, choosing the integration by precedence (override → bundle-declared → Copilot + OS default script type). - T051: surface foreseeable component overlaps during info and install. - T052: `bundle update` refreshes already-installed components via a new refresh path in install_bundle, preserving primitive-level overrides. Adds unit/contract/integration coverage (107 tests pass). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-run of converge after Phase 8. The seven Phase 8 tasks are verified closed. One residual partial gap remains: the `verified`/trust indicator (FR-010, FR-027) is exposed only in `bundle info --json`, absent from `bundle search` (the primary discovery surface) and `bundle info` text. Appended as a single new task for implement to complete. Append-only; no code changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`bundle search` (text + JSON) and `bundle info` (text + JSON) now expose each catalog entry's verification/trust level (verified vs community), so users can judge a bundle's trust before installing, per FR-010 / FR-027. Previously `verified` was only present in `bundle info --json`. Adds contract coverage; 108 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eat-bundler-spec-dogfood
There was a problem hiding this comment.
Pull request overview
Adds a new Spec Kit Bundler subsystem and wires it into the specify CLI as specify bundle ..., enabling role-based project setup via versioned “bundle” artifacts that compose existing primitives (extensions/presets/steps/workflows). The PR also includes a substantial dogfooding trail (spec artifacts + generated runtime scaffolding) and a comprehensive bundler-focused test suite.
Changes:
- Introduces
src/specify_cli/bundler/**(models/services/lib) implementing manifest validation, catalog stacking, install planning, installation/removal with provenance records, and artifact packaging. - Adds extensive unit/contract/integration tests for offline behavior, security path confinement, and install/update/remove lifecycle.
- Updates documentation and examples to describe/illustrate bundle authoring, discovery, and installation.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_bundler_versioning.py | Unit tests for version parsing/constraints |
| tests/unit/test_bundler_resolver.py | Resolver tests (version gate, integration compatibility) |
| tests/unit/test_bundler_references.py | Reference-checker tests (online vs offline behavior) |
| tests/unit/test_bundler_records.py | Installed-bundle record persistence/removal tests |
| tests/unit/test_bundler_primitives.py | Primitive installer routing/offline gating tests |
| tests/unit/test_bundler_packager.py | Bundle artifact packaging tests |
| tests/unit/test_bundler_conflict.py | Conflict detection tests (integration + overlaps) |
| tests/integration/test_bundler_security_paths.py | Path traversal/symlink confinement security tests |
| tests/integration/test_bundler_offline.py | Offline-first catalog resolution tests |
| tests/integration/test_bundler_local_install.py | Local path/zip install tests + end-to-end offline install |
| tests/integration/test_bundler_install_flow.py | Install/record/remove lifecycle integration tests |
| tests/integration/test_bundler_init_install.py | Init-on-install + integration precedence tests |
| tests/integration/test_bundler_catalog_stack.py | Catalog stack precedence/policy/search integration tests |
| tests/contract/test_manifest_schema.py | Manifest “schema contract” tests via model validation |
| tests/contract/test_catalog_schema.py | Catalog “schema contract” tests + default stack shape |
| tests/bundler_helpers.py | Shared helpers/fakes for bundler tests |
| src/specify_cli/bundler/services/validator.py | Manifest structural + reference validation service |
| src/specify_cli/bundler/services/resolver.py | Manifest → ordered install plan resolver |
| src/specify_cli/bundler/services/references.py | Offline-first component reference resolution |
| src/specify_cli/bundler/services/packager.py | Build bundle zip artifacts from bundle directories |
| src/specify_cli/bundler/services/installer.py | Apply install plans; rollback; provenance record write |
| src/specify_cli/bundler/services/conflict.py | Cross-bundle conflict detection (integration + overlaps) |
| src/specify_cli/bundler/services/catalog_stack.py | Multi-source catalog stack resolution + search |
| src/specify_cli/bundler/services/adapters.py | Concrete fetch/install adapters (network + primitives) |
| src/specify_cli/bundler/services/init.py | Bundler services package init |
| src/specify_cli/bundler/models/records.py | Installed bundle record model + persistence helpers |
| src/specify_cli/bundler/models/manifest.py | Bundle manifest parsing + structural validation |
| src/specify_cli/bundler/models/catalog.py | Catalog source/entry models + stack merge logic |
| src/specify_cli/bundler/models/init.py | Bundler models package init |
| src/specify_cli/bundler/lib/yamlio.py | Confined YAML/JSON IO helpers |
| src/specify_cli/bundler/lib/versioning.py | SemVer + constraint evaluation helpers |
| src/specify_cli/bundler/lib/project.py | Project root detection + active integration lookup |
| src/specify_cli/bundler/lib/init.py | Bundler lib package init |
| src/specify_cli/bundler/commands_impl/catalog_config.py | Project catalog config persistence helpers |
| src/specify_cli/bundler/commands_impl/init.py | Bundler commands-impl package init |
| src/specify_cli/bundler/init.py | Bundler package + BundlerError |
| src/specify_cli/init.py | Registers the new bundle command group on the root CLI |
| specs/001-spec-kit-bundler/research.md | Dogfooding research artifact |
| specs/001-spec-kit-bundler/quickstart.md | Dogfooding quickstart/validation artifact |
| specs/001-spec-kit-bundler/plan.md | Dogfooding implementation plan artifact |
| specs/001-spec-kit-bundler/data-model.md | Dogfooding data model artifact |
| specs/001-spec-kit-bundler/contracts/cli-commands.md | Bundler CLI behavior contract doc |
| specs/001-spec-kit-bundler/contracts/bundle-manifest.schema.md | Bundle manifest contract doc |
| specs/001-spec-kit-bundler/contracts/bundle-catalog.schema.md | Bundle catalog contract doc |
| specs/001-spec-kit-bundler/checklists/requirements.md | Dogfooding requirements checklist |
| README.md | Adds “Bundles” overview + usage examples |
| examples/bundles/security-researcher/README.md | Example bundle documentation |
| examples/bundles/security-researcher/bundle.yml | Example manifest |
| examples/bundles/product-manager/README.md | Example bundle documentation |
| examples/bundles/product-manager/bundle.yml | Example manifest |
| examples/bundles/developer/README.md | Example bundle documentation |
| examples/bundles/developer/bundle.yml | Example manifest |
| examples/bundles/business-analyst/README.md | Example bundle documentation |
| examples/bundles/business-analyst/bundle.yml | Example manifest |
| docs/reference/overview.md | Adds “Bundles” reference entry |
| docs/reference/bundles.md | New Bundles reference documentation |
| CONTRIBUTING.md | Adds guidance for running tests in a local venv reliably |
| AGENTS.md | Markdown formatting fixes + adds test-env gotcha |
| .specify/workflows/workflow-registry.json | Generated workflow registry (dogfooding/runtime) |
| .specify/workflows/speckit/workflow.yml | Generated workflow definition (dogfooding/runtime) |
| .specify/templates/spec-template.md | Generated template (dogfooding/runtime) |
| .specify/templates/plan-template.md | Generated template (dogfooding/runtime) |
| .specify/templates/constitution-template.md | Generated template (dogfooding/runtime) |
| .specify/templates/checklist-template.md | Generated template (dogfooding/runtime) |
| .specify/scripts/bash/setup-tasks.sh | Generated script (dogfooding/runtime) |
| .specify/scripts/bash/setup-plan.sh | Generated script (dogfooding/runtime) |
| .specify/scripts/bash/check-prerequisites.sh | Generated script (dogfooding/runtime) |
| .specify/integrations/speckit.manifest.json | Generated integration manifest (dogfooding/runtime) |
| .specify/integrations/copilot.manifest.json | Generated integration manifest (dogfooding/runtime) |
| .specify/integration.json | Generated runtime integration state (dogfooding/runtime) |
| .specify/init-options.json | Generated init options (dogfooding/runtime) |
| .specify/feature.json | Generated feature pointer (dogfooding/runtime) |
| .specify/extensions/agent-context/scripts/bash/update-agent-context.sh | Generated extension script (dogfooding/runtime) |
| .specify/extensions/agent-context/README.md | Generated extension docs (dogfooding/runtime) |
| .specify/extensions/agent-context/extension.yml | Generated extension manifest (dogfooding/runtime) |
| .specify/extensions/agent-context/commands/speckit.agent-context.update.md | Generated extension command (dogfooding/runtime) |
| .specify/extensions/agent-context/agent-context-config.yml | Generated extension config (dogfooding/runtime) |
| .specify/extensions/.registry | Generated extensions registry (dogfooding/runtime) |
| .specify/extensions.yml | Generated extensions config (dogfooding/runtime) |
| .github/prompts/speckit.taskstoissues.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.tasks.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.specify.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.plan.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.implement.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.constitution.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.clarify.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.checklist.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.analyze.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/prompts/speckit.agent-context.update.prompt.md | Generated Copilot prompt scaffold (dogfooding/runtime) |
| .github/copilot-instructions.md | Generated Copilot context pointer (dogfooding/runtime) |
| .github/agents/speckit.taskstoissues.agent.md | Generated Copilot agent scaffold (dogfooding/runtime) |
| .github/agents/speckit.plan.agent.md | Generated Copilot agent scaffold (dogfooding/runtime) |
| .github/agents/speckit.constitution.agent.md | Generated Copilot agent scaffold (dogfooding/runtime) |
| .github/agents/speckit.agent-context.update.agent.md | Generated Copilot agent scaffold (dogfooding/runtime) |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 110/110 changed files
- Comments generated: 8
…errors, reproducible builds Resolves automated review feedback on github#3070: - validator: drop redundant string-quoting on ReferenceChecker's `str | None` return so the annotation evaluates as a real union under `from __future__ import annotations`. - adapters: normalize Windows drive-letter paths (e.g. C:\...) to the local-file branch so offline file catalogs resolve on Windows. - adapters: enforce HTTPS (HTTP only for localhost) and require a host on remote catalog URLs before any network call, mirroring specify_cli.catalogs URL validation (MITM/downgrade protection). - adapters: pass `origin` to loads_json for local files and HTTP payloads so JSON parse errors name the real source instead of <string>. - manifest: parse component `priority` defensively, raising an actionable BundlerError on non-integer values instead of a raw ValueError. - packager: write zip members with a fixed timestamp + permissions so identical inputs yield byte-for-byte identical artifacts (genuinely reproducible builds), and strengthen the determinism test accordingly. Adds regression tests for priority validation, plain-HTTP/host rejection, and byte-level artifact reproducibility (111 bundler tests pass; ruff clean). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the review. Addressed all seven code findings in
Added regression tests for priority validation, plain-HTTP/missing-host rejection, and byte-level artifact reproducibility. 111 bundler tests pass; ruff clean. The remaining comment on Posted by GitHub Copilot (model: Claude Opus 4.8) on behalf of @mnriem. |
… URLs - packager: when --output points inside the bundle directory, exclude the whole output subtree from collection so previously-built artifacts are never re-packaged (prevents broken reproducibility and unbounded growth). - adapters: resolve file:// catalog URLs via url2pathname and preserve netloc, so Windows file URLs (file:///C:/...) and UNC shares (file://server/share) resolve correctly instead of dropping the host or producing /C:/x. Adds regression tests for nested-output exclusion and file:// resolution (113 bundler tests pass; ruff clean). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed both findings in
Added regression tests for nested-output exclusion and |
| _validate_remote_url(source.id, url) | ||
| return _http_get_json(url) | ||
|
|
||
| raise BundlerError(f"Unsupported catalog URL scheme: {url}") | ||
|
|
||
| return fetch | ||
|
|
||
|
|
||
| def _http_get_json(url: str) -> dict: | ||
| import urllib.request | ||
|
|
||
| try: | ||
| with urllib.request.urlopen(url, timeout=HTTP_TIMEOUT_SECONDS) as response: # noqa: S310 | ||
| raw = response.read().decode("utf-8") | ||
| except Exception as exc: # noqa: BLE001 | ||
| raise BundlerError(f"Failed to fetch catalog from {url}: {exc}") from exc | ||
| return loads_json(raw, origin=url) |
| data = load_yaml(path) | ||
| catalogs = data.get("catalogs") if isinstance(data, dict) else None | ||
| return list(catalogs) if catalogs else [] |
…he repo root (github#2892) * feat(scripts): add SPECIFY_INIT_DIR to target a member project from the repo root Resolve an explicit SPECIFY_INIT_DIR project override once in the core get_repo_root / Get-RepoRoot, so a non-interactive / CI caller can target a member project (the directory containing .specify/) from a monorepo root without cd. Strict by design: the path must exist and contain .specify/, otherwise it hard-errors with no silent fallback. - Single resolver in core; the git feature-branch script inherits it by sourcing core, with no per-extension copies. - PS resolver verifies the resolved path is a directory (Resolve-Path also succeeds for files) so a file value errors as "not an existing directory". - get_feature_paths splits decl/assignment so a SPECIFY_INIT_DIR failure propagates instead of being masked by `local`. - create-new-feature-branch: when core is absent (only git-common loaded) and SPECIFY_INIT_DIR is set, hard-error rather than silently using the git root. - Document SPECIFY_INIT_DIR and SPECIFY_FEATURE_DIRECTORY in the core reference. - Tests for valid/relative/trailing-slash/file/missing/no-.specify targets, feature-axis composition, the no-core guard, and a PowerShell mirror. * fix: guard SPECIFY_INIT_DIR with stale core scripts * docs: clarify SPECIFY_FEATURE_DIRECTORY precedence wording * fix: normalize trailing slash in PowerShell SPECIFY_INIT_DIR resolver Resolve-Path preserves a trailing separator from its input, so a SPECIFY_INIT_DIR ending in a slash returned a root that didn't match the bash resolver (whose `cd && pwd` strips it). That broke test_ps_trailing_slash_tolerated on the CI runners, which do have pwsh. Trim it with TrimEndingDirectorySeparator (no-op on a bare root or a path with no trailing separator). Also fix the misleading test comment: the PowerShell mirror runs on the CI ubuntu/windows runners (they ship pwsh), it is not skipped there. * test: normalize bash path expectations on Windows * docs: clarify SPECIFY_INIT_DIR root helpers
Mirror the SPECIFY_INIT_DIR resolver (resolve_specify_init_dir in common.sh) into the committed dogfooding .specify/scripts/bash copies so the git extension's create-new-feature-branch.sh finds an up-to-date common.sh instead of failing with "requires updated Spec Kit core scripts". Fixes the test_init_dir.py CI failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- adapters: route catalog HTTP fetches through the shared authenticated client (authentication.http.open_url) so auth.json tokens apply and the Authorization header is stripped on cross-host/downgrade redirects. Reject any redirect that leaves HTTPS via a redirect_validator and re-validate the final URL after redirects, closing the urlopen auto-redirect MITM/downgrade gap. - catalog_config._read: raise an actionable BundlerError when the config top level is not a mapping, 'catalogs' is not a list, or an entry is not a mapping, instead of letting list(<str>) produce a downstream AttributeError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review 4534421867 plus the Review findings
CI: Added regression tests for redirect downgrade rejection, non-HTTPS final URL, and malformed catalog-config shapes. Full bundler suite + |
…dence Addresses review 4534504799: - records.load_records: confine the read via ensure_within(project_root, ...) so a symlinked/traversal-escaping .specify cannot read arbitrary files outside the project (matches the write path's within= guard). - catalog_config._slug: lowercase so derived catalog ids are deterministic across platforms and case-variant duplicates can't slip past the case-sensitive dup check. - installer.install_bundle: reword the docstring's misleading "atomic on failure" claim to describe the real scoped guarantee (record written only on full success; rollback limited to newly-installed components). - bundle update: enforce the source install_policy like install, refusing to update from a discovery-only source (FR-025). - catalog source precedence: the CLI now passes ~/.specify as the user config dir so project > user > built-in precedence is actually reachable (previously the user scope was silently ignored). - .gitattributes: scope the specs whitespace exemption to the generated dogfooding feature dir (specs/001-spec-kit-bundler/**) instead of all of specs/**. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review 4534504799 (commit 71f18d2).
Added regressions: record-read symlink-escape rejection, slug lowercase determinism, and discovery-only update refusal. Bundler suite + |
Addresses review 4534571362: - installer: in refresh mode (bundle update) only re-apply already- installed components that this bundle (or a sibling) owns. Components installed independently and tracked by no bundle are now skipped, never refreshed, so update cannot make collateral changes (FR-022). - catalog.load_catalog_payload: validate each entry's own id is present and matches its enclosing bundles key, rejecting catalogs that would otherwise list a spoofed or unresolvable id. - bundle info: stop swallowing manifest download failures. If the manifest can't be resolved (e.g. --offline against an https download_url or a download failure), surface the error and exit non-zero instead of silently degrading to catalog `provides` counts, preserving the "info == what install applies" guarantee. Added regressions: refresh leaves independently-installed components untouched, catalog id key/field mismatch + missing id rejection, and info exits non-zero when the manifest is unresolvable offline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review 4534571362 (commit c3fc064).
Added regressions: refresh leaves independently-installed components untouched, catalog id key/field mismatch + missing-id rejection, and |
Addresses review 4534716790: two more state reads bypassed the symlink/path-escape confinement that records and the write paths already enforce. - catalog_config._read: validate the config path with ensure_within(project_root, ...) before exists()/read, so a symlinked .specify resolving outside project_root is rejected instead of read. - lib.project.active_integration: confine the .specify/integration.json read the same way; an out-of-tree escape is treated as "not determinable" (returns None) rather than followed. Added regressions covering both via a symlinked .specify pointing outside the project root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review 4534716790 (commit beb978b). Both findings were the same class of issue — state reads that bypassed the symlink/path-escape confinement records and the write paths already enforce:
Added regressions for both, using a symlinked |
| provides = data.get("provides") or {} | ||
| if not isinstance(provides, dict): | ||
| raise BundlerError("'provides' must be a mapping when present.") | ||
|
|
||
| manifest = cls( | ||
| schema_version=schema_version, | ||
| bundle=meta, | ||
| requires=requires, | ||
| integration=integration, | ||
| extensions=_parse_refs("extensions", provides.get("extensions")), | ||
| presets=_parse_refs("presets", provides.get("presets")), | ||
| steps=_parse_refs("steps", provides.get("steps")), | ||
| workflows=_parse_refs("workflows", provides.get("workflows")), | ||
| tags=tuple(str(t) for t in (data.get("tags") or [])), | ||
| ) |
| - feat(scripts): add SPECIFY_INIT_DIR to target a member project from the repo root (#2892) | ||
|
|
| # Scope the specs exemption to the generated dogfooding feature dir only, so | ||
| # any hand-authored specs elsewhere keep full whitespace validation. | ||
| specs/001-spec-kit-bundler/** -whitespace |
| host = parsed.netloc.split("@")[-1].split(":")[0] | ||
| # Hostnames are case-insensitive; _slug() lowercases so 'Example.com' | ||
| # and 'example.com' derive the same, deterministic id. | ||
| host_label = Path(host).stem or host | ||
| path_stem = Path(parsed.path).stem if parsed.path else "" | ||
| parts = [p for p in (_slug(host_label), _slug(path_stem)) if p] |
…l host Addresses review 4534768419: - manifest.from_dict: reject a non-list `tags` (e.g. a bare string) instead of splitting it character-by-character, matching the catalog parser and the schema contract (tags = list of strings). - catalog_config._derive_id: derive ids from the full host (TLD included) so example.com and example.net no longer collide on the same id. Updated the affected id assertions. - CHANGELOG: call out the new `specify bundle` command group in the unreleased section (the PR's headline user-facing feature). - .gitattributes: clarify the specs whitespace exemption — the dogfooding feature dir is scrubbed before merge (not retained), so it doesn't weaken checks for kept docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review 4534768419 (commit 1bf4e9a) and updated the PR description.
Added regressions: string |
The project constitution (.specify/memory/constitution.md) is the one dogfooding artifact carried forward past the pre-merge scrub. Give it its own standalone whitespace exemption so it survives removal of the broader .specify/** generated-scaffolding exemption. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| for component in target.contributed_components: | ||
| key = (component.kind, component.id) | ||
| if key in still_needed: | ||
| result.skipped.append(component) | ||
| continue | ||
| if installer.is_installed(project_root, component): | ||
| installer.remove(project_root, component) | ||
| result.uninstalled.append(component) | ||
|
|
||
| save_records(project_root, remove_record(records, bundle_id)) |
| if user_config_dir is not None: | ||
| _merge_config(by_id, Path(user_config_dir) / CONFIG_FILENAME, Scope.USER) | ||
|
|
||
| _merge_config(by_id, Path(project_root) / ".specify" / CONFIG_FILENAME, Scope.PROJECT) | ||
|
|
| out_dir = Path(output_dir).resolve() if output_dir else bundle_dir | ||
| out_dir.mkdir(parents=True, exist_ok=True) | ||
| artifact_name = f"{manifest.bundle.id}-{manifest.bundle.version}.zip" | ||
| artifact_path = out_dir / artifact_name |
Summary
Adds a
specify bundlesubcommand group that composes existing Spec Kit primitives — extensions, presets, workflows, and steps — into versioned, installable units ("bundles") for role-based project setup.The command group follows a thin-CLI-over-services design (all logic lives in
src/specify_cli/bundler/services/, the CLI layer just wires arguments and rendering), is offline-first, and ships with a catalog stack for discovery.Commands
specify bundle search— discover bundles in the catalog (with a verified/community trust indicator)specify bundle info <name>— inspect a bundle's full component set + trust levelspecify bundle install <name>— install/compose a bundle into a project (supports--refresh)specify bundle update <name>— re-resolve and refresh installed componentsspecify bundle remove <name>— cleanly uninstallspecify bundle validate/specify bundle build— author-side validation and packagingHighlights: semantic-version resolution, conflict detection, reference/security-path checking, local-artifact installs, and a verified-vs-community trust badge surfaced across
searchandinfo.Dogfooding — submitted in flight
This feature was built using Spec Kit on itself (
specify init --integration copilot, then the full specify → clarify → plan → tasks → constitution → analyze → implement → converge loop). We're intentionally opening the PR with the generated scaffolding still present so the dogfooding is visible while the PR is in review.To scrub before merge (generated — do not merge)
All generated dogfooding scaffolding is removed before merge:
.specify/**(runtime scripts, templates, workflows, integrations, extensions,feature.json,init-options.json,integration.json,extensions.yml) — except.specify/memory/constitution.md, see below.github/agents/speckit.*,.github/prompts/speckit.*,.github/copilot-instructions.mdspecs/001-spec-kit-bundler/**(spec, plan, tasks, research, data-model, contracts, quickstart).gitattributes/ markdownlint exemptions that cover the above generated pathsRetained
.specify/memory/constitution.md— the one dogfooding artifact we carry forward, as the project constitution governing this and future Spec-Driven work.What lands in the tool
src/specify_cli/bundler/**,src/specify_cli/commands/bundle/__init__.py, the bundler test suites (tests/contract,tests/unit,tests/integration,tests/bundler_helpers.py),examples/bundles/**, and docs (docs/reference/bundles.md,overview.md).Testing
Full suite green; the bundler subset (
tests/contract tests/unit tests/integration) passes locally on every push. Verified self-contained in a fresh worktree + venv.