fix: Fix 2 critical CLI issues blocking production (Kimi QA)

**Critical Issues Fixed:**

Issue #1: CLI Commands Were BROKEN ⚠️ CRITICAL
- Problem: 4 CLI commands existed but failed at runtime with ImportError
- Root Cause: Modules had example_usage() instead of main() functions
- Impact: Users couldn't use quality, stream, update, multilang features

**Fixed Files:**
- src/skill_seekers/cli/quality_metrics.py
  - Renamed example_usage() → main()
  - Added argparse with --report, --output flags
  - Proper exit codes and error handling

- src/skill_seekers/cli/streaming_ingest.py
  - Renamed example_usage() → main()
  - Added argparse with --chunk-size, --batch-size, --checkpoint flags
  - Supports both file and directory inputs

- src/skill_seekers/cli/incremental_updater.py
  - Renamed example_usage() → main()
  - Added argparse with --check-changes, --generate-package, --apply-update flags
  - Proper error handling and exit codes

- src/skill_seekers/cli/multilang_support.py
  - Renamed example_usage() → main()
  - Added argparse with --detect, --report, --export flags
  - Loads skill documents from directory

Issue #2: Haystack Missing from Package Choices ⚠️ CRITICAL
- Problem: Haystack adaptor worked but couldn't be used via CLI
- Root Cause: package_skill.py missing "haystack" in --target choices
- Impact: Users got "invalid choice" error when packaging for Haystack

**Fixed:**
- src/skill_seekers/cli/package_skill.py:188
  - Added "haystack" to --target choices list
  - Now matches main.py choices (all 11 platforms)

**Verification:**
 All 4 CLI commands now work:
   $ skill-seekers quality --help
   $ skill-seekers stream --help
   $ skill-seekers update --help
   $ skill-seekers multilang --help

 Haystack now available:
   $ skill-seekers package output/skill --target haystack

 All 164 adaptor tests still passing
 No regressions detected

**Credits:**
- Issues identified by: Kimi QA Review
- Fixes implemented by: Claude Sonnet 4.5

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
yusyus
2026-02-07 23:12:40 +03:00
parent 6f9584ba67
commit a332507b1d
5 changed files with 151 additions and 56 deletions

View File

@@ -411,15 +411,37 @@ class IncrementalUpdater:
return False return False
def example_usage(): def main():
"""Example usage of incremental updater.""" """CLI entry point for incremental updates."""
import argparse
from pathlib import Path from pathlib import Path
skill_dir = Path("output/react") parser = argparse.ArgumentParser(description="Detect and apply incremental skill updates")
parser.add_argument("skill_dir", help="Path to skill directory")
parser.add_argument("--check-changes", action="store_true", help="Check for changes only")
parser.add_argument("--generate-package", help="Generate update package at specified path")
parser.add_argument("--apply-update", help="Apply update package from specified path")
args = parser.parse_args()
skill_dir = Path(args.skill_dir)
if not skill_dir.exists():
print(f"❌ Error: Directory not found: {skill_dir}")
return 1
# Initialize updater # Initialize updater
updater = IncrementalUpdater(skill_dir) updater = IncrementalUpdater(skill_dir)
# Apply update if specified
if args.apply_update:
update_path = Path(args.apply_update)
if not update_path.exists():
print(f"❌ Error: Update package not found: {update_path}")
return 1
print(f"📥 Applying update from: {update_path}")
success = updater.apply_update_package(update_path)
return 0 if success else 1
# Detect changes # Detect changes
print("🔍 Detecting changes...") print("🔍 Detecting changes...")
change_set = updater.detect_changes() change_set = updater.detect_changes()
@@ -428,13 +450,18 @@ def example_usage():
report = updater.generate_diff_report(change_set) report = updater.generate_diff_report(change_set)
print(report) print(report)
if args.check_changes:
return 0 if not change_set.has_changes else 1
if change_set.has_changes: if change_set.has_changes:
# Generate update package # Generate update package if specified
if args.generate_package:
package_path = Path(args.generate_package)
else:
package_path = skill_dir.parent / f"{skill_dir.name}-update.json"
print("\n📦 Generating update package...") print("\n📦 Generating update package...")
package_path = updater.generate_update_package( package_path = updater.generate_update_package(change_set, package_path)
change_set,
skill_dir.parent / f"{skill_dir.name}-update.json"
)
print(f"✅ Package created: {package_path}") print(f"✅ Package created: {package_path}")
# Save versions # Save versions
@@ -443,6 +470,9 @@ def example_usage():
else: else:
print("\n✅ No changes detected - skill is up to date!") print("\n✅ No changes detected - skill is up to date!")
return 0
if __name__ == "__main__": if __name__ == "__main__":
example_usage() import sys
sys.exit(main())

