Files
skill-seekers-reference/src/skill_seekers/cli/how_to_guide_builder.py
yusyus 596b219599 fix: Resolve remaining 188 linting errors (249 total fixed)
Second batch of comprehensive linting fixes:

Unused Arguments/Variables (136 errors):
- ARG002/ARG001 (91 errors): Prefixed unused method/function arguments with '_'
  - Interface methods in adaptors (base.py, gemini.py, markdown.py)
  - AST analyzer methods maintaining signatures (code_analyzer.py)
  - Test fixtures and hooks (conftest.py)
  - Added noqa: ARG001/ARG002 for pytest hooks requiring exact names
- F841 (45 errors): Prefixed unused local variables with '_'
  - Tuple unpacking where some values aren't needed
  - Variables assigned but not referenced

Loop & Boolean Quality (28 errors):
- B007 (18 errors): Prefixed unused loop control variables with '_'
  - enumerate() loops where index not used
  - for-in loops where loop variable not referenced
- E712 (10 errors): Simplified boolean comparisons
  - Changed '== True' to direct boolean check
  - Changed '== False' to 'not' expression
  - Improved test readability

Code Quality (24 errors):
- SIM201 (4 errors): Already fixed in previous commit
- SIM118 (2 errors): Already fixed in previous commit
- E741 (4 errors): Already fixed in previous commit
- Config manager loop variable fix (1 error)

All Tests Passing:
- test_scraper_features.py: 42 passed
- test_integration.py: 51 passed
- test_architecture_scenarios.py: 11 passed
- test_real_world_fastmcp.py: 19 passed, 1 skipped

Note: Some SIM errors (nested if, multiple with) remain unfixed as they
would require non-trivial refactoring. Focus was on functional correctness.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 23:02:11 +03:00

1250 lines
43 KiB
Python

