bioguider 0.2.29__py3-none-any.whl → 0.2.31__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.

Potentially problematic release.


This version of bioguider might be problematic. Click here for more details.

@@ -24,6 +24,7 @@ class DocumentationGenerationManager:
24
24
  self.llm = llm
25
25
  self.step_callback = step_callback
26
26
  self.repo_url_or_path: str | None = None
27
+ self.start_time = None
27
28
 
28
29
  self.loader = EvaluationReportLoader()
29
30
  self.extractor = SuggestionExtractor()
@@ -42,12 +43,36 @@ class DocumentationGenerationManager:
42
43
  def prepare_repo(self, repo_url_or_path: str):
43
44
  self.repo_url_or_path = repo_url_or_path
44
45
 
46
+ def _get_generation_time(self) -> str:
47
+ """Get formatted generation time with start, end, and duration"""
48
+ if self.start_time is None:
49
+ return "Not tracked"
50
+ import time
51
+ import datetime
52
+ end_time = time.time()
53
+ duration = end_time - self.start_time
54
+
55
+ start_str = datetime.datetime.fromtimestamp(self.start_time).strftime("%H:%M:%S")
56
+ end_str = datetime.datetime.fromtimestamp(end_time).strftime("%H:%M:%S")
57
+
58
+ if duration < 60:
59
+ duration_str = f"{duration:.1f}s"
60
+ elif duration < 3600:
61
+ duration_str = f"{duration/60:.1f}m"
62
+ else:
63
+ duration_str = f"{duration/3600:.1f}h"
64
+
65
+ return f"{start_str} → {end_str} ({duration_str})"
66
+
45
67
  def run(self, report_path: str, repo_path: str | None = None) -> str:
68
+ import time
69
+ self.start_time = time.time()
46
70
  repo_path = repo_path or self.repo_url_or_path or ""
47
- self.print_step(step_name="LoadReport", step_output=f"report_path={report_path}")
71
+ self.print_step(step_name="LoadReport", step_output=f"Loading evaluation report from {report_path}...")
48
72
  report, report_abs = self.loader.load(report_path)
73
+ self.print_step(step_name="LoadReport", step_output="✓ Evaluation report loaded successfully")
49
74
 
50
- self.print_step(step_name="ReadRepoFiles", step_output=f"repo_path={repo_path}")
75
+ self.print_step(step_name="ReadRepoFiles", step_output=f"Reading repository files from {repo_path}...")
51
76
  reader = RepoReader(repo_path)
52
77
  # Prefer report-listed files if available; include all report-declared file lists
53
78
  target_files = []
@@ -64,54 +89,111 @@ class DocumentationGenerationManager:
64
89
  if isinstance(key, str) and key.strip():
65
90
  userguide_files.append(key)
66
91
  target_files.extend(userguide_files)
92
+
93
+ # Add tutorial files from tutorial_evaluation keys
94
+ tutorial_files: list[str] = []
95
+ if getattr(report, "tutorial_files", None):
96
+ tutorial_files.extend([p for p in report.tutorial_files if isinstance(p, str)])
97
+ elif getattr(report, "tutorial_evaluation", None) and isinstance(report.tutorial_evaluation, dict):
98
+ for key in report.tutorial_evaluation.keys():
99
+ if isinstance(key, str) and key.strip():
100
+ tutorial_files.append(key)
101
+ target_files.extend(tutorial_files)
102
+
67
103
  if getattr(report, "submission_requirements_files", None):
68
104
  target_files.extend(report.submission_requirements_files)
69
105
  target_files = [p for p in target_files if isinstance(p, str) and p.strip()]
70
106
  target_files = list(dict.fromkeys(target_files)) # de-dup
71
107
  files, missing = reader.read_files(target_files) if target_files else reader.read_default_targets()
108
+ self.print_step(step_name="ReadRepoFiles", step_output=f"✓ Read {len(files)} files from repository")
72
109
 
73
- self.print_step(step_name="AnalyzeStyle", step_output=f"files={[p for p in files.keys()]}")
110
+ self.print_step(step_name="AnalyzeStyle", step_output="Analyzing document style and formatting...")
74
111
  style = self.style_analyzer.analyze(files)
112
+ self.print_step(step_name="AnalyzeStyle", step_output="✓ Document style analysis completed")
75
113
 
76
- self.print_step(step_name="ExtractSuggestions")
114
+ self.print_step(step_name="ExtractSuggestions", step_output="Extracting suggestions from evaluation report...")
77
115
  suggestions = self.extractor.extract(report)
78
- self.print_step(step_name="Suggestions", step_output=f"count={len(suggestions)} ids={[s.id for s in suggestions]}")
116
+ self.print_step(step_name="Suggestions", step_output=f"✓ Extracted {len(suggestions)} suggestions from evaluation report")
79
117
 
80
- self.print_step(step_name="PlanChanges")
118
+ self.print_step(step_name="PlanChanges", step_output="Planning changes based on suggestions...")
81
119
  plan = self.planner.build_plan(repo_path=repo_path, style=style, suggestions=suggestions, available_files=files)
82
- self.print_step(step_name="PlannedEdits", step_output=f"count={len(plan.planned_edits)} files={list(set(e.file_path for e in plan.planned_edits))}")
120
+ self.print_step(step_name="PlannedEdits", step_output=f"✓ Planned {len(plan.planned_edits)} edits across {len(set(e.file_path for e in plan.planned_edits))} files")
83
121
 
84
- self.print_step(step_name="RenderDocuments")
85
- # Apply edits cumulatively per file to ensure multiple suggestions are realized
122
+ self.print_step(step_name="RenderDocuments", step_output=f"Rendering documents with LLM (processing {len(plan.planned_edits)} edits)...")
123
+ # Apply edits; support full-file regeneration using the evaluation report as the sole authority
86
124
  revised: Dict[str, str] = {}
87
125
  diff_stats: Dict[str, dict] = {}
88
126
  edits_by_file: Dict[str, list] = {}
89
127
  for e in plan.planned_edits:
90
128
  edits_by_file.setdefault(e.file_path, []).append(e)
129
+
130
+ total_files = len(edits_by_file)
131
+ processed_files = 0
132
+
133
+ # Prepare evaluation data subset to drive LLM full document generation
134
+ evaluation_data = {
135
+ "readme_evaluation": getattr(report, "readme_evaluation", None),
136
+ "installation_evaluation": getattr(report, "installation_evaluation", None),
137
+ "userguide_evaluation": getattr(report, "userguide_evaluation", None),
138
+ "tutorial_evaluation": getattr(report, "tutorial_evaluation", None),
139
+ }
140
+
91
141
  for fpath, edits in edits_by_file.items():
92
- content = files.get(fpath, "")
142
+ processed_files += 1
143
+ self.print_step(step_name="ProcessingFile", step_output=f"Processing {fpath} ({processed_files}/{total_files}) - {len(edits)} edits")
144
+
145
+ original_content = files.get(fpath, "")
146
+ content = original_content
93
147
  total_stats = {"added_lines": 0}
94
148
  for e in edits:
95
- # Generate LLM content for section if template is generic
96
- context = files.get(fpath, "")
97
- gen_section, gen_usage = self.llm_gen.generate_section(
98
- suggestion=next((s for s in suggestions if s.id == e.suggestion_id), None) if e.suggestion_id else None,
99
- style=plan.style_profile,
100
- context=context,
101
- ) if e.suggestion_id else ""
102
- if isinstance(gen_section, str) and gen_section:
103
- self.print_step(step_name="LLMSection", step_output=f"file={fpath} suggestion={e.suggestion_id} tokens={gen_usage.get('total_tokens', 0)}\n{gen_section}")
104
- # Ensure header present
105
- if gen_section.lstrip().startswith("#"):
106
- e.content_template = gen_section
107
- else:
108
- title = e.anchor.get('value', '').strip() or ''
109
- e.content_template = f"## {title}\n\n{gen_section}" if title else gen_section
149
+ context = original_content
150
+ if not e.content_template or e.content_template.strip() == "":
151
+ # Generate LLM content - use full document generation for full_replace, section generation for others
152
+ suggestion = next((s for s in suggestions if s.id == e.suggestion_id), None) if e.suggestion_id else None
153
+ if suggestion:
154
+ if e.edit_type == "full_replace":
155
+ self.print_step(step_name="GeneratingContent", step_output=f"Generating full document for {e.suggestion_id} using LLM...")
156
+ gen_content, gen_usage = self.llm_gen.generate_full_document(
157
+ target_file=e.file_path,
158
+ evaluation_report={"suggestion": suggestion.content_guidance, "evidence": suggestion.source.get("evidence", "") if suggestion.source else ""},
159
+ context=context,
160
+ )
161
+ if isinstance(gen_content, str) and gen_content:
162
+ self.print_step(step_name="LLMFullDoc", step_output=f"✓ Generated full document for {e.suggestion_id} ({gen_usage.get('total_tokens', 0)} tokens)")
163
+ e.content_template = gen_content
164
+ else:
165
+ self.print_step(step_name="GeneratingContent", step_output=f"Generating section for {e.suggestion_id} using LLM...")
166
+ gen_section, gen_usage = self.llm_gen.generate_section(
167
+ suggestion=suggestion,
168
+ style=plan.style_profile,
169
+ context=context,
170
+ )
171
+ if isinstance(gen_section, str) and gen_section:
172
+ self.print_step(step_name="LLMSection", step_output=f"✓ Generated section for {e.suggestion_id} ({gen_usage.get('total_tokens', 0)} tokens)")
173
+ # Ensure header present
174
+ if gen_section.lstrip().startswith("#"):
175
+ e.content_template = gen_section
176
+ else:
177
+ title = e.anchor.get('value', '').strip() or ''
178
+ e.content_template = f"## {title}\n\n{gen_section}" if title else gen_section
110
179
  content, stats = self.renderer.apply_edit(content, e)
