diff --git a/CLAUDE.md b/CLAUDE.md index cc49840..f23d246 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -173,7 +173,7 @@ skill-seekers github --repo owner/repo --non-interactive # CI/CD mode skill-seekers scrape --config configs/react.json --dry-run # Test codebase analysis (C2.x features) -skill-seekers codebase --directory . --output output/codebase/ +skill-seekers analyze --directory . --output output/codebase/ # Test pattern detection (C3.1) skill-seekers patterns --file src/skill_seekers/cli/code_analyzer.py @@ -283,14 +283,17 @@ The project has comprehensive codebase analysis capabilities (C3.1-C3.8): **C3.8 Standalone Codebase Scraper** (`codebase_scraper.py`): ```bash -# All C3.x features enabled by default, use --skip-* to disable -skill-seekers codebase --directory /path/to/repo +# Quick analysis (1-2 min, basic features only) +skill-seekers analyze --directory /path/to/repo --quick + +# Comprehensive analysis (20-60 min, all features + AI) +skill-seekers analyze --directory . --comprehensive + +# With AI enhancement (auto-detects API or LOCAL) +skill-seekers analyze --directory . --enhance # Disable specific features -skill-seekers codebase --directory . --skip-patterns --skip-how-to-guides - -# Legacy flags (deprecated but still work) -skill-seekers codebase --directory . --build-api-reference --build-dependency-graph +skill-seekers analyze --directory . --skip-patterns --skip-how-to-guides ``` - Generates 300+ line standalone SKILL.md files from codebases diff --git a/README.md b/README.md index 98644b9..5fc6b53 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ export ANTHROPIC_BASE_URL=https://glm-4-7-endpoint.com/v1 # All AI enhancement features will use the configured endpoint skill-seekers enhance output/react/ -skill-seekers codebase --directory . --enhance +skill-seekers analyze --directory . --enhance ``` **Note**: Setting `ANTHROPIC_BASE_URL` allows you to use any Claude-compatible API endpoint, such as GLM-4.7 (ζ™Ίθ°± AI) or other compatible services. @@ -321,17 +321,14 @@ ls ~/.claude/skills/skill-seekers/SKILL.md **Usage:** ```bash -# AUTO mode (default) - automatically detects best option -skill-seekers-codebase tests/ --build-how-to-guides --ai-mode auto +# Quick analysis (1-2 min, basic features only) +skill-seekers analyze --directory tests/ --quick -# API mode - fast, efficient (requires ANTHROPIC_API_KEY) -skill-seekers-codebase tests/ --build-how-to-guides --ai-mode api +# Comprehensive analysis with AI (20-60 min, all features) +skill-seekers analyze --directory tests/ --comprehensive -# LOCAL mode - FREE using Claude Code Max (no API key needed) -skill-seekers-codebase tests/ --build-how-to-guides --ai-mode local - -# Disable enhancement - basic guides only -skill-seekers-codebase tests/ --build-how-to-guides --ai-mode none +# With AI enhancement +skill-seekers analyze --directory tests/ --enhance ``` **Full Documentation:** [docs/HOW_TO_GUIDES.md](docs/HOW_TO_GUIDES.md#ai-enhancement-new) diff --git a/src/skill_seekers/cli/codebase_scraper.py b/src/skill_seekers/cli/codebase_scraper.py index 4d7bf88..bca2223 100644 --- a/src/skill_seekers/cli/codebase_scraper.py +++ b/src/skill_seekers/cli/codebase_scraper.py @@ -1082,7 +1082,13 @@ Examples: "--depth", choices=["surface", "deep", "full"], default="deep", - help="Analysis depth (default: deep)", + help=( + "Analysis depth: " + "surface (basic code structure, ~1-2 min), " + "deep (code + patterns + tests, ~5-10 min, DEFAULT), " + "full (everything + AI enhancement, ~20-60 min). " + "πŸ’‘ TIP: Use --quick or --comprehensive presets instead for better UX!" + ), ) parser.add_argument( "--languages", help="Comma-separated languages to analyze (e.g., Python,JavaScript,C++)" @@ -1130,7 +1136,14 @@ Examples: "--ai-mode", choices=["auto", "api", "local", "none"], default="auto", - help="AI enhancement mode for how-to guides: auto (detect best), api (Claude API), local (Claude Code CLI), none (disable) (default: auto)", + help=( + "AI enhancement mode for how-to guides: " + "auto (auto-detect: API if ANTHROPIC_API_KEY set, else LOCAL), " + "api (Claude API, requires ANTHROPIC_API_KEY), " + "local (Claude Code Max, FREE, no API key), " + "none (disable AI enhancement). " + "πŸ’‘ TIP: Use --enhance flag instead for simpler UX!" + ), ) parser.add_argument("--no-comments", action="store_true", help="Skip comment extraction") parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") @@ -1155,6 +1168,31 @@ Examples: args = parser.parse_args() + # Handle presets (Phase 1 feature - NEW) + if hasattr(args, "quick") and args.quick and hasattr(args, "comprehensive") and args.comprehensive: + logger.error("❌ Cannot use --quick and --comprehensive together. Choose one.") + return 1 + + if hasattr(args, "quick") and args.quick: + # Override depth and disable advanced features + args.depth = "surface" + args.skip_patterns = True + args.skip_test_examples = True + args.skip_how_to_guides = True + args.skip_config_patterns = True + args.ai_mode = "none" + logger.info("⚑ Quick analysis mode: surface depth, basic features only (~1-2 min)") + + if hasattr(args, "comprehensive") and args.comprehensive: + # Override depth and enable all features + args.depth = "full" + args.skip_patterns = False + args.skip_test_examples = False + args.skip_how_to_guides = False + args.skip_config_patterns = False + args.ai_mode = "auto" + logger.info("πŸš€ Comprehensive analysis mode: all features + AI enhancement (~20-60 min)") + # Set logging level if args.verbose: logging.getLogger().setLevel(logging.DEBUG) diff --git a/src/skill_seekers/cli/main.py b/src/skill_seekers/cli/main.py index d1cf9d8..78be7b1 100644 --- a/src/skill_seekers/cli/main.py +++ b/src/skill_seekers/cli/main.py @@ -13,6 +13,7 @@ Commands: github Scrape GitHub repository pdf Extract from PDF file unified Multi-source scraping (docs + GitHub + PDF) + analyze Analyze local codebase and extract code knowledge enhance AI-powered enhancement (local, no API key) enhance-status Check enhancement status (for background/daemon modes) package Package skill into .zip file @@ -270,6 +271,45 @@ For more information: https://github.com/yusufkaraaslan/Skill_Seekers "--dry-run", action="store_true", help="Preview installation without making changes" ) + # === analyze subcommand === + analyze_parser = subparsers.add_parser( + "analyze", + help="Analyze local codebase and extract code knowledge", + description="Standalone codebase analysis with C3.x features (patterns, tests, guides)", + ) + analyze_parser.add_argument("--directory", required=True, help="Directory to analyze") + analyze_parser.add_argument( + "--output", default="output/codebase/", help="Output directory (default: output/codebase/)" + ) + analyze_parser.add_argument( + "--quick", action="store_true", help="Quick analysis (1-2 min, basic features only)" + ) + analyze_parser.add_argument( + "--comprehensive", + action="store_true", + help="Comprehensive analysis (20-60 min, all features + AI)" + ) + analyze_parser.add_argument( + "--depth", + choices=["surface", "deep", "full"], + help="Analysis depth (deprecated - use --quick or --comprehensive instead)", + ) + analyze_parser.add_argument( + "--languages", help="Comma-separated languages (e.g., Python,JavaScript,C++)" + ) + analyze_parser.add_argument("--file-patterns", help="Comma-separated file patterns") + analyze_parser.add_argument( + "--enhance", action="store_true", help="Enable AI enhancement (auto-detects API or LOCAL)" + ) + analyze_parser.add_argument("--skip-api-reference", action="store_true", help="Skip API docs") + analyze_parser.add_argument("--skip-dependency-graph", action="store_true", help="Skip dep graph") + analyze_parser.add_argument("--skip-patterns", action="store_true", help="Skip pattern detection") + analyze_parser.add_argument("--skip-test-examples", action="store_true", help="Skip test examples") + analyze_parser.add_argument("--skip-how-to-guides", action="store_true", help="Skip guides") + analyze_parser.add_argument("--skip-config-patterns", action="store_true", help="Skip config") + analyze_parser.add_argument("--no-comments", action="store_true", help="Skip comments") + analyze_parser.add_argument("--verbose", action="store_true", help="Verbose logging") + # === install subcommand === install_parser = subparsers.add_parser( "install", @@ -499,6 +539,57 @@ def main(argv: list[str] | None = None) -> int: sys.argv.append("--markdown") return test_examples_main() or 0 + elif args.command == "analyze": + from skill_seekers.cli.codebase_scraper import main as analyze_main + + sys.argv = ["codebase_scraper.py", "--directory", args.directory] + + if args.output: + sys.argv.extend(["--output", args.output]) + + # Handle preset flags (new) + if args.quick: + # Quick = surface depth + skip advanced features + sys.argv.extend([ + "--depth", "surface", + "--skip-patterns", + "--skip-test-examples", + "--skip-how-to-guides", + "--skip-config-patterns", + ]) + elif args.comprehensive: + # Comprehensive = full depth + all features + AI + sys.argv.extend(["--depth", "full", "--ai-mode", "auto"]) + elif args.depth: + sys.argv.extend(["--depth", args.depth]) + + if args.languages: + sys.argv.extend(["--languages", args.languages]) + if args.file_patterns: + sys.argv.extend(["--file-patterns", args.file_patterns]) + if args.enhance: + sys.argv.extend(["--ai-mode", "auto"]) + + # Pass through skip flags + if args.skip_api_reference: + sys.argv.append("--skip-api-reference") + if args.skip_dependency_graph: + sys.argv.append("--skip-dependency-graph") + if args.skip_patterns: + sys.argv.append("--skip-patterns") + if args.skip_test_examples: + sys.argv.append("--skip-test-examples") + if args.skip_how_to_guides: + sys.argv.append("--skip-how-to-guides") + if args.skip_config_patterns: + sys.argv.append("--skip-config-patterns") + if args.no_comments: + sys.argv.append("--no-comments") + if args.verbose: + sys.argv.append("--verbose") + + return analyze_main() or 0 + elif args.command == "install-agent": from skill_seekers.cli.install_agent import main as install_agent_main diff --git a/tests/test_analyze_command.py b/tests/test_analyze_command.py new file mode 100644 index 0000000..2ceeac9 --- /dev/null +++ b/tests/test_analyze_command.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Tests for analyze subcommand integration in main CLI.""" + +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from skill_seekers.cli.main import create_parser + + +class TestAnalyzeSubcommand(unittest.TestCase): + """Test analyze subcommand registration and argument parsing.""" + + def setUp(self): + """Create parser for testing.""" + self.parser = create_parser() + + def test_analyze_subcommand_exists(self): + """Test that analyze subcommand is registered.""" + args = self.parser.parse_args(["analyze", "--directory", "."]) + self.assertEqual(args.command, "analyze") + self.assertEqual(args.directory, ".") + + def test_analyze_with_output_directory(self): + """Test analyze with custom output directory.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--output", "custom/"]) + self.assertEqual(args.output, "custom/") + + def test_quick_preset_flag(self): + """Test --quick preset flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--quick"]) + self.assertTrue(args.quick) + self.assertFalse(args.comprehensive) + + def test_comprehensive_preset_flag(self): + """Test --comprehensive preset flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--comprehensive"]) + self.assertTrue(args.comprehensive) + self.assertFalse(args.quick) + + def test_quick_and_comprehensive_mutually_exclusive(self): + """Test that both flags can be parsed (mutual exclusion enforced at runtime).""" + # The parser allows both flags; runtime logic prevents simultaneous use + args = self.parser.parse_args(["analyze", "--directory", ".", "--quick", "--comprehensive"]) + self.assertTrue(args.quick) + self.assertTrue(args.comprehensive) + # Note: Runtime will catch this and return error code 1 + + def test_enhance_flag(self): + """Test --enhance flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--enhance"]) + self.assertTrue(args.enhance) + + def test_skip_flags_passed_through(self): + """Test that skip flags are recognized.""" + args = self.parser.parse_args([ + "analyze", + "--directory", ".", + "--skip-patterns", + "--skip-test-examples" + ]) + self.assertTrue(args.skip_patterns) + self.assertTrue(args.skip_test_examples) + + def test_all_skip_flags(self): + """Test all skip flags are properly parsed.""" + args = self.parser.parse_args([ + "analyze", + "--directory", ".", + "--skip-api-reference", + "--skip-dependency-graph", + "--skip-patterns", + "--skip-test-examples", + "--skip-how-to-guides", + "--skip-config-patterns" + ]) + self.assertTrue(args.skip_api_reference) + self.assertTrue(args.skip_dependency_graph) + self.assertTrue(args.skip_patterns) + self.assertTrue(args.skip_test_examples) + self.assertTrue(args.skip_how_to_guides) + self.assertTrue(args.skip_config_patterns) + + def test_backward_compatible_depth_flag(self): + """Test that deprecated --depth flag still works.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--depth", "full"]) + self.assertEqual(args.depth, "full") + + def test_depth_flag_choices(self): + """Test that depth flag accepts correct values.""" + for depth in ["surface", "deep", "full"]: + args = self.parser.parse_args(["analyze", "--directory", ".", "--depth", depth]) + self.assertEqual(args.depth, depth) + + def test_languages_flag(self): + """Test languages flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--languages", "Python,JavaScript"]) + self.assertEqual(args.languages, "Python,JavaScript") + + def test_file_patterns_flag(self): + """Test file patterns flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--file-patterns", "*.py,src/**/*.js"]) + self.assertEqual(args.file_patterns, "*.py,src/**/*.js") + + def test_no_comments_flag(self): + """Test no-comments flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--no-comments"]) + self.assertTrue(args.no_comments) + + def test_verbose_flag(self): + """Test verbose flag parsing.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--verbose"]) + self.assertTrue(args.verbose) + + def test_complex_command_combination(self): + """Test complex command with multiple flags.""" + args = self.parser.parse_args([ + "analyze", + "--directory", "./src", + "--output", "analysis/", + "--quick", + "--languages", "Python", + "--skip-patterns", + "--verbose" + ]) + self.assertEqual(args.directory, "./src") + self.assertEqual(args.output, "analysis/") + self.assertTrue(args.quick) + self.assertEqual(args.languages, "Python") + self.assertTrue(args.skip_patterns) + self.assertTrue(args.verbose) + + def test_directory_is_required(self): + """Test that directory argument is required.""" + with self.assertRaises(SystemExit): + self.parser.parse_args(["analyze"]) + + def test_default_output_directory(self): + """Test default output directory value.""" + args = self.parser.parse_args(["analyze", "--directory", "."]) + self.assertEqual(args.output, "output/codebase/") + + +class TestAnalyzePresetBehavior(unittest.TestCase): + """Test preset flag behavior and argument transformation.""" + + def setUp(self): + """Create parser for testing.""" + self.parser = create_parser() + + def test_quick_preset_implies_surface_depth(self): + """Test that --quick preset should trigger surface depth.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--quick"]) + self.assertTrue(args.quick) + # Note: Depth transformation happens in dispatch handler + + def test_comprehensive_preset_implies_full_depth(self): + """Test that --comprehensive preset should trigger full depth.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--comprehensive"]) + self.assertTrue(args.comprehensive) + # Note: Depth transformation happens in dispatch handler + + def test_enhance_flag_standalone(self): + """Test --enhance flag can be used without presets.""" + args = self.parser.parse_args(["analyze", "--directory", ".", "--enhance"]) + self.assertTrue(args.enhance) + self.assertFalse(args.quick) + self.assertFalse(args.comprehensive) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_cli_paths.py b/tests/test_cli_paths.py index 42dbfb3..6484611 100644 --- a/tests/test_cli_paths.py +++ b/tests/test_cli_paths.py @@ -138,7 +138,7 @@ class TestUnifiedCLIEntryPoints(unittest.TestCase): # Should show version output = result.stdout + result.stderr - self.assertIn("2.7.0", output) + self.assertIn("2.7.2", output) except FileNotFoundError: # If skill-seekers is not installed, skip this test diff --git a/tests/test_package_structure.py b/tests/test_package_structure.py index 8fea08e..d1233f9 100644 --- a/tests/test_package_structure.py +++ b/tests/test_package_structure.py @@ -24,7 +24,7 @@ class TestCliPackage: import skill_seekers.cli assert hasattr(skill_seekers.cli, "__version__") - assert skill_seekers.cli.__version__ == "2.7.0" + assert skill_seekers.cli.__version__ == "2.7.2" def test_cli_has_all(self): """Test that skill_seekers.cli package has __all__ export list.""" @@ -88,7 +88,7 @@ class TestMcpPackage: import skill_seekers.mcp assert hasattr(skill_seekers.mcp, "__version__") - assert skill_seekers.mcp.__version__ == "2.7.0" + assert skill_seekers.mcp.__version__ == "2.7.2" def test_mcp_has_all(self): """Test that skill_seekers.mcp package has __all__ export list.""" @@ -108,7 +108,7 @@ class TestMcpPackage: import skill_seekers.mcp.tools assert hasattr(skill_seekers.mcp.tools, "__version__") - assert skill_seekers.mcp.tools.__version__ == "2.7.0" + assert skill_seekers.mcp.tools.__version__ == "2.7.2" class TestPackageStructure: @@ -212,7 +212,7 @@ class TestRootPackage: import skill_seekers assert hasattr(skill_seekers, "__version__") - assert skill_seekers.__version__ == "2.7.0" + assert skill_seekers.__version__ == "2.7.2" def test_root_has_metadata(self): """Test that skill_seekers root package has metadata."""