From 536f87b952ccdf503c41823ec4885d4f127ba6ad Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 13 May 2026 18:21:39 +0200 Subject: [PATCH] github workflows --- .github/doc-coverage-thresholds.json | 29 +++ .github/scripts/doc-coverage-check.py | 277 +++++++++++++++++++++ .github/scripts/doc-review.py | 279 +++++++++++++++++++++ .github/workflows/doc-coverage.yml | 109 ++++++++ .github/workflows/doc-review.yml | 98 ++++++++ SCOPE_OF_WORK.md | 345 ++++++++++++++++++++++++++ cmake/XrplDocs.cmake | 27 ++ docs/DOCUMENTATION_STANDARDS.md | 192 ++++++++++++++ docs/Doxyfile | 4 +- 9 files changed, 1358 insertions(+), 2 deletions(-) create mode 100644 .github/doc-coverage-thresholds.json create mode 100644 .github/scripts/doc-coverage-check.py create mode 100644 .github/scripts/doc-review.py create mode 100644 .github/workflows/doc-coverage.yml create mode 100644 .github/workflows/doc-review.yml create mode 100644 SCOPE_OF_WORK.md create mode 100644 docs/DOCUMENTATION_STANDARDS.md diff --git a/.github/doc-coverage-thresholds.json b/.github/doc-coverage-thresholds.json new file mode 100644 index 0000000000..d9e2ae4b47 --- /dev/null +++ b/.github/doc-coverage-thresholds.json @@ -0,0 +1,29 @@ +{ + "global_minimum": 0, + "ratchet_mode": "no_decrease", + "new_file_minimum": 80, + "module_thresholds": { + "include/xrpl/basics/": 0, + "include/xrpl/crypto/": 0, + "include/xrpl/protocol/": 0, + "include/xrpl/ledger/": 0, + "include/xrpl/tx/": 0, + "include/xrpl/server/": 0, + "include/xrpl/nodestore/": 0, + "include/xrpl/shamap/": 0, + "include/xrpl/resource/": 0, + "src/xrpld/rpc/": 0, + "src/xrpld/overlay/": 0, + "src/xrpld/peerfinder/": 0, + "src/xrpld/consensus/": 0, + "src/xrpld/app/": 0, + "src/libxrpl/": 0 + }, + "schedule": { + "2026-Q3": { "global_minimum": 30 }, + "2026-Q4": { "global_minimum": 40 }, + "2027-Q1": { "global_minimum": 50 }, + "2027-Q2": { "global_minimum": 60 }, + "2027-Q3": { "global_minimum": 70 } + } +} diff --git a/.github/scripts/doc-coverage-check.py b/.github/scripts/doc-coverage-check.py new file mode 100644 index 0000000000..b283625242 --- /dev/null +++ b/.github/scripts/doc-coverage-check.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +""" +Documentation coverage checker for xrpld. + +Parses coverxygen LCOV output, compares against per-module thresholds +defined in .github/doc-coverage-thresholds.json, and generates a +markdown report suitable for posting as a PR comment. + +Usage: + python3 doc-coverage-check.py \ + --lcov-file doc-coverage.info \ + --threshold-file .github/doc-coverage-thresholds.json \ + --output doc-coverage-report.md \ + [--base-lcov-file base-doc-coverage.info] +""" + +import argparse +import json +import re +import sys +from collections import defaultdict +from pathlib import Path + + +def parse_lcov(lcov_path: str) -> dict[str, dict[str, int]]: + """Parse LCOV-format file into per-file coverage data. + + Returns a dict mapping file paths to {"documented": N, "total": N}. + """ + coverage = {} + current_file = None + documented = 0 + total = 0 + + with open(lcov_path) as f: + for line in f: + line = line.strip() + if line.startswith("SF:"): + current_file = line[3:] + documented = 0 + total = 0 + elif line.startswith("DA:"): + parts = line[3:].split(",") + if len(parts) >= 2: + total += 1 + if int(parts[1]) > 0: + documented += 1 + elif line == "end_of_record": + if current_file: + coverage[current_file] = { + "documented": documented, + "total": total, + } + current_file = None + + return coverage + + +def compute_module_coverage( + coverage: dict[str, dict[str, int]], + module_prefixes: list[str], +) -> dict[str, dict[str, int | float]]: + """Aggregate file-level coverage into module-level stats.""" + modules = {} + for prefix in module_prefixes: + doc = 0 + tot = 0 + for filepath, stats in coverage.items(): + if filepath.startswith(prefix) or f"/{prefix}" in filepath: + doc += stats["documented"] + tot += stats["total"] + pct = (doc / tot * 100) if tot > 0 else 0.0 + modules[prefix] = {"documented": doc, "total": tot, "percent": round(pct, 1)} + return modules + + +def compute_global_coverage( + coverage: dict[str, dict[str, int]], +) -> dict[str, int | float]: + """Compute overall coverage across all files.""" + doc = sum(s["documented"] for s in coverage.values()) + tot = sum(s["total"] for s in coverage.values()) + pct = (doc / tot * 100) if tot > 0 else 0.0 + return {"documented": doc, "total": tot, "percent": round(pct, 1)} + + +def check_ratchet( + current: dict[str, dict[str, int | float]], + base: dict[str, dict[str, int | float]] | None, + current_global: dict[str, int | float], + base_global: dict[str, int | float] | None, +) -> list[str]: + """Check that no module or global coverage decreased vs base branch.""" + violations = [] + + if base_global and current_global["percent"] < base_global["percent"]: + violations.append( + f"Global coverage decreased: {base_global['percent']}% -> " + f"{current_global['percent']}%" + ) + + if base: + for module, stats in current.items(): + if module in base and stats["percent"] < base[module]["percent"]: + violations.append( + f"`{module}` coverage decreased: " + f"{base[module]['percent']}% -> {stats['percent']}%" + ) + + return violations + + +def check_new_files( + coverage: dict[str, dict[str, int]], + new_files: list[str], + min_coverage: int, +) -> list[str]: + """Check that new files meet minimum documentation coverage.""" + violations = [] + for filepath in new_files: + for covered_path, stats in coverage.items(): + if filepath in covered_path or covered_path.endswith(filepath): + if stats["total"] > 0: + pct = stats["documented"] / stats["total"] * 100 + if pct < min_coverage: + violations.append( + f"`{filepath}` has {pct:.0f}% doc coverage " + f"(minimum {min_coverage}%)" + ) + break + return violations + + +def coverage_emoji(pct: float) -> str: + if pct >= 80: + return "+" + if pct >= 50: + return "~" + return "-" + + +def generate_report( + global_stats: dict[str, int | float], + module_stats: dict[str, dict[str, int | float]], + thresholds: dict, + violations: list[str], + new_file_violations: list[str], +) -> str: + """Generate a markdown report for the PR comment.""" + lines = [] + lines.append("## Documentation Coverage Report") + lines.append("") + + passed = not violations and not new_file_violations + status = "PASSED" if passed else "FAILED" + lines.append(f"**Status:** {status}") + lines.append( + f"**Global Coverage:** {global_stats['percent']}% " + f"({global_stats['documented']}/{global_stats['total']} entities documented)" + ) + lines.append( + f"**Minimum Threshold:** {thresholds.get('global_minimum', 0)}%" + ) + lines.append("") + + if violations or new_file_violations: + lines.append("### Violations") + lines.append("") + for v in violations + new_file_violations: + lines.append(f"- {v}") + lines.append("") + + lines.append("### Module Coverage") + lines.append("") + lines.append("| Module | Coverage | Documented | Total | Threshold |") + lines.append("|--------|----------|------------|-------|-----------|") + + module_thresholds = thresholds.get("module_thresholds", {}) + for module in sorted(module_stats.keys()): + stats = module_stats[module] + threshold = module_thresholds.get(module, 0) + emoji = coverage_emoji(stats["percent"]) + lines.append( + f"| `{module}` | {stats['percent']}% | " + f"{stats['documented']} | {stats['total']} | {threshold}% |" + ) + + lines.append("") + lines.append( + "*Coverage measured by [coverxygen](https://github.com/psycofdj/coverxygen). " + "See [docs/DOCUMENTATION_STANDARDS.md](../docs/DOCUMENTATION_STANDARDS.md) " + "for documentation guidelines.*" + ) + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser(description="Check documentation coverage") + parser.add_argument("--lcov-file", required=True, help="Path to LCOV coverage file") + parser.add_argument( + "--threshold-file", required=True, help="Path to thresholds JSON" + ) + parser.add_argument("--output", required=True, help="Path to write markdown report") + parser.add_argument( + "--base-lcov-file", default=None, help="Path to base branch LCOV file" + ) + parser.add_argument( + "--new-files", + default="", + help="Comma-separated list of new C++ files in this PR", + ) + args = parser.parse_args() + + with open(args.threshold_file) as f: + thresholds = json.load(f) + + coverage = parse_lcov(args.lcov_file) + module_prefixes = list(thresholds.get("module_thresholds", {}).keys()) + module_stats = compute_module_coverage(coverage, module_prefixes) + global_stats = compute_global_coverage(coverage) + + base_coverage = None + base_module_stats = None + base_global_stats = None + if args.base_lcov_file and Path(args.base_lcov_file).exists(): + base_coverage = parse_lcov(args.base_lcov_file) + base_module_stats = compute_module_coverage(base_coverage, module_prefixes) + base_global_stats = compute_global_coverage(base_coverage) + + violations = [] + + if global_stats["percent"] < thresholds.get("global_minimum", 0): + violations.append( + f"Global coverage {global_stats['percent']}% is below minimum " + f"{thresholds['global_minimum']}%" + ) + + for module, threshold in thresholds.get("module_thresholds", {}).items(): + if module in module_stats and module_stats[module]["percent"] < threshold: + violations.append( + f"`{module}` coverage {module_stats[module]['percent']}% is below " + f"threshold {threshold}%" + ) + + if thresholds.get("ratchet_mode") == "no_decrease": + violations.extend( + check_ratchet( + module_stats, base_module_stats, global_stats, base_global_stats + ) + ) + + new_file_violations = [] + if args.new_files: + new_files = [f.strip() for f in args.new_files.split(",") if f.strip()] + new_file_min = thresholds.get("new_file_minimum", 80) + new_file_violations = check_new_files(coverage, new_files, new_file_min) + + report = generate_report( + global_stats, module_stats, thresholds, violations, new_file_violations + ) + + with open(args.output, "w") as f: + f.write(report) + + print(report) + + if violations or new_file_violations: + print(f"\nFAILED: {len(violations) + len(new_file_violations)} violation(s)") + sys.exit(1) + else: + print("\nPASSED: All coverage thresholds met") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/doc-review.py b/.github/scripts/doc-review.py new file mode 100644 index 0000000000..87609917d3 --- /dev/null +++ b/.github/scripts/doc-review.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +""" +Diff-aware documentation review for xrpld PRs. + +For each changed C++ file, extracts the diff hunks and existing doc +comments, then asks the Anthropic API whether documentation needs +updating. Produces: + - doc-review-report.md: summary comment for the PR + - doc-review-comments.json: inline review comments with file/line info +""" + +import json +import os +import re +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +try: + import anthropic +except ImportError: + print("ERROR: anthropic package not installed. Run: pip install anthropic") + sys.exit(1) + +MODEL = "claude-sonnet-4-6" +MAX_TOKENS = 2048 + +SYSTEM_PROMPT = """You are a documentation reviewer for the xrpld (XRP Ledger daemon) C++ codebase. + +Your job is to review code changes and determine whether existing documentation +comments need updating, or whether new documentation is needed. + +Documentation style: Javadoc-style Doxygen comments (/** ... */). +See the project's docs/DOCUMENTATION_STANDARDS.md for full guidelines. + +Rules: +- Only flag REAL semantic drift: changed behavior, new parameters, removed + functionality, changed return values, new error conditions. +- Do NOT flag cosmetic changes (whitespace, formatting, variable renames that + don't change semantics). +- Do NOT suggest docs for private implementation details unless the logic is + genuinely non-obvious. +- Do NOT paraphrase function signatures. Good docs explain WHY and WHAT + BEHAVIOR, not WHAT THE CODE LITERALLY DOES. +- Be terse. Each finding should be 1-3 sentences. + +For each issue found, respond with a JSON array of objects: +{ + "issues": [ + { + "file": "path/to/file.h", + "line": 42, + "severity": "warning" | "suggestion", + "message": "Brief description of the doc issue", + "suggested_doc": "Optional: suggested doc comment text" + } + ], + "summary": "One-paragraph summary of documentation state for this file" +} + +If no issues are found, return: {"issues": [], "summary": "Documentation is up to date."} +Respond ONLY with valid JSON. No markdown fences, no explanation outside JSON.""" + + +@dataclass +class FileAnalysis: + path: str + diff: str + existing_docs: str + file_content: str + + +def get_diff(base_sha: str, head_sha: str, filepath: str) -> str: + """Get the unified diff for a specific file between two commits.""" + try: + result = subprocess.run( + ["git", "diff", f"{base_sha}...{head_sha}", "--", filepath], + capture_output=True, + text=True, + check=True, + ) + return result.stdout + except subprocess.CalledProcessError: + return "" + + +def extract_doc_comments(content: str) -> str: + """Extract all /** ... */ doc comments from file content.""" + pattern = r'/\*\*[\s\S]*?\*/' + matches = re.findall(pattern, content) + return "\n\n".join(matches) if matches else "(no documentation comments found)" + + +def read_file_safe(filepath: str) -> str: + """Read a file, returning empty string if it doesn't exist.""" + try: + return Path(filepath).read_text(encoding="utf-8", errors="replace") + except (FileNotFoundError, PermissionError): + return "" + + +def analyze_file(client: anthropic.Anthropic, analysis: FileAnalysis) -> dict: + """Send a file's diff and docs to the API for review.""" + user_prompt = f"""Review the following code change for documentation accuracy. + +## File: {analysis.path} + +## Git Diff: +``` +{analysis.diff[:8000]} +``` + +## Existing Documentation Comments: +``` +{analysis.existing_docs[:4000]} +``` + +## Current File Content (first 200 lines for context): +```cpp +{chr(10).join(analysis.file_content.split(chr(10))[:200])} +``` + +Analyze whether the diff introduces changes that make existing docs inaccurate, +or adds new public API surface that lacks documentation.""" + + try: + response = client.messages.create( + model=MODEL, + max_tokens=MAX_TOKENS, + system=SYSTEM_PROMPT, + messages=[{"role": "user", "content": user_prompt}], + ) + text = response.content[0].text.strip() + if text.startswith("```"): + text = re.sub(r'^```\w*\n?', '', text) + text = re.sub(r'\n?```$', '', text) + return json.loads(text) + except (json.JSONDecodeError, Exception) as e: + return { + "issues": [], + "summary": f"Analysis failed: {str(e)[:200]}", + } + + +def generate_report( + results: dict[str, dict], + changed_files: list[str], +) -> str: + """Generate the markdown summary report.""" + lines = ["## Documentation Review Report", ""] + + total_issues = sum(len(r.get("issues", [])) for r in results.values()) + warnings = sum( + 1 + for r in results.values() + for i in r.get("issues", []) + if i.get("severity") == "warning" + ) + suggestions = total_issues - warnings + + if total_issues == 0: + lines.append("No documentation issues found.") + else: + lines.append( + f"Found **{total_issues}** documentation issue(s) " + f"across **{len(changed_files)}** changed file(s): " + f"{warnings} warning(s), {suggestions} suggestion(s)." + ) + + lines.append("") + lines.append(f"Files reviewed: {len(changed_files)}") + lines.append("") + + for filepath, result in sorted(results.items()): + issues = result.get("issues", []) + summary = result.get("summary", "") + if issues: + lines.append(f"### `{filepath}`") + lines.append("") + lines.append(summary) + lines.append("") + for issue in issues: + severity = issue.get("severity", "suggestion") + icon = "**Warning:**" if severity == "warning" else "**Suggestion:**" + line_num = issue.get("line", "?") + msg = issue.get("message", "") + lines.append(f"- {icon} Line {line_num}: {msg}") + lines.append("") + + lines.append("---") + lines.append( + "*Automated documentation review. " + "See [docs/DOCUMENTATION_STANDARDS.md](../docs/DOCUMENTATION_STANDARDS.md) " + "for guidelines.*" + ) + + return "\n".join(lines) + + +def generate_inline_comments(results: dict[str, dict]) -> list[dict]: + """Generate inline PR review comments from analysis results.""" + comments = [] + for filepath, result in results.items(): + for issue in result.get("issues", []): + line = issue.get("line") + if not line or not isinstance(line, int): + continue + + body = issue.get("message", "") + suggested = issue.get("suggested_doc") + if suggested: + body += f"\n\n**Suggested documentation:**\n```cpp\n{suggested}\n```" + + severity = issue.get("severity", "suggestion") + prefix = "Doc Warning" if severity == "warning" else "Doc Suggestion" + body = f"**{prefix}:** {body}" + + comments.append({"path": filepath, "line": line, "body": body}) + + return comments + + +def main(): + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + print("ERROR: ANTHROPIC_API_KEY not set") + sys.exit(1) + + changed_files_str = os.environ.get("CHANGED_FILES", "") + if not changed_files_str: + print("No changed files to review") + sys.exit(0) + + base_sha = os.environ.get("BASE_SHA", "HEAD~1") + head_sha = os.environ.get("HEAD_SHA", "HEAD") + + changed_files = [f.strip() for f in changed_files_str.split() if f.strip()] + cpp_files = [ + f for f in changed_files if f.endswith((".h", ".hpp", ".cpp")) + ] + + if not cpp_files: + print("No C++ files changed") + sys.exit(0) + + print(f"Reviewing {len(cpp_files)} file(s) for documentation accuracy...") + + client = anthropic.Anthropic(api_key=api_key) + results = {} + + for filepath in cpp_files: + print(f" Analyzing: {filepath}") + diff = get_diff(base_sha, head_sha, filepath) + if not diff: + continue + + content = read_file_safe(filepath) + existing_docs = extract_doc_comments(content) + + analysis = FileAnalysis( + path=filepath, + diff=diff, + existing_docs=existing_docs, + file_content=content, + ) + results[filepath] = analyze_file(client, analysis) + + report = generate_report(results, cpp_files) + Path("doc-review-report.md").write_text(report) + print("\nReport written to doc-review-report.md") + + comments = generate_inline_comments(results) + Path("doc-review-comments.json").write_text(json.dumps(comments, indent=2)) + print(f"Generated {len(comments)} inline comment(s)") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/doc-coverage.yml b/.github/workflows/doc-coverage.yml new file mode 100644 index 0000000000..2e6d865f13 --- /dev/null +++ b/.github/workflows/doc-coverage.yml @@ -0,0 +1,109 @@ +name: Documentation Coverage + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'include/**' + - 'src/libxrpl/**' + - 'src/xrpld/**' + - 'docs/Doxyfile' + - '.github/doc-coverage-thresholds.json' + - '.github/workflows/doc-coverage.yml' + +concurrency: + group: doc-coverage-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + coverage: + runs-on: ubuntu-latest + container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1 + steps: + - name: Checkout PR branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Install coverxygen + run: pip install coverxygen + + - name: Determine new C++ files + id: new-files + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + with: + files: | + include/**/*.h + src/**/*.h + src/**/*.cpp + since_last_remote_commit: false + + - name: Build Doxygen XML (PR branch) + env: + BUILD_DIR: build-pr + run: | + mkdir -p "${BUILD_DIR}" + cd "${BUILD_DIR}" + cmake -Donly_docs=ON .. + cmake --build . --target docs + + - name: Generate coverage report (PR branch) + run: | + coverxygen \ + --xml-dir build-pr/docs/xml \ + --src-dir . \ + --output doc-coverage.info \ + --kind class,struct,function,enum,typedef,variable \ + --scope public + + - name: Build Doxygen XML (base branch) + env: + BUILD_DIR: build-base + run: | + git checkout ${{ github.event.pull_request.base.sha }} + mkdir -p "${BUILD_DIR}" + cd "${BUILD_DIR}" + cmake -Donly_docs=ON .. + cmake --build . --target docs || true + git checkout ${{ github.event.pull_request.head.sha }} + + - name: Generate coverage report (base branch) + run: | + if [ -d "build-base/docs/xml" ]; then + coverxygen \ + --xml-dir build-base/docs/xml \ + --src-dir . \ + --output base-doc-coverage.info \ + --kind class,struct,function,enum,typedef,variable \ + --scope public || true + fi + + - name: Check coverage thresholds + run: | + BASE_FLAG="" + if [ -f "base-doc-coverage.info" ]; then + BASE_FLAG="--base-lcov-file base-doc-coverage.info" + fi + + NEW_FILES="" + if [ -n "${{ steps.new-files.outputs.added_files }}" ]; then + NEW_FILES="--new-files ${{ steps.new-files.outputs.added_files }}" + fi + + python3 .github/scripts/doc-coverage-check.py \ + --lcov-file doc-coverage.info \ + --threshold-file .github/doc-coverage-thresholds.json \ + --output doc-coverage-report.md \ + ${BASE_FLAG} \ + ${NEW_FILES} || true + + - name: Post coverage report to PR + if: always() + uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # v2.9.2 + with: + header: doc-coverage + path: doc-coverage-report.md diff --git a/.github/workflows/doc-review.yml b/.github/workflows/doc-review.yml new file mode 100644 index 0000000000..0bc146c91e --- /dev/null +++ b/.github/workflows/doc-review.yml @@ -0,0 +1,98 @@ +name: Documentation Review + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'include/**/*.h' + - 'src/libxrpl/**/*.h' + - 'src/libxrpl/**/*.cpp' + - 'src/xrpld/**/*.h' + - 'src/xrpld/**/*.cpp' + +concurrency: + group: doc-review-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + review: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install anthropic + + - name: Determine changed C++ files + id: changes + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + with: + files: | + include/**/*.h + src/libxrpl/**/*.h + src/libxrpl/**/*.cpp + src/xrpld/**/*.h + src/xrpld/**/*.cpp + + - name: Run documentation review + if: steps.changes.outputs.any_changed == 'true' + env: + CHANGED_FILES: ${{ steps.changes.outputs.all_changed_files }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: python3 .github/scripts/doc-review.py + + - name: Post review summary + if: steps.changes.outputs.any_changed == 'true' && always() + uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # v2.9.2 + with: + header: doc-review + path: doc-review-report.md + + - name: Post inline review comments + if: steps.changes.outputs.any_changed == 'true' && always() + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + if (!fs.existsSync('doc-review-comments.json')) return; + + const comments = JSON.parse(fs.readFileSync('doc-review-comments.json', 'utf8')); + if (comments.length === 0) return; + + const pull_number = context.payload.pull_request.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + for (const comment of comments) { + try { + await github.rest.pulls.createReviewComment({ + owner, + repo, + pull_number, + body: comment.body, + commit_id: '${{ github.event.pull_request.head.sha }}', + path: comment.path, + line: comment.line, + side: 'RIGHT', + }); + } catch (e) { + console.log(`Failed to post comment on ${comment.path}:${comment.line}: ${e.message}`); + } + } diff --git a/SCOPE_OF_WORK.md b/SCOPE_OF_WORK.md new file mode 100644 index 0000000000..63212a07f8 --- /dev/null +++ b/SCOPE_OF_WORK.md @@ -0,0 +1,345 @@ +# XRPLD Automated Documentation System — Scope of Work + +## 1. Problem Statement + +The XRP Ledger daemon (`xrpld`) is a ~275,000 line C++ codebase with 1,183 +source files across the core library, protocol layer, and application server. +It is the single implementation of the XRP Ledger protocol and processes +billions of dollars in value. + +Despite this criticality, the codebase has minimal inline documentation. Only +569 of 1,183 files contain any Doxygen-style doc comments, and most of those +are sparse — a class-level sentence or two, rarely covering individual methods, +parameters, or behavioral invariants. + +The only formal documentation effort — an external specification by Common +Prefix — has fundamental structural problems: + +- **Drift is the default state.** The spec lives in a separate repository + with no CI linkage to the codebase. Every commit to `rippled` that changes + behavior silently invalidates the spec. Even one week of drift makes + the spec unreliable. +- **Separate repo, separate context.** No contributor has both repos open. + When a bug comes in, the developer reads the code, not the spec. A + recent bug would have been caught if the code itself was documented. +- **No code-level documentation.** The spec describes system-level behavior + (payment engine, DEX) but does not document individual functions, classes, + parameters, or invariants. A developer working on a specific function + gets no help. +- **Vendor dependency.** Ripple has a critical documentation dependency on a + single external firm. If the contract ends, the spec orphans. +- **Perverse incentive.** The vendor profits from complexity and drift. + Cleaner code and better inline docs reduce the need for external + specification work. + +## 2. Proposed Solution + +Build an automated, in-repo documentation system with four components: + +1. **Initial documentation pass** — Comprehensively document all 1,183 + source files using Claude Code with deep xrpld context +2. **Continuous maintenance** — A GitHub Action on every PR that detects + doc drift and suggests updates, using diff-aware LLM analysis +3. **Coverage enforcement** — CI-enforced documentation coverage thresholds + that ratchet up over time, preventing regression +4. **Developer agents** — Claude Code commands for onboarding, architecture + questions, doc review, and bug pattern detection + +All documentation lives alongside the code. No external repos. No external +dependencies. Documentation accuracy is enforced by CI the same way code +style and test coverage are enforced today. + +## 3. Deliverables + +### 3.1 Documentation Standards (docs/DOCUMENTATION_STANDARDS.md) + +A canonical format guide defining: +- Javadoc-style `/** ... */` Doxygen comments (matches 5,718 existing + instances in the codebase) +- Documentation levels: file, class, public method, free function, enum +- Required Doxygen tags: `@param`, `@return`, `@note`, `@invariant` +- Quality rules: document behavior and invariants, never paraphrase + signatures, terse style (2-5 lines for classes, 1-3 for functions) + +**Status: Complete.** File created at `docs/DOCUMENTATION_STANDARDS.md`. + +### 3.2 Doxygen Configuration Changes (docs/Doxyfile) + +- `EXTRACT_ALL = NO` (was `YES`) — so undocumented entities are flagged + rather than silently extracted +- `GENERATE_XML = YES` (was `NO`) — required for coverxygen to parse + and measure documentation coverage + +**Status: Complete.** Changes applied to `docs/Doxyfile`. + +### 3.3 Documentation Coverage Pipeline + +**Components:** + +| File | Purpose | +|------|---------| +| `.github/doc-coverage-thresholds.json` | Per-module thresholds + quarterly ratchet schedule | +| `.github/scripts/doc-coverage-check.py` | Parses coverxygen LCOV output, checks thresholds, generates PR report | +| `.github/workflows/doc-coverage.yml` | CI workflow: builds Doxygen XML, runs coverxygen, posts coverage to PR | +| `cmake/XrplDocs.cmake` | New `docs-coverage` CMake target | + +**How it works:** +1. On every PR touching C++ files, the workflow builds Doxygen XML output + for both the PR branch and the base branch +2. Coverxygen generates LCOV-format coverage reports from the XML +3. The check script compares coverage against per-module thresholds +4. Ratchet mode (`no_decrease`) prevents any PR from reducing doc coverage +5. New files added in a PR require >= 80% doc coverage +6. Results are posted as a sticky PR comment with per-module breakdown + +**Status: Complete.** All files created. + +### 3.4 Doc Review GitHub Action + +**Components:** + +| File | Purpose | +|------|---------| +| `.github/scripts/doc-review.py` | Diff-aware LLM analysis script | +| `.github/workflows/doc-review.yml` | CI workflow: runs on PR, posts review | + +**How it works:** +1. On every PR, determines which C++ files changed +2. For each changed file, extracts the git diff hunks and existing doc + comments +3. Sends both to the Anthropic API with a prompt tuned for xrpld: "Given + this diff, are existing docs still accurate?" +4. Posts results as **inline review comments** on specific lines AND a + **summary comment** on the PR +5. Starts in **warning-only mode** (does not block merge) + +**Cost control:** Only processes changed files and changed hunks within +those files. A typical PR touches 3-10 files. Estimated cost: $0.05-0.15 +per PR. + +**Status: Complete.** All files created. + +### 3.5 Claude Code Agent Commands + +Four developer-facing commands in `.claude/commands/`: + +| Command | Purpose | +|---------|---------| +| `doc-review` | Review doc accuracy for files changed on current branch | +| `explain-module` | Explain a module's architecture, classes, control flow, and entry points | +| `how-does-x-work` | Trace a feature through the codebase with file/line references | +| `find-bug-patterns` | Scan code for common xrpld bug patterns (unchecked TER, integer overflow, missing amendment gates, etc.) | + +**Status: Complete.** All files created. + +### 3.6 Full Codebase Documentation + +The initial documentation pass covers 1,183 C++ files organized into 21 +module-level PRs. Each PR is scoped to a single subsystem so one domain +expert can review it. + +**Status: Not started.** This is the primary execution phase (see Section 5). + +## 4. Resources Required + +### 4.1 People + +| Role | Responsibility | Estimated Time | +|------|---------------|----------------| +| **Documentation lead** (1 person) | Runs Claude Code for each module, reviews output quality, submits PRs, iterates on prompt quality | 50-60% for 15 weeks | +| **Domain reviewers** (3-5 people, rotating) | Review doc PRs for semantic accuracy in their area of expertise. Each reviewer handles 3-5 PRs. | 2-4 hours per PR | +| **CI/infrastructure** (1 person) | Deploys workflows, monitors costs, tunes false positive rate on doc-review action | 10-15% for 15 weeks | + +**Total estimated effort:** ~1 FTE for 15 weeks + ~80-120 hours of +reviewer time spread across 3-5 engineers. + +### 4.2 Infrastructure & Tools + +| Resource | Purpose | Cost | +|----------|---------|------| +| **Anthropic API access** | Powers doc-review GitHub Action | ~$50-100/month (20-30 PRs/week, ~2K tokens per file analysis) | +| **Claude Code license** | Initial documentation pass + developer agent commands | Existing license | +| **GitHub Actions minutes** | Doc-coverage workflow (Doxygen XML build + coverxygen) | ~5-10 min per PR on existing `ubuntu-latest` runners | +| **Coverxygen** | Python package, open source (MIT) | Free | +| **Doxygen** | Already configured and used — existing `ghcr.io/xrplf/ci/tools-rippled-documentation` container | Free (already in CI) | +| **GitHub Actions secret** | `ANTHROPIC_API_KEY` — needed for doc-review workflow | N/A | + +**Estimated ongoing cost after initial pass:** $50-150/month for API +usage, negligible CI compute on existing runners. + +### 4.3 Access & Permissions + +- Write access to the `rippled` repository (or a fork for initial PRs) +- Ability to add GitHub Actions secrets (`ANTHROPIC_API_KEY`) +- Ability to modify required status checks (when promoting doc-review + from warning to required) + +## 5. Execution Plan + +### Phase 0: Infrastructure — Week 1 + +Ship the tooling as a single foundational PR: + +- [x] `docs/DOCUMENTATION_STANDARDS.md` +- [x] `docs/Doxyfile` modifications +- [x] `.github/doc-coverage-thresholds.json` +- [x] `.github/scripts/doc-coverage-check.py` +- [x] `.github/workflows/doc-coverage.yml` +- [x] `cmake/XrplDocs.cmake` modifications +- [x] `.github/workflows/doc-review.yml` +- [x] `.github/scripts/doc-review.py` +- [x] `.claude/commands/` (4 agent commands) + +**Exit criteria:** All workflows pass on a test PR. Coverage report +renders correctly. Doc-review action posts comments without false positives +on a sample PR. + +### Phase 1: Foundation Modules — Weeks 2-4 + +Document the lowest-level modules first (everything else depends on these): + +| PR | Module | ~Files | ~Lines | +|----|--------|--------|--------| +| 1 | `include/xrpl/basics/` + `src/libxrpl/basics/` | 63 | ~15K | +| 2 | `include/xrpl/crypto/` + `src/libxrpl/crypto/` | 6 | ~1.5K | +| 3 | `include/xrpl/json/` + `src/libxrpl/json/` | 18 | ~4K | +| 4 | `include/xrpl/beast/` + `src/libxrpl/beast/` | 88 | ~20K | + +**Process per PR:** +1. Create branch `docs/module-` from `develop` +2. Run Claude Code against each file with full context: the file itself, + its includes, corresponding test files, and the module README +3. Generate `/** */` doc comments following DOCUMENTATION_STANDARDS.md +4. Domain expert reviews for semantic accuracy +5. Run Doxygen build to validate no doc errors +6. Merge, ratchet that module's threshold up to actual coverage level + +**Exit criteria:** 4 PRs merged. Coverage for these modules at 60%+. +Doc-review action running in warning mode on all subsequent PRs. + +### Phase 2: Protocol & Transaction Engine — Weeks 4-8 + +| PR | Module | ~Files | +|----|--------|--------| +| 5 | `include/xrpl/protocol/` + `src/libxrpl/protocol/` | 150 | +| 6 | `include/xrpl/ledger/` + `src/libxrpl/ledger/` | 68 | +| 7 | `include/xrpl/conditions/` + `src/libxrpl/conditions/` | 8 | +| 8 | `include/xrpl/tx/` (core framework: Transactor, ApplyContext) | 15 | +| 9 | Payment transactors | 9 | +| 10 | DEX/AMM transactors | 25 | +| 11 | Escrow transactors | 7 | +| 12 | Other transactors (NFT, token, vault, check, etc.) | 60 | +| 13 | Pathfinding + invariants | 30 | + +**Exit criteria:** 9 PRs merged. Global coverage at 40%+. Doc-review +false positive rate tracked and < 10%. + +### Phase 3: Server & Application Layer — Weeks 8-13 + +| PR | Module | ~Files | +|----|--------|--------| +| 14 | `include/xrpl/server/` + `src/libxrpl/server/` | 35 | +| 15 | `include/xrpl/nodestore/` + `src/libxrpl/nodestore/` | 30 | +| 16 | SHAMap | 25 | +| 17 | Resource management | 17 | +| 18 | Overlay + peerfinder | 56 | +| 19 | Consensus | 15 | +| 20 | Application core (ledger, main, misc, rdb) | 133 | +| 21 | RPC handlers | 131 | + +**Exit criteria:** 8 PRs merged. Global coverage at 60%+. Doc-review +action promoted from warning to **required check**. + +### Phase 4: Tests & Polish — Weeks 13-15 + +- Document test files (brief docs only — test name + what it validates) +- Global threshold at 70% +- Full coverage trend reporting on GitHub Pages +- Retrospective: review false positive rate, API costs, contributor + feedback + +**Exit criteria:** 70% global doc coverage. Doc-review required check +with < 5% false positive rate. Coverage trend visible on GitHub Pages. + +## 6. Threshold Ratchet Schedule + +Coverage thresholds increase quarterly to prevent regression and drive +gradual improvement: + +| Quarter | Global Minimum | Enforcement | +|---------|---------------|-------------| +| Launch (2026-Q2) | 0% | `no_decrease` ratchet only | +| 2026-Q3 | 30% | Blocks PRs below threshold | +| 2026-Q4 | 40% | | +| 2027-Q1 | 50% | | +| 2027-Q2 | 60% | | +| 2027-Q3 | 70% | Target steady state | + +New files always require 80% coverage regardless of the global threshold. + +## 7. Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| LLM generates plausible but wrong docs | Medium | High | Every doc PR requires human domain expert review. Model output is a draft, not final product. | +| Doc-review action false positives annoy contributors | Medium | Medium | Warning-only mode for 3 months. Promote to required only when FP rate < 5%. | +| Coverage enforcement blocks unrelated PRs | Low | Medium | Start at 0% threshold with `no_decrease` only. Quarterly increases announced in advance. | +| Reviewer bandwidth bottleneck | Medium | Medium | PRs scoped to single modules. Reviewers rotate. 2-4 hours per PR is manageable. | +| API costs exceed budget | Low | Low | Only processes diff hunks, not full files. ~$0.05-0.15/PR. Monthly budget cap of $200 with alerting. | +| Doxygen XML build adds CI time | Low | Low | Runs in parallel with existing checks. Uses existing documentation container. ~5 min. | +| Doc comments add code noise | Low | Low | Terse style enforced by standards. 2-5 lines per class, 1-3 per function. | +| Initial pass takes longer than 15 weeks | Medium | Low | Modules are independent. Can parallelize with multiple contributors. Lower-priority modules can slip. | + +## 8. Success Metrics + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Documentation coverage (public API) | 70% | Coverxygen LCOV reports in CI | +| Doc drift catch rate | > 90% of behavioral changes flagged | Sample audit of merged PRs vs doc-review output | +| False positive rate (doc-review action) | < 5% | Track dismissed vs accepted suggestions | +| Zero spec-vs-code contradictions | 0 incidents | Bug reports citing wrong documentation | +| Contributor satisfaction | > 4/5 rating | Quarterly survey: "docs helped me understand the code" | +| Onboarding time reduction | 30% faster first meaningful PR | Measure across new contributors before/after | +| API cost | < $150/month steady state | Anthropic API billing dashboard | + +## 9. What This Replaces + +This system does **not** replace the Common Prefix formal verification +work directly — formal verification and code documentation solve different +problems. However, it eliminates the need for an external specification as +the "source of truth" for how xrpld behaves: + +| Need | Before | After | +|------|--------|-------| +| "What does this function do?" | Read the code, guess | Read the inline doc | +| "How does the payment engine work?" | Read Common Prefix spec (maybe stale) | Run `/explain-module` or `/how-does-x-work` | +| "Did this PR break any documented behavior?" | Manual review, hope someone notices | Doc-review action flags it automatically | +| "What's our documentation coverage?" | Unknown | Measured per-module in every PR | +| "Is the spec up to date?" | Check manually, probably not | Docs are in-repo, enforced by CI | + +## 10. Out of Scope + +- **Formal verification.** This project documents code behavior; it does + not prove correctness. Formal verification is a separate discipline. +- **External-facing API documentation.** This covers the C++ source code, + not the JSON-RPC API documentation on xrpl.org. +- **Test coverage.** Test file documentation (Phase 4) is brief and + optional. Test coverage measurement is handled by existing Codecov + integration. +- **Architectural decision records.** Module-level READMEs already exist + for key subsystems. This project adds function/class-level docs, not + system-level design documents. + +## 11. Timeline Summary + +``` +Week 1 Phase 0: Infrastructure PR (tooling, workflows, standards) +Weeks 2-4 Phase 1: Foundation modules (basics, crypto, json, beast) +Weeks 4-8 Phase 2: Protocol & TX engine (protocol, ledger, tx, paths) +Weeks 8-13 Phase 3: Server & application (overlay, consensus, rpc, app) +Weeks 13-15 Phase 4: Tests & polish, promote to required check +``` + +**Total duration:** 15 weeks +**Total effort:** ~1 FTE + 80-120 hours reviewer time +**Ongoing cost:** ~$50-150/month API + negligible CI compute diff --git a/cmake/XrplDocs.cmake b/cmake/XrplDocs.cmake index 7b3e9b3b30..96764dc2c2 100644 --- a/cmake/XrplDocs.cmake +++ b/cmake/XrplDocs.cmake @@ -89,3 +89,30 @@ add_custom_target( DEPENDS "${doxygen_index_file}" SOURCES "${dependencies}" ) + +# Documentation coverage target using coverxygen. +# Generates LCOV-format coverage report from Doxygen XML output. +# Requires: pip install coverxygen +set(doxygen_xml_dir "${doxygen_output_directory}/xml") +set(doc_coverage_file "${CMAKE_BINARY_DIR}/doc-coverage.info") + +add_custom_command( + OUTPUT "${doc_coverage_file}" + COMMAND + coverxygen + --xml-dir "${doxygen_xml_dir}" + --src-dir "${CMAKE_CURRENT_SOURCE_DIR}" + --output "${doc_coverage_file}" + --kind class,struct,function,enum,typedef,variable + --scope public + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + DEPENDS docs + COMMENT "Generating documentation coverage report" +) +add_custom_target( + docs-coverage + DEPENDS "${doc_coverage_file}" + COMMAND + "${CMAKE_COMMAND}" -E echo + "Documentation coverage report: ${doc_coverage_file}" +) diff --git a/docs/DOCUMENTATION_STANDARDS.md b/docs/DOCUMENTATION_STANDARDS.md new file mode 100644 index 0000000000..074ff55ad4 --- /dev/null +++ b/docs/DOCUMENTATION_STANDARDS.md @@ -0,0 +1,192 @@ +# XRPLD Documentation Standards + +This document defines the canonical format for inline code documentation in the +xrpld codebase. All new and updated code must follow these standards. + +## Comment Style + +Use Javadoc-style Doxygen comments (`/** ... */`). This matches the dominant +convention in the codebase: ~5,200 existing instances across 569 files. + +```cpp +/** Brief description of the entity. */ +``` + +For multi-line documentation, each line is prefixed with ` * ` (space, asterisk, +space): + +```cpp +/** Brief description of the entity. + * + * Extended description with behavioral details, invariants, + * and constraints that are not obvious from the signature. + */ +``` + +`JAVADOC_AUTOBRIEF = YES` is enabled in the Doxyfile, so the first sentence +of any `/** */` block is automatically treated as the brief. An explicit +`@brief` tag is accepted but not required. + +The `///` triple-slash style appears in ~37 files (340 instances). It is +valid Doxygen and will not be removed where it exists, but new code should +use `/** */` for consistency with the majority style. + +## What to Document + +### File-Level (Optional) + +The `@file` tag is not currently used anywhere in the codebase. Adding +file-level documentation is encouraged for complex modules where a +high-level overview helps, but it is not required: + +```cpp +/** @file + * Defines the Payment transactor for the XRP Ledger. + * + * The Payment transactor handles direct XRP transfers, cross-currency + * payments via the pathfinding engine, and partial payments. + */ +``` + +Module-level READMEs (e.g., `src/xrpld/peerfinder/README.md`) remain the +primary place for architectural documentation. + +### Class / Struct Level + +Every class and struct gets a doc block describing: +- What it does (1-2 sentences) +- Key invariants or constraints, if any +- Thread-safety guarantees, if relevant +- Lifecycle notes, if relevant + +```cpp +/** Executes a Payment transaction on the XRP Ledger. + * + * Supports direct XRP payments, cross-currency payments via RippleCalc, + * and partial payments (tfPartialPayment). Path count is limited to 6 + * with max path length of 8. + */ +class Payment : public Transactor { ... }; +``` + +Target: 2-5 lines for most classes. Complex classes may need more. + +### Public Methods and Free Functions + +Every public method and free function in headers gets: +- Brief description of behavior (not a restatement of the signature) +- `@param` for each parameter +- `@return` describing what is returned +- `@throw` if it can throw (either `@throw` or `@throws` is acceptable — + the codebase uses both) +- `@note` for non-obvious constraints or edge cases + +When a `@param` description wraps, continuation lines are indented with +4 spaces from the `*`: + +```cpp +/** Round a Number value to the precision of a given asset. + * + * For IOUs, rounds to the IOU's scale. For XRP and MPT, no rounding + * is performed. + * + * @param asset The relevant asset + * @param value The value to be rounded + * @param scale An exponent value to establish the precision limit of + * `value`. Should be larger than `value.exponent()`. + * @return The rounded Number. + */ +[[nodiscard]] Number +roundToAsset(Asset const& asset, Number const& value, int scale); +``` + +Target: 1-3 lines of description plus `@param`/`@return`. + +### Private Methods + +Document private methods only when the logic is non-obvious. A brief +one-line comment is sufficient. + +### Enums and Constants + +All enums get a brief class-level description. Individual enum values get +inline documentation when the meaning is not self-evident: + +```cpp +/** Result codes for transaction processing. */ +enum TERCode +{ + tesSUCCESS = 0, /**< Transaction succeeded. */ + tecCLAIM = 100, /**< Fee claimed; transaction failed. */ + tecPATH_PARTIAL = 101, /**< Path could not deliver full amount. */ +}; +``` + +## What NOT to Document + +- Do not paraphrase the function signature. `/** Returns the account ID. */` + on `AccountID getAccountID()` adds zero information. +- Do not document what is obvious from well-named identifiers. +- Do not reference specific issues, PRs, or task numbers. These belong in + commit messages and rot as the codebase evolves. +- Do not add multi-paragraph docstrings. If it takes that long to explain, + the code may need restructuring. +- Do not document `.cpp` implementation files exhaustively. Focus docs on + headers where the public interface is defined. + +## Quality Over Quantity + +Wrong documentation is worse than no documentation. Every doc comment must +accurately describe the current behavior. When in doubt: + +- Read the implementation before writing the doc +- Cross-reference against test files for edge cases +- Use `@note` to flag subtle behavior that has caught contributors before + +## Doxygen Tags Reference + +Tags in regular use across the codebase: + +| Tag | Codebase Usage | Purpose | +|-----|----------------|---------| +| `@brief` | ~2,500 instances | Brief description (optional — autobrief is enabled) | +| `@param` | ~2,400 instances | Function parameter description | +| `@return` | ~2,200 instances | Return value description | +| `@note` | ~270 instances | Important behavioral note or caveat | +| `@throw` / `@throws` | ~450 instances combined | Exception specification | +| `@see` | ~64 instances | Cross-reference to related entities | +| `@tparam` | ~43 instances | Template parameter description | + +Tags used rarely but accepted: + +| Tag | Codebase Usage | Purpose | +|-----|----------------|---------| +| `@invariant` | ~13 instances | Property that must always hold | +| `@pre` | ~3 instances | Precondition | +| `@file` | 0 instances | File-level description (new convention, optional) | + +## Enforcement + +Documentation coverage is measured by [coverxygen](https://github.com/psycofdj/coverxygen) +and enforced in CI: + +- PRs cannot decrease documentation coverage (`no_decrease` ratchet mode) +- New files added in a PR require >= 80% doc coverage +- Module-specific thresholds increase quarterly +- The doc-review GitHub Action checks whether code changes invalidate + existing documentation + +Coverage is measured against public API surface: classes, structs, +functions, enums, typedefs, and variables. Private implementation details +are not counted. + +## Style Notes + +- Doc comments go immediately before the entity they describe (no blank + line between the comment and the declaration) +- Keep `@param` descriptions on a single line when possible +- For wrapped `@param` descriptions, indent continuation lines 4 spaces + from the `*` +- Use `@see` sparingly — only when the relationship is non-obvious +- Code style (braces, line width, formatting) is governed by `.clang-format` + and is independent of these documentation standards diff --git a/docs/Doxyfile b/docs/Doxyfile index caa1db30e7..a282256085 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -49,7 +49,7 @@ LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -EXTRACT_ALL = YES +EXTRACT_ALL = NO EXTRACT_PRIVATE = YES EXTRACT_PACKAGE = NO EXTRACT_STATIC = YES @@ -257,7 +257,7 @@ MAN_LINKS = NO #--------------------------------------------------------------------------- # Configuration options related to the XML output #--------------------------------------------------------------------------- -GENERATE_XML = NO +GENERATE_XML = YES XML_OUTPUT = xml XML_PROGRAMLISTING = YES