180
+ # After applying full document or section changes, run a general cleaner pass for all text files
181
+ # to fix markdown/formatting issues without changing meaning.
182
+ try:
183
+ if fpath.endswith((".md", ".rst", ".Rmd", ".Rd")) and content:
184
+ self.print_step(step_name="CleaningContent", step_output=f"Cleaning formatting for {fpath}...")
185
+ cleaned, _usage = self.llm_cleaner.clean_readme(content)
186
+ if isinstance(cleaned, str) and cleaned.strip():
187
+ content = cleaned
188
+
189
+ # LLM cleaner now handles markdown fences and unwanted summaries
190
+
191
+ except Exception:
192
+ pass
111
193
  total_stats["added_lines"] = total_stats.get("added_lines", 0) + stats.get("added_lines", 0)
112
194
  revised[fpath] = content
113
195
  diff_stats[fpath] = total_stats
114
- self.print_step(step_name="RenderedFile", step_output=f"file={fpath} added_lines={total_stats['added_lines']}")
196
+ self.print_step(step_name="RenderedFile", step_output=f"✓ Completed {fpath} - added {total_stats['added_lines']} lines")
115
197
 
116
198
  # Removed cleaner: duplication and fixes handled in prompts and renderer
117
199
 
@@ -128,11 +210,23 @@ class DocumentationGenerationManager:
128
210
  else:
129
211
  out_repo_key = self.repo_url_or_path or "repo"
130
212
 
131
- self.print_step(step_name="WriteOutputs", step_output=f"repo_key={out_repo_key}")
213
+ self.print_step(step_name="WriteOutputs", step_output=f"Writing outputs to {out_repo_key}...")
132
214
  out_dir = self.output.prepare_output_dir(out_repo_key)
133
215
  # Ensure all files we read (even without edits) are written to outputs alongside revisions
134
216
  all_files_to_write: Dict[str, str] = dict(files)
135
217
  all_files_to_write.update(revised)
218
+ # Also copy originals next to the new files for side-by-side comparison
219
+ def original_copy_name(path: str) -> str:
220
+ # Handle all file extensions properly
221
+ if "." in path:
222
+ base, ext = path.rsplit(".", 1)
223
+ return f"{base}.original.{ext}"
224
+ return f"{path}.original"
225
+
226
+ for orig_path, orig_content in files.items():
227
+ all_files_to_write[original_copy_name(orig_path)] = orig_content
228
+
229
+ self.print_step(step_name="WritingFiles", step_output=f"Writing {len(all_files_to_write)} files to output directory...")
136
230
  artifacts = self.output.write_files(out_dir, all_files_to_write, diff_stats_by_file=diff_stats)
137
231
 
138
232
  manifest = GenerationManifest(
@@ -144,8 +238,11 @@ class DocumentationGenerationManager:
144
238
  artifacts=artifacts,
145
239
  skipped=missing,
146
240
  )
241
+ self.print_step(step_name="WritingManifest", step_output="Writing generation manifest...")
147
242
  self.output.write_manifest(out_dir, manifest)
243
+
148
244
  # Write human-readable generation report
245
+ self.print_step(step_name="WritingReport", step_output="Writing generation report...")
149
246
  gen_report_path = self._write_generation_report(
150
247
  out_dir,
151
248
  report.repo_url or str(self.repo_url_or_path or ""),
@@ -155,7 +252,7 @@ class DocumentationGenerationManager:
155
252
  artifacts,
156
253
  missing,
157
254
  )
158
- self.print_step(step_name="Done", step_output=f"output_dir={out_dir}")
255
+ self.print_step(step_name="Done", step_output=f"✓ Generation completed! Output directory: {out_dir}")
159
256
  return out_dir
160
257
 
161
258
  def _write_generation_report(
@@ -168,35 +265,279 @@ class DocumentationGenerationManager:
168
265
  artifacts,
169
266
  skipped: List[str],
170
267
  ):
171
- # Build a simple markdown report
268
+ # Build a user-friendly markdown report
172
269
  lines: list[str] = []
173
- lines.append(f"# Documentation Changelog\n")
174
- lines.append(f"Repo: {repo_url}\n")
175
- lines.append(f"Output: {out_dir}\n")
176
- lines.append("\n## Summary of Changes\n")
270
+ lines.append(f"# Documentation Generation Report\n")
271
+ lines.append(f"**Repository:** {repo_url}\n")
272
+ lines.append(f"**Generated:** {out_dir}\n")
273
+
274
+ # Processing timeline
275
+ total_improvements = len(plan.planned_edits)
276
+ start_time_str = self._get_generation_time().split(" → ")[0] if self.start_time else "Not tracked"
277
+ end_time_str = self._get_generation_time().split(" → ")[1].split(" (")[0] if self.start_time else "Not tracked"
278
+ duration_str = self._get_generation_time().split("(")[1].replace(")", "") if self.start_time else "Not tracked"
279
+
280
+ lines.append(f"**Processing Timeline:**\n")
281
+ lines.append(f"- **Start Time:** {start_time_str}\n")
282
+ lines.append(f"- **End Time:** {end_time_str}\n")
283
+ lines.append(f"- **Duration:** {duration_str}\n")
284
+
285
+ # Calculate statistics by category
286
+ category_stats = {}
287
+ file_stats = {}
177
288
  for e in plan.planned_edits:
178
289
  sug = next((s for s in suggestions if s.id == e.suggestion_id), None)
179
- why = sug.source.get("evidence", "") if sug and sug.source else ""
180
- lines.append(f"- File: `{e.file_path}` | Action: {e.edit_type} | Section: {e.anchor.get('value','')} | Added lines: {diff_stats.get(e.file_path,{}).get('added_lines',0)}")
181
- if why:
182
- lines.append(f" - Why: {why}")
183
- lines.append("\n## Planned Edits\n")
290
+ if sug and sug.category:
291
+ category = sug.category.split('.')[0] # e.g., "readme.dependencies" -> "readme"
292
+ category_stats[category] = category_stats.get(category, 0) + 1
293
+
294
+ file_stats[e.file_path] = file_stats.get(e.file_path, 0) + 1
295
+
296
+ # Calculate evaluation report statistics
297
+ score_stats = {"Excellent": 0, "Good": 0, "Fair": 0, "Poor": 0}
298
+ processed_suggestions = set()
184
299
  for e in plan.planned_edits:
185
- lines.append(f"- `{e.file_path}` -> {e.edit_type} -> {e.anchor.get('value','')}")
300
+ sug = next((s for s in suggestions if s.id == e.suggestion_id), None)
301
+ if sug and sug.source and sug.id not in processed_suggestions:
302
+ score = sug.source.get("score", "")
303
+ if score in score_stats:
304
+ score_stats[score] += 1
305
+ processed_suggestions.add(sug.id)
306
+
307
+ # Calculate success rate based on processed suggestions only
308
+ processed_suggestions_count = len([s for s in suggestions if s.source and s.source.get("score", "") in ("Fair", "Poor")])
309
+ fixed_suggestions = len([s for s in processed_suggestions if s in [sug.id for sug in suggestions if sug.source and sug.source.get("score", "") in ("Fair", "Poor")]])
310
+
311
+ # Add professional summary and key metrics
312
+ lines.append(f"\n## Summary\n")
313
+
314
+ # Concise summary for busy developers
315
+ lines.append(f"This is a report of automated documentation enhancements generated by BioGuider.\n")
316
+ lines.append(f"\nOur AI analyzed your existing documentation to identify areas for improvement based on standards for high-quality scientific software. It then automatically rewrote the files to be more accessible and useful for biomedical researchers.\n")
317
+ lines.append(f"\nThis changelog provides a transparent record of what was modified and why. We encourage you to review the changes before committing. Original file versions are backed up with a `.original` extension.\n")
318
+
319
+ # Core metrics
320
+ total_lines_added = sum(stats.get('added_lines', 0) for stats in diff_stats.values())
321
+ success_rate = (fixed_suggestions/processed_suggestions_count*100) if processed_suggestions_count > 0 else 0
322
+
323
+ # Lead with success rate - the most important outcome
324
+ lines.append(f"\n### Key Metrics\n")
325
+ lines.append(f"- **Success Rate:** {success_rate:.1f}% ({fixed_suggestions} of {processed_suggestions_count} processed suggestions addressed)\n")
326
+ lines.append(f"- **Total Impact:** {total_improvements} improvements across {len(file_stats)} files\n")
327
+ lines.append(f"- **Content Added:** {total_lines_added} lines of enhanced documentation\n")
328
+
329
+ # Explain why some suggestions were filtered out
330
+ total_suggestions = len(suggestions)
331
+ filtered_count = total_suggestions - processed_suggestions_count
332
+ if filtered_count > 0:
333
+ lines.append(f"\n### Processing Strategy\n")
334
+ lines.append(f"- **Suggestions filtered out:** {filtered_count} items\n")
335
+ lines.append(f"- **Reason:** Only 'Fair' and 'Poor' priority suggestions were processed\n")
336
+ lines.append(f"- **Rationale:** Focus on critical issues that need immediate attention\n")
337
+ lines.append(f"- **Quality threshold:** 'Excellent' and 'Good' suggestions already meet standards\n")
338
+
339
+ # Priority breakdown - answer "Was it important work?"
340
+ lines.append(f"\n### Priority Breakdown\n")
341
+ priority_fixed = 0
342
+ priority_total = 0
343
+
344
+ for score in ["Poor", "Fair"]:
345
+ count = score_stats[score]
346
+ if count > 0:
347
+ priority_total += count
348
+ priority_fixed += count
349
+ lines.append(f"- **{score} Priority:** {count} items → 100% addressed\n")
186
350
 
187
- # Summarize all files written with basic status
188
- lines.append("\n## Files Written\n")
189
- for art in artifacts:
190
- stats = art.diff_stats or {}
191
- added = stats.get("added_lines", 0)
192
- status = "Revised" if added and added > 0 else "Copied"
193
- lines.append(f"- {art.dest_rel_path} | status: {status} | added_lines: {added}")
351
+ # Remove confusing "Critical Issues" bullet - success rate already shown above
194
352
 
195
- # Skipped or missing files
353
+ # Quality assurance note
354
+ excellent_count = score_stats.get("Excellent", 0)
355
+ good_count = score_stats.get("Good", 0)
356
+ if excellent_count > 0 or good_count > 0:
357
+ lines.append(f"\n### Quality Assurance\n")
358
+ lines.append(f"- **High-Quality Items:** {excellent_count + good_count} suggestions already meeting standards (no changes needed)\n")
359
+
360
+ # Group improvements by file type for better readability
361
+ by_file = {}
362
+ for e in plan.planned_edits:
363
+ if e.file_path not in by_file:
364
+ by_file[e.file_path] = []
365
+ by_file[e.file_path].append(e)
366
+
367
+ lines.append(f"\n## Files Improved\n")
368
+ for file_path, edits in by_file.items():
369
+ added_lines = diff_stats.get(file_path, {}).get('added_lines', 0)
370
+ lines.append(f"\n### {file_path}\n")
371
+ lines.append(f"**Changes made:** {len(edits)} improvement(s), {added_lines} lines added\n")
372
+
373
+ for e in edits:
374
+ sug = next((s for s in suggestions if s.id == e.suggestion_id), None)
375
+ guidance = sug.content_guidance if sug else ""
376
+ evidence = sug.source.get("evidence", "") if sug and sug.source else ""
377
+ section = e.anchor.get('value', 'General improvements')
378
+
379
+ # Convert technical action names to user-friendly descriptions
380
+ # Use the suggestion action if available, otherwise fall back to edit type
381
+ action_key = sug.action if sug else e.edit_type
382
+
383
+ # Generate category-based description for full_replace actions
384
+ if action_key == 'full_replace' and sug:
385
+ category = sug.category or ""
386
+ category_display = category.split('.')[-1].replace('_', ' ').title() if category else ""
387
+
388
+ # Create specific descriptions based on category
389
+ if 'readme' in category.lower():
390
+ action_desc = 'Enhanced README documentation'
391
+ elif 'tutorial' in category.lower():
392
+ action_desc = 'Improved tutorial content'
393
+ elif 'userguide' in category.lower():
394
+ action_desc = 'Enhanced user guide documentation'
395
+ elif 'installation' in category.lower():
396
+ action_desc = 'Improved installation instructions'
397
+ elif 'dependencies' in category.lower():
398
+ action_desc = 'Enhanced dependency information'
399
+ elif 'readability' in category.lower():
400
+ action_desc = 'Improved readability and clarity'
401
+ elif 'setup' in category.lower():
402
+ action_desc = 'Enhanced setup and configuration'
403
+ elif 'reproducibility' in category.lower():
404
+ action_desc = 'Improved reproducibility'
405
+ elif 'structure' in category.lower():
406
+ action_desc = 'Enhanced document structure'
407
+ elif 'code_quality' in category.lower():
408
+ action_desc = 'Improved code quality'
409
+ elif 'verification' in category.lower():
410
+ action_desc = 'Enhanced result verification'
411
+ elif 'performance' in category.lower():
412
+ action_desc = 'Added performance considerations'
413
+ elif 'context' in category.lower():
414
+ action_desc = 'Enhanced context and purpose'
415
+ elif 'error_handling' in category.lower():
416
+ action_desc = 'Improved error handling'
417
+ else:
418
+ action_desc = f'Enhanced {category_display}' if category_display else 'Comprehensive rewrite'
419
+ else:
420
+ # Use existing action descriptions for non-full_replace actions
421
+ action_desc = {
422
+ 'append_section': f'Added "{section}" section',
423
+ 'insert_after_header': f'Enhanced content in "{section}"',
424
+ 'rmarkdown_integration': f'Integrated improvements in "{section}"',
425
+ 'replace_intro_block': f'Improved "{section}" section',
426
+ 'add_dependencies_section': 'Added dependencies information',
427
+ 'add_system_requirements_section': 'Added system requirements',
428
+ 'add_hardware_requirements': 'Added hardware requirements',
429
+ 'clarify_mandatory_vs_optional': 'Clarified dependencies',
430
+ 'improve_readability': f'Improved readability in "{section}"',
431
+ 'improve_setup': f'Enhanced setup instructions in "{section}"',
432
+ 'improve_reproducibility': f'Improved reproducibility in "{section}"',
433
+ 'improve_structure': f'Enhanced structure in "{section}"',
434
+ 'improve_code_quality': f'Improved code quality in "{section}"',
435
+ 'improve_verification': f'Enhanced result verification in "{section}"',
436
+ 'improve_performance': f'Added performance notes in "{section}"',
437
+ 'improve_clarity_and_error_handling': f'Improved clarity and error handling in "{section}"',
438
+ 'improve_consistency': f'Improved consistency in "{section}"',
439
+ 'improve_context': f'Enhanced context in "{section}"',
440
+ 'improve_error_handling': f'Improved error handling in "{section}"',
441
+ 'add_overview_section': f'Added "{section}" section'
442
+ }.get(action_key, f'Improved {action_key}')
443
+
444
+ lines.append(f"- **{action_desc}**")
445
+
446
+ # Show evaluation reasoning that triggered this improvement
447
+ if sug and sug.source:
448
+ evidence = sug.source.get("evidence", "")
449
+ score = sug.source.get("score", "")
450
+ category = sug.category or ""
451
+
452
+ # Format category for display (e.g., "readme.dependencies" -> "Dependencies")
453
+ category_display = category.split('.')[-1].replace('_', ' ').title() if category else ""
454
+
455
+ if evidence:
456
+ # Handle different evidence types
457
+ if isinstance(evidence, dict):
458
+ # Extract key information from dict evidence
459
+ evidence_text = evidence.get("dependency_suggestions", "") or evidence.get("evidence", "")
460
+ if not evidence_text:
461
+ evidence_text = f"Installation evaluation: {evidence.get('overall_score', 'Unknown')} score"
462
+ else:
463
+ evidence_text = str(evidence)
464
+ # Handle Python dict string evidence (from full_replace actions)
465
+ if evidence_text.startswith("{") and evidence_text.endswith("}"):
466
+ try:
467
+ import ast
468
+ evidence_dict = ast.literal_eval(evidence_text)
469
+ # Extract specific suggestions from the evaluation report
470
+ dep_sugg = evidence_dict.get("dependency_suggestions", "")
471
+ hw_req = evidence_dict.get("hardware_requirements", False)
472
+ compat_os = evidence_dict.get("compatible_os", True)
473
+ overall_score = evidence_dict.get("overall_score", "")
474
+
475
+ # Build specific reason based on evaluation findings
476
+ reasons = []
477
+ if dep_sugg:
478
+ reasons.append(f"Dependencies: {dep_sugg}")
479
+ if hw_req is False:
480
+ reasons.append("Hardware requirements not specified")
481
+ if compat_os is False:
482
+ reasons.append("Operating system compatibility unclear")
483
+ if overall_score and overall_score not in ("Excellent", "Good"):
484
+ reasons.append(f"Overall score: {overall_score}")
485
+
486
+ if reasons:
487
+ evidence_text = "; ".join(reasons)
488
+ else:
489
+ evidence_text = f"Installation evaluation score: {overall_score}"
490
+ except:
491
+ evidence_text = "Installation documentation needs improvement"
492
+
493
+ if score and category_display:
494
+ lines.append(f" - *Reason:* [{category_display} - {score}] {evidence_text}")
495
+ elif score:
496
+ lines.append(f" - *Reason:* [{score}] {evidence_text}")
497
+ elif category_display:
498
+ lines.append(f" - *Reason:* [{category_display}] {evidence_text}")
499
+ else:
500
+ lines.append(f" - *Reason:* {evidence_text}")
501
+ elif score:
502
+ lines.append(f" - *Reason:* Evaluation score was '{score}' - needs improvement")
503
+
504
+ # Show what was actually implemented (different from reason)
505
+ if guidance:
506
+ # Extract key action from guidance to show what was implemented
507
+ if "dependencies" in guidance.lower():
508
+ lines.append(f" - *Implemented:* Added comprehensive dependency list with installation instructions")
509
+ elif "system requirements" in guidance.lower() or "hardware" in guidance.lower():
510
+ lines.append(f" - *Implemented:* Added system requirements and platform-specific installation details")
511
+ elif "comparative statement" in guidance.lower() or "beneficial" in guidance.lower():
512
+ lines.append(f" - *Implemented:* Added comparative analysis highlighting Seurat's advantages")
513
+ elif "readability" in guidance.lower() or "bullet" in guidance.lower():
514
+ lines.append(f" - *Implemented:* Enhanced readability with structured lists and examples")
515
+ elif "overview" in guidance.lower() or "summary" in guidance.lower():
516
+ lines.append(f" - *Implemented:* Improved overview section with clear, professional tone")
517
+ elif "accessible" in guidance.lower() or "non-experts" in guidance.lower():
518
+ lines.append(f" - *Implemented:* Simplified language for broader accessibility")
519
+ elif "examples" in guidance.lower() or "usage" in guidance.lower():
520
+ lines.append(f" - *Implemented:* Added practical examples and usage scenarios")
521
+ elif "error" in guidance.lower() or "debug" in guidance.lower():
522
+ lines.append(f" - *Implemented:* Added error handling guidance and troubleshooting tips")
523
+ elif "context" in guidance.lower() or "scenarios" in guidance.lower():
524
+ lines.append(f" - *Implemented:* Expanded context and real-world application examples")
525
+ elif "structure" in guidance.lower() or "organization" in guidance.lower():
526
+ lines.append(f" - *Implemented:* Improved document structure and organization")
527
+ else:
528
+ # Truncate long guidance to avoid repetition
529
+ short_guidance = guidance[:100] + "..." if len(guidance) > 100 else guidance
530
+ lines.append(f" - *Implemented:* {short_guidance}")
531
+
532
+ lines.append("")
533
+
534
+ # Note about skipped files
196
535
  if skipped:
197
- lines.append("\n## Skipped or Missing Files\n")
536
+ lines.append(f"\n## Note\n")
537
+ lines.append(f"The following files were not modified as they were not found in the repository:")
198
538
  for rel in skipped:
199
539
  lines.append(f"- {rel}")
540
+
200
541
  report_md = "\n".join(lines)
201
542
  dest = os.path.join(out_dir, "GENERATION_REPORT.md")
202
543
  with open(dest, "w", encoding="utf-8") as fobj:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bioguider
3
- Version: 0.2.29
3
+ Version: 0.2.31
4
4
  Summary: An AI-Powered package to help biomedical developers to generate clear documentation
5
5
  License: MIT
6
6
  Author: Cankun Wang
@@ -43,20 +43,20 @@ bioguider/conversation.py,sha256=DIvk_d7pz_guuORByK1eaaF09FAK-8shcNTrbSUHz9Y,177
43
43
  bioguider/database/code_structure_db.py,sha256=q9tGZLWrjPi7a3u1b2iUnMO30lNWKbeMOkpDRffev2M,16973
44
44
  bioguider/database/summarized_file_db.py,sha256=U60c62e2Bx7PwsTAcCQgljNxD5u5awjpj5qpHEgJbac,4801
45
45
  bioguider/generation/__init__.py,sha256=esV02QgCsY67-HBwSHDbA5AcbKzNRIT3wDwwh6N4OFM,945
46
- bioguider/generation/change_planner.py,sha256=1u_1tcVwggVw6QebXvPJVULYnRAIkKMteFxbnf42aAw,6964
47
- bioguider/generation/document_renderer.py,sha256=67-XGFGNe8WgHJpqkHLMecgk2OeV8a5cJRAeDTLsXG4,1806
48
- bioguider/generation/llm_cleaner.py,sha256=aMxc3j6IH_9mCfBjxY1bn0bAUtUkUoHjumobzGkN0h8,1422
49
- bioguider/generation/llm_content_generator.py,sha256=1pfXYRykvGgLGu9YJH-DhvaMzb7hy_EyvHQ8XojUBQ8,3684
46
+ bioguider/generation/change_planner.py,sha256=0N10jvkfn2J9b598FKOKPQecwmQv68yeuUvMZn81nOI,9715
47
+ bioguider/generation/document_renderer.py,sha256=Md8NMo0CXNIqatWOdKE-_4k02Y3T_BCLmEPLTEiYUCA,7984
48
+ bioguider/generation/llm_cleaner.py,sha256=qFgS5xi7bBO8HAJ9WFNzH3p9AhOsAkYjchKQHuAUWWM,2917
49
+ bioguider/generation/llm_content_generator.py,sha256=UbRURH6RdEWBVVqQi96SlTkNEOt01yyuxr76jR8__GA,10983
50
50
  bioguider/generation/llm_injector.py,sha256=bVxP6Asv2em4MBOB5yFsS14AuaeT7NLKQQMcsEqXjPY,17352
