Merge branch 'main' into create-new-feature
@@ -84,6 +84,8 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
||||
|
||||
### 2. Establish project principles
|
||||
|
||||
Launch your AI assistant in the project directory. The `/speckit.*` commands are available in the assistant.
|
||||
|
||||
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
||||
|
||||
```bash
|
||||
|
||||
|
Before Width: | Height: | Size: 529 KiB After Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 1.7 MiB |
@@ -203,4 +203,4 @@ else
|
||||
echo "SPEC_FILE: $SPEC_FILE"
|
||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -250,7 +250,7 @@ get_commands_for_language() {
|
||||
echo "cargo test && cargo clippy"
|
||||
;;
|
||||
*"JavaScript"*|*"TypeScript"*)
|
||||
echo "npm test \&\& npm run lint"
|
||||
echo "npm test \\&\\& npm run lint"
|
||||
;;
|
||||
*)
|
||||
echo "# Add commands for $lang"
|
||||
|
||||
@@ -485,6 +485,73 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> Tuple[bool, Option
|
||||
finally:
|
||||
os.chdir(original_cwd)
|
||||
|
||||
def handle_vscode_settings(sub_item, dest_file, rel_path, verbose=False, tracker=None) -> None:
|
||||
"""Handle merging or copying of .vscode/settings.json files."""
|
||||
def log(message, color="green"):
|
||||
if verbose and not tracker:
|
||||
console.print(f"[{color}]{message}[/] {rel_path}")
|
||||
|
||||
try:
|
||||
with open(sub_item, 'r', encoding='utf-8') as f:
|
||||
new_settings = json.load(f)
|
||||
|
||||
if dest_file.exists():
|
||||
merged = merge_json_files(dest_file, new_settings, verbose=verbose and not tracker)
|
||||
with open(dest_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(merged, f, indent=4)
|
||||
f.write('\n')
|
||||
log("Merged:", "green")
|
||||
else:
|
||||
shutil.copy2(sub_item, dest_file)
|
||||
log("Copied (no existing settings.json):", "blue")
|
||||
|
||||
except Exception as e:
|
||||
log(f"Warning: Could not merge, copying instead: {e}", "yellow")
|
||||
shutil.copy2(sub_item, dest_file)
|
||||
|
||||
def merge_json_files(existing_path: Path, new_content: dict, verbose: bool = False) -> dict:
|
||||
"""Merge new JSON content into existing JSON file.
|
||||
|
||||
Performs a deep merge where:
|
||||
- New keys are added
|
||||
- Existing keys are preserved unless overwritten by new content
|
||||
- Nested dictionaries are merged recursively
|
||||
- Lists and other values are replaced (not merged)
|
||||
|
||||
Args:
|
||||
existing_path: Path to existing JSON file
|
||||
new_content: New JSON content to merge in
|
||||
verbose: Whether to print merge details
|
||||
|
||||
Returns:
|
||||
Merged JSON content as dict
|
||||
"""
|
||||
try:
|
||||
with open(existing_path, 'r', encoding='utf-8') as f:
|
||||
existing_content = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
# If file doesn't exist or is invalid, just use new content
|
||||
return new_content
|
||||
|
||||
def deep_merge(base: dict, update: dict) -> dict:
|
||||
"""Recursively merge update dict into base dict."""
|
||||
result = base.copy()
|
||||
for key, value in update.items():
|
||||
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||
# Recursively merge nested dictionaries
|
||||
result[key] = deep_merge(result[key], value)
|
||||
else:
|
||||
# Add new key or replace existing value
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
merged = deep_merge(existing_content, new_content)
|
||||
|
||||
if verbose:
|
||||
console.print(f"[cyan]Merged JSON file:[/cyan] {existing_path.name}")
|
||||
|
||||
return merged
|
||||
|
||||
def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Tuple[Path, dict]:
|
||||
repo_owner = "github"
|
||||
repo_name = "spec-kit"
|
||||
@@ -676,7 +743,11 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
|
||||
rel_path = sub_item.relative_to(item)
|
||||
dest_file = dest_path / rel_path
|
||||
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(sub_item, dest_file)
|
||||
# Special handling for .vscode/settings.json - merge instead of overwrite
|
||||
if dest_file.name == "settings.json" and dest_file.parent.name == ".vscode":
|
||||
handle_vscode_settings(sub_item, dest_file, rel_path, verbose, tracker)
|
||||
else:
|
||||
shutil.copy2(sub_item, dest_file)
|
||||
else:
|
||||
shutil.copytree(item, dest_path)
|
||||
else:
|
||||
@@ -1093,18 +1164,25 @@ def check():
|
||||
|
||||
tracker.add("git", "Git version control")
|
||||
git_ok = check_tool("git", tracker=tracker)
|
||||
|
||||
|
||||
agent_results = {}
|
||||
for agent_key, agent_config in AGENT_CONFIG.items():
|
||||
agent_name = agent_config["name"]
|
||||
|
||||
requires_cli = agent_config["requires_cli"]
|
||||
|
||||
tracker.add(agent_key, agent_name)
|
||||
agent_results[agent_key] = check_tool(agent_key, tracker=tracker)
|
||||
|
||||
|
||||
if requires_cli:
|
||||
agent_results[agent_key] = check_tool(agent_key, tracker=tracker)
|
||||
else:
|
||||
# IDE-based agent - skip CLI check and mark as optional
|
||||
tracker.skip(agent_key, "IDE-based, no CLI check")
|
||||
agent_results[agent_key] = False # Don't count IDE agents as "found"
|
||||
|
||||
# Check VS Code variants (not in agent config)
|
||||
tracker.add("code", "Visual Studio Code")
|
||||
code_ok = check_tool("code", tracker=tracker)
|
||||
|
||||
|
||||
tracker.add("code-insiders", "Visual Studio Code Insiders")
|
||||
code_insiders_ok = check_tool("code-insiders", tracker=tracker)
|
||||
|
||||
|
||||