Skip to content

feat(init): support extension install during initialization#2180

Open
officialasishkumar wants to merge 1 commit intogithub:mainfrom
officialasishkumar:feat/init-extension-transition
Open

feat(init): support extension install during initialization#2180
officialasishkumar wants to merge 1 commit intogithub:mainfrom
officialasishkumar:feat/init-extension-transition

Conversation

@officialasishkumar
Copy link
Copy Markdown

Description

Closes #2165
Closes #2166
Closes #2167
Closes #2169

Adds the init-time extension transition path:

  • Adds repeatable specify init --extension <value> support for bundled extension IDs, local extension directories, and extension archive URLs.
  • Persists requested extensions in .specify/init-options.json and installs them before the default git-extension auto-install, so explicit --extension git does not double-install.
  • Emits visible deprecation warnings for --ai and --no-git while preserving current behavior.
  • Shows a non-blocking notice when the git extension is auto-enabled by default, pointing users to future explicit opt-in via --extension git or specify extension add git.
  • Updates README init option documentation and examples for the integration/extension path.

Testing

  • Tested locally with .venv/bin/specify --help (uv is not installed in this environment)
  • Ran existing tests with .venv/bin/python -m pytest -q
  • Tested with a sample project (covered by CLI integration tests that initialize temp projects with local extensions, repeated extensions, explicit --extension git, and default git auto-install)

Additional checks:

  • .venv/bin/ruff check src/
  • npx --yes markdownlint-cli2 README.md
  • git diff --check

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

I used AI assistance for codebase analysis, implementation support, test updates, and PR drafting. I reviewed the resulting changes and validated them locally with the commands listed above.

Add repeatable --extension support to specify init for bundled extension IDs, local extension directories, and archive URLs. Persist requested extensions in init options and install them before the default git-extension auto-install so explicit git opt-in does not double-install.

Warn when legacy --ai and --no-git flags are used, and show a visible notice when the git extension is still auto-enabled by default ahead of the v1.0.0 opt-in change.

Update README init option docs and add CLI regression coverage for init-time extension installation and deprecation messaging.
Copilot AI review requested due to automatic review settings April 11, 2026 20:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for installing extensions during specify init, including a transition path away from legacy init flags and toward explicit extension opt-in.

Changes:

  • Introduces repeatable specify init --extension <value> for bundled IDs, local extension directories, and archive URLs, and persists selected extensions in .specify/init-options.json.
  • Ensures init-time extension installs occur before the default git-extension auto-install, and adds user-visible notices/deprecation warnings (--ai, --no-git).
  • Expands CLI integration tests and updates README init documentation/examples.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/specify_cli/__init__.py Adds init-time extension installation flow (name/path/URL), persistence of init options, and deprecation/notice messaging.
tests/integrations/test_cli.py Adds integration tests covering local extension install, repeatable --extension, explicit --extension git with --no-git, and new warnings/notices.
README.md Updates init option docs to include --extension and deprecations; adjusts headings and examples.
Comments suppressed due to low confidence (1)

README.md:424

  • The examples section mixes the new recommended --integration flag with several --ai examples (e.g. qodercli/windsurf/kiro-cli/etc.), even though --ai is now documented as deprecated above. To avoid encouraging deprecated usage, update these remaining examples to use --integration <key> (or explicitly label --ai examples as legacy/deprecated).
# Initialize with Cursor support
specify init my-project --integration cursor-agent

# Initialize with Qoder support
specify init my-project --ai qodercli


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +705 to +707
if member.issym() or member.islnk():
raise ValidationError(
f"Unsafe link in TAR archive: {member.name}"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TAR extraction validation currently only rejects symlinks/hardlinks, but still allows other non-regular member types (e.g., character/block devices, FIFOs). That can be a security risk if a crafted archive is installed (especially on systems running with elevated privileges). Consider explicitly allowing only directories and regular files (and/or using Python’s tarfile extraction filter where available), rejecting all other member types before calling extractall().

Suggested change
if member.issym() or member.islnk():
raise ValidationError(
f"Unsafe link in TAR archive: {member.name}"
if not (member.isdir() or member.isreg()):
raise ValidationError(
f"Unsupported member type in TAR archive: {member.name}"

Copilot uses AI. Check for mistakes.
Comment on lines +776 to +778
try:
with urllib.request.urlopen(source_url, timeout=60) as response:
archive_path.write_bytes(response.read())
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downloading the extension archive uses response.read() and writes the entire payload at once. This can lead to unbounded memory usage (and potentially hangs) if the URL serves a very large file. Prefer streaming the response to disk in chunks and enforcing a reasonable maximum size (and optionally honoring Content-Length when present).

Suggested change
try:
with urllib.request.urlopen(source_url, timeout=60) as response:
archive_path.write_bytes(response.read())
max_archive_size = 100 * 1024 * 1024
chunk_size = 1024 * 1024
try:
with urllib.request.urlopen(source_url, timeout=60) as response:
content_length = response.headers.get("Content-Length")
if content_length is not None:
try:
expected_size = int(content_length)
except ValueError:
expected_size = None
else:
if expected_size > max_archive_size:
raise ExtensionError(
f"Extension archive from {source_url} exceeds the "
f"maximum allowed size of {max_archive_size} bytes"
)
total_downloaded = 0
with archive_path.open("wb") as archive_file:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
total_downloaded += len(chunk)
if total_downloaded > max_archive_size:
raise ExtensionError(
f"Extension archive from {source_url} exceeds the "
f"maximum allowed size of {max_archive_size} bytes"
)
archive_file.write(chunk)

Copilot uses AI. Check for mistakes.
@@ -333,7 +334,7 @@ Community projects that extend, visualize, or build on Spec Kit:

After running `specify init`, your AI coding agent will have access to these slash commands for structured development. If you pass `--ai <agent> --ai-skills`, Spec Kit installs agent skills instead of slash-command prompt files; `--ai-skills` requires `--ai`.
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph still documents the legacy --ai ... --ai-skills behavior as the primary path, but the PR deprecates --ai and --ai-skills in favor of --integration (with skills selection handled by the integration). The docs should be updated here to describe --integration as the recommended option and clarify that --ai* flags are deprecated.

Suggested change
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. If you pass `--ai <agent> --ai-skills`, Spec Kit installs agent skills instead of slash-command prompt files; `--ai-skills` requires `--ai`.
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. The recommended way to configure agent behavior is with `--integration <name>`, which determines whether Spec Kit installs slash-command prompt files or integration-specific skills. The legacy `--ai` and `--ai-skills` flags are deprecated.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants