feat: Implement Agentic Architecture for robust session workflows
Phase 1 - Session Bootstrap: - Add .claude/ATOMIZER_CONTEXT.md as single entry point for new sessions - Add study state detection and task routing Phase 2 - Code Deduplication: - Add optimization_engine/base_runner.py (ConfigDrivenRunner) - Add optimization_engine/generic_surrogate.py (ConfigDrivenSurrogate) - Add optimization_engine/study_state.py for study detection - Add optimization_engine/templates/ with registry and templates - Studies now require ~50 lines instead of ~300 Phase 3 - Skill Consolidation: - Add YAML frontmatter metadata to all skills (versioning, dependencies) - Consolidate create-study.md into core/study-creation-core.md - Update 00_BOOTSTRAP.md, 01_CHEATSHEET.md, 02_CONTEXT_LOADER.md Phase 4 - Self-Expanding Knowledge: - Add optimization_engine/auto_doc.py for auto-generating documentation - Generate docs/generated/EXTRACTORS.md (27 extractors documented) - Generate docs/generated/TEMPLATES.md (6 templates) - Generate docs/generated/EXTRACTOR_CHEATSHEET.md Phase 5 - Subagent Implementation: - Add .claude/commands/study-builder.md (create studies) - Add .claude/commands/nx-expert.md (NX Open API) - Add .claude/commands/protocol-auditor.md (config validation) - Add .claude/commands/results-analyzer.md (results analysis) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
341
optimization_engine/auto_doc.py
Normal file
341
optimization_engine/auto_doc.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""
|
||||
Auto-Documentation Generator for Atomizer
|
||||
|
||||
This module automatically generates documentation from code, ensuring
|
||||
that skills and protocols stay in sync with the implementation.
|
||||
|
||||
Usage:
|
||||
python -m optimization_engine.auto_doc extractors
|
||||
python -m optimization_engine.auto_doc templates
|
||||
python -m optimization_engine.auto_doc all
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
|
||||
def get_extractor_info() -> List[Dict[str, Any]]:
|
||||
"""Extract information about all registered extractors."""
|
||||
from optimization_engine import extractors
|
||||
|
||||
extractor_info = []
|
||||
|
||||
# Get all exported functions
|
||||
for name in extractors.__all__:
|
||||
obj = getattr(extractors, name)
|
||||
|
||||
if callable(obj):
|
||||
# Get function signature
|
||||
try:
|
||||
sig = inspect.signature(obj)
|
||||
params = [
|
||||
{
|
||||
'name': p.name,
|
||||
'default': str(p.default) if p.default != inspect.Parameter.empty else None,
|
||||
'annotation': str(p.annotation) if p.annotation != inspect.Parameter.empty else None
|
||||
}
|
||||
for p in sig.parameters.values()
|
||||
]
|
||||
except (ValueError, TypeError):
|
||||
params = []
|
||||
|
||||
# Get docstring
|
||||
doc = inspect.getdoc(obj) or "No documentation available"
|
||||
|
||||
# Determine category
|
||||
category = "general"
|
||||
if "stress" in name.lower():
|
||||
category = "stress"
|
||||
elif "temperature" in name.lower() or "thermal" in name.lower() or "heat" in name.lower():
|
||||
category = "thermal"
|
||||
elif "modal" in name.lower() or "frequency" in name.lower():
|
||||
category = "modal"
|
||||
elif "zernike" in name.lower():
|
||||
category = "optical"
|
||||
elif "mass" in name.lower():
|
||||
category = "mass"
|
||||
elif "strain" in name.lower():
|
||||
category = "strain"
|
||||
elif "spc" in name.lower() or "reaction" in name.lower() or "force" in name.lower():
|
||||
category = "forces"
|
||||
|
||||
# Determine phase
|
||||
phase = "Phase 1"
|
||||
if name in ['extract_principal_stress', 'extract_max_principal_stress',
|
||||
'extract_min_principal_stress', 'extract_strain_energy',
|
||||
'extract_total_strain_energy', 'extract_strain_energy_density',
|
||||
'extract_spc_forces', 'extract_total_reaction_force',
|
||||
'extract_reaction_component', 'check_force_equilibrium']:
|
||||
phase = "Phase 2"
|
||||
elif name in ['extract_temperature', 'extract_temperature_gradient',
|
||||
'extract_heat_flux', 'get_max_temperature',
|
||||
'extract_modal_mass', 'extract_frequencies',
|
||||
'get_first_frequency', 'get_modal_mass_ratio']:
|
||||
phase = "Phase 3"
|
||||
|
||||
extractor_info.append({
|
||||
'name': name,
|
||||
'module': obj.__module__,
|
||||
'category': category,
|
||||
'phase': phase,
|
||||
'parameters': params,
|
||||
'docstring': doc,
|
||||
'is_class': inspect.isclass(obj)
|
||||
})
|
||||
|
||||
return extractor_info
|
||||
|
||||
|
||||
def get_template_info() -> List[Dict[str, Any]]:
|
||||
"""Extract information about available study templates."""
|
||||
templates_file = Path(__file__).parent / 'templates' / 'registry.json'
|
||||
|
||||
if not templates_file.exists():
|
||||
return []
|
||||
|
||||
with open(templates_file) as f:
|
||||
data = json.load(f)
|
||||
|
||||
return data.get('templates', [])
|
||||
|
||||
|
||||
def generate_extractor_markdown(extractors: List[Dict[str, Any]]) -> str:
|
||||
"""Generate markdown documentation for extractors."""
|
||||
lines = [
|
||||
"# Atomizer Extractor Library",
|
||||
"",
|
||||
f"*Auto-generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*",
|
||||
"",
|
||||
"This document is automatically generated from the extractor source code.",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## Quick Reference",
|
||||
"",
|
||||
"| Extractor | Category | Phase | Description |",
|
||||
"|-----------|----------|-------|-------------|",
|
||||
]
|
||||
|
||||
for ext in sorted(extractors, key=lambda x: (x['category'], x['name'])):
|
||||
doc_first_line = ext['docstring'].split('\n')[0][:60]
|
||||
lines.append(f"| `{ext['name']}` | {ext['category']} | {ext['phase']} | {doc_first_line} |")
|
||||
|
||||
lines.extend(["", "---", ""])
|
||||
|
||||
# Group by category
|
||||
categories = {}
|
||||
for ext in extractors:
|
||||
cat = ext['category']
|
||||
if cat not in categories:
|
||||
categories[cat] = []
|
||||
categories[cat].append(ext)
|
||||
|
||||
for cat_name, cat_extractors in sorted(categories.items()):
|
||||
lines.append(f"## {cat_name.title()} Extractors")
|
||||
lines.append("")
|
||||
|
||||
for ext in sorted(cat_extractors, key=lambda x: x['name']):
|
||||
lines.append(f"### `{ext['name']}`")
|
||||
lines.append("")
|
||||
lines.append(f"**Module**: `{ext['module']}`")
|
||||
lines.append(f"**Phase**: {ext['phase']}")
|
||||
lines.append("")
|
||||
|
||||
# Parameters
|
||||
if ext['parameters']:
|
||||
lines.append("**Parameters**:")
|
||||
lines.append("")
|
||||
for param in ext['parameters']:
|
||||
default_str = f" = `{param['default']}`" if param['default'] else ""
|
||||
lines.append(f"- `{param['name']}`{default_str}")
|
||||
lines.append("")
|
||||
|
||||
# Docstring
|
||||
lines.append("**Description**:")
|
||||
lines.append("")
|
||||
lines.append("```")
|
||||
lines.append(ext['docstring'])
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def generate_template_markdown(templates: List[Dict[str, Any]]) -> str:
|
||||
"""Generate markdown documentation for templates."""
|
||||
lines = [
|
||||
"# Atomizer Study Templates",
|
||||
"",
|
||||
f"*Auto-generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*",
|
||||
"",
|
||||
"Available templates for quick study creation.",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## Template Reference",
|
||||
"",
|
||||
"| Template | Objectives | Extractors |",
|
||||
"|----------|------------|------------|",
|
||||
]
|
||||
|
||||
for tmpl in templates:
|
||||
# Handle objectives that might be dicts or strings
|
||||
obj_list = tmpl.get('objectives', [])
|
||||
if obj_list and isinstance(obj_list[0], dict):
|
||||
objectives = ', '.join([o.get('name', str(o)) for o in obj_list])
|
||||
else:
|
||||
objectives = ', '.join(obj_list)
|
||||
extractors = ', '.join(tmpl.get('extractors', []))
|
||||
lines.append(f"| `{tmpl['name']}` | {objectives} | {extractors} |")
|
||||
|
||||
lines.extend(["", "---", ""])
|
||||
|
||||
for tmpl in templates:
|
||||
lines.append(f"## {tmpl['name']}")
|
||||
lines.append("")
|
||||
lines.append(f"**Description**: {tmpl.get('description', 'N/A')}")
|
||||
lines.append("")
|
||||
lines.append(f"**Category**: {tmpl.get('category', 'N/A')}")
|
||||
lines.append(f"**Solver**: {tmpl.get('solver', 'N/A')}")
|
||||
lines.append(f"**Sampler**: {tmpl.get('sampler', 'N/A')}")
|
||||
lines.append(f"**Turbo Suitable**: {'Yes' if tmpl.get('turbo_suitable') else 'No'}")
|
||||
lines.append("")
|
||||
lines.append(f"**Example Study**: `{tmpl.get('example_study', 'N/A')}`")
|
||||
lines.append("")
|
||||
|
||||
if tmpl.get('objectives'):
|
||||
lines.append("**Objectives**:")
|
||||
for obj in tmpl['objectives']:
|
||||
if isinstance(obj, dict):
|
||||
lines.append(f"- {obj.get('name', '?')} ({obj.get('direction', '?')}) - Extractor: {obj.get('extractor', '?')}")
|
||||
else:
|
||||
lines.append(f"- {obj}")
|
||||
lines.append("")
|
||||
|
||||
if tmpl.get('extractors'):
|
||||
lines.append("**Extractors Used**:")
|
||||
for ext in tmpl['extractors']:
|
||||
lines.append(f"- {ext}")
|
||||
lines.append("")
|
||||
|
||||
if tmpl.get('recommended_trials'):
|
||||
lines.append("**Recommended Trials**:")
|
||||
for key, val in tmpl['recommended_trials'].items():
|
||||
lines.append(f"- {key}: {val}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def generate_cheatsheet_update(extractors: List[Dict[str, Any]]) -> str:
|
||||
"""Generate the extractor quick reference for 01_CHEATSHEET.md."""
|
||||
lines = [
|
||||
"## Extractor Quick Reference",
|
||||
"",
|
||||
"| Physics | Extractor | Function Call |",
|
||||
"|---------|-----------|---------------|",
|
||||
]
|
||||
|
||||
# Map categories to physics names
|
||||
physics_map = {
|
||||
'stress': 'Von Mises stress',
|
||||
'thermal': 'Temperature',
|
||||
'modal': 'Natural frequency',
|
||||
'optical': 'Zernike WFE',
|
||||
'mass': 'Mass',
|
||||
'strain': 'Strain energy',
|
||||
'forces': 'Reaction forces',
|
||||
'general': 'Displacement',
|
||||
}
|
||||
|
||||
for ext in sorted(extractors, key=lambda x: x['category']):
|
||||
if ext['is_class']:
|
||||
continue
|
||||
physics = physics_map.get(ext['category'], ext['category'])
|
||||
# Build function call example
|
||||
params = ext['parameters'][:2] if ext['parameters'] else []
|
||||
param_str = ', '.join([p['name'] for p in params])
|
||||
lines.append(f"| {physics} | {ext['name']} | `{ext['name']}({param_str})` |")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def update_atomizer_context(extractors: List[Dict[str, Any]], templates: List[Dict[str, Any]]):
|
||||
"""Update ATOMIZER_CONTEXT.md with current extractor count."""
|
||||
context_file = Path(__file__).parent.parent / '.claude' / 'ATOMIZER_CONTEXT.md'
|
||||
|
||||
if not context_file.exists():
|
||||
print(f"Warning: {context_file} not found")
|
||||
return
|
||||
|
||||
content = context_file.read_text()
|
||||
|
||||
# Update extractor library version based on count
|
||||
extractor_count = len(extractors)
|
||||
template_count = len(templates)
|
||||
|
||||
print(f"Found {extractor_count} extractors and {template_count} templates")
|
||||
|
||||
# Could add logic here to update version info based on changes
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python -m optimization_engine.auto_doc [extractors|templates|all]")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
output_dir = Path(__file__).parent.parent / 'docs' / 'generated'
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if command in ['extractors', 'all']:
|
||||
print("Generating extractor documentation...")
|
||||
extractors = get_extractor_info()
|
||||
|
||||
# Write full documentation
|
||||
doc_content = generate_extractor_markdown(extractors)
|
||||
(output_dir / 'EXTRACTORS.md').write_text(doc_content)
|
||||
print(f" Written: {output_dir / 'EXTRACTORS.md'}")
|
||||
|
||||
# Write cheatsheet update
|
||||
cheatsheet = generate_cheatsheet_update(extractors)
|
||||
(output_dir / 'EXTRACTOR_CHEATSHEET.md').write_text(cheatsheet)
|
||||
print(f" Written: {output_dir / 'EXTRACTOR_CHEATSHEET.md'}")
|
||||
|
||||
print(f" Found {len(extractors)} extractors")
|
||||
|
||||
if command in ['templates', 'all']:
|
||||
print("Generating template documentation...")
|
||||
templates = get_template_info()
|
||||
|
||||
if templates:
|
||||
doc_content = generate_template_markdown(templates)
|
||||
(output_dir / 'TEMPLATES.md').write_text(doc_content)
|
||||
print(f" Written: {output_dir / 'TEMPLATES.md'}")
|
||||
print(f" Found {len(templates)} templates")
|
||||
else:
|
||||
print(" No templates found")
|
||||
|
||||
if command == 'all':
|
||||
print("\nUpdating ATOMIZER_CONTEXT.md...")
|
||||
extractors = get_extractor_info()
|
||||
templates = get_template_info()
|
||||
update_atomizer_context(extractors, templates)
|
||||
|
||||
print("\nDone!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user