View File

@@ -397,40 +397,68 @@ class MultiLanguageManager:
return "\n".join(lines) return "\n".join(lines)
def example_usage(): def main():
"""Example usage of multi-language support.""" """CLI entry point for multi-language support."""
import argparse
from pathlib import Path from pathlib import Path
parser = argparse.ArgumentParser(description="Manage multi-language skill documents")
parser.add_argument("skill_dir", help="Path to skill directory")
parser.add_argument("--detect", action="store_true", help="Detect languages in skill")
parser.add_argument("--report", action="store_true", help="Generate translation report")
parser.add_argument("--export", help="Export by language to specified directory")
args = parser.parse_args()
skill_dir = Path(args.skill_dir)
if not skill_dir.exists():
print(f"❌ Error: Directory not found: {skill_dir}")
return 1
manager = MultiLanguageManager() manager = MultiLanguageManager()
# Add documents in different languages # Load skill documents
manager.add_document( print("📥 Loading skill documents...")
"README.md", skill_md = skill_dir / "SKILL.md"
"# Getting Started\n\nThis is an English document about the project.", if skill_md.exists():
{"category": "overview"} manager.add_document(
) "SKILL.md",
skill_md.read_text(encoding="utf-8"),
{"category": "overview"}
)
manager.add_document( # Load reference files
"README.es.md", refs_dir = skill_dir / "references"
"# Empezando\n\nEste es un documento en español sobre el proyecto.", if refs_dir.exists():
{"category": "overview"} for ref_file in refs_dir.glob("*.md"):
) manager.add_document(
ref_file.name,
ref_file.read_text(encoding="utf-8"),
{"category": ref_file.stem}
)
manager.add_document( # Detect languages
"README.fr.md", if args.detect:
"# Commencer\n\nCeci est un document en français sur le projet.", detected = manager.detect_languages()
{"category": "overview"} print(f"\n🌍 Detected languages: {', '.join(detected.keys())}")
) for lang, count in detected.items():
print(f" {lang}: {count} documents")
# Generate report # Generate report
print(manager.generate_translation_report()) if args.report:
print(manager.generate_translation_report())
# Export by language # Export by language
exports = manager.export_by_language(Path("output/multilang")) if args.export:
print(f"\n✅ Exported {len(exports)} language files:") output_dir = Path(args.export)
for lang, path in exports.items(): output_dir.mkdir(parents=True, exist_ok=True)
print(f" {lang}: {path}") exports = manager.export_by_language(output_dir)
print(f"\n✅ Exported {len(exports)} language files:")
for lang, path in exports.items():
print(f" {lang}: {path}")
return 0
if __name__ == "__main__": if __name__ == "__main__":
example_usage() import sys
sys.exit(main())

View File

@@ -185,7 +185,7 @@ Examples:
parser.add_argument( parser.add_argument(
"--target", "--target",
choices=["claude", "gemini", "openai", "markdown", "langchain", "llama-index", "weaviate", "chroma", "faiss", "qdrant"], choices=["claude", "gemini", "openai", "markdown", "langchain", "llama-index", "haystack", "weaviate", "chroma", "faiss", "qdrant"],
default="claude", default="claude",
help="Target LLM platform (default: claude)", help="Target LLM platform (default: claude)",
) )

View File

