99from . import copytree_manifest
1010from .docker import DockerManager
1111from .launcher import ClaudeLauncher
12- from .plugin_manager import PluginManager
1312from .proxy import ProxyConfig , ProxyManager
1413from .utils import is_uvx_deployment
1514
@@ -75,7 +74,7 @@ def launch_command(args: argparse.Namespace, claude_args: list[str] | None = Non
7574
7675 return docker_manager .run_command (docker_args )
7776
78- # In UVX mode, Claude uses --add-dir for both project directory and plugin directory
77+ # In UVX mode, Claude now runs from the current directory so no --add-dir needed
7978
8079 proxy_manager = None
8180 system_prompt_path = None
@@ -410,34 +409,6 @@ def create_parser() -> argparse.ArgumentParser:
410409 local_install_parser = subparsers .add_parser ("_local_install" , help = argparse .SUPPRESS )
411410 local_install_parser .add_argument ("repo_root" , help = "Repository root directory" )
412411
413- # Plugin management commands
414- plugin_parser = subparsers .add_parser ("plugin" , help = "Plugin management commands" )
415- plugin_subparsers = plugin_parser .add_subparsers (
416- dest = "plugin_command" , help = "Plugin subcommands"
417- )
418-
419- # Link plugin command
420- link_parser = plugin_subparsers .add_parser (
421- "link" , help = "Link installed plugin to Claude Code settings"
422- )
423- link_parser .add_argument (
424- "plugin_name" ,
425- nargs = "?" ,
426- default = "amplihack" ,
427- help = "Plugin name to link (default: amplihack)" ,
428- )
429-
430- # Verify plugin command
431- verify_parser = plugin_subparsers .add_parser (
432- "verify" , help = "Verify plugin installation and discoverability"
433- )
434- verify_parser .add_argument (
435- "plugin_name" ,
436- nargs = "?" ,
437- default = "amplihack" ,
438- help = "Plugin name to verify (default: amplihack)" ,
439- )
440-
441412 # Memory tree visualization command
442413 memory_parser = subparsers .add_parser ("memory" , help = "Memory system commands" )
443414 memory_subparsers = memory_parser .add_subparsers (
@@ -528,71 +499,66 @@ def main(argv: list[str] | None = None) -> int:
528499 print (" Commit your changes and try again\n " )
529500 sys .exit (0 )
530501
502+ temp_claude_dir = str (copy_strategy .target_dir )
503+
531504 # Store original_cwd for auto mode (always set, regardless of conflicts)
532505 os .environ ["AMPLIHACK_ORIGINAL_CWD" ] = original_cwd
533506
534507 if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
535- print (f"UVX mode: Using plugin architecture " )
508+ print (f"UVX mode: Staging Claude environment in current directory: { original_cwd } " )
536509 print (f"Working directory remains: { original_cwd } " )
537510
538- # Setup plugin architecture
539- # 1. Check if plugin already installed at ~/.amplihack/.claude/
540- plugin_root = Path .home () / ".amplihack" / ".claude"
541- plugin_manager = PluginManager (plugin_root = plugin_root .parent / "plugins" )
542-
543- # Check if local .claude/ exists (backward compatibility)
544- local_claude_dir = Path (original_cwd ) / ".claude"
545- if local_claude_dir .exists () and (local_claude_dir / "settings.json" ).exists ():
546- if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
547- print (f"Using existing local .claude/ directory at { local_claude_dir } " )
548- temp_claude_dir = str (local_claude_dir )
549- else :
550- # Use plugin architecture
551- # 2. Install plugin if not already installed
552- import amplihack
553- amplihack_src = Path (amplihack .__file__ ).parent
554-
555- # Check if plugin already installed
556- if not plugin_root .exists () or not (plugin_root / "tools" ).exists ():
557- if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
558- print (f"Installing plugin to { plugin_root } " )
511+ # Stage framework files to the current directory's .claude directory
512+ # Find the amplihack package location
513+ # Find amplihack package location for .claude files
514+ import amplihack
559515
560- # Create plugin directory
561- plugin_root .mkdir (parents = True , exist_ok = True )
516+ amplihack_src = os .path .dirname (os .path .abspath (amplihack .__file__ ))
562517
563- # Copy .claude contents to plugin root
564- copied = copytree_manifest (str (amplihack_src ), str (plugin_root ), ".claude" )
518+ # Copy .claude contents to temp .claude directory
519+ # Note: copytree_manifest copies TO the dst, not INTO dst/.claude
520+ copied = copytree_manifest (amplihack_src , temp_claude_dir , ".claude" )
565521
566- if not copied :
567- print ("\n ❌ Failed to install plugin - cannot proceed" )
568- print (f" Package location: { amplihack_src } " )
569- sys .exit (1 )
522+ # Bug #2 Fix: Detect empty copy results (Issue #1940)
523+ # When copytree_manifest returns empty list, no files were copied
524+ # This indicates a package installation problem - exit with clear error
525+ if not copied :
526+ print ("\n ❌ Failed to copy .claude files - cannot proceed" )
527+ print (f" Package location: { amplihack_src } " )
528+ print (f" Looking for .claude/ at: { amplihack_src } /.claude/" )
529+ print (" This may indicate a package installation problem\n " )
530+ sys .exit (1 )
570531
532+ # Smart PROJECT.md initialization for UVX mode
533+ if copied :
534+ try :
535+ from .utils .project_initializer import InitMode , initialize_project_md
536+
537+ result = initialize_project_md (Path (original_cwd ), mode = InitMode .FORCE )
538+ if result .success and result .action_taken .value in ["initialized" , "regenerated" ]:
539+ if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
540+ print (
541+ f"PROJECT.md { result .action_taken .value } for { Path (original_cwd ).name } "
542+ )
543+ except Exception as e :
571544 if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
572- print (f"Plugin installed successfully to { plugin_root } " )
573- else :
574- if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
575- print (f"Plugin already installed at { plugin_root } " )
545+ print (f"Warning: PROJECT.md initialization failed: { e } " )
576546
577- # 3. Generate settings.json in project's .claude/ that references plugin
578- temp_claude_dir = str (local_claude_dir )
579- local_claude_dir .mkdir (parents = True , exist_ok = True )
580-
581- settings_path = local_claude_dir / "settings.json"
547+ # Create settings.json with relative paths (Claude will resolve relative to CLAUDE_PROJECT_DIR)
548+ # When CLAUDE_PROJECT_DIR is set, Claude will use settings.json from that directory only
549+ if copied :
550+ settings_path = os .path .join (temp_claude_dir , "settings.json" )
582551 import json
583552
584- # Set CLAUDE_PLUGIN_ROOT for path resolution
585- os .environ ["CLAUDE_PLUGIN_ROOT" ] = str (plugin_root )
586-
587- # Create settings.json with plugin references
553+ # Create minimal settings.json with just amplihack hooks
588554 settings = {
589555 "hooks" : {
590556 "SessionStart" : [
591557 {
592558 "hooks" : [
593559 {
594560 "type" : "command" ,
595- "command" : "${CLAUDE_PLUGIN_ROOT} /tools/amplihack/hooks/session_start.py" ,
561+ "command" : "$CLAUDE_PROJECT_DIR/.claude /tools/amplihack/hooks/session_start.py" ,
596562 "timeout" : 10000 ,
597563 }
598564 ]
@@ -603,7 +569,7 @@ def main(argv: list[str] | None = None) -> int:
603569 "hooks" : [
604570 {
605571 "type" : "command" ,
606- "command" : "${CLAUDE_PLUGIN_ROOT} /tools/amplihack/hooks/stop.py" ,
572+ "command" : "$CLAUDE_PROJECT_DIR/.claude /tools/amplihack/hooks/stop.py" ,
607573 "timeout" : 30000 ,
608574 }
609575 ]
@@ -615,7 +581,7 @@ def main(argv: list[str] | None = None) -> int:
615581 "hooks" : [
616582 {
617583 "type" : "command" ,
618- "command" : "${CLAUDE_PLUGIN_ROOT} /tools/amplihack/hooks/post_tool_use.py" ,
584+ "command" : "$CLAUDE_PROJECT_DIR/.claude /tools/amplihack/hooks/post_tool_use.py" ,
619585 }
620586 ],
621587 }
@@ -625,7 +591,7 @@ def main(argv: list[str] | None = None) -> int:
625591 "hooks" : [
626592 {
627593 "type" : "command" ,
628- "command" : "${CLAUDE_PLUGIN_ROOT} /tools/amplihack/hooks/pre_compact.py" ,
594+ "command" : "$CLAUDE_PROJECT_DIR/.claude /tools/amplihack/hooks/pre_compact.py" ,
629595 "timeout" : 30000 ,
630596 }
631597 ]
@@ -634,39 +600,26 @@ def main(argv: list[str] | None = None) -> int:
634600 }
635601 }
636602
603+ # Write settings.json
604+ os .makedirs (temp_claude_dir , exist_ok = True )
637605 with open (settings_path , "w" ) as f :
638606 json .dump (settings , f , indent = 2 )
639607
640608 if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
641- print (f"Created settings.json at { settings_path } " )
642- print (f"Settings reference plugin at { plugin_root } " )
643-
644- # Smart PROJECT.md initialization for UVX mode
645- try :
646- from .utils .project_initializer import InitMode , initialize_project_md
647-
648- result = initialize_project_md (Path (original_cwd ), mode = InitMode .FORCE )
649- if result .success and result .action_taken .value in ["initialized" , "regenerated" ]:
650- if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
651- print (
652- f"PROJECT.md { result .action_taken .value } for { Path (original_cwd ).name } "
653- )
654- except Exception as e :
655- if os .environ .get ("AMPLIHACK_DEBUG" , "" ).lower () == "true" :
656- print (f"Warning: PROJECT.md initialization failed: { e } " )
609+ print (f"UVX staging completed to { temp_claude_dir } " )
610+ print ("Created settings.json with relative hook paths" )
657611
658612 args , claude_args = parse_args_with_passthrough (argv )
659613
660614 if not args .command :
661615 # If we have claude_args but no command, default to launching Claude directly
662616 if claude_args :
663- # If in UVX mode, ensure we use --add-dir for BOTH the original directory AND plugin directory
617+ # If in UVX mode, ensure we use --add-dir for the ORIGINAL directory
664618 if is_uvx_deployment ():
665619 # Get the original directory (before we changed to temp)
666620 original_cwd = os .environ .get ("AMPLIHACK_ORIGINAL_CWD" , os .getcwd ())
667- plugin_root = str (Path .home () / ".amplihack" / ".claude" )
668621 if "--add-dir" not in claude_args :
669- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ] + claude_args
622+ claude_args = ["--add-dir" , original_cwd ] + claude_args
670623
671624 # Check if Docker should be used for direct launch
672625 if DockerManager .should_use_docker ():
@@ -711,16 +664,15 @@ def main(argv: list[str] | None = None) -> int:
711664 if getattr (args , "append" , None ):
712665 return handle_append_instruction (args )
713666
714- # If in UVX mode, ensure we use --add-dir for BOTH the original directory AND plugin directory
667+ # If in UVX mode, ensure we use --add-dir for the ORIGINAL directory
715668 if is_uvx_deployment ():
716669 # Get the original directory (before we changed to temp)
717670 original_cwd = os .environ .get ("AMPLIHACK_ORIGINAL_CWD" , os .getcwd ())
718- plugin_root = str (Path .home () / ".amplihack" / ".claude" )
719671 # Add --add-dir to claude_args if not already present
720672 if claude_args and "--add-dir" not in claude_args :
721- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ] + claude_args
673+ claude_args = ["--add-dir" , original_cwd ] + claude_args
722674 elif not claude_args :
723- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ]
675+ claude_args = ["--add-dir" , original_cwd ]
724676
725677 # Handle auto mode
726678 exit_code = handle_auto_mode ("claude" , args , claude_args )
@@ -737,11 +689,10 @@ def main(argv: list[str] | None = None) -> int:
737689 # Claude is an alias for launch
738690 if is_uvx_deployment ():
739691 original_cwd = os .environ .get ("AMPLIHACK_ORIGINAL_CWD" , os .getcwd ())
740- plugin_root = str (Path .home () / ".amplihack" / ".claude" )
741692 if claude_args and "--add-dir" not in claude_args :
742- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ] + claude_args
693+ claude_args = ["--add-dir" , original_cwd ] + claude_args
743694 elif not claude_args :
744- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ]
695+ claude_args = ["--add-dir" , original_cwd ]
745696
746697 # Handle auto mode
747698 exit_code = handle_auto_mode ("claude" , args , claude_args )
@@ -762,11 +713,10 @@ def main(argv: list[str] | None = None) -> int:
762713 # RustyClawd launcher setup (similar to claude command)
763714 if is_uvx_deployment ():
764715 original_cwd = os .environ .get ("AMPLIHACK_ORIGINAL_CWD" , os .getcwd ())
765- plugin_root = str (Path .home () / ".amplihack" / ".claude" )
766716 if claude_args and "--add-dir" not in claude_args :
767- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ] + claude_args
717+ claude_args = ["--add-dir" , original_cwd ] + claude_args
768718 elif not claude_args :
769- claude_args = ["--add-dir" , original_cwd , "--add-dir" , plugin_root ]
719+ claude_args = ["--add-dir" , original_cwd ]
770720
771721 # Handle auto mode
772722 exit_code = handle_auto_mode ("claude" , args , claude_args ) # Reuse claude auto mode
@@ -835,106 +785,6 @@ def main(argv: list[str] | None = None) -> int:
835785 print_uvx_usage_instructions ()
836786 return 0
837787
838- elif args .command == "plugin" :
839- if args .plugin_command == "link" :
840- from .plugin_manager import PluginManager
841-
842- plugin_name = args .plugin_name
843- plugin_root = Path .home () / ".amplihack" / "plugins"
844- plugin_path = plugin_root / plugin_name
845-
846- if not plugin_path .exists ():
847- print (f"Error: Plugin not found at { plugin_path } " )
848- print (f"Install the plugin first with: amplihack install" )
849- return 1
850-
851- # Create plugin manager and link plugin
852- manager = PluginManager (plugin_root = plugin_root )
853- if manager ._register_plugin (plugin_name , plugin_path ):
854- print (f"{ EMOJI ['check' ]} Plugin linked successfully: { plugin_name } " )
855- print (f" Settings updated in: ~/.claude/settings.json" )
856- print (f" Plugin should now appear in /plugin command" )
857- return 0
858- else :
859- print (f"Error: Failed to link plugin: { plugin_name } " )
860- return 1
861-
862- elif args .plugin_command == "verify" :
863- plugin_name = args .plugin_name
864- plugin_root = Path .home () / ".amplihack" / "plugins"
865- plugin_path = plugin_root / plugin_name
866- settings_path = Path .home () / ".claude" / "settings.json"
867-
868- print (f"Verifying plugin: { plugin_name } \n " )
869-
870- # Check 1: Plugin directory exists
871- if plugin_path .exists ():
872- print (f"{ EMOJI ['check' ]} Plugin directory exists: { plugin_path } " )
873- else :
874- print (f"❌ Plugin directory not found: { plugin_path } " )
875- print (f" Install with: amplihack install" )
876- return 1
877-
878- # Check 2: Manifest exists
879- manifest_path = plugin_path / "manifest.json"
880- if manifest_path .exists ():
881- print (f"{ EMOJI ['check' ]} Manifest file exists" )
882- try :
883- import json
884- manifest = json .loads (manifest_path .read_text ())
885- print (f" Name: { manifest .get ('name' , 'N/A' )} " )
886- print (f" Version: { manifest .get ('version' , 'N/A' )} " )
887- except json .JSONDecodeError :
888- print (f" ⚠️ Warning: Manifest is not valid JSON" )
889- else :
890- print (f"❌ Manifest file not found: { manifest_path } " )
891-
892- # Check 3: Settings.json exists and contains plugin
893- if settings_path .exists ():
894- print (f"{ EMOJI ['check' ]} Settings file exists: { settings_path } " )
895- try :
896- import json
897- settings = json .loads (settings_path .read_text ())
898-
899- if 'enabledPlugins' in settings :
900- if plugin_name in settings ['enabledPlugins' ]:
901- print (f"{ EMOJI ['check' ]} Plugin is registered in enabledPlugins array" )
902- else :
903- print (f"❌ Plugin NOT in enabledPlugins array" )
904- print (f" Fix with: amplihack plugin link { plugin_name } " )
905- return 1
906- else :
907- print (f"❌ No enabledPlugins array in settings" )
908- print (f" Fix with: amplihack plugin link { plugin_name } " )
909- return 1
910- except json .JSONDecodeError :
911- print (f" ⚠️ Warning: Settings file is not valid JSON" )
912- else :
913- print (f"❌ Settings file not found: { settings_path } " )
914- print (f" Fix with: amplihack plugin link { plugin_name } " )
915- return 1
916-
917- # Check 4: CLAUDE_PLUGIN_ROOT environment variable
918- plugin_root_env = os .environ .get ("CLAUDE_PLUGIN_ROOT" )
919- expected_root = str (Path .home () / ".amplihack" / ".claude" )
920- if plugin_root_env :
921- if plugin_root_env == expected_root :
922- print (f"{ EMOJI ['check' ]} CLAUDE_PLUGIN_ROOT is set correctly" )
923- else :
924- print (f"⚠️ CLAUDE_PLUGIN_ROOT mismatch:" )
925- print (f" Current: { plugin_root_env } " )
926- print (f" Expected: { expected_root } " )
927- else :
928- print (f"⚠️ CLAUDE_PLUGIN_ROOT not set (will be set on launch)" )
929-
930- print (f"\n { EMOJI ['check' ]} Plugin verification complete!" )
931- print (f" Plugin should be discoverable via /plugin command in Claude Code" )
932- return 0
933-
934- else :
935- create_parser ().print_help ()
936- return 1
937-
938788 elif args .command == "memory" :
939789 if args .memory_command == "tree" :
940790 from .memory .cli_visualize import visualize_memory_tree
0 commit comments