#!/usr/bin/env python3
"""
How-To Guide Builder (C3.3) - Build step-by-step guides from workflow examples
Transforms multi-step test workflows (from C3.2) into educational "how to" guides with:
- Step-by-step breakdowns
- Prerequisites and setup requirements
- Verification checkpoints
- Troubleshooting sections
- Complexity levels (beginner/intermediate/advanced)
Usage:
# From test examples JSON
skill-seekers build-how-to-guides --input test_examples.json
# From directory (auto-extracts workflows)
skill-seekers build-how-to-guides tests/
# With AI enhancement
skill-seekers build-how-to-guides tests/ --enhance-with-ai
Example workflow → guide transformation:
Input: Multi-step test showing user registration + login + session
Output: "How To: Complete User Authentication" guide with:
- 5 discrete steps with explanations
- Prerequisites (database, email service)
- Verification points at each step
- Common pitfalls and troubleshooting
- Related guides suggestions
"""
import ast
import hashlib
import json
import logging
import re
from collections import defaultdict
from dataclasses import asdict, dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Literal
logger = logging.getLogger(__name__)
# ============================================================================
# DATA MODELS
# ============================================================================
@dataclass
class PrerequisiteItem:
"""Enhanced prerequisite with explanation (AI enhancement)"""
name: str
why: str # Why this is needed
setup: str # How to install/configure
@dataclass
class TroubleshootingItem:
"""Enhanced troubleshooting with solutions (AI enhancement)"""
problem: str
symptoms: list[str] = field(default_factory=list) # How to recognize this issue
solution: str = "" # Step-by-step fix
diagnostic_steps: list[str] = field(default_factory=list) # How to diagnose
@dataclass
class WorkflowStep:
"""Single step in a workflow guide"""
step_number: int
code: str
description: str
expected_result: str | None = None
verification: str | None = None # Assertion or checkpoint
setup_required: str | None = None
explanation: str | None = None # Why this step matters
common_pitfall: str | None = None # Warning for this step
common_variations: list[str] = field(default_factory=list) # AI: Alternative approaches
@dataclass
class HowToGuide:
"""Complete how-to guide generated from workflow(s)"""
guide_id: str
title: str
overview: str
complexity_level: Literal["beginner", "intermediate", "advanced"]
# Prerequisites
prerequisites: list[str] = field(default_factory=list)
required_imports: list[str] = field(default_factory=list)
required_fixtures: list[str] = field(default_factory=list)
# Content
workflows: list[dict] = field(default_factory=list) # Source workflow examples
steps: list[WorkflowStep] = field(default_factory=list)
# Metadata
use_case: str = ""
tags: list[str] = field(default_factory=list)
estimated_time: str = "10 minutes"
source_files: list[str] = field(default_factory=list)
# Optional AI enhancement (basic)
common_pitfalls: list[str] = field(default_factory=list)
troubleshooting: dict[str, str] = field(default_factory=dict)
variations: list[str] = field(default_factory=list)
related_guides: list[str] = field(default_factory=list)
# AI enhancement (comprehensive - NEW)
prerequisites_detailed: list[PrerequisiteItem] = field(default_factory=list)
troubleshooting_detailed: list[TroubleshootingItem] = field(default_factory=list)
next_steps_detailed: list[str] = field(default_factory=list)
use_cases: list[str] = field(default_factory=list)
def to_dict(self) -> dict:
"""Convert to dictionary"""
result = asdict(self)
# Convert WorkflowStep objects to dicts
result["steps"] = [asdict(step) for step in self.steps]
return result
@dataclass
class GuideCollection:
"""Collection of guides organized by category"""
total_guides: int
guides_by_complexity: dict[str, int]
guides_by_use_case: dict[str, list[HowToGuide]]
guides: list[HowToGuide]
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"total_guides": self.total_guides,
"guides_by_complexity": self.guides_by_complexity,
"guides_by_use_case": {
k: [g.to_dict() for g in v] for k, v in self.guides_by_use_case.items()
},
"guides": [g.to_dict() for g in self.guides],
}
# ============================================================================
# WORKFLOW ANALYZER
# ============================================================================
class WorkflowAnalyzer:
"""Analyze workflow examples to extract steps and metadata"""
def analyze_workflow(self, workflow: dict) -> tuple[list[WorkflowStep], dict]:
"""
Deep analysis of workflow structure.
Args:
workflow: TestExample dict from C3.2
Returns:
(steps, metadata) where metadata includes prerequisites, complexity, etc.
"""
code = workflow.get("code", "")
language = workflow.get("language", "python").lower()
# Extract steps based on language
if language == "python":
steps = self._extract_steps_python(code, workflow)
else:
steps = self._extract_steps_heuristic(code, workflow)
# Detect prerequisites
metadata = self._detect_prerequisites(workflow)
# Find verification points
verifications = self._find_verification_points(code)
# Associate verifications with steps
for i, step in enumerate(steps):
if i < len(verifications):
step.verification = verifications[i]
# Calculate complexity
metadata["complexity_level"] = self._calculate_complexity(steps, workflow)
metadata["estimated_time"] = self._estimate_time(steps)
return steps, metadata
def _extract_steps_python(self, code: str, workflow: dict) -> list[WorkflowStep]:
"""Extract steps from Python code using AST"""
steps = []
try:
tree = ast.parse(code)
statements = []
# Collect all statements
for node in ast.walk(tree):
if isinstance(node, (ast.Assign, ast.Expr, ast.Assert)):
statements.append(node)
step_num = 1
for stmt in statements:
# Skip assertions for now (they're verifications)
if isinstance(stmt, ast.Assert):
continue
# Get code for this statement
step_code = ast.get_source_segment(code, stmt)
if not step_code:
continue
# Generate description from code
description = self._generate_step_description(stmt, step_code)
# Check if next statement is assertion (verification)
idx = statements.index(stmt)
verification = None
if idx + 1 < len(statements) and isinstance(statements[idx + 1], ast.Assert):
verification = ast.get_source_segment(code, statements[idx + 1])
steps.append(
WorkflowStep(
step_number=step_num,
code=step_code,
description=description,
verification=verification,
)
)
step_num += 1
except SyntaxError:
# Fall back to heuristic method
return self._extract_steps_heuristic(code, workflow)
return steps
def _extract_steps_heuristic(self, code: str, _workflow: dict) -> list[WorkflowStep]:
"""Extract steps using heuristics (for non-Python or invalid syntax)"""
steps = []
lines = code.split("\n")
current_step = []
step_num = 1
for line in lines:
line_stripped = line.strip()
# Skip empty lines and comments
if not line_stripped or line_stripped.startswith("#"):
if current_step:
# End of current step
step_code = "\n".join(current_step)
description = self._infer_description_from_code(step_code)
steps.append(
WorkflowStep(step_number=step_num, code=step_code, description=description)
)
step_num += 1
current_step = []
continue
current_step.append(line)
# Add final step
if current_step:
step_code = "\n".join(current_step)
description = self._infer_description_from_code(step_code)
steps.append(
WorkflowStep(step_number=step_num, code=step_code, description=description)
)
return steps
def _generate_step_description(self, node: ast.AST, code: str) -> str:
"""Generate human-readable description from AST node"""
if isinstance(node, ast.Assign):
targets = [self._get_name(t) for t in node.targets]
value_desc = self._describe_value(node.value)
return f"Assign {', '.join(targets)} = {value_desc}"
elif isinstance(node, ast.Expr):
if isinstance(node.value, ast.Call):
func_name = self._get_name(node.value.func)
return f"Call {func_name}()"
return code.split("\n")[0] # First line as fallback
def _describe_value(self, node: ast.AST) -> str:
"""Describe AST value node"""
if isinstance(node, ast.Call):
func_name = self._get_name(node.func)
return f"{func_name}(...)"
elif isinstance(node, ast.Constant):
return repr(node.value)
elif isinstance(node, ast.Name):
return node.id
return "value"
def _get_name(self, node: ast.AST) -> str:
"""Extract name from AST node"""
if isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.Attribute):
return f"{self._get_name(node.value)}.{node.attr}"
elif isinstance(node, ast.Call):
return self._get_name(node.func)
return "unknown"
def _infer_description_from_code(self, code: str) -> str:
"""Infer description from code using patterns"""
code = code.strip()
# Method call patterns
if "(" in code and ")" in code:
match = re.search(r"(\w+)\s*\(", code)
if match:
return f"Call {match.group(1)}()"
# Assignment patterns
if "=" in code and not code.startswith("assert"):
parts = code.split("=", 1)
var_name = parts[0].strip()
return f"Create {var_name}"
# Assertion patterns
if code.startswith("assert"):
return "Verify result"
return code.split("\n")[0] # First line
def _detect_prerequisites(self, workflow: dict) -> dict:
"""Detect prerequisites from workflow"""
metadata = {"prerequisites": [], "required_imports": [], "required_fixtures": []}
# Get dependencies from workflow
dependencies = workflow.get("dependencies", [])
metadata["required_imports"] = dependencies
# Get setup code
setup_code = workflow.get("setup_code")
if setup_code:
metadata["prerequisites"].append("Setup code must be executed first")
# Check for common fixtures in test name or setup
test_name = workflow.get("test_name", "").lower()
if "database" in test_name or (setup_code and "database" in setup_code.lower()):
metadata["required_fixtures"].append("database")
if "api" in test_name or (setup_code and "api" in setup_code.lower()):
metadata["required_fixtures"].append("api_client")
return metadata
def _find_verification_points(self, code: str) -> list[str]:
"""Find assertion statements in code"""
verifications = []
for line in code.split("\n"):
line_stripped = line.strip()
if line_stripped.startswith("assert"):
verifications.append(line_stripped)
return verifications
def _calculate_complexity(self, steps: list[WorkflowStep], workflow: dict) -> str:
"""Calculate complexity level"""
num_steps = len(steps)
# Check for advanced patterns
code = workflow.get("code", "")
has_async = "async" in code or "await" in code
has_mock = "mock" in code.lower() or "patch" in code.lower()
has_error_handling = "try" in code or "except" in code
_complexity_score = workflow.get("complexity_score", 0.5)
# Determine level
if num_steps <= 3 and not has_async and not has_mock:
return "beginner"
elif num_steps >= 8 or has_async or has_error_handling:
return "advanced"
else:
return "intermediate"
def _estimate_time(self, steps: list[WorkflowStep]) -> str:
"""Estimate time to complete guide"""
num_steps = len(steps)
if num_steps <= 3:
return "5 minutes"
elif num_steps <= 6:
return "10 minutes"
elif num_steps <= 10:
return "15 minutes"
else:
return "20 minutes"
# ============================================================================
# WORKFLOW GROUPER
# ============================================================================
class WorkflowGrouper:
"""Group related workflows into coherent guides"""
def group_workflows(
self, workflows: list[dict], strategy: str = "ai-tutorial-group"
) -> dict[str, list[dict]]:
"""
Group workflows using specified strategy.
Args:
workflows: List of workflow examples
strategy: "ai-tutorial-group", "file-path", "test-name", "complexity"
Returns:
Dict mapping group name to list of workflows
"""
if strategy == "ai-tutorial-group":
return self._group_by_ai_tutorial_group(workflows)
elif strategy == "file-path":
return self._group_by_file_path(workflows)
elif strategy == "test-name":
return self._group_by_test_name(workflows)
elif strategy == "complexity":
return self._group_by_complexity(workflows)
else:
# Default: AI tutorial group with fallback
groups = self._group_by_ai_tutorial_group(workflows)
if not groups or len(groups) == len(workflows):
# Fallback to file path if AI grouping didn't work well
groups = self._group_by_file_path(workflows)
return groups
def _group_by_ai_tutorial_group(self, workflows: list[dict]) -> dict[str, list[dict]]:
"""Group by AI-generated tutorial_group (from C3.6 enhancement)"""
groups = defaultdict(list)
ungrouped = []
for workflow in workflows:
ai_analysis = workflow.get("ai_analysis", {})
tutorial_group = ai_analysis.get("tutorial_group")
if tutorial_group:
groups[tutorial_group].append(workflow)
else:
ungrouped.append(workflow)
# Put ungrouped workflows in individual guides
for workflow in ungrouped:
test_name = workflow.get("test_name", "Unknown")
# Clean test name for title
title = self._clean_test_name(test_name)
groups[title] = [workflow]
return dict(groups)
def _group_by_file_path(self, workflows: list[dict]) -> dict[str, list[dict]]:
"""Group workflows from same test file"""
groups = defaultdict(list)
for workflow in workflows:
file_path = workflow.get("file_path", "")
# Extract meaningful name from file path
file_name = Path(file_path).stem if file_path else "Unknown"
# Remove test_ prefix
group_name = file_name.replace("test_", "").replace("_", " ").title()
groups[group_name].append(workflow)
return dict(groups)
def _group_by_test_name(self, workflows: list[dict]) -> dict[str, list[dict]]:
"""Group by common test name prefixes"""
groups = defaultdict(list)
for workflow in workflows:
test_name = workflow.get("test_name", "")
# Extract prefix (e.g., test_auth_login → auth)
prefix = self._extract_prefix(test_name)
groups[prefix].append(workflow)
return dict(groups)
def _group_by_complexity(self, workflows: list[dict]) -> dict[str, list[dict]]:
"""Group by complexity level"""
groups = {"Beginner": [], "Intermediate": [], "Advanced": []}
for workflow in workflows:
complexity_score = workflow.get("complexity_score", 0.5)
if complexity_score < 0.4:
groups["Beginner"].append(workflow)
elif complexity_score < 0.7:
groups["Intermediate"].append(workflow)
else:
groups["Advanced"].append(workflow)
# Remove empty groups
return {k: v for k, v in groups.items() if v}
def _clean_test_name(self, test_name: str) -> str:
"""Clean test name to readable title"""
# Remove test_ prefix
name = test_name.replace("test_", "")
# Replace underscores with spaces
name = name.replace("_", " ")
# Title case
return name.title()
def _extract_prefix(self, test_name: str) -> str:
"""Extract prefix from test name"""
# Remove test_ prefix
name = test_name.replace("test_", "")
# Get first part before underscore
parts = name.split("_")
if len(parts) > 1:
return parts[0].title()
return self._clean_test_name(test_name)
# ============================================================================
# GUIDE GENERATOR
# ============================================================================
class GuideGenerator:
"""Generate markdown guides from workflow data"""
def generate_guide_markdown(self, guide: HowToGuide) -> str:
"""
Generate complete markdown guide.
Args:
guide: HowToGuide object with all data
Returns:
Complete markdown string
"""
sections = []
# Header
sections.append(self._create_header(guide))
# Overview
sections.append(self._create_overview(guide))
# Prerequisites
if guide.prerequisites or guide.required_imports or guide.required_fixtures:
sections.append(self._create_prerequisites(guide))
# Step-by-step guide
sections.append(self._create_steps_section(guide.steps))
# Complete example
sections.append(self._create_complete_example(guide))
# Troubleshooting (if available)
if guide.common_pitfalls or guide.troubleshooting:
sections.append(self._create_troubleshooting(guide))
# Next steps and related guides
sections.append(self._create_next_steps(guide))
# Footer
sections.append(self._create_footer(guide))
return "\n\n".join(sections)
def _create_header(self, guide: HowToGuide) -> str:
"""Create guide header with metadata"""
lines = [f"# How To: {guide.title}"]
lines.append("")
lines.append(f"**Difficulty**: {guide.complexity_level.title()}")
lines.append(f"**Estimated Time**: {guide.estimated_time}")
if guide.tags:
lines.append(f"**Tags**: {', '.join(guide.tags)}")
return "\n".join(lines)
def _create_overview(self, guide: HowToGuide) -> str:
"""Create overview section"""
return f"## Overview\n\n{guide.overview}"
def _create_prerequisites(self, guide: HowToGuide) -> str:
"""Create prerequisites section"""
lines = ["## Prerequisites"]
lines.append("")
# Checklist format
if guide.prerequisites:
for prereq in guide.prerequisites:
lines.append(f"- [ ] {prereq}")
lines.append("")
# Required imports
if guide.required_imports:
lines.append("**Required Modules:**")
for imp in guide.required_imports:
lines.append(f"- `{imp}`")
lines.append("")
# Required fixtures
if guide.required_fixtures:
lines.append("**Required Fixtures:**")
for fixture in guide.required_fixtures:
lines.append(f"- `{fixture}` fixture")
lines.append("")
# Setup code if available
if guide.workflows and guide.workflows[0].get("setup_code"):
setup_code = guide.workflows[0]["setup_code"]
lines.append("**Setup Required:**")
lines.append("```python")
lines.append(setup_code)
lines.append("```")
return "\n".join(lines)
def _create_steps_section(self, steps: list[WorkflowStep]) -> str:
"""Create step-by-step guide section"""
lines = ["## Step-by-Step Guide"]
lines.append("")
for step in steps:
lines.append(f"### Step {step.step_number}: {step.description}")
lines.append("")
# Explanation if available
if step.explanation:
lines.append(f"**What you're doing:** {step.explanation}")
lines.append("")
# Code
lines.append("```python")
lines.append(step.code)
lines.append("```")
lines.append("")
# Expected result
if step.expected_result:
lines.append(f"**Expected Result:** {step.expected_result}")
lines.append("")
# Verification checkpoint
if step.verification:
lines.append("**Verification:**")
lines.append("```python")
lines.append(step.verification)
lines.append("```")
lines.append("")
# Common pitfall warning
if step.common_pitfall:
lines.append(f"⚠️ **Common Pitfall:** {step.common_pitfall}")
lines.append("")
return "\n".join(lines)
def _create_complete_example(self, guide: HowToGuide) -> str:
"""Create complete working example"""
lines = ["## Complete Example"]
lines.append("")
lines.append("```python")
# If we have workflows, use the first one's code
if guide.workflows:
workflow = guide.workflows[0]
# Add setup code if present
if workflow.get("setup_code"):
lines.append("# Setup")
lines.append(workflow["setup_code"])
lines.append("")
# Add main workflow code
lines.append("# Workflow")
lines.append(workflow.get("code", ""))
else:
# Combine all steps
for step in guide.steps:
lines.append(f"# Step {step.step_number}: {step.description}")
lines.append(step.code)
if step.verification:
lines.append(step.verification)
lines.append("")
lines.append("```")
return "\n".join(lines)
def _create_troubleshooting(self, guide: HowToGuide) -> str:
"""Create troubleshooting section"""
lines = ["## Troubleshooting"]
lines.append("")
# Common pitfalls
if guide.common_pitfalls:
lines.append("### Common Issues")
lines.append("")
for i, pitfall in enumerate(guide.common_pitfalls, 1):
lines.append(f"{i}. {pitfall}")
lines.append("")
# Specific troubleshooting
if guide.troubleshooting:
for problem, solution in guide.troubleshooting.items():
lines.append(f"### Problem: {problem}")
lines.append("")
lines.append(f"**Solution:** {solution}")
lines.append("")
return "\n".join(lines)
def _create_next_steps(self, guide: HowToGuide) -> str:
"""Create next steps and related guides"""
lines = ["## Next Steps"]
lines.append("")
# Variations if available
if guide.variations:
lines.append("**Try these variations:**")
for variation in guide.variations:
lines.append(f"- {variation}")
lines.append("")
# Related guides
if guide.related_guides:
lines.append("## Related Guides")
lines.append("")
for related in guide.related_guides:
lines.append(f"- [{related}]")
lines.append("")
return "\n".join(lines)
def _create_footer(self, guide: HowToGuide) -> str:
"""Create guide footer with metadata"""
source_info = []
if guide.source_files:
source_info.append(f"Source: {', '.join(guide.source_files)}")
source_info.append(f"Complexity: {guide.complexity_level.title()}")
source_info.append(f"Last updated: {datetime.now().strftime('%Y-%m-%d')}")
return f"---\n\n*{' | '.join(source_info)}*"
def generate_index(self, guides: list[HowToGuide]) -> str:
"""
Generate index/TOC markdown.
Args:
guides: List of all guides
Returns:
Index markdown string
"""
lines = ["# How-To Guides Index"]
lines.append("")
lines.append(f"**Total Guides**: {len(guides)}")
lines.append(f"**Last Updated**: {datetime.now().strftime('%Y-%m-%d')}")
lines.append("")
# Group by use case
by_use_case = defaultdict(list)
for guide in guides:
use_case = guide.use_case or "Other"
by_use_case[use_case].append(guide)
lines.append("## By Use Case")
lines.append("")
for use_case in sorted(by_use_case.keys()):
case_guides = by_use_case[use_case]
lines.append(f"### {use_case} ({len(case_guides)} guides)")
for guide in sorted(case_guides, key=lambda g: g.complexity_level):
# Create filename from guide title
filename = guide.title.lower().replace(" ", "-").replace(":", "")
lines.append(
f"- [How To: {guide.title}]({use_case.lower()}/{filename}.md) - {guide.complexity_level.title()}"
)
lines.append("")
# Group by difficulty
by_complexity = defaultdict(list)
for guide in guides:
by_complexity[guide.complexity_level].append(guide)
lines.append("## By Difficulty Level")
lines.append("")
for level in ["beginner", "intermediate", "advanced"]:
if level in by_complexity:
level_guides = by_complexity[level]
lines.append(f"### {level.title()} ({len(level_guides)} guides)")
for guide in sorted(level_guides, key=lambda g: g.title):
lines.append(f"- {guide.title}")
lines.append("")
return "\n".join(lines)
# ============================================================================
# HOW-TO GUIDE BUILDER (Main Orchestrator)
# ============================================================================
class HowToGuideBuilder:
"""Main orchestrator for building how-to guides from workflow examples"""
def __init__(self, enhance_with_ai: bool = True):
"""
Initialize guide builder.
Args:
enhance_with_ai: Enable AI enhancement (requires C3.6 AI analysis in workflows)
"""
self.enhance_with_ai = enhance_with_ai
self.analyzer = WorkflowAnalyzer()
self.grouper = WorkflowGrouper()
self.generator = GuideGenerator()
def build_guides_from_examples(
self,
examples: list[dict],
grouping_strategy: str = "ai-tutorial-group",
output_dir: Path | None = None,
enhance_with_ai: bool = True,
ai_mode: str = "auto",
) -> GuideCollection:
"""
Main entry point - build guides from workflow examples.
Args:
examples: List of TestExample dicts from C3.2
grouping_strategy: How to group workflows ("ai-tutorial-group", "file-path", etc.)
output_dir: Optional directory to save markdown files
enhance_with_ai: Enable comprehensive AI enhancement (default: True)
ai_mode: AI enhancement mode - "auto", "api", "local", or "none"
Returns:
GuideCollection with all generated guides
"""
logger.info(f"Building how-to guides from {len(examples)} examples...")
# Initialize AI enhancer if requested
enhancer = None
if enhance_with_ai and ai_mode != "none":
try:
from .guide_enhancer import GuideEnhancer
enhancer = GuideEnhancer(mode=ai_mode)
logger.info(f"✨ AI enhancement enabled (mode: {enhancer.mode})")
except Exception as e:
logger.warning(f"⚠️ AI enhancement unavailable: {e}")
logger.info("📝 Falling back to basic guide generation")
# Filter to workflow examples only
workflows = self._extract_workflow_examples(examples)
logger.info(f"Found {len(workflows)} workflow examples")
if not workflows:
logger.warning("No workflow examples found!")
return GuideCollection(
total_guides=0, guides_by_complexity={}, guides_by_use_case={}, guides=[]
)
# Group workflows
grouped_workflows = self.grouper.group_workflows(workflows, grouping_strategy)
logger.info(f"Grouped into {len(grouped_workflows)} guide categories")
# Build guides
guides = []
for title, workflow_group in grouped_workflows.items():
guide = self._create_guide(title, workflow_group, enhancer)
guides.append(guide)
# Create collection
collection = self._create_collection(guides)
# Save to files if output directory provided
if output_dir:
self._save_guides_to_files(collection, output_dir)
logger.info(f"✅ Generated {len(guides)} how-to guides")
return collection
def _extract_workflow_examples(self, examples: list[dict]) -> list[dict]:
"""Filter to workflow category only"""
return [ex for ex in examples if ex.get("category") == "workflow"]
def _create_guide(self, title: str, workflows: list[dict], enhancer=None) -> HowToGuide:
"""
Generate single guide from workflow(s).
Args:
title: Guide title
workflows: List of related workflow examples
enhancer: Optional GuideEnhancer instance for AI enhancement
Returns:
Complete HowToGuide object
"""
# Use first workflow as primary
primary_workflow = workflows[0]
# Analyze workflow to extract steps
steps, metadata = self.analyzer.analyze_workflow(primary_workflow)
# Generate guide ID
guide_id = hashlib.md5(title.encode()).hexdigest()[:12]
# Extract use case from AI analysis or title
use_case = title
if primary_workflow.get("ai_analysis"):
use_case = primary_workflow["ai_analysis"].get("tutorial_group", title)
# Determine overview
overview = self._generate_overview(primary_workflow, workflows)
# Extract tags
tags = primary_workflow.get("tags", [])
# Extract source files
source_files = [w.get("file_path", "") for w in workflows]
source_files = [
f"{Path(f).name}:{w.get('line_start', 0)}" for f, w in zip(source_files, workflows)
]
# Create guide
guide = HowToGuide(
guide_id=guide_id,
title=title,
overview=overview,
complexity_level=metadata.get("complexity_level", "intermediate"),
prerequisites=metadata.get("prerequisites", []),
required_imports=metadata.get("required_imports", []),
required_fixtures=metadata.get("required_fixtures", []),
workflows=workflows,
steps=steps,
use_case=use_case,
tags=tags,
estimated_time=metadata.get("estimated_time", "10 minutes"),
source_files=source_files,
)
# Add AI enhancements if enhancer is available
if enhancer:
self._enhance_guide_with_ai(guide, primary_workflow.get("ai_analysis", {}), enhancer)
elif self.enhance_with_ai and primary_workflow.get("ai_analysis"):
# Fallback to old enhancement method (basic)
self._enhance_guide_with_ai_basic(guide, primary_workflow["ai_analysis"])
return guide
def _generate_overview(self, primary_workflow: dict, _all_workflows: list[dict]) -> str:
"""Generate guide overview"""
# Try to get explanation from AI analysis
if primary_workflow.get("ai_analysis"):
explanation = primary_workflow["ai_analysis"].get("explanation")
if explanation:
return explanation
# Fallback to description
description = primary_workflow.get("description", "")
if description:
return description
# Final fallback
return f"Learn how to use {primary_workflow.get('test_name', 'this feature')} in your code."
def _enhance_guide_with_ai(self, guide: HowToGuide, _ai_analysis: dict, enhancer):
"""
Comprehensively enhance guide with AI using GuideEnhancer.
Applies all 5 enhancements:
1. Step descriptions - Natural language explanations 2. Troubleshooting - Diagnostic flows + solutions
3. Prerequisites - Why needed + setup
4. Next steps - Related guides, variations
5. Use cases - Real-world scenarios
Args:
guide: HowToGuide object to enhance
ai_analysis: AI analysis data from C3.6 (for context)
enhancer: GuideEnhancer instance
"""
# Prepare guide data for enhancer
guide_data = {
"title": guide.title,
"steps": [{"description": step.description, "code": step.code} for step in guide.steps],
"language": "python", # TODO: Detect from code
"prerequisites": guide.prerequisites,
"description": guide.overview,
}
# Call enhancer to get all 5 enhancements
enhanced_data = enhancer.enhance_guide(guide_data)
# Apply step enhancements
if "step_enhancements" in enhanced_data:
for enhancement in enhanced_data["step_enhancements"]:
idx = enhancement.step_index
if 0 <= idx < len(guide.steps):
guide.steps[idx].explanation = enhancement.explanation
guide.steps[idx].common_variations = enhancement.variations
# Apply detailed prerequisites
if "prerequisites_detailed" in enhanced_data:
guide.prerequisites_detailed = enhanced_data["prerequisites_detailed"]
# Apply troubleshooting
if "troubleshooting_detailed" in enhanced_data:
guide.troubleshooting_detailed = enhanced_data["troubleshooting_detailed"]
# Apply next steps
if "next_steps_detailed" in enhanced_data:
guide.next_steps_detailed = enhanced_data["next_steps_detailed"]
# Apply use cases
if "use_cases" in enhanced_data:
guide.use_cases = enhanced_data["use_cases"]
logger.info(f"✨ Enhanced guide '{guide.title}' with comprehensive AI improvements")
def _enhance_guide_with_ai_basic(self, guide: HowToGuide, ai_analysis: dict):
"""
Basic enhancement using pre-computed AI analysis from C3.6.
This is a fallback when GuideEnhancer is not available.
Args:
guide: HowToGuide object to enhance
ai_analysis: AI analysis data from C3.6
"""
# Add best practices as variations
best_practices = ai_analysis.get("best_practices", [])
guide.variations = best_practices
# Add common mistakes as pitfalls
common_mistakes = ai_analysis.get("common_mistakes", [])
guide.common_pitfalls = common_mistakes
# Add related examples as related guides
related_examples = ai_analysis.get("related_examples", [])
guide.related_guides = [f"How To: {ex}" for ex in related_examples]
# Enhance step explanations
for step in guide.steps:
# Add explanation to steps based on best practices
if best_practices and step.step_number <= len(best_practices):
step.explanation = best_practices[step.step_number - 1]
def _create_collection(self, guides: list[HowToGuide]) -> GuideCollection:
"""Create GuideCollection from guides"""
# Count by complexity
by_complexity = defaultdict(int)
for guide in guides:
by_complexity[guide.complexity_level] += 1
# Group by use case
by_use_case = defaultdict(list)
for guide in guides:
use_case = guide.use_case or "Other"
by_use_case[use_case].append(guide)
return GuideCollection(
total_guides=len(guides),
guides_by_complexity=dict(by_complexity),
guides_by_use_case=dict(by_use_case),
guides=guides,
)
def _save_guides_to_files(self, collection: GuideCollection, output_dir: Path):
"""Save guides to markdown files"""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"Saving guides to {output_dir}...")
# Save individual guides
for use_case, guides in collection.guides_by_use_case.items():
# Create use case directory
use_case_dir = output_dir / use_case.lower().replace(" ", "-")
use_case_dir.mkdir(parents=True, exist_ok=True)
for guide in guides:
# Generate filename from title
filename = guide.title.lower().replace(" ", "-").replace(":", "") + ".md"
file_path = use_case_dir / filename
# Generate and save markdown
markdown = self.generator.generate_guide_markdown(guide)
file_path.write_text(markdown, encoding="utf-8")
# Save index
index_markdown = self.generator.generate_index(collection.guides)
(output_dir / "index.md").write_text(index_markdown, encoding="utf-8")
logger.info(f"✅ Saved {collection.total_guides} guides + index to {output_dir}")
# ============================================================================
# CLI INTERFACE
# ============================================================================
def main():
"""CLI entry point for how-to guide builder"""
import argparse
import sys
parser = argparse.ArgumentParser(
description="Build how-to guides from workflow test examples (C3.3)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# From test examples JSON (C3.2 output)
skill-seekers build-how-to-guides --input test_examples.json
# From directory (extracts workflows)
skill-seekers build-how-to-guides tests/
# Custom grouping strategy
skill-seekers build-how-to-guides tests/ --group-by file-path
# Custom output directory
skill-seekers build-how-to-guides tests/ --output tutorials/
# Without AI enhancement
skill-seekers build-how-to-guides tests/ --no-ai
Grouping Strategies:
- ai-tutorial-group: Use AI-generated tutorial groups (default, best)
- file-path: Group by source test file
- test-name: Group by test name patterns
- complexity: Group by difficulty level
""",
)
parser.add_argument(
"input", nargs="?", help="Input: directory with test files OR test_examples.json file"
)
parser.add_argument(
"--input", dest="input_file", help="Input JSON file with test examples (from C3.2)"
)
parser.add_argument(
"--output",
default="output/codebase/tutorials",
help="Output directory for generated guides (default: output/codebase/tutorials)",
)
parser.add_argument(
"--group-by",
choices=["ai-tutorial-group", "file-path", "test-name", "complexity"],
default="ai-tutorial-group",
help="Grouping strategy (default: ai-tutorial-group)",
)
parser.add_argument("--no-ai", action="store_true", help="Disable AI enhancement")
parser.add_argument(
"--json-output", action="store_true", help="Output JSON summary instead of markdown files"
)
args = parser.parse_args()
# Determine input source
input_path = args.input or args.input_file
if not input_path:
parser.print_help()
print("\n❌ Error: No input provided")
print(" Provide either a directory or --input JSON file")
sys.exit(1)
input_path = Path(input_path)
# Load examples
examples = []
if input_path.is_file() and input_path.suffix == ".json":
# Load from JSON file
logger.info(f"Loading examples from {input_path}...")
with open(input_path) as f:
data = json.load(f)
if isinstance(data, dict) and "examples" in data:
examples = data["examples"]
elif isinstance(data, list):
examples = data
else:
print(f"❌ Error: Invalid JSON format in {input_path}")
sys.exit(1)
elif input_path.is_dir():
# Extract from directory using test example extractor
print("⚠️ Directory input requires test example extractor")
print(" Please use test_examples.json output from C3.2")
print(f" Or run: skill-seekers extract-test-examples {input_path} --json > examples.json")
sys.exit(1)
else:
print(f"❌ Error: Input path not found: {input_path}")
sys.exit(1)
# Build guides
builder = HowToGuideBuilder(enhance_with_ai=not args.no_ai)
output_dir = Path(args.output) if not args.json_output else None
collection = builder.build_guides_from_examples(
examples, grouping_strategy=args.group_by, output_dir=output_dir
)
# Output results
if args.json_output:
# JSON output
print(json.dumps(collection.to_dict(), indent=2))
else:
# Summary
print()
print("=" * 60)
print("HOW-TO GUIDES GENERATED")
print("=" * 60)
print()
print(f"Total Guides: {collection.total_guides}")
print()
print("By Complexity:")
for level, count in collection.guides_by_complexity.items():
print(f" - {level.title()}: {count} guides")
print()
print("By Use Case:")
for use_case, guides in collection.guides_by_use_case.items():
print(f" - {use_case}: {len(guides)} guides")
print()
if output_dir:
print(f"📁 Output directory: {output_dir}")
print(f"📄 Index file: {output_dir}/index.md")
print()
sys.exit(0)
if __name__ == "__main__":
main()