DocSavvy
Mission: Generates and maintains comprehensive documentation (user guides, release notes, API references) based on your codebase’s docstrings, comment blocks, and structure.
Doc Generation Example with Multi-Repo & Search Indexing
# CLI Example: Generate multi-repo documentation, enabling search indexing
forge doc-generate --agent=DocSavvy \
--path=./repos/monolith:./repos/microserviceA \
--output=./docs/combined \
--format=html \
--enable-search-index \
--include-private-methods \
--cross-reference \
--api-key=<API_KEY>
import os
import re
import logging
from typing import List
class DocSavvyAgent:
def __init__(self, repos: List[str], output_path: str, enable_search: bool):
self.repos = repos
self.output_path = output_path
self.enable_search = enable_search
self.doc_index = {}
def generate_docs(self):
all_files = self.collect_files(self.repos)
for fpath in all_files:
content = self.parse_file(fpath)
doc_text = self.extract_docstrings(content)
# Cross-reference creation
cross_refs = self.extract_cross_refs(content)
# Potential indexing for search
if self.enable_search:
self.update_search_index(fpath, doc_text, cross_refs)
self.write_doc_to_output(fpath, doc_text, cross_refs)
logging.info("Documentation generation complete.")
def collect_files(self, repo_paths: List[str]) -> List[str]:
# Gather .py, .js, .go, etc. from multiple repos
all_files = []
for rp in repo_paths:
if ':' in rp:
# Format is ./repos/monolith:./repos/microserviceA
# Parse them individually
splitted = rp.split(':')
for path in splitted:
all_files.extend(self._collect_repo_files(path))
else:
all_files.extend(self._collect_repo_files(rp))
return all_files
def _collect_repo_files(self, repo_path: str) -> List[str]:
matched_files = []
for root, _, files in os.walk(repo_path):
for filename in files:
if filename.endswith(('.py', '.go', '.ts', '.js', '.rs')):
matched_files.append(os.path.join(root, filename))
return matched_files
def parse_file(self, fpath: str) -> str:
with open(fpath, 'r', encoding='utf-8') as f:
return f.read()
def extract_docstrings(self, content: str) -> str:
# Regex-based extraction of docstrings or comment blocks
docstrings = re.findall(r'("""[\s\S]*?"""|//.*?$|/\*[\s\S]*?\*/)', content, re.MULTILINE)
return "\n\n".join(docstrings)
def extract_cross_refs(self, content: str) -> dict:
# Simple approach: find 'import' or 'from' statements
cross_imports = re.findall(r'(import\s+\S+|from\s+\S+\s+import\s+\S+)', content)
return {'imports': cross_imports}
def update_search_index(self, fpath: str, doc_text: str, cross_refs: dict):
self.doc_index[fpath] = {'docs': doc_text, 'refs': cross_refs}
def write_doc_to_output(self, fpath: str, doc_text: str, cross_refs: dict):
# Convert fpath to a safe HTML or Markdown file name
out_name = fpath.replace('/', '_').replace('\\', '_')
out_file = os.path.join(self.output_path, f"{out_name}.html")
with open(out_file, 'w', encoding='utf-8') as f:
f.write("<html><body>")
f.write(f"<h1>File: {fpath}</h1>\n")
f.write("<pre style='background:#f4f4f4;'>\n")
f.write(doc_text)
f.write("</pre><hr/>\n")
f.write("<h2>Cross-References</h2>\n")
f.write(str(cross_refs))
f.write("</body></html>")
Last updated