diff --git a/README.md b/README.md index 80f451ba..cb20ad6c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ uvx --from git+https://github.com/github/spec-kit.git specify init 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)