feat(renderer): implement module-level documentation generation

- Added module_md template to renderer for generating detailed module documentation
- Updated CLI to use renderer for module docs with fallback to simple template
- Generated module documentation for test project files with symbols, dependencies, and integrations
- Added proper error handling when module rendering fails

This implements the core functionality for generating detailed architectural documentation at the module level, including symbols, dependencies, and integration points.
This commit is contained in:
2026-01-25 21:24:54 +03:00
parent 3ffe5e235f
commit b7d3e3e488
9 changed files with 377 additions and 4 deletions

View File

View File

@@ -325,10 +325,20 @@ fn generate_docs(model: &ProjectModel, out: &str) -> Result<()> {
// Create individual documentation files for modules and files // Create individual documentation files for modules and files
for (module_id, _module) in &model.modules { for (module_id, _module) in &model.modules {
let module_doc_path = modules_path.join(format!("{}.md", sanitize_filename(module_id))); let module_doc_path = modules_path.join(format!("{}.md", sanitize_filename(module_id)));
match renderer.render_module_md(model, module_id) {
Ok(module_content) => {
std::fs::write(&module_doc_path, module_content)
.map_err(|e| anyhow::anyhow!("Failed to create module doc {}: {}", module_doc_path.display(), e))?;
}
Err(e) => {
eprintln!("Warning: Failed to render module doc for {}: {}", module_id, e);
// Fallback to simple template
let module_content = format!("# Module: {}\n\nTODO: Add module documentation\n", module_id); let module_content = format!("# Module: {}\n\nTODO: Add module documentation\n", module_id);
std::fs::write(&module_doc_path, module_content) std::fs::write(&module_doc_path, module_content)
.map_err(|e| anyhow::anyhow!("Failed to create module doc {}: {}", module_doc_path.display(), e))?; .map_err(|e| anyhow::anyhow!("Failed to create module doc {}: {}", module_doc_path.display(), e))?;
} }
}
}
for (_file_id, file_doc) in &model.files { for (_file_id, file_doc) in &model.files {
let file_doc_path = files_path.join(format!("{}.md", sanitize_filename(&file_doc.path))); let file_doc_path = files_path.join(format!("{}.md", sanitize_filename(&file_doc.path)));

View File

@@ -28,7 +28,9 @@ impl Renderer {
handlebars.register_template_string("architecture_md", Self::architecture_md_template()) handlebars.register_template_string("architecture_md", Self::architecture_md_template())
.expect("Failed to register architecture_md template"); .expect("Failed to register architecture_md template");
// TODO: Register other templates // Register module documentation template
handlebars.register_template_string("module_md", Self::module_md_template())
.expect("Failed to register module_md template");
Self { Self {
templates: handlebars, templates: handlebars,
@@ -151,6 +153,82 @@ impl Renderer {
"# "#
} }
fn module_md_template() -> &'static str {
r#"# Module: {{{module_name}}}
{{{module_summary}}}
## Symbols
{{#each symbols}}
### {{{name}}}
{{{signature}}}
{{{docstring}}}
**Type:** {{{kind}}}
**Metrics:**
- Fan-in: {{{fan_in}}}
- Fan-out: {{{fan_out}}}
{{#if is_critical}}
- Critical: Yes
{{/if}}
{{/each}}
## Dependencies
### Imports
{{#each imports}}
- {{{this}}}
{{/each}}
### Outbound Modules
{{#each outbound_modules}}
- {{{this}}}
{{/each}}
### Inbound Modules
{{#each inbound_modules}}
- {{{this}}}
{{/each}}
## Integrations
{{#if has_db_integrations}}
### Database Integrations
{{#each db_symbols}}
- {{{this}}}
{{/each}}
{{/if}}
{{#if has_http_integrations}}
### HTTP/API Integrations
{{#each http_symbols}}
- {{{this}}}
{{/each}}
{{/if}}
{{#if has_queue_integrations}}
### Queue Integrations
{{#each queue_symbols}}
- {{{this}}}
{{/each}}
{{/if}}
## Usage Examples
{{#each usage_examples}}
```python
{{{this}}}
```
{{/each}}
"#
}
pub fn render_architecture_md(&self, model: &ProjectModel) -> Result<String, anyhow::Error> { pub fn render_architecture_md(&self, model: &ProjectModel) -> Result<String, anyhow::Error> {
// Collect integration information // Collect integration information
let mut db_integrations = Vec::new(); let mut db_integrations = Vec::new();
@@ -188,6 +266,72 @@ impl Renderer {
.map_err(|e| anyhow::anyhow!("Failed to render architecture.md: {}", e)) .map_err(|e| anyhow::anyhow!("Failed to render architecture.md: {}", e))
} }
pub fn render_module_md(&self, model: &ProjectModel, module_id: &str) -> Result<String, anyhow::Error> {
// Find the module in the project model
let module = model.modules.get(module_id)
.ok_or_else(|| anyhow::anyhow!("Module {} not found", module_id))?;
// Collect symbols for this module
let mut symbols = Vec::new();
for symbol_id in &module.symbols {
if let Some(symbol) = model.symbols.get(symbol_id) {
symbols.push(serde_json::json!({
"name": symbol.qualname,
"signature": symbol.signature,
"docstring": symbol.docstring_first_line.as_deref().unwrap_or("No documentation available"),
"kind": format!("{:?}", symbol.kind),
"fan_in": symbol.metrics.fan_in,
"fan_out": symbol.metrics.fan_out,
"is_critical": symbol.metrics.is_critical,
}));
}
}
// Collect integration information for this module
let mut db_symbols = Vec::new();
let mut http_symbols = Vec::new();
let mut queue_symbols = Vec::new();
for symbol_id in &module.symbols {
if let Some(symbol) = model.symbols.get(symbol_id) {
if symbol.integrations_flags.db {
db_symbols.push(symbol.qualname.clone());
}
if symbol.integrations_flags.http {
http_symbols.push(symbol.qualname.clone());
}
if symbol.integrations_flags.queue {
queue_symbols.push(symbol.qualname.clone());
}
}
}
// Prepare usage examples (for now, just placeholders)
let usage_examples = vec![
"// Example usage of module functions\n// TODO: Add real usage examples based on module analysis".to_string()
];
// Prepare data for template
let data = serde_json::json!({
"module_name": module_id,
"module_summary": module.doc_summary.as_deref().unwrap_or("No summary available"),
"symbols": symbols,
"imports": model.files.get(&module.files[0]).map(|f| f.imports.clone()).unwrap_or_default(),
"outbound_modules": module.outbound_modules,
"inbound_modules": module.inbound_modules,
"has_db_integrations": !db_symbols.is_empty(),
"has_http_integrations": !http_symbols.is_empty(),
"has_queue_integrations": !queue_symbols.is_empty(),
"db_symbols": db_symbols,
"http_symbols": http_symbols,
"queue_symbols": queue_symbols,
"usage_examples": usage_examples,
});
self.templates.render("module_md", &data)
.map_err(|e| anyhow::anyhow!("Failed to render module.md: {}", e))
}
pub fn render_integrations_section(&self, model: &ProjectModel) -> Result<String, anyhow::Error> { pub fn render_integrations_section(&self, model: &ProjectModel) -> Result<String, anyhow::Error> {
// Collect integration information // Collect integration information
let mut db_integrations = Vec::new(); let mut db_integrations = Vec::new();

View File

@@ -0,0 +1,3 @@
# File: ../test-project/src/__init__.py
TODO: Add file documentation

View File

@@ -0,0 +1,3 @@
# File: ../test-project/src/core.py
TODO: Add file documentation

View File

@@ -0,0 +1,3 @@
# File: ../test-project/src/utils.py
TODO: Add file documentation

View File

@@ -0,0 +1,27 @@
# Module: ../test-project/src/__init__.py
No summary available
## Symbols
## Dependencies
### Imports
### Outbound Modules
### Inbound Modules
## Integrations
## Usage Examples
```python
// Example usage of module functions
// TODO: Add real usage examples based on module analysis
```

View File

@@ -0,0 +1,106 @@
# Module: ../test-project/src/core.py
No summary available
## Symbols
### DatabaseManager
class DatabaseManager
No documentation available
**Type:** Class
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### __init__
def __init__(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### connect
def connect(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### execute_query
def execute_query(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### fetch_external_data
def fetch_external_data(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### process_user_data
def process_user_data(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 1
## Dependencies
### Imports
- sqlite3
- requests
### Outbound Modules
### Inbound Modules
## Integrations
### Database Integrations
- DatabaseManager
- connect
### HTTP/API Integrations
- fetch_external_data
## Usage Examples
```python
// Example usage of module functions
// TODO: Add real usage examples based on module analysis
```

View File

@@ -0,0 +1,77 @@
# Module: ../test-project/src/utils.py
No summary available
## Symbols
### load_config
def load_config(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### save_config
def save_config(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### get_file_size
def get_file_size(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
### format_bytes
def format_bytes(...)
No documentation available
**Type:** Function
**Metrics:**
- Fan-in: 0
- Fan-out: 0
## Dependencies
### Imports
- json
- os
### Outbound Modules
### Inbound Modules
## Integrations
## Usage Examples
```python
// Example usage of module functions
// TODO: Add real usage examples based on module analysis
```