@@ -516,26 +516,44 @@ class QualityAnalyzer:
return "\n".join(lines) return "\n".join(lines)
def example_usage(): def main():
"""Example usage of quality metrics.""" """CLI entry point for quality metrics."""
import argparse
from pathlib import Path from pathlib import Path
parser = argparse.ArgumentParser(description="Analyze skill quality metrics")
parser.add_argument("skill_dir", help="Path to skill directory")
parser.add_argument("--report", action="store_true", help="Generate detailed report")
parser.add_argument("--output", help="Output path for JSON report")
args = parser.parse_args()
# Analyze skill # Analyze skill
skill_dir = Path("output/ansible") skill_dir = Path(args.skill_dir)
if not skill_dir.exists():
print(f"❌ Error: Directory not found: {skill_dir}")
return 1
analyzer = QualityAnalyzer(skill_dir) analyzer = QualityAnalyzer(skill_dir)
# Generate report # Generate report
report = analyzer.generate_report() report = analyzer.generate_report()
# Display report # Display report
formatted = analyzer.format_report(report) if args.report:
print(formatted) formatted = analyzer.format_report(report)
print(formatted)
# Save report # Save report
report_path = skill_dir / "quality_report.json" if args.output:
report_path = Path(args.output)
else:
report_path = skill_dir / "quality_report.json"
report_path.write_text(json.dumps(asdict(report), indent=2, default=str)) report_path.write_text(json.dumps(asdict(report), indent=2, default=str))
print(f"\n✅ Report saved: {report_path}") print(f"\n✅ Report saved: {report_path}")
return 0
if __name__ == "__main__": if __name__ == "__main__":
example_usage() import sys
sys.exit(main())

View File

@@ -379,14 +379,23 @@ class StreamingIngester:
return "\n".join(lines) return "\n".join(lines)
def example_usage(): def main():
"""Example usage of streaming ingestion.""" """CLI entry point for streaming ingestion."""
import argparse
parser = argparse.ArgumentParser(description="Stream and chunk skill documents")
parser.add_argument("input", help="Input file or directory path")
parser.add_argument("--chunk-size", type=int, default=4000, help="Chunk size in characters")
parser.add_argument("--chunk-overlap", type=int, default=200, help="Chunk overlap in characters")
parser.add_argument("--batch-size", type=int, default=100, help="Batch size for processing")
parser.add_argument("--checkpoint", help="Checkpoint file path")
args = parser.parse_args()
# Initialize ingester # Initialize ingester
ingester = StreamingIngester( ingester = StreamingIngester(
chunk_size=4000, chunk_size=args.chunk_size,
chunk_overlap=200, chunk_overlap=args.chunk_overlap,
batch_size=100 batch_size=args.batch_size
) )
# Progress callback # Progress callback
@@ -395,26 +404,36 @@ def example_usage():
print(f"Progress: {progress.progress_percent:.1f}% - " print(f"Progress: {progress.progress_percent:.1f}% - "
f"{progress.processed_chunks}/{progress.total_chunks} chunks") f"{progress.processed_chunks}/{progress.total_chunks} chunks")
# Stream skill directory # Stream input
skill_dir = Path("output/react") input_path = Path(args.input)
chunks = ingester.stream_skill_directory(skill_dir, callback=on_progress) if not input_path.exists():
print(f"❌ Error: Path not found: {input_path}")
return 1
if input_path.is_dir():
chunks = ingester.stream_skill_directory(input_path, callback=on_progress)
else:
chunks = ingester.stream_file(input_path, callback=on_progress)
# Process in batches # Process in batches
all_chunks = [] all_chunks = []
for batch in ingester.batch_iterator(chunks, batch_size=50): for batch in ingester.batch_iterator(chunks, batch_size=args.batch_size):
print(f"\nProcessing batch of {len(batch)} chunks...") print(f"\nProcessing batch of {len(batch)} chunks...")
all_chunks.extend(batch) all_chunks.extend(batch)
# Save checkpoint every batch # Save checkpoint if specified
ingester.save_checkpoint( if args.checkpoint:
Path("output/.checkpoints/react.json"), ingester.save_checkpoint(
{"processed_batches": len(all_chunks) // 50} Path(args.checkpoint),
) {"processed_batches": len(all_chunks) // args.batch_size}
)
# Final progress # Final progress
print("\n" + ingester.format_progress()) print("\n" + ingester.format_progress())
print(f"\n✅ Processed {len(all_chunks)} total chunks") print(f"\n✅ Processed {len(all_chunks)} total chunks")
return 0
if __name__ == "__main__": if __name__ == "__main__":
example_usage() import sys
sys.exit(main())