Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ All notable changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.23] - 2026-01-16

### Added

- **AI Agent Switching**: Added `--keep-memory` flag to `specify init` command to switch between AI agents without overwriting `memory/constitution.md`
- Use case: When switching AI assistants on an existing project, your custom constitution is preserved
- Example: `specify init . --ai gemini --keep-memory`

## [0.0.22] - 2025-11-07

- Support for VS Code/Copilot agents, and moving away from prompts to proper agents with hand-offs.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
version = "0.0.22"
version = "0.0.23"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [
Expand Down
11 changes: 9 additions & 2 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
}
return zip_path, metadata

def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Path:
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False, github_token: str = None, preserve_constitution: bool = False) -> Path:
"""Download the latest release and extract it to create a new project.
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
"""
Expand Down Expand Up @@ -828,6 +828,11 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
if sub_item.is_file():
rel_path = sub_item.relative_to(item)
dest_file = dest_path / rel_path
# Skip constitution.md if preserve_constitution is True and file exists
if preserve_constitution and dest_file.exists() and rel_path == Path("constitution.md") and item.name == "memory":
Comment on lines +831 to +832
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The constitution preservation logic is incorrect. When iterating through the extracted template, item represents the .specify directory (not memory), and rel_path would be memory/constitution.md (not just constitution.md). The condition item.name == "memory" will never be true, so the --keep-memory flag won't work. The correct check should be item.name == ".specify" and rel_path == Path("memory/constitution.md") or rel_path == Path("memory") / "constitution.md".

Suggested change
# Skip constitution.md if preserve_constitution is True and file exists
if preserve_constitution and dest_file.exists() and rel_path == Path("constitution.md") and item.name == "memory":
# Skip memory/constitution.md if preserve_constitution is True and file exists
if preserve_constitution and dest_file.exists() and rel_path == Path("memory") / "constitution.md":

Copilot uses AI. Check for mistakes.
if verbose and not tracker:
console.print(f"[cyan]Preserving existing:[/cyan] memory/constitution.md")
continue
dest_file.parent.mkdir(parents=True, exist_ok=True)
# Special handling for .vscode/settings.json - merge instead of overwrite
if dest_file.name == "settings.json" and dest_file.parent.name == ".vscode":
Expand Down Expand Up @@ -951,6 +956,7 @@ def init(
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)"),
keep_memory: bool = typer.Option(False, "--keep-memory", help="Switch AI agent without overwriting memory/constitution.md (preserves existing constitution)"),
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
Expand Down Expand Up @@ -978,6 +984,7 @@ def init(
specify init --here --ai codebuddy
specify init --here
specify init --here --force # Skip confirmation when current directory not empty
specify init . --ai gemini --keep-memory # Switch to Gemini without overwriting constitution
"""

show_banner()
Expand Down Expand Up @@ -1124,7 +1131,7 @@ def init(
local_ssl_context = ssl_context if verify else False
local_client = httpx.Client(verify=local_ssl_context)

download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token, preserve_constitution=keep_memory)

ensure_executable_scripts(project_path, tracker=tracker)

Expand Down
Loading