Skip to content

feat(extensions): scaffold config templates on extension add/enable (import of github/spec-kit#2000) - #30

Open
one-kash wants to merge 2 commits into
mainfrom
import/pr-2000
Open

feat(extensions): scaffold config templates on extension add/enable (import of github/spec-kit#2000)#30
one-kash wants to merge 2 commits into
mainfrom
import/pr-2000

Conversation

@one-kash

Copy link
Copy Markdown

Imported from upstream PR github#2000
Original author: @mvanhorn
Original head branch: osc/fix-extension-scaffolding-lifecycle


Description

Rework of github#1929 per @mnriem's feedback. Instead of a standalone specify extension init command, config scaffolding now runs automatically as part of the extension lifecycle:

  • extension add: after installing an extension, config templates from provides.config are deployed to .specify/
  • extension enable: when re-enabling a disabled extension, missing config templates are deployed

Existing user-customized config files are never overwritten. Extensions without a provides.config section are unaffected.

Changes:

  • ExtensionManifest.config property reads provides.config from the manifest
  • ExtensionManager.scaffold_config() copies config templates to the project, skipping existing files
  • extension_add replaces the "Configuration may be required" warning with automatic deployment
  • extension_enable deploys config on re-enable

Video Demo

Config scaffolding demo

The demo shows: config template exists in extension dir, no project config yet, scaffold_config deploys it, re-running preserves existing config (idempotent).

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync --extra test && uv run python -m pytest - all 890 tests pass
  • Added 4 new tests covering scaffold_config: deploy, preserve existing, no config section, missing template
  • Tested with a sample project (see demo above)

AI Disclosure

  • I did use AI assistance (describe below)

This contribution was developed with AI assistance (Claude Code + Codex CLI).

Closes github#1929

mvanhorn added 2 commits July 29, 2026 12:32
Deploy an extension's provides.config templates into .specify/ when the
extension is added or enabled. Existing files are never overwritten, so
user customizations are preserved.

Addresses the review on github#2000:
- ExtensionManifest.config returns [] unless provides.config is a list of
  dicts, so a malformed manifest cannot crash callers.
- scaffold_config returns a consistent (deployed, skipped_existing, failed)
  tuple on every path, including a missing manifest.
- Template paths must resolve inside the extension dir and targets inside
  .specify/; symlinks and non-regular files are rejected.
- Callers distinguish "already exists (preserved)" from "not scaffolded",
  and extension_enable no longer crashes on a corrupt manifest.
- Tests cover traversal, absolute paths, symlinks, directory templates,
  malformed provides.config, and the missing-manifest tuple shape.

Ported onto the extensions package introduced by github#3014: the manager and
manifest changes land in extensions/__init__.py and the CLI wiring in
extensions/_commands.py.
Addresses @Copilot's review.

Config now lands in .specify/extensions/<id>/ rather than the .specify/
root. ConfigManager._get_project_config() reads
.specify/extensions/<id>/<id>-config.yml, and the bundled scripts and
READMEs use the same path, so a scaffolded git-config.yml was being
written somewhere the git extension never looks.

Containment is checked component by component before .specify is used as
the root. Resolving it first and trusting the result let a symlinked
component point outside the project, after which every target satisfied
relative_to and copy2 wrote externally. This matches the project
safe-write path in shared_infra.

mkdir moved inside the OSError handler. A nested target like
foo/config.yml raised out of scaffolding when its parent could not be
created, and on extension add that happened after the extension was
already installed.

The 'Configuration may be required' warning is now conditional. It ran
unconditionally after the scaffolding block, so it contradicted the
success output directly above it and fired for extensions with no
provides.config at all.

Tests cover the corrected location, a symlinked config root, and an
uncreatable nested target.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants