skill-seekers 2.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. skill_seekers/__init__.py +22 -0
  2. skill_seekers/cli/__init__.py +39 -0
  3. skill_seekers/cli/adaptors/__init__.py +120 -0
  4. skill_seekers/cli/adaptors/base.py +221 -0
  5. skill_seekers/cli/adaptors/claude.py +485 -0
  6. skill_seekers/cli/adaptors/gemini.py +453 -0
  7. skill_seekers/cli/adaptors/markdown.py +269 -0
  8. skill_seekers/cli/adaptors/openai.py +503 -0
  9. skill_seekers/cli/ai_enhancer.py +310 -0
  10. skill_seekers/cli/api_reference_builder.py +373 -0
  11. skill_seekers/cli/architectural_pattern_detector.py +525 -0
  12. skill_seekers/cli/code_analyzer.py +1462 -0
  13. skill_seekers/cli/codebase_scraper.py +1225 -0
  14. skill_seekers/cli/config_command.py +563 -0
  15. skill_seekers/cli/config_enhancer.py +431 -0
  16. skill_seekers/cli/config_extractor.py +871 -0
  17. skill_seekers/cli/config_manager.py +452 -0
  18. skill_seekers/cli/config_validator.py +394 -0
  19. skill_seekers/cli/conflict_detector.py +528 -0
  20. skill_seekers/cli/constants.py +72 -0
  21. skill_seekers/cli/dependency_analyzer.py +757 -0
  22. skill_seekers/cli/doc_scraper.py +2332 -0
  23. skill_seekers/cli/enhance_skill.py +488 -0
  24. skill_seekers/cli/enhance_skill_local.py +1096 -0
  25. skill_seekers/cli/enhance_status.py +194 -0
  26. skill_seekers/cli/estimate_pages.py +433 -0
  27. skill_seekers/cli/generate_router.py +1209 -0
  28. skill_seekers/cli/github_fetcher.py +534 -0
  29. skill_seekers/cli/github_scraper.py +1466 -0
  30. skill_seekers/cli/guide_enhancer.py +723 -0
  31. skill_seekers/cli/how_to_guide_builder.py +1267 -0
  32. skill_seekers/cli/install_agent.py +461 -0
  33. skill_seekers/cli/install_skill.py +178 -0
  34. skill_seekers/cli/language_detector.py +614 -0
  35. skill_seekers/cli/llms_txt_detector.py +60 -0
  36. skill_seekers/cli/llms_txt_downloader.py +104 -0
  37. skill_seekers/cli/llms_txt_parser.py +150 -0
  38. skill_seekers/cli/main.py +558 -0
  39. skill_seekers/cli/markdown_cleaner.py +132 -0
  40. skill_seekers/cli/merge_sources.py +806 -0
  41. skill_seekers/cli/package_multi.py +77 -0
  42. skill_seekers/cli/package_skill.py +241 -0
  43. skill_seekers/cli/pattern_recognizer.py +1825 -0
  44. skill_seekers/cli/pdf_extractor_poc.py +1166 -0
  45. skill_seekers/cli/pdf_scraper.py +617 -0
  46. skill_seekers/cli/quality_checker.py +519 -0
  47. skill_seekers/cli/rate_limit_handler.py +438 -0
  48. skill_seekers/cli/resume_command.py +160 -0
  49. skill_seekers/cli/run_tests.py +230 -0
  50. skill_seekers/cli/setup_wizard.py +93 -0
  51. skill_seekers/cli/split_config.py +390 -0
  52. skill_seekers/cli/swift_patterns.py +560 -0
  53. skill_seekers/cli/test_example_extractor.py +1081 -0
  54. skill_seekers/cli/test_unified_simple.py +179 -0
  55. skill_seekers/cli/unified_codebase_analyzer.py +572 -0
  56. skill_seekers/cli/unified_scraper.py +932 -0
  57. skill_seekers/cli/unified_skill_builder.py +1605 -0
  58. skill_seekers/cli/upload_skill.py +162 -0
  59. skill_seekers/cli/utils.py +432 -0
  60. skill_seekers/mcp/__init__.py +33 -0
  61. skill_seekers/mcp/agent_detector.py +316 -0
  62. skill_seekers/mcp/git_repo.py +273 -0
  63. skill_seekers/mcp/server.py +231 -0
  64. skill_seekers/mcp/server_fastmcp.py +1249 -0
  65. skill_seekers/mcp/server_legacy.py +2302 -0
  66. skill_seekers/mcp/source_manager.py +285 -0
  67. skill_seekers/mcp/tools/__init__.py +115 -0
  68. skill_seekers/mcp/tools/config_tools.py +251 -0
  69. skill_seekers/mcp/tools/packaging_tools.py +826 -0
  70. skill_seekers/mcp/tools/scraping_tools.py +842 -0
  71. skill_seekers/mcp/tools/source_tools.py +828 -0
  72. skill_seekers/mcp/tools/splitting_tools.py +212 -0
  73. skill_seekers/py.typed +0 -0
  74. skill_seekers-2.7.3.dist-info/METADATA +2027 -0
  75. skill_seekers-2.7.3.dist-info/RECORD +79 -0
  76. skill_seekers-2.7.3.dist-info/WHEEL +5 -0
  77. skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
  78. skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
  79. skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1267 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ How-To Guide Builder (C3.3) - Build step-by-step guides from workflow examples
