@@ -485,6 +485,73 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> Tuple[bool, Option
485485 finally :
486486 os .chdir (original_cwd )
487487
488+ def handle_vscode_settings (sub_item , dest_file , rel_path , verbose = False , tracker = None ) -> None :
489+ """Handle merging or copying of .vscode/settings.json files."""
490+ def log (message , color = "green" ):
491+ if verbose and not tracker :
492+ console .print (f"[{ color } ]{ message } [/] { rel_path } " )
493+
494+ try :
495+ with open (sub_item , 'r' , encoding = 'utf-8' ) as f :
496+ new_settings = json .load (f )
497+
498+ if dest_file .exists ():
499+ merged = merge_json_files (dest_file , new_settings , verbose = verbose and not tracker )
500+ with open (dest_file , 'w' , encoding = 'utf-8' ) as f :
501+ json .dump (merged , f , indent = 4 )
502+ f .write ('\n ' )
503+ log ("Merged:" , "green" )
504+ else :
505+ shutil .copy2 (sub_item , dest_file )
506+ log ("Copied (no existing settings.json):" , "blue" )
507+
508+ except Exception as e :
509+ log (f"Warning: Could not merge, copying instead: { e } " , "yellow" )
510+ shutil .copy2 (sub_item , dest_file )
511+
512+ def merge_json_files (existing_path : Path , new_content : dict , verbose : bool = False ) -> dict :
513+ """Merge new JSON content into existing JSON file.
514+
515+ Performs a deep merge where:
516+ - New keys are added
517+ - Existing keys are preserved unless overwritten by new content
518+ - Nested dictionaries are merged recursively
519+ - Lists and other values are replaced (not merged)
520+
521+ Args:
522+ existing_path: Path to existing JSON file
523+ new_content: New JSON content to merge in
524+ verbose: Whether to print merge details
525+
526+ Returns:
527+ Merged JSON content as dict
528+ """
529+ try :
530+ with open (existing_path , 'r' , encoding = 'utf-8' ) as f :
531+ existing_content = json .load (f )
532+ except (FileNotFoundError , json .JSONDecodeError ):
533+ # If file doesn't exist or is invalid, just use new content
534+ return new_content
535+
536+ def deep_merge (base : dict , update : dict ) -> dict :
537+ """Recursively merge update dict into base dict."""
538+ result = base .copy ()
539+ for key , value in update .items ():
540+ if key in result and isinstance (result [key ], dict ) and isinstance (value , dict ):
541+ # Recursively merge nested dictionaries
542+ result [key ] = deep_merge (result [key ], value )
543+ else :
544+ # Add new key or replace existing value
545+ result [key ] = value
546+ return result
547+
548+ merged = deep_merge (existing_content , new_content )
549+
550+ if verbose :
551+ console .print (f"[cyan]Merged JSON file:[/cyan] { existing_path .name } " )
552+
553+ return merged
554+
488555def 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 ]:
489556 repo_owner = "github"
490557 repo_name = "spec-kit"
@@ -676,7 +743,11 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
676743 rel_path = sub_item .relative_to (item )
677744 dest_file = dest_path / rel_path
678745 dest_file .parent .mkdir (parents = True , exist_ok = True )
679- shutil .copy2 (sub_item , dest_file )
746+ # Special handling for .vscode/settings.json - merge instead of overwrite
747+ if dest_file .name == "settings.json" and dest_file .parent .name == ".vscode" :
748+ handle_vscode_settings (sub_item , dest_file , rel_path , verbose , tracker )
749+ else :
750+ shutil .copy2 (sub_item , dest_file )
680751 else :
681752 shutil .copytree (item , dest_path )
682753 else :
@@ -1093,18 +1164,25 @@ def check():
10931164
10941165 tracker .add ("git" , "Git version control" )
10951166 git_ok = check_tool ("git" , tracker = tracker )
1096-
1167+
10971168 agent_results = {}
10981169 for agent_key , agent_config in AGENT_CONFIG .items ():
10991170 agent_name = agent_config ["name" ]
1100-
1171+ requires_cli = agent_config ["requires_cli" ]
1172+
11011173 tracker .add (agent_key , agent_name )
1102- agent_results [agent_key ] = check_tool (agent_key , tracker = tracker )
1103-
1174+
1175+ if requires_cli :
1176+ agent_results [agent_key ] = check_tool (agent_key , tracker = tracker )
1177+ else :
1178+ # IDE-based agent - skip CLI check and mark as optional
1179+ tracker .skip (agent_key , "IDE-based, no CLI check" )
1180+ agent_results [agent_key ] = False # Don't count IDE agents as "found"
1181+
11041182 # Check VS Code variants (not in agent config)
11051183 tracker .add ("code" , "Visual Studio Code" )
11061184 code_ok = check_tool ("code" , tracker = tracker )
1107-
1185+
11081186 tracker .add ("code-insiders" , "Visual Studio Code Insiders" )
11091187 code_insiders_ok = check_tool ("code-insiders" , tracker = tracker )
11101188
0 commit comments