51
- bioguider/generation/models.py,sha256=q9sF32Iu5VAa59UbheqPWEZn4bYeGGUVzpW24NJR_SM,2518
51
+ bioguider/generation/models.py,sha256=MlJOLjPHk8xs-UGW-TGN_M9cevTuxTG4tjm1d1L15go,2699
52
52
  bioguider/generation/output_manager.py,sha256=uwLyavND4kXOHlsXB0Berab3y8u6bhaEmQOQLl7wDAM,1963
53
53
  bioguider/generation/repo_reader.py,sha256=ivTURU61fR8er4ev7gSpOxER3FJv2d9GAx_X5JoVTvQ,1177
54
- bioguider/generation/report_loader.py,sha256=m6_zcXsm5X6cFUB0ypVo1rjwjQzPAwwg83tc4H0X_eA,5598
54
+ bioguider/generation/report_loader.py,sha256=bxajeTDxod36iFsbSZhXSQjotxqP7LuAg5MC9OqX_p0,5911
55
55
  bioguider/generation/style_analyzer.py,sha256=Vn9FAK1qJBNLolLC1tz362k4UBaPl107BlvkQc8pV2I,983
56
- bioguider/generation/suggestion_extractor.py,sha256=tfkyWtdbAo-maLCF_wqwBXyh93yjulvDY17FuvTnTjk,7611
56
+ bioguider/generation/suggestion_extractor.py,sha256=kkPOYE6FXRtYlogV0GQdBraZZJm08I6Oux5YKGUF1UU,29442
57
57
  bioguider/generation/test_metrics.py,sha256=ACXmSZc2L_UkkmC5h2s4tG44MXW1d-hClFwPCD5_BFI,7505
58
58
  bioguider/managers/evaluation_manager.py,sha256=EoZ8V4rmx16zk1J3N9cNjeo0aCa7i32fLEQ3b2UolEQ,5917
59
- bioguider/managers/generation_manager.py,sha256=94Fe-0q37t7QIP2lkdmEiTjb8-hbiz9XX9h8Gi6BkSU,9594
59
+ bioguider/managers/generation_manager.py,sha256=GqjCci2eWHnIcJ-SOn5-hEMi8p3Jk4Q5E4KSObKELPs,31945
60
60
  bioguider/managers/generation_test_manager.py,sha256=3mOBzQVpsLo_LpSspJcofn3CNtvgagS1DMr9Zuwkzq4,5307
61
61
  bioguider/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  bioguider/rag/config.py,sha256=5g4IqTzgyfZfax9Af9CTkXShgItPOt4_9TEMSekCPik,4602
@@ -74,7 +74,7 @@ bioguider/utils/pyphen_utils.py,sha256=cdZc3qphkvMDeL5NiZ8Xou13M_uVNP7ifJ-FwxO-0
74
74
  bioguider/utils/python_file_handler.py,sha256=BERiE2RHxpu3gAzv26jr8ZQetkrtnMZOv9SjpQ7WIdg,2650
75
75
  bioguider/utils/r_file_handler.py,sha256=8HpFaYKP8N1nItwr9tOx49m99pcLSt8EUtTNTJ7xNoE,19564
76
76
  bioguider/utils/utils.py,sha256=h8OhCjzLpHkb3ndnjRBUOBHD7csbHdEVNXf75SRN8Zc,4413
77
- bioguider-0.2.29.dist-info/LICENSE,sha256=qzkvZcKwwA5DuSuhXMOm2LcO6BdEr4V7jwFZVL2-jL4,1065
78
- bioguider-0.2.29.dist-info/METADATA,sha256=O4IRhR2hhEuFMeWOYCSeQvyKHZUdWdGosGssGN0gohs,1962
79
- bioguider-0.2.29.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
80
- bioguider-0.2.29.dist-info/RECORD,,
77
+ bioguider-0.2.31.dist-info/LICENSE,sha256=qzkvZcKwwA5DuSuhXMOm2LcO6BdEr4V7jwFZVL2-jL4,1065
78
+ bioguider-0.2.31.dist-info/METADATA,sha256=k5RSl8D1JHB5mFSCWqcJjTLF1WDDhtBw2BbFfmg-Trw,1962
79
+ bioguider-0.2.31.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
80
+ bioguider-0.2.31.dist-info/RECORD,,