4
+
5
+ Transforms multi-step test workflows (from C3.2) into educational "how to" guides with:
6
+ - Step-by-step breakdowns
7
+ - Prerequisites and setup requirements
8
+ - Verification checkpoints
9
+ - Troubleshooting sections
10
+ - Complexity levels (beginner/intermediate/advanced)
11
+
12
+ Usage:
13
+ # From test examples JSON
14
+ skill-seekers build-how-to-guides --input test_examples.json
15
+
16
+ # From directory (auto-extracts workflows)
17
+ skill-seekers build-how-to-guides tests/
18
+
19
+ # With AI enhancement
20
+ skill-seekers build-how-to-guides tests/ --enhance-with-ai
21
+
22
+ Example workflow → guide transformation:
23
+ Input: Multi-step test showing user registration + login + session
24
+ Output: "How To: Complete User Authentication" guide with:
25
+ - 5 discrete steps with explanations
26
+ - Prerequisites (database, email service)
27
+ - Verification points at each step
28
+ - Common pitfalls and troubleshooting
29
+ - Related guides suggestions
30
+ """
31
+
32
+ import ast
33
+ import hashlib
34
+ import json
35
+ import logging
36
+ import re
37
+ from collections import defaultdict
38
+ from dataclasses import asdict, dataclass, field
39
+ from datetime import datetime
40
+ from pathlib import Path
41
+ from typing import Literal
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ # ============================================================================
47
+ # DATA MODELS
48
+ # ============================================================================
49
+
50
+
51
+ @dataclass
52
+ class PrerequisiteItem:
53
+ """Enhanced prerequisite with explanation (AI enhancement)"""
54
+
55
+ name: str
56
+ why: str # Why this is needed
57
+ setup: str # How to install/configure
58
+
59
+
60
+ @dataclass
61
+ class TroubleshootingItem:
62
+ """Enhanced troubleshooting with solutions (AI enhancement)"""
63
+
64
+ problem: str
65
+ symptoms: list[str] = field(default_factory=list) # How to recognize this issue
66
+ solution: str = "" # Step-by-step fix
67
+ diagnostic_steps: list[str] = field(default_factory=list) # How to diagnose
68
+
69
+
70
+ @dataclass
71
+ class WorkflowStep:
72
+ """Single step in a workflow guide"""
73
+
74
+ step_number: int
75
+ code: str
76
+ description: str
77
+ expected_result: str | None = None
78
+ verification: str | None = None # Assertion or checkpoint
79
+ setup_required: str | None = None
80
+ explanation: str | None = None # Why this step matters
81
+ common_pitfall: str | None = None # Warning for this step
82
+ common_variations: list[str] = field(default_factory=list) # AI: Alternative approaches
83
+
84
+
85
+ @dataclass
86
+ class HowToGuide:
87
+ """Complete how-to guide generated from workflow(s)"""
88
+
89
+ guide_id: str
90
+ title: str
91
+ overview: str
92
+ complexity_level: Literal["beginner", "intermediate", "advanced"]
93
+
94
+ # Prerequisites
95
+ prerequisites: list[str] = field(default_factory=list)
96
+ required_imports: list[str] = field(default_factory=list)
97
+ required_fixtures: list[str] = field(default_factory=list)
98
+
99
+ # Content
100
+ workflows: list[dict] = field(default_factory=list) # Source workflow examples
101
+ steps: list[WorkflowStep] = field(default_factory=list)
102
+
103
+ # Metadata
104
+ use_case: str = ""
105
+ tags: list[str] = field(default_factory=list)
106
+ estimated_time: str = "10 minutes"
107
+ source_files: list[str] = field(default_factory=list)
108
+
109
+ # Optional AI enhancement (basic)
110
+ common_pitfalls: list[str] = field(default_factory=list)
111
+ troubleshooting: dict[str, str] = field(default_factory=dict)
112
+ variations: list[str] = field(default_factory=list)
113
+ related_guides: list[str] = field(default_factory=list)
114
+
115
+ # AI enhancement (comprehensive - NEW)
116
+ prerequisites_detailed: list[PrerequisiteItem] = field(default_factory=list)
117
+ troubleshooting_detailed: list[TroubleshootingItem] = field(default_factory=list)
118
+ next_steps_detailed: list[str] = field(default_factory=list)
119
+ use_cases: list[str] = field(default_factory=list)
120
+
121
+ def to_dict(self) -> dict:
122
+ """Convert to dictionary"""
123
+ result = asdict(self)
124
+ # Convert WorkflowStep objects to dicts
125
+ result["steps"] = [asdict(step) for step in self.steps]
126
+ return result
127
+
128
+
129
+ @dataclass
130
+ class GuideCollection:
131
+ """Collection of guides organized by category"""
132
+
133
+ total_guides: int
134
+ guides_by_complexity: dict[str, int]
135
+ guides_by_use_case: dict[str, list[HowToGuide]]
136
+ guides: list[HowToGuide]
137
+
138
+ def to_dict(self) -> dict:
139
+ """Convert to dictionary"""
140
+ return {
141
+ "total_guides": self.total_guides,
142
+ "guides_by_complexity": self.guides_by_complexity,
143
+ "guides_by_use_case": {
144
+ k: [g.to_dict() for g in v] for k, v in self.guides_by_use_case.items()
145
+ },
146
+ "guides": [g.to_dict() for g in self.guides],
147
+ }
148
+
149
+
150
+ # ============================================================================
151
+ # WORKFLOW ANALYZER
152
+ # ============================================================================
153
+
154
+
155
+ class WorkflowAnalyzer:
156
+ """Analyze workflow examples to extract steps and metadata"""
157
+
158
+ def analyze_workflow(self, workflow: dict) -> tuple[list[WorkflowStep], dict]:
159
+ """
160
+ Deep analysis of workflow structure.
161
+
162
+ Args:
163
+ workflow: TestExample dict from C3.2
164
+
165
+ Returns:
166
+ (steps, metadata) where metadata includes prerequisites, complexity, etc.
167
+ """
168
+ code = workflow.get("code", "")
169
+ language = workflow.get("language", "python").lower()
170
+
171
+ # Extract steps based on language
172
+ if language == "python":
173
+ steps = self._extract_steps_python(code, workflow)
174
+ else:
175
+ steps = self._extract_steps_heuristic(code, workflow)
176
+
177
+ # Detect prerequisites
178
+ metadata = self._detect_prerequisites(workflow)
179
+
180
+ # Find verification points
181
+ verifications = self._find_verification_points(code)
182
+
183
+ # Associate verifications with steps
184
+ for i, step in enumerate(steps):
185
+ if i < len(verifications):
186
+ step.verification = verifications[i]
187
+
188
+ # Calculate complexity
189
+ metadata["complexity_level"] = self._calculate_complexity(steps, workflow)
190
+ metadata["estimated_time"] = self._estimate_time(steps)
191
+
192
+ return steps, metadata
193
+
194
+ def _extract_steps_python(self, code: str, workflow: dict) -> list[WorkflowStep]:
195
+ """Extract steps from Python code using AST"""
196
+ steps = []
197
+
198
+ try:
199
+ tree = ast.parse(code)
200
+ statements = []
201
+
202
+ # Collect all statements
203
+ for node in ast.walk(tree):
204
+ if isinstance(node, (ast.Assign, ast.Expr, ast.Assert)):
205
+ statements.append(node)
206
+
207
+ step_num = 1
208
+ for stmt in statements:
209
+ # Skip assertions for now (they're verifications)
210
+ if isinstance(stmt, ast.Assert):
211
+ continue
212
+
213
+ # Get code for this statement
214
+ step_code = ast.get_source_segment(code, stmt)
215
+ if not step_code:
216
+ continue
217
+
218
+ # Generate description from code
219
+ description = self._generate_step_description(stmt, step_code)
220
+
221
+ # Check if next statement is assertion (verification)
222
+ idx = statements.index(stmt)
223
+ verification = None
224
+ if idx + 1 < len(statements) and isinstance(statements[idx + 1], ast.Assert):
225
+ verification = ast.get_source_segment(code, statements[idx + 1])
226
+
227
+ steps.append(
228
+ WorkflowStep(
229
+ step_number=step_num,
230
+ code=step_code,
231
+ description=description,
232
+ verification=verification,
233
+ )
234
+ )
235
+ step_num += 1
236
+
237
+ except SyntaxError:
238
+ # Fall back to heuristic method
239
+ return self._extract_steps_heuristic(code, workflow)
240
+
241
+ return steps
242
+
243
+ def _extract_steps_heuristic(self, code: str, _workflow: dict) -> list[WorkflowStep]:
244
+ """Extract steps using heuristics (for non-Python or invalid syntax)"""
245
+ steps = []
246
+ lines = code.split("\n")
247
+
248
+ current_step = []
249
+ step_num = 1
250
+
251
+ for line in lines:
252
+ line_stripped = line.strip()
253
+
254
+ # Skip empty lines and comments
255
+ if not line_stripped or line_stripped.startswith("#"):
256
+ if current_step:
257
+ # End of current step
258
+ step_code = "\n".join(current_step)
259
+ description = self._infer_description_from_code(step_code)
260
+
261
+ steps.append(
262
+ WorkflowStep(
263
+ step_number=step_num,
264
+ code=step_code,
265
+ description=description,
266
+ )
267
+ )
268
+ step_num += 1
269
+ current_step = []
270
+ continue
271
+
272
+ current_step.append(line)
273
+
274
+ # Add final step
275
+ if current_step:
276
+ step_code = "\n".join(current_step)
277
+ description = self._infer_description_from_code(step_code)
278
+ steps.append(
279
+ WorkflowStep(step_number=step_num, code=step_code, description=description)
280
+ )
281
+
282
+ return steps
283
+
284
+ def _generate_step_description(self, node: ast.AST, code: str) -> str:
285
+ """Generate human-readable description from AST node"""
286
+ if isinstance(node, ast.Assign):
287
+ targets = [self._get_name(t) for t in node.targets]
288
+ value_desc = self._describe_value(node.value)
289
+ return f"Assign {', '.join(targets)} = {value_desc}"
290
+
291
+ elif isinstance(node, ast.Expr):
292
+ if isinstance(node.value, ast.Call):
293
+ func_name = self._get_name(node.value.func)
294
+ return f"Call {func_name}()"
295
+
296
+ return code.split("\n")[0] # First line as fallback
297
+
298
+ def _describe_value(self, node: ast.AST) -> str:
299
+ """Describe AST value node"""
300
+ if isinstance(node, ast.Call):
301
+ func_name = self._get_name(node.func)
302
+ return f"{func_name}(...)"
303
+ elif isinstance(node, ast.Constant):
304
+ return repr(node.value)
305
+ elif isinstance(node, ast.Name):
306
+ return node.id
307
+ return "value"
308
+
309
+ def _get_name(self, node: ast.AST) -> str:
310
+ """Extract name from AST node"""
311
+ if isinstance(node, ast.Name):
312
+ return node.id
313
+ elif isinstance(node, ast.Attribute):
314
+ return f"{self._get_name(node.value)}.{node.attr}"
315
+ elif isinstance(node, ast.Call):
316
+ return self._get_name(node.func)
317
+ return "unknown"
318
+
319
+ def _infer_description_from_code(self, code: str) -> str:
320
+ """Infer description from code using patterns"""
321
+ code = code.strip()
322
+
323
+ # Method call patterns
324
+ if "(" in code and ")" in code:
325
+ match = re.search(r"(\w+)\s*\(", code)
326
+ if match:
327
+ return f"Call {match.group(1)}()"
328
+
329
+ # Assignment patterns
330
+ if "=" in code and not code.startswith("assert"):
331
+ parts = code.split("=", 1)
332
+ var_name = parts[0].strip()
333
+ return f"Create {var_name}"
334
+
335
+ # Assertion patterns
336
+ if code.startswith("assert"):
337
+ return "Verify result"
338
+
339
+ return code.split("\n")[0] # First line
340
+
341
+ def _detect_prerequisites(self, workflow: dict) -> dict:
342
+ """Detect prerequisites from workflow"""
343
+ metadata = {
344
+ "prerequisites": [],
345
+ "required_imports": [],
346
+ "required_fixtures": [],
347
+ }
348
+
349
+ # Get dependencies from workflow
350
+ dependencies = workflow.get("dependencies", [])
351
+ metadata["required_imports"] = dependencies
352
+
353
+ # Get setup code
354
+ setup_code = workflow.get("setup_code")
355
+ if setup_code:
356
+ metadata["prerequisites"].append("Setup code must be executed first")
357
+
358
+ # Check for common fixtures in test name or setup
359
+ test_name = workflow.get("test_name", "").lower()
360
+ if "database" in test_name or (setup_code and "database" in setup_code.lower()):
361
+ metadata["required_fixtures"].append("database")
362
+ if "api" in test_name or (setup_code and "api" in setup_code.lower()):
363
+ metadata["required_fixtures"].append("api_client")
364
+
365
+ return metadata
366
+
367
+ def _find_verification_points(self, code: str) -> list[str]:
368
+ """Find assertion statements in code"""
369
+ verifications = []
370
+
371
+ for line in code.split("\n"):
372
+ line_stripped = line.strip()
373
+ if line_stripped.startswith("assert"):
374
+ verifications.append(line_stripped)
375
+
376
+ return verifications
377
+
378
+ def _calculate_complexity(self, steps: list[WorkflowStep], workflow: dict) -> str:
379
+ """Calculate complexity level"""
380
+ num_steps = len(steps)
381
+
382
+ # Check for advanced patterns
383
+ code = workflow.get("code", "")
384
+ has_async = "async" in code or "await" in code
385
+ has_mock = "mock" in code.lower() or "patch" in code.lower()
386
+ has_error_handling = "try" in code or "except" in code
387
+
388
+ _complexity_score = workflow.get("complexity_score", 0.5)
389
+
390
+ # Determine level
391
+ if num_steps <= 3 and not has_async and not has_mock:
392
+ return "beginner"
393
+ elif num_steps >= 8 or has_async or has_error_handling:
394
+ return "advanced"
395
+ else:
396
+ return "intermediate"
397
+
398
+ def _estimate_time(self, steps: list[WorkflowStep]) -> str:
399
+ """Estimate time to complete guide"""
400
+ num_steps = len(steps)
401
+
402
+ if num_steps <= 3:
403
+ return "5 minutes"
404
+ elif num_steps <= 6:
405
+ return "10 minutes"
406
+ elif num_steps <= 10:
407
+ return "15 minutes"
408
+ else:
409
+ return "20 minutes"
410
+
411
+
412
+ # ============================================================================
413
+ # WORKFLOW GROUPER
414
+ # ============================================================================
415
+
416
+
417
+ class WorkflowGrouper:
418
+ """Group related workflows into coherent guides"""
419
+
420
+ def group_workflows(
421
+ self, workflows: list[dict], strategy: str = "ai-tutorial-group"
422
+ ) -> dict[str, list[dict]]:
423
+ """
424
+ Group workflows using specified strategy.
425
+
426
+ Args:
427
+ workflows: List of workflow examples
428
+ strategy: "ai-tutorial-group", "file-path", "test-name", "complexity"
429
+
430
+ Returns:
431
+ Dict mapping group name to list of workflows
432
+ """
433
+ if strategy == "ai-tutorial-group":
434
+ return self._group_by_ai_tutorial_group(workflows)
435
+ elif strategy == "file-path":
436
+ return self._group_by_file_path(workflows)
437
+ elif strategy == "test-name":
438
+ return self._group_by_test_name(workflows)
439
+ elif strategy == "complexity":
440
+ return self._group_by_complexity(workflows)
441
+ else:
442
+ # Default: AI tutorial group with fallback
443
+ groups = self._group_by_ai_tutorial_group(workflows)
444
+ if not groups or len(groups) == len(workflows):
445
+ # Fallback to file path if AI grouping didn't work well
446
+ groups = self._group_by_file_path(workflows)
447
+ return groups
448
+
449
+ def _group_by_ai_tutorial_group(self, workflows: list[dict]) -> dict[str, list[dict]]:
450
+ """Group by AI-generated tutorial_group (from C3.6 enhancement)"""
451
+ groups = defaultdict(list)
452
+ ungrouped = []
453
+
454
+ for workflow in workflows:
455
+ ai_analysis = workflow.get("ai_analysis", {})
456
+ tutorial_group = ai_analysis.get("tutorial_group")
457
+
458
+ if tutorial_group:
459
+ groups[tutorial_group].append(workflow)
460
+ else:
461
+ ungrouped.append(workflow)
462
+
463
+ # Put ungrouped workflows in individual guides
464
+ for workflow in ungrouped:
465
+ test_name = workflow.get("test_name", "Unknown")
466
+ # Clean test name for title
467
+ title = self._clean_test_name(test_name)
468
+ groups[title] = [workflow]
469
+
470
+ return dict(groups)
471
+
472
+ def _group_by_file_path(self, workflows: list[dict]) -> dict[str, list[dict]]:
473
+ """Group workflows from same test file"""
474
+ groups = defaultdict(list)
475
+
476
+ for workflow in workflows:
477
+ file_path = workflow.get("file_path", "")
478
+ # Extract meaningful name from file path
479
+ file_name = Path(file_path).stem if file_path else "Unknown"
480
+ # Remove test_ prefix
481
+ group_name = file_name.replace("test_", "").replace("_", " ").title()
482
+ groups[group_name].append(workflow)
483
+
484
+ return dict(groups)
485
+
486
+ def _group_by_test_name(self, workflows: list[dict]) -> dict[str, list[dict]]:
487
+ """Group by common test name prefixes"""
488
+ groups = defaultdict(list)
489
+
490
+ for workflow in workflows:
491
+ test_name = workflow.get("test_name", "")
492
+ # Extract prefix (e.g., test_auth_login → auth)
493
+ prefix = self._extract_prefix(test_name)
494
+ groups[prefix].append(workflow)
495
+
496
+ return dict(groups)
497
+
498
+ def _group_by_complexity(self, workflows: list[dict]) -> dict[str, list[dict]]:
499
+ """Group by complexity level"""
500
+ groups = {"Beginner": [], "Intermediate": [], "Advanced": []}
501
+
502
+ for workflow in workflows:
503
+ complexity_score = workflow.get("complexity_score", 0.5)
504
+
505
+ if complexity_score < 0.4:
506
+ groups["Beginner"].append(workflow)
507
+ elif complexity_score < 0.7:
508
+ groups["Intermediate"].append(workflow)
509
+ else:
510
+ groups["Advanced"].append(workflow)
511
+
512
+ # Remove empty groups
513
+ return {k: v for k, v in groups.items() if v}
514
+
515
+ def _clean_test_name(self, test_name: str) -> str:
516
+ """Clean test name to readable title"""
517
+ # Remove test_ prefix
518
+ name = test_name.replace("test_", "")
519
+ # Replace underscores with spaces
520
+ name = name.replace("_", " ")
521
+ # Title case
522
+ return name.title()
523
+
524
+ def _extract_prefix(self, test_name: str) -> str:
525
+ """Extract prefix from test name"""
526
+ # Remove test_ prefix
527
+ name = test_name.replace("test_", "")
528
+ # Get first part before underscore
529
+ parts = name.split("_")
530
+ if len(parts) > 1:
531
+ return parts[0].title()
532
+ return self._clean_test_name(test_name)
533
+
534
+
535
+ # ============================================================================
536
+ # GUIDE GENERATOR
537
+ # ============================================================================
538
+
539
+
540
+ class GuideGenerator:
541
+ """Generate markdown guides from workflow data"""
542
+
543
+ def generate_guide_markdown(self, guide: HowToGuide) -> str:
544
+ """
545
+ Generate complete markdown guide.
546
+
547
+ Args:
548
+ guide: HowToGuide object with all data
549
+
550
+ Returns:
551
+ Complete markdown string
552
+ """
553
+ sections = []
554
+
555
+ # Header
556
+ sections.append(self._create_header(guide))
557
+
558
+ # Overview
559
+ sections.append(self._create_overview(guide))
560
+
561
+ # Prerequisites
562
+ if guide.prerequisites or guide.required_imports or guide.required_fixtures:
563
+ sections.append(self._create_prerequisites(guide))
564
+
565
+ # Step-by-step guide
566
+ sections.append(self._create_steps_section(guide.steps))
567
+
568
+ # Complete example
569
+ sections.append(self._create_complete_example(guide))
570
+
571
+ # Troubleshooting (if available)
572
+ if guide.common_pitfalls or guide.troubleshooting:
573
+ sections.append(self._create_troubleshooting(guide))
574
+
575
+ # Next steps and related guides
576
+ sections.append(self._create_next_steps(guide))
577
+
578
+ # Footer
579
+ sections.append(self._create_footer(guide))
580
+
581
+ return "\n\n".join(sections)
582
+
583
+ def _create_header(self, guide: HowToGuide) -> str:
584
+ """Create guide header with metadata"""
585
+ lines = [f"# How To: {guide.title}"]
586
+ lines.append("")
587
+ lines.append(f"**Difficulty**: {guide.complexity_level.title()}")
588
+ lines.append(f"**Estimated Time**: {guide.estimated_time}")
589
+
590
+ if guide.tags:
591
+ lines.append(f"**Tags**: {', '.join(guide.tags)}")
592
+
593
+ return "\n".join(lines)
594
+
595
+ def _create_overview(self, guide: HowToGuide) -> str:
596
+ """Create overview section"""
597
+ return f"## Overview\n\n{guide.overview}"
598
+
599
+ def _create_prerequisites(self, guide: HowToGuide) -> str:
600
+ """Create prerequisites section"""
601
+ lines = ["## Prerequisites"]
602
+ lines.append("")
603
+
604
+ # Checklist format
605
+ if guide.prerequisites:
606
+ for prereq in guide.prerequisites:
607
+ lines.append(f"- [ ] {prereq}")
608
+ lines.append("")
609
+
610
+ # Required imports
611
+ if guide.required_imports:
612
+ lines.append("**Required Modules:**")
613
+ for imp in guide.required_imports:
614
+ lines.append(f"- `{imp}`")
615
+ lines.append("")
616
+
617
+ # Required fixtures
618
+ if guide.required_fixtures:
619
+ lines.append("**Required Fixtures:**")
620
+ for fixture in guide.required_fixtures:
621
+ lines.append(f"- `{fixture}` fixture")
622
+ lines.append("")
623
+
624
+ # Setup code if available
625
+ if guide.workflows and guide.workflows[0].get("setup_code"):
626
+ setup_code = guide.workflows[0]["setup_code"]
627
+ lines.append("**Setup Required:**")
628
+ lines.append("```python")
629
+ lines.append(setup_code)
630
+ lines.append("```")
631
+
632
+ return "\n".join(lines)
633
+
634
+ def _create_steps_section(self, steps: list[WorkflowStep]) -> str:
635
+ """Create step-by-step guide section"""
636
+ lines = ["## Step-by-Step Guide"]
637
+ lines.append("")
638
+
639
+ for step in steps:
640
+ lines.append(f"### Step {step.step_number}: {step.description}")
641
+ lines.append("")
642
+
643
+ # Explanation if available
644
+ if step.explanation:
645
+ lines.append(f"**What you're doing:** {step.explanation}")
646
+ lines.append("")
647
+
648
+ # Code
649
+ lines.append("```python")
650
+ lines.append(step.code)
651
+ lines.append("```")
652
+ lines.append("")
653
+
654
+ # Expected result
655
+ if step.expected_result:
656
+ lines.append(f"**Expected Result:** {step.expected_result}")
657
+ lines.append("")
658
+
659
+ # Verification checkpoint
660
+ if step.verification:
661
+ lines.append("**Verification:**")
662
+ lines.append("```python")
663
+ lines.append(step.verification)
664
+ lines.append("```")
665
+ lines.append("")
666
+
667
+ # Common pitfall warning
668
+ if step.common_pitfall:
669
+ lines.append(f"⚠️ **Common Pitfall:** {step.common_pitfall}")
670
+ lines.append("")
671
+
672
+ return "\n".join(lines)
673
+
674
+ def _create_complete_example(self, guide: HowToGuide) -> str:
675
+ """Create complete working example"""
676
+ lines = ["## Complete Example"]
677
+ lines.append("")
678
+ lines.append("```python")
679
+
680
+ # If we have workflows, use the first one's code
681
+ if guide.workflows:
682
+ workflow = guide.workflows[0]
683
+
684
+ # Add setup code if present
685
+ if workflow.get("setup_code"):
686
+ lines.append("# Setup")
687
+ lines.append(workflow["setup_code"])
688
+ lines.append("")
689
+
690
+ # Add main workflow code
691
+ lines.append("# Workflow")
692
+ lines.append(workflow.get("code", ""))
693
+ else:
694
+ # Combine all steps
695
+ for step in guide.steps:
696
+ lines.append(f"# Step {step.step_number}: {step.description}")
697
+ lines.append(step.code)
698
+ if step.verification:
699
+ lines.append(step.verification)
700
+ lines.append("")
701
+
702
+ lines.append("```")
703
+ return "\n".join(lines)
704
+
705
+ def _create_troubleshooting(self, guide: HowToGuide) -> str:
706
+ """Create troubleshooting section"""
707
+ lines = ["## Troubleshooting"]
708
+ lines.append("")
709
+
710
+ # Common pitfalls
711
+ if guide.common_pitfalls:
712
+ lines.append("### Common Issues")
713
+ lines.append("")
714
+ for i, pitfall in enumerate(guide.common_pitfalls, 1):
715
+ lines.append(f"{i}. {pitfall}")
716
+ lines.append("")
717
+
718
+ # Specific troubleshooting
719
+ if guide.troubleshooting:
720
+ for problem, solution in guide.troubleshooting.items():
721
+ lines.append(f"### Problem: {problem}")
722
+ lines.append("")
723
+ lines.append(f"**Solution:** {solution}")
724
+ lines.append("")
725
+
726
+ return "\n".join(lines)
727
+
728
+ def _create_next_steps(self, guide: HowToGuide) -> str:
729
+ """Create next steps and related guides"""
730
+ lines = ["## Next Steps"]
731
+ lines.append("")
732
+
733
+ # Variations if available
734
+ if guide.variations:
735
+ lines.append("**Try these variations:**")
736
+ for variation in guide.variations:
737
+ lines.append(f"- {variation}")
738
+ lines.append("")
739
+
740
+ # Related guides
741
+ if guide.related_guides:
742
+ lines.append("## Related Guides")
743
+ lines.append("")
744
+ for related in guide.related_guides:
745
+ lines.append(f"- [{related}]")
746
+ lines.append("")
747
+
748
+ return "\n".join(lines)
749
+
750
+ def _create_footer(self, guide: HowToGuide) -> str:
751
+ """Create guide footer with metadata"""
752
+ source_info = []
753
+ if guide.source_files:
754
+ source_info.append(f"Source: {', '.join(guide.source_files)}")
755
+ source_info.append(f"Complexity: {guide.complexity_level.title()}")
756
+ source_info.append(f"Last updated: {datetime.now().strftime('%Y-%m-%d')}")
757
+
758
+ return f"---\n\n*{' | '.join(source_info)}*"
759
+
760
+ def generate_index(self, guides: list[HowToGuide]) -> str:
761
+ """
762
+ Generate index/TOC markdown.
763
+
764
+ Args:
765
+ guides: List of all guides
766
+
767
+ Returns:
768
+ Index markdown string
769
+ """
770
+ lines = ["# How-To Guides Index"]
771
+ lines.append("")
772
+ lines.append(f"**Total Guides**: {len(guides)}")
773
+ lines.append(f"**Last Updated**: {datetime.now().strftime('%Y-%m-%d')}")
774
+ lines.append("")
775
+
776
+ # Group by use case
777
+ by_use_case = defaultdict(list)
778
+ for guide in guides:
779
+ use_case = guide.use_case or "Other"
780
+ by_use_case[use_case].append(guide)
781
+
782
+ lines.append("## By Use Case")
783
+ lines.append("")
784
+
785
+ for use_case in sorted(by_use_case.keys()):
786
+ case_guides = by_use_case[use_case]
787
+ lines.append(f"### {use_case} ({len(case_guides)} guides)")
788
+ for guide in sorted(case_guides, key=lambda g: g.complexity_level):
789
+ # Create filename from guide title
790
+ filename = guide.title.lower().replace(" ", "-").replace(":", "")
791
+ lines.append(
792
+ f"- [How To: {guide.title}]({use_case.lower()}/{filename}.md) - {guide.complexity_level.title()}"
793
+ )
794
+ lines.append("")
795
+
796
+ # Group by difficulty
797
+ by_complexity = defaultdict(list)
798
+ for guide in guides:
799
+ by_complexity[guide.complexity_level].append(guide)
800
+
801
+ lines.append("## By Difficulty Level")
802
+ lines.append("")
803
+
804
+ for level in ["beginner", "intermediate", "advanced"]:
805
+ if level in by_complexity:
806
+ level_guides = by_complexity[level]
807
+ lines.append(f"### {level.title()} ({len(level_guides)} guides)")
808
+ for guide in sorted(level_guides, key=lambda g: g.title):
809
+ lines.append(f"- {guide.title}")
810
+ lines.append("")
811
+
812
+ return "\n".join(lines)
813
+
814
+
815
+ # ============================================================================
816
+ # HOW-TO GUIDE BUILDER (Main Orchestrator)
817
+ # ============================================================================
818
+
819
+
820
+ class HowToGuideBuilder:
821
+ """Main orchestrator for building how-to guides from workflow examples"""
822
+
823
+ def __init__(self, enhance_with_ai: bool = True):
824
+ """
825
+ Initialize guide builder.
826
+
827
+ Args:
828
+ enhance_with_ai: Enable AI enhancement (requires C3.6 AI analysis in workflows)
829
+ """
830
+ self.enhance_with_ai = enhance_with_ai
831
+ self.analyzer = WorkflowAnalyzer()
832
+ self.grouper = WorkflowGrouper()
833
+ self.generator = GuideGenerator()
834
+
835
+ def build_guides_from_examples(
836
+ self,
837
+ examples: list[dict],
838
+ grouping_strategy: str = "ai-tutorial-group",
839
+ output_dir: Path | None = None,
840
+ enhance_with_ai: bool = True,
841
+ ai_mode: str = "auto",
842
+ ) -> GuideCollection:
843
+ """
844
+ Main entry point - build guides from workflow examples.
845
+
846
+ Args:
847
+ examples: List of TestExample dicts from C3.2
848
+ grouping_strategy: How to group workflows ("ai-tutorial-group", "file-path", etc.)
849
+ output_dir: Optional directory to save markdown files
850
+ enhance_with_ai: Enable comprehensive AI enhancement (default: True)
851
+ ai_mode: AI enhancement mode - "auto", "api", "local", or "none"
852
+
853
+ Returns:
854
+ GuideCollection with all generated guides
855
+ """
856
+ logger.info(f"Building how-to guides from {len(examples)} examples...")
857
+
858
+ # Initialize AI enhancer if requested
859
+ enhancer = None
860
+ if enhance_with_ai and ai_mode != "none":
861
+ try:
862
+ from .guide_enhancer import GuideEnhancer
863
+
864
+ enhancer = GuideEnhancer(mode=ai_mode)
865
+ logger.info(f"✨ AI enhancement enabled (mode: {enhancer.mode})")
866
+ except Exception as e:
867
+ logger.warning(f"⚠️ AI enhancement unavailable: {e}")
868
+ logger.info("📝 Falling back to basic guide generation")
869
+
870
+ # Filter to workflow examples only
871
+ workflows = self._extract_workflow_examples(examples)
872
+ logger.info(f"Found {len(workflows)} workflow examples")
873
+
874
+ if not workflows:
875
+ logger.warning("No workflow examples found!")
876
+ return GuideCollection(
877
+ total_guides=0,
878
+ guides_by_complexity={},
879
+ guides_by_use_case={},
880
+ guides=[],
881
+ )
882
+
883
+ # Group workflows
884
+ grouped_workflows = self.grouper.group_workflows(workflows, grouping_strategy)
885
+ logger.info(f"Grouped into {len(grouped_workflows)} guide categories")
886
+
887
+ # Build guides
888
+ guides = []
889
+ for title, workflow_group in grouped_workflows.items():
890
+ guide = self._create_guide(title, workflow_group, enhancer)
891
+ guides.append(guide)
892
+
893
+ # Create collection
894
+ collection = self._create_collection(guides)
895
+
896
+ # Save to files if output directory provided
897
+ if output_dir:
898
+ self._save_guides_to_files(collection, output_dir)
899
+
900
+ logger.info(f"✅ Generated {len(guides)} how-to guides")
901
+ return collection
902
+
903
+ def _extract_workflow_examples(self, examples: list[dict]) -> list[dict]:
904
+ """Filter to workflow category only"""
905
+ return [ex for ex in examples if ex.get("category") == "workflow"]
906
+
907
+ def _create_guide(self, title: str, workflows: list[dict], enhancer=None) -> HowToGuide:
908
+ """
909
+ Generate single guide from workflow(s).
910
+
911
+ Args:
912
+ title: Guide title
913
+ workflows: List of related workflow examples
914
+ enhancer: Optional GuideEnhancer instance for AI enhancement
915
+
916
+ Returns:
917
+ Complete HowToGuide object
918
+ """
919
+ # Use first workflow as primary
920
+ primary_workflow = workflows[0]
921
+
922
+ # Analyze workflow to extract steps
923
+ steps, metadata = self.analyzer.analyze_workflow(primary_workflow)
924
+
925
+ # Generate guide ID
926
+ guide_id = hashlib.md5(title.encode()).hexdigest()[:12]
927
+
928
+ # Extract use case from AI analysis or title
929
+ use_case = title
930
+ if primary_workflow.get("ai_analysis"):
931
+ use_case = primary_workflow["ai_analysis"].get("tutorial_group", title)
932
+
933
+ # Determine overview
934
+ overview = self._generate_overview(primary_workflow, workflows)
935
+
936
+ # Extract tags
937
+ tags = primary_workflow.get("tags", [])
938
+
939
+ # Extract source files
940
+ source_files = [w.get("file_path", "") for w in workflows]
941
+ source_files = [
942
+ f"{Path(f).name}:{w.get('line_start', 0)}"
943
+ for f, w in zip(source_files, workflows, strict=False)
944
+ ]
945
+
946
+ # Create guide
947
+ guide = HowToGuide(
948
+ guide_id=guide_id,
949
+ title=title,
950
+ overview=overview,
951
+ complexity_level=metadata.get("complexity_level", "intermediate"),
952
+ prerequisites=metadata.get("prerequisites", []),
953
+ required_imports=metadata.get("required_imports", []),
954
+ required_fixtures=metadata.get("required_fixtures", []),
955
+ workflows=workflows,
956
+ steps=steps,
957
+ use_case=use_case,
958
+ tags=tags,
959
+ estimated_time=metadata.get("estimated_time", "10 minutes"),
960
+ source_files=source_files,
961
+ )
962
+
963
+ # Add AI enhancements if enhancer is available
964
+ if enhancer:
965
+ self._enhance_guide_with_ai(guide, primary_workflow.get("ai_analysis", {}), enhancer)
966
+ elif self.enhance_with_ai and primary_workflow.get("ai_analysis"):
967
+ # Fallback to old enhancement method (basic)
968
+ self._enhance_guide_with_ai_basic(guide, primary_workflow["ai_analysis"])
969
+
970
+ return guide
971
+
972
+ def _generate_overview(self, primary_workflow: dict, _all_workflows: list[dict]) -> str:
973
+ """Generate guide overview"""
974
+ # Try to get explanation from AI analysis
975
+ if primary_workflow.get("ai_analysis"):
976
+ explanation = primary_workflow["ai_analysis"].get("explanation")
977
+ if explanation:
978
+ return explanation
979
+
980
+ # Fallback to description
981
+ description = primary_workflow.get("description", "")
982
+ if description:
983
+ return description
984
+
985
+ # Final fallback
986
+ return f"Learn how to use {primary_workflow.get('test_name', 'this feature')} in your code."
987
+
988
+ def _enhance_guide_with_ai(self, guide: HowToGuide, _ai_analysis: dict, enhancer):
989
+ """
990
+ Comprehensively enhance guide with AI using GuideEnhancer.
991
+
992
+ Applies all 5 enhancements:
993
+ 1. Step descriptions - Natural language explanations 2. Troubleshooting - Diagnostic flows + solutions
994
+ 3. Prerequisites - Why needed + setup
995
+ 4. Next steps - Related guides, variations
996
+ 5. Use cases - Real-world scenarios
997
+
998
+ Args:
999
+ guide: HowToGuide object to enhance
1000
+ ai_analysis: AI analysis data from C3.6 (for context)
1001
+ enhancer: GuideEnhancer instance
1002
+ """
1003
+ # Prepare guide data for enhancer
1004
+ guide_data = {
1005
+ "title": guide.title,
1006
+ "steps": [{"description": step.description, "code": step.code} for step in guide.steps],
1007
+ "language": "python", # TODO: Detect from code
1008
+ "prerequisites": guide.prerequisites,
1009
+ "description": guide.overview,
1010
+ }
1011
+
1012
+ # Call enhancer to get all 5 enhancements
1013
+ enhanced_data = enhancer.enhance_guide(guide_data)
1014
+
1015
+ # Apply step enhancements
1016
+ if "step_enhancements" in enhanced_data:
1017
+ for enhancement in enhanced_data["step_enhancements"]:
1018
+ idx = enhancement.step_index
1019
+ if 0 <= idx < len(guide.steps):
1020
+ guide.steps[idx].explanation = enhancement.explanation
1021
+ guide.steps[idx].common_variations = enhancement.variations
1022
+
1023
+ # Apply detailed prerequisites
1024
+ if "prerequisites_detailed" in enhanced_data:
1025
+ guide.prerequisites_detailed = enhanced_data["prerequisites_detailed"]
1026
+
1027
+ # Apply troubleshooting
1028
+ if "troubleshooting_detailed" in enhanced_data:
1029
+ guide.troubleshooting_detailed = enhanced_data["troubleshooting_detailed"]
1030
+
1031
+ # Apply next steps
1032
+ if "next_steps_detailed" in enhanced_data:
1033
+ guide.next_steps_detailed = enhanced_data["next_steps_detailed"]
1034
+
1035
+ # Apply use cases
1036
+ if "use_cases" in enhanced_data:
1037
+ guide.use_cases = enhanced_data["use_cases"]
1038
+
1039
+ logger.info(f"✨ Enhanced guide '{guide.title}' with comprehensive AI improvements")
1040
+
1041
+ def _enhance_guide_with_ai_basic(self, guide: HowToGuide, ai_analysis: dict):
1042
+ """
1043
+ Basic enhancement using pre-computed AI analysis from C3.6.
1044
+
1045
+ This is a fallback when GuideEnhancer is not available.
1046
+
1047
+ Args:
1048
+ guide: HowToGuide object to enhance
1049
+ ai_analysis: AI analysis data from C3.6
1050
+ """
1051
+ # Add best practices as variations
1052
+ best_practices = ai_analysis.get("best_practices", [])
1053
+ guide.variations = best_practices
1054
+
1055
+ # Add common mistakes as pitfalls
1056
+ common_mistakes = ai_analysis.get("common_mistakes", [])
1057
+ guide.common_pitfalls = common_mistakes
1058
+
1059
+ # Add related examples as related guides
1060
+ related_examples = ai_analysis.get("related_examples", [])
1061
+ guide.related_guides = [f"How To: {ex}" for ex in related_examples]
1062
+
1063
+ # Enhance step explanations
1064
+ for step in guide.steps:
1065
+ # Add explanation to steps based on best practices
1066
+ if best_practices and step.step_number <= len(best_practices):
1067
+ step.explanation = best_practices[step.step_number - 1]
1068
+
1069
+ def _create_collection(self, guides: list[HowToGuide]) -> GuideCollection:
1070
+ """Create GuideCollection from guides"""
1071
+ # Count by complexity
1072
+ by_complexity = defaultdict(int)
1073
+ for guide in guides:
1074
+ by_complexity[guide.complexity_level] += 1
1075
+
1076
+ # Group by use case
1077
+ by_use_case = defaultdict(list)
1078
+ for guide in guides:
1079
+ use_case = guide.use_case or "Other"
1080
+ by_use_case[use_case].append(guide)
1081
+
1082
+ return GuideCollection(
1083
+ total_guides=len(guides),
1084
+ guides_by_complexity=dict(by_complexity),
1085
+ guides_by_use_case=dict(by_use_case),
1086
+ guides=guides,
1087
+ )
1088
+
1089
+ def _save_guides_to_files(self, collection: GuideCollection, output_dir: Path):
1090
+ """Save guides to markdown files"""
1091
+ output_dir = Path(output_dir)
1092
+ output_dir.mkdir(parents=True, exist_ok=True)
1093
+
1094
+ logger.info(f"Saving guides to {output_dir}...")
1095
+
1096
+ # Save individual guides
1097
+ for use_case, guides in collection.guides_by_use_case.items():
1098
+ # Create use case directory
1099
+ use_case_dir = output_dir / use_case.lower().replace(" ", "-")
1100
+ use_case_dir.mkdir(parents=True, exist_ok=True)
1101
+
1102
+ for guide in guides:
1103
+ # Generate filename from title
1104
+ filename = guide.title.lower().replace(" ", "-").replace(":", "") + ".md"
1105
+ file_path = use_case_dir / filename
1106
+
1107
+ # Generate and save markdown
1108
+ markdown = self.generator.generate_guide_markdown(guide)
1109
+ file_path.write_text(markdown, encoding="utf-8")
1110
+
1111
+ # Save index
1112
+ index_markdown = self.generator.generate_index(collection.guides)
1113
+ (output_dir / "index.md").write_text(index_markdown, encoding="utf-8")
1114
+
1115
+ logger.info(f"✅ Saved {collection.total_guides} guides + index to {output_dir}")
1116
+
1117
+
1118
+ # ============================================================================
1119
+ # CLI INTERFACE
1120
+ # ============================================================================
1121
+
1122
+
1123
+ def main():
1124
+ """CLI entry point for how-to guide builder"""
1125
+ import argparse
1126
+ import sys
1127
+
1128
+ parser = argparse.ArgumentParser(
1129
+ description="Build how-to guides from workflow test examples (C3.3)",
1130
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1131
+ epilog="""
1132
+ Examples:
1133
+ # From test examples JSON (C3.2 output)
1134
+ skill-seekers build-how-to-guides --input test_examples.json
1135
+
1136
+ # From directory (extracts workflows)
1137
+ skill-seekers build-how-to-guides tests/
1138
+
1139
+ # Custom grouping strategy
1140
+ skill-seekers build-how-to-guides tests/ --group-by file-path
1141
+
1142
+ # Custom output directory
1143
+ skill-seekers build-how-to-guides tests/ --output tutorials/
1144
+
1145
+ # Without AI enhancement
1146
+ skill-seekers build-how-to-guides tests/ --no-ai
1147
+
1148
+ Grouping Strategies:
1149
+ - ai-tutorial-group: Use AI-generated tutorial groups (default, best)
1150
+ - file-path: Group by source test file
1151
+ - test-name: Group by test name patterns
1152
+ - complexity: Group by difficulty level
1153
+ """,
1154
+ )
1155
+
1156
+ parser.add_argument(
1157
+ "input",
1158
+ nargs="?",
1159
+ help="Input: directory with test files OR test_examples.json file",
1160
+ )
1161
+
1162
+ parser.add_argument(
1163
+ "--input",
1164
+ dest="input_file",
1165
+ help="Input JSON file with test examples (from C3.2)",
1166
+ )
1167
+
1168
+ parser.add_argument(
1169
+ "--output",
1170
+ default="output/codebase/tutorials",
1171
+ help="Output directory for generated guides (default: output/codebase/tutorials)",
1172
+ )
1173
+
1174
+ parser.add_argument(
1175
+ "--group-by",
1176
+ choices=["ai-tutorial-group", "file-path", "test-name", "complexity"],
1177
+ default="ai-tutorial-group",
1178
+ help="Grouping strategy (default: ai-tutorial-group)",
1179
+ )
1180
+
1181
+ parser.add_argument("--no-ai", action="store_true", help="Disable AI enhancement")
1182
+
1183
+ parser.add_argument(
1184
+ "--json-output",
1185
+ action="store_true",
1186
+ help="Output JSON summary instead of markdown files",
1187
+ )
1188
+
1189
+ args = parser.parse_args()
1190
+
1191
+ # Determine input source
1192
+ input_path = args.input or args.input_file
1193
+
1194
+ if not input_path:
1195
+ parser.print_help()
1196
+ print("\n❌ Error: No input provided")
1197
+ print(" Provide either a directory or --input JSON file")
1198
+ sys.exit(1)
1199
+
1200
+ input_path = Path(input_path)
1201
+
1202
+ # Load examples
1203
+ examples = []
1204
+
1205
+ if input_path.is_file() and input_path.suffix == ".json":
1206
+ # Load from JSON file
1207
+ logger.info(f"Loading examples from {input_path}...")
1208
+ with open(input_path) as f:
1209
+ data = json.load(f)
1210
+ if isinstance(data, dict) and "examples" in data:
1211
+ examples = data["examples"]
1212
+ elif isinstance(data, list):
1213
+ examples = data
1214
+ else:
1215
+ print(f"❌ Error: Invalid JSON format in {input_path}")
1216
+ sys.exit(1)
1217
+
1218
+ elif input_path.is_dir():
1219
+ # Extract from directory using test example extractor
1220
+ print("⚠️ Directory input requires test example extractor")
1221
+ print(" Please use test_examples.json output from C3.2")
1222
+ print(f" Or run: skill-seekers extract-test-examples {input_path} --json > examples.json")
1223
+ sys.exit(1)
1224
+
1225
+ else:
1226
+ print(f"❌ Error: Input path not found: {input_path}")
1227
+ sys.exit(1)
1228
+
1229
+ # Build guides
1230
+ builder = HowToGuideBuilder(enhance_with_ai=not args.no_ai)
1231
+ output_dir = Path(args.output) if not args.json_output else None
1232
+
1233
+ collection = builder.build_guides_from_examples(
1234
+ examples, grouping_strategy=args.group_by, output_dir=output_dir
1235
+ )
1236
+
1237
+ # Output results
1238
+ if args.json_output:
1239
+ # JSON output
1240
+ print(json.dumps(collection.to_dict(), indent=2))
1241
+ else:
1242
+ # Summary
1243
+ print()
1244
+ print("=" * 60)
1245
+ print("HOW-TO GUIDES GENERATED")
1246
+ print("=" * 60)
1247
+ print()
1248
+ print(f"Total Guides: {collection.total_guides}")
1249
+ print()
1250
+ print("By Complexity:")
1251
+ for level, count in collection.guides_by_complexity.items():
1252
+ print(f" - {level.title()}: {count} guides")
1253
+ print()
1254
+ print("By Use Case:")
1255
+ for use_case, guides in collection.guides_by_use_case.items():
1256
+ print(f" - {use_case}: {len(guides)} guides")
1257
+ print()
1258
+ if output_dir:
1259
+ print(f"📁 Output directory: {output_dir}")
1260
+ print(f"📄 Index file: {output_dir}/index.md")
1261
+ print()
1262
+
1263
+ sys.exit(0)
1264
+
1265
+
1266
+ if __name__ == "__main__":
1267
+ main()