feat(init)!: make git extension opt-in and remove --no-git at v0.10.0#2873
Draft
Copilot wants to merge 5 commits into
Draft
feat(init)!: make git extension opt-in and remove --no-git at v0.10.0#2873Copilot wants to merge 5 commits into
Copilot wants to merge 5 commits into
Conversation
5 tasks
Copilot
AI
changed the title
[WIP] Finalize git extension as opt-in and remove --no-git flag
feat(init)!: make git extension opt-in and remove --no-git at v0.10.0
Jun 5, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR finalizes the v0.10.0 breaking change around Git behavior during specify init: the bundled git extension is no longer auto-installed, and the deprecated --no-git flag is removed, while git repository initialization (git init) still occurs when git is available and no repo exists.
Changes:
- Removes
--no-gitfrom theinitcommand and drops bundled git extension auto-installation from the init flow. - Updates integration tests to stop passing
--no-gitand adds new opt-in assertions around the git extension. - Updates docs/changelog to reflect the new “git extension is opt-in” behavior and the flag removal.
Show a summary per file
| File | Description |
|---|---|
| src/specify_cli/commands/init.py | Removes the --no-git flag and bundled git extension auto-install; keeps git init when available. |
| tests/integrations/test_cli.py | Removes --no-git usage and replaces auto-install tests with opt-in expectations. |
| docs/upgrade.md | Updates upgrade guidance for projects previously relying on --no-git. |
| docs/reference/core.md | Removes --no-git from specify init reference and updates notes/examples. |
| docs/local-development.md | Updates troubleshooting guidance related to the git step. |
| CHANGELOG.md | Adds a v0.10.0 entry documenting the breaking changes. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 6/6 changed files
- Comments generated: 5
|
|
||
| ### Breaking Changes | ||
|
|
||
| - **Git extension is now opt-in** — `specify init` no longer auto-installs the git extension. Use `specify extension add git` to install it, or pass `--extension git` during init. |
| ### Scenario 4: "I'm working on a project without Git" | ||
|
|
||
| If you initialized your project with `--no-git`, you can still upgrade: | ||
| If your project directory does not use Git, Spec Kit will automatically detect this and skip repository initialization. You can still upgrade normally: |
Comment on lines
+826
to
+830
| ]) | ||
| finally: | ||
| os.chdir(old_cwd) | ||
|
|
||
| normalized_output = _normalize_cli_output(result.output) | ||
| assert result.exit_code == 0, result.output | ||
| assert "--no-git" in normalized_output | ||
| assert "deprecated" in normalized_output | ||
| assert "0.10.0" in normalized_output | ||
| assert "specify extension" in normalized_output | ||
| assert "will be removed" in normalized_output | ||
| assert "git extension will no longer be enabled by default" in normalized_output | ||
| assert result.exit_code != 0, "--no-git should be rejected as an unknown option" |
Comment on lines
+852
to
+856
| # Git extension skill commands should NOT be present | ||
| claude_skills = project / ".claude" / "skills" | ||
| assert claude_skills.exists(), "Claude skills directory was not created" | ||
| git_skills = [f for f in claude_skills.iterdir() if f.name.startswith("speckit-git-")] | ||
| assert len(git_skills) > 0, "no git extension commands registered" | ||
| if claude_skills.exists(): | ||
| git_skills = [f for f in claude_skills.iterdir() if f.name.startswith("speckit-git-")] | ||
| assert len(git_skills) == 0, "git extension commands should not be registered by default" |
Comment on lines
98
to
105
| def init( | ||
| project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"), | ||
| ai_assistant: str = typer.Option(None, "--ai", help=AI_ASSISTANT_HELP), | ||
| ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"), | ||
| script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), | ||
| ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for coding agent tools like Claude Code"), | ||
| no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), | ||
| here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"), | ||
| force: bool = typer.Option(False, "--force", help="Force merge/overwrite when using --here (skip confirmation)"), |
- Remove --no-git parameter from specify init command - Remove git extension auto-installation from init flow - Git repository initialization (git init) still runs when git is available - Remove --no-git from all test invocations across the test suite - Update docs to reflect opt-in git extension behavior - Replace TestGitExtensionAutoInstall with TestGitExtensionOptIn tests BREAKING CHANGE: specify init no longer auto-installs the git extension. Use `specify extension add git` to install it explicitly. The --no-git flag has been removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
558bfa9 to
90d1d69
Compare
Git functionality is now entirely managed by the git extension. Core scripts only handle directory-based feature creation and numbering. - Remove has_git(), check_feature_branch(), git branch creation from core - Simplify number detection to use only spec directory scanning - Remove HAS_GIT output from get_feature_paths() - Remove git remote fetching and branch querying - Keep BRANCH_NAME output key for backward compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| ) | ||
| from .._console import StepTracker, console, select_with_arrows, show_banner | ||
| from .._utils import check_tool, init_git_repo, is_git_repo | ||
| from .._utils import check_tool |
Comment on lines
292
to
298
| for key, label in [ | ||
| ("chmod", "Ensure scripts executable"), | ||
| ("constitution", "Constitution setup"), | ||
| ("git", "Install git extension"), | ||
| ("workflow", "Install bundled workflow"), | ||
| ("agent-context", "Install agent-context extension"), | ||
| ("final", "Finalize"), | ||
| ]: |
Comment on lines
351
to
352
| ensure_constitution_from_template(project_path, tracker=tracker) | ||
|
|
Comment on lines
50
to
53
| local repo_root=$(get_repo_root) | ||
| if has_git; then | ||
| git -C "$repo_root" rev-parse --abbrev-ref HEAD | ||
| return | ||
| fi | ||
|
|
||
| # For non-git repos, try to find the latest feature directory | ||
| # Fall back to the latest feature directory | ||
| local specs_dir="$repo_root/specs" |
Comment on lines
47
to
50
| $repoRoot = Get-RepoRoot | ||
| if (Test-HasGit) { | ||
| try { | ||
| $result = git -C $repoRoot rev-parse --abbrev-ref HEAD 2>$null | ||
| if ($LASTEXITCODE -eq 0) { | ||
| return $result | ||
| } | ||
| } catch { | ||
| # Git command failed | ||
| } | ||
| } | ||
|
|
||
| # For non-git repos, try to find the latest feature directory | ||
| # Fall back to the latest feature directory | ||
| $specsDir = Join-Path $repoRoot "specs" |
Comment on lines
743
to
+747
| assert result.exit_code == 0, f"init failed: {result.output}" | ||
|
|
||
| # Check that the tracker didn't report a git error | ||
| assert "install failed" not in result.output, f"git extension install failed: {result.output}" | ||
|
|
||
| # Git extension files should be installed | ||
| ext_dir = project / ".specify" / "extensions" / "git" | ||
| assert ext_dir.exists(), "git extension directory not installed" | ||
| assert (ext_dir / "extension.yml").exists() | ||
| assert (ext_dir / "scripts" / "bash" / "create-new-feature.sh").exists() | ||
| assert (ext_dir / "scripts" / "bash" / "initialize-repo.sh").exists() | ||
|
|
||
| # Hooks should be registered | ||
| extensions_yml = project / ".specify" / "extensions.yml" | ||
| assert extensions_yml.exists(), "extensions.yml not created" | ||
| hooks_data = yaml.safe_load(extensions_yml.read_text(encoding="utf-8")) | ||
| assert "hooks" in hooks_data | ||
| assert "before_specify" in hooks_data["hooks"] | ||
| assert "before_constitution" in hooks_data["hooks"] | ||
|
|
||
| def test_no_git_skips_extension(self, tmp_path): | ||
| """With --no-git, the git extension is NOT installed.""" | ||
| from typer.testing import CliRunner | ||
| from specify_cli import app | ||
|
|
||
| project = tmp_path / "no-git" | ||
| project.mkdir() | ||
| old_cwd = os.getcwd() | ||
| try: | ||
| os.chdir(project) | ||
| runner = CliRunner() | ||
| result = runner.invoke(app, [ | ||
| "init", "--here", "--integration", "claude", "--script", "sh", | ||
| "--no-git", "--ignore-agent-tools", | ||
| ], catch_exceptions=False) | ||
| finally: | ||
| os.chdir(old_cwd) | ||
|
|
||
| assert result.exit_code == 0, f"init failed: {result.output}" | ||
|
|
||
| # Git extension should NOT be installed | ||
| # Git extension directory should NOT be present after init | ||
| ext_dir = project / ".specify" / "extensions" / "git" | ||
| assert not ext_dir.exists(), "git extension should not be installed with --no-git" | ||
| assert not ext_dir.exists(), "git extension should not be auto-installed" |
- Remove is_git_repo() and init_git_repo() dead code from _utils.py - Remove --branch-numbering from init command - Remove git from 'specify check' (now extension-only) - Update docs: git is optional prerequisite, check command description - Fix tests to reflect no-git-in-core reality (fallback to main) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… core Core scripts now resolve feature context exclusively from: 1. SPECIFY_FEATURE env var (set by git extension) 2. .specify/feature.json (persisted by specify command) Removed find_feature_dir_by_prefix() and directory scanning heuristics — these are the git extension's responsibility. Scripts error clearly when no feature context is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines
+131
to
132
| echo "ERROR: Feature directory not found. Set SPECIFY_FEATURE or ensure .specify/feature.json contains feature_directory." >&2 | ||
| return 1 |
Comment on lines
+135
to
136
| echo "ERROR: Feature directory not found. Set SPECIFY_FEATURE or run the specify command to create .specify/feature.json." >&2 | ||
| return 1 |
Comment on lines
+151
to
+152
| [Console]::Error.WriteLine("ERROR: Feature directory not found. Set SPECIFY_FEATURE or ensure .specify/feature.json contains feature_directory.") | ||
| exit 1 |
Comment on lines
+155
to
+156
| [Console]::Error.WriteLine("ERROR: Feature directory not found. Set SPECIFY_FEATURE or run the specify command to create .specify/feature.json.") | ||
| exit 1 |
| If you use `--no-git`, you'll need to manage feature directories manually: | ||
|
|
||
| **Set the `SPECIFY_FEATURE` environment variable** before using planning commands: | ||
| Projects that do not use Git can still work with Spec Kit by setting `SPECIFY_FEATURE` manually before planning commands: |
Comment on lines
285
to
288
| export SPECIFY_FEATURE="001-my-feature" | ||
|
|
||
| # PowerShell | ||
| $env:SPECIFY_FEATURE = "001-my-feature" |
| | `ModuleNotFoundError: typer` | Run `uv pip install -e .` | | ||
| | Scripts not executable (Linux) | Re-run init or `chmod +x scripts/*.sh` | | ||
| | Git step skipped | You passed `--no-git` or Git not installed | | ||
| | Git step skipped | Git not installed on your system | |
Comment on lines
91
to
+97
| This command will: | ||
| 1. Check that required tools are installed (git is optional) | ||
| 1. Check that required tools are installed | ||
| 2. Let you choose your coding agent integration, or default to Copilot | ||
| in non-interactive sessions | ||
| 3. Install bundled Spec Kit templates, scripts, workflow, and shared | ||
| project infrastructure | ||
| 4. Initialize a fresh git repository (if not --no-git and no existing repo) | ||
| 5. Set up coding agent integration commands and optional presets | ||
| 4. Set up coding agent integration commands and optional presets |
…-options - specify command template now reads feature_numbering (preferred) with fallback to branch_numbering (deprecated) from init-options.json - Git extension reads git-config.yml > feature_numbering > branch_numbering - init now writes feature_numbering: sequential to init-options.json - Deprecation warning emitted when branch_numbering is used as fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finalizes the git extension default change at v0.10.0:
specify initno longer auto-installs the bundled git extension, and the deprecated--no-gitflag is removed. Users who want the git extension must opt in viaspecify extension add git.Core changes
src/specify_cli/commands/init.py--no-gitparameter and its deprecation warning blockExtensionManager.install_from_directoryforgit) from the init flowgit_default_noticevariable and the "Notice: Git Default Changing" panelgit init) still runs unconditionally when git is available and no repo exists — only the extension install is dropped"Install git extension"→"Git repository setup"Tests
TestGitExtensionAutoInstall→ replaced withTestGitExtensionOptIn:test_git_extension_not_auto_installed— extension dir absent after plainspecify inittest_no_git_flag_is_rejected—--no-gitnow exits non-zero (unknown option)test_git_extension_commands_not_registered_by_default— nospeckit-git-*skills after init--no-gitfrom all other test invocations throughouttests/integrations/test_cli.pyDocs & changelog
docs/reference/core.md: removed--no-gitrow from options table and the pre-v0.10.0 NOTE; updated examplesdocs/upgrade.md: updated Scenario 4 (no-git projects); removed the## Using --no-git Flagsectiondocs/local-development.md: updated common-issues tableCHANGELOG.md: added## [0.10.0] - TBDsection documenting both breaking changes