From e5b999570dfddca315c540c5e8e971f85caddc1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:55:43 +0000 Subject: [PATCH 1/3] Initial plan From 4a8d073ea4fb36de443272bbbdb47e4a4f32882a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:04:47 +0000 Subject: [PATCH 2/3] Add wiki documentation generator and GitHub Actions workflow - Create Python-based doc generator (.github/scripts/generate-wiki-docs.py) that parses Java source files and generates markdown wiki pages - Create GitHub Actions workflow (.github/workflows/wiki-publish.yml) that runs the generator and publishes to GitHub wiki - Update .gitignore to exclude generated wiki output Co-authored-by: mercyblitz <533114+mercyblitz@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsphere-projects/microsphere-java/sessions/7650185e-b104-4ff0-9746-890b795ffe8c --- .github/scripts/generate-wiki-docs.py | 777 ++++++++++++++++++++++++++ .github/workflows/wiki-publish.yml | 76 +++ .gitignore | 6 +- 3 files changed, 858 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/generate-wiki-docs.py create mode 100644 .github/workflows/wiki-publish.yml diff --git a/.github/scripts/generate-wiki-docs.py b/.github/scripts/generate-wiki-docs.py new file mode 100755 index 000000000..23a53a5fb --- /dev/null +++ b/.github/scripts/generate-wiki-docs.py @@ -0,0 +1,777 @@ +#!/usr/bin/env python3 +""" +Microsphere Java Wiki Documentation Generator + +Parses Java source files in the microsphere-java project and generates +Markdown wiki pages for each public Java component (class, interface, enum, annotation). + +Each wiki page includes: +- Detailed explanation of the component +- Example code extracted from Javadoc +- Version compatibility information +- Since version metadata + +Generated pages are written to a specified output directory, one page per component, +ready to be pushed to the GitHub wiki repository. +""" + +import os +import re +import sys +import argparse +from collections import OrderedDict + +# ────────────────────────────────────────────── +# Constants +# ────────────────────────────────────────────── + +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +MODULES = [ + "microsphere-java-annotations", + "microsphere-java-core", + "microsphere-jdk-tools", + "microsphere-lang-model", + "microsphere-annotation-processor", + "microsphere-java-test", +] + +JAVA_VERSIONS = ["8", "11", "17", "21", "25"] + +PROJECT_VERSION = "0.1.10-SNAPSHOT" + +# Source directory path suffix +SRC_MAIN_JAVA = os.path.join("src", "main", "java") + +# Regex patterns +CLASS_DECL_RE = re.compile( + r'^(?:public\s+)?(?:abstract\s+)?(?:final\s+)?' + r'(?:(?:class|interface|enum|@interface)\s+)' + r'(\w+)' + r'(?:<[^{]*>)?' + r'(?:\s+extends\s+[\w.<>, ]+)?' + r'(?:\s+implements\s+[\w.<>, ]+)?', + re.MULTILINE, +) + +PACKAGE_RE = re.compile(r'^\s*package\s+([\w.]+)\s*;', re.MULTILINE) + +JAVADOC_BLOCK_RE = re.compile(r'/\*\*(.*?)\*/', re.DOTALL) + +SINCE_TAG_RE = re.compile(r'@since\s+(.+?)(?:\n|\r|$)') +AUTHOR_TAG_RE = re.compile(r'@author\s+(.+?)(?:\n|\r|$)') +SEE_TAG_RE = re.compile(r'@see\s+(.+?)(?:\n|\r|$)') +PARAM_TAG_RE = re.compile(r'@param\s+(\S+)\s+(.*?)(?=@\w|\Z)', re.DOTALL) + +# Matches @Since annotation on a class (not inside Javadoc) +SINCE_ANNOTATION_RE = re.compile(r'@Since\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']\s*\)') + +# Code example blocks in Javadoc +CODE_EXAMPLE_RE = re.compile(r'
\s*\{@code\s*(.*?)\}
', re.DOTALL) +CODE_EXAMPLE_ALT_RE = re.compile(r'
\s*(.*?)
', re.DOTALL) + +# HTML tags used in Javadoc +LINK_TAG_RE = re.compile(r'\{@link\s+([^}]+)\}') +CODE_TAG_RE = re.compile(r'\{@code\s+([^}]+)\}') +LINKPLAIN_TAG_RE = re.compile(r'\{@linkplain\s+([^}]+)\}') +VALUE_TAG_RE = re.compile(r'\{@value\s+([^}]+)\}') + +# Method/field signatures +PUBLIC_METHOD_RE = re.compile( + r'(?:/\*\*(.*?)\*/\s*)?' + r'(?:@\w+(?:\([^)]*\))?\s*)*' + r'(public\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?' + r'(?:<[^>]+>\s+)?' + r'\S+\s+' # return type + r'(\w+)\s*' # method name + r'\([^)]*\))', # parameters + re.DOTALL, +) + + +# ────────────────────────────────────────────── +# Javadoc Parsing Utilities +# ────────────────────────────────────────────── + +def clean_javadoc_line(line): + """Remove leading whitespace, asterisks, and extra spaces from a Javadoc line.""" + line = line.strip() + if line.startswith('*'): + line = line[1:] + if line.startswith(' '): + line = line[1:] + return line + + +def parse_javadoc(javadoc_text): + """Parse a Javadoc comment block into structured components.""" + if not javadoc_text: + return { + "description": "", + "since": "", + "author": "", + "see": [], + "params": [], + "examples": [], + } + + lines = javadoc_text.split('\n') + cleaned_lines = [clean_javadoc_line(line) for line in lines] + full_text = '\n'.join(cleaned_lines) + + # Extract tags + since_match = SINCE_TAG_RE.search(full_text) + since = since_match.group(1).strip() if since_match else "" + + author_match = AUTHOR_TAG_RE.search(full_text) + author = author_match.group(1).strip() if author_match else "" + # Clean HTML from author + author = re.sub(r'<[^>]+>', '', author).strip() + + see_matches = SEE_TAG_RE.findall(full_text) + see_refs = [s.strip() for s in see_matches] + + # Extract description (text before any @tag) + desc_lines = [] + for line in cleaned_lines: + stripped = line.strip() + if stripped.startswith('@'): + break + desc_lines.append(line) + description = '\n'.join(desc_lines).strip() + + # Extract code examples + examples = [] + for match in CODE_EXAMPLE_RE.finditer(javadoc_text): + code = match.group(1).strip() + # Clean Javadoc asterisks from code lines + code_lines = code.split('\n') + cleaned_code = '\n'.join(clean_javadoc_line(l) for l in code_lines) + examples.append(cleaned_code.strip()) + + if not examples: + for match in CODE_EXAMPLE_ALT_RE.finditer(javadoc_text): + code = match.group(1).strip() + if '{@code' not in code and len(code) > 10: + code_lines = code.split('\n') + cleaned_code = '\n'.join(clean_javadoc_line(l) for l in code_lines) + examples.append(cleaned_code.strip()) + + return { + "description": description, + "since": since, + "author": author, + "see": see_refs, + "params": [], + "examples": examples, + } + + +def convert_javadoc_to_markdown(text): + """Convert Javadoc HTML/tags to Markdown.""" + if not text: + return "" + + # Convert {@link ...} to `...` + text = LINK_TAG_RE.sub(r'`\1`', text) + text = LINKPLAIN_TAG_RE.sub(r'`\1`', text) + text = CODE_TAG_RE.sub(r'`\1`', text) + text = VALUE_TAG_RE.sub(r'`\1`', text) + + # Convert basic HTML + text = re.sub(r'', '\n\n', text) + text = re.sub(r'

', '', text) + text = re.sub(r'', '\n', text) + text = re.sub(r'(.*?)', r'**\1**', text) + text = re.sub(r'(.*?)', r'*\1*', text) + text = re.sub(r'(.*?)', r'*\1*', text) + text = re.sub(r'(.*?)', r'**\1**', text) + text = re.sub(r'(.*?)', r'`\1`', text) + text = re.sub(r'

(.*?)

', r'### \1', text) + text = re.sub(r'

(.*?)

', r'#### \1', text) + text = re.sub(r'', '', text) + text = re.sub(r'
  • (.*?)
  • ', r'- \1', text, flags=re.DOTALL) + text = re.sub(r'
  • ', '- ', text) + + # Remove remaining HTML tags (except
    )
    +    text = re.sub(r'<(?!pre|/pre)[^>]+>', '', text)
    +
    +    return text.strip()
    +
    +
    +# ──────────────────────────────────────────────
    +# Java Source File Parser
    +# ──────────────────────────────────────────────
    +
    +class JavaComponent:
    +    """Represents a parsed Java component (class, interface, enum, annotation)."""
    +
    +    def __init__(self):
    +        self.name = ""
    +        self.package = ""
    +        self.module = ""
    +        self.component_type = ""  # class, interface, enum, annotation
    +        self.description = ""
    +        self.since_version = ""
    +        self.author = ""
    +        self.see_refs = []
    +        self.examples = []
    +        self.extends_class = ""
    +        self.implements_interfaces = []
    +        self.declaration_line = ""
    +        self.public_methods = []
    +        self.source_path = ""
    +
    +    @property
    +    def fully_qualified_name(self):
    +        if self.package:
    +            return f"{self.package}.{self.name}"
    +        return self.name
    +
    +    @property
    +    def wiki_page_name(self):
    +        """Generate a wiki-friendly page name."""
    +        return self.fully_qualified_name.replace('.', '-')
    +
    +
    +class JavaMethod:
    +    """Represents a parsed public method."""
    +
    +    def __init__(self):
    +        self.name = ""
    +        self.signature = ""
    +        self.description = ""
    +        self.since_version = ""
    +        self.examples = []
    +        self.params = []
    +
    +
    +def parse_java_file(filepath, module_name):
    +    """Parse a Java source file and extract component information."""
    +    try:
    +        with open(filepath, 'r', encoding='utf-8') as f:
    +            content = f.read()
    +    except (IOError, UnicodeDecodeError):
    +        return None
    +
    +    # Extract package
    +    pkg_match = PACKAGE_RE.search(content)
    +    package_name = pkg_match.group(1) if pkg_match else ""
    +
    +    # Check for package-info.java
    +    if os.path.basename(filepath) == 'package-info.java':
    +        return None
    +
    +    # Find the class/interface/enum/annotation declaration
    +    # First, find the Javadoc that precedes the class declaration
    +    class_javadoc = None
    +    class_decl_match = None
    +
    +    # Strategy: find all Javadoc blocks and the class declaration
    +    javadoc_blocks = list(JAVADOC_BLOCK_RE.finditer(content))
    +
    +    # Find the main type declaration
    +    for line in content.split('\n'):
    +        stripped = line.strip()
    +        if re.match(r'(?:public\s+)?(?:abstract\s+)?(?:final\s+)?(?:class|interface|enum|@interface)\s+', stripped):
    +            class_decl_match = stripped
    +            break
    +
    +    if not class_decl_match:
    +        return None
    +
    +    # Determine component type (check @interface before interface)
    +    comp_type = "class"
    +    if re.search(r'@interface\s+', class_decl_match):
    +        comp_type = "annotation"
    +    elif re.search(r'\binterface\s+', class_decl_match):
    +        comp_type = "interface"
    +    elif re.search(r'\benum\s+', class_decl_match):
    +        comp_type = "enum"
    +
    +    # Extract class name
    +    name_match = re.search(
    +        r'(?:class|interface|enum|@interface)\s+(\w+)', class_decl_match
    +    )
    +    if not name_match:
    +        return None
    +
    +    class_name = name_match.group(1)
    +
    +    # Skip non-public classes, inner classes, and module-info
    +    if 'public' not in class_decl_match and comp_type != "annotation":
    +        # Check if the file name matches the class name (top-level class)
    +        file_basename = os.path.splitext(os.path.basename(filepath))[0]
    +        if file_basename != class_name:
    +            return None
    +
    +    if class_name in ('module-info', 'package-info'):
    +        return None
    +
    +    # Find the class-level Javadoc (the last Javadoc before the class declaration)
    +    class_decl_pos = content.find(class_decl_match)
    +    for jd_block in reversed(javadoc_blocks):
    +        if jd_block.end() <= class_decl_pos:
    +            # Verify there's no other declaration between this Javadoc and the class
    +            between = content[jd_block.end():class_decl_pos].strip()
    +            # Remove annotations between Javadoc and class decl
    +            between_cleaned = re.sub(r'@\w+(?:\([^)]*\))?', '', between).strip()
    +            if not between_cleaned or between_cleaned.startswith('@'):
    +                class_javadoc = jd_block.group(1)
    +                break
    +
    +    # Parse the class Javadoc
    +    javadoc_info = parse_javadoc(class_javadoc)
    +
    +    # Check for @Since annotation on the class
    +    since_annotation = SINCE_ANNOTATION_RE.search(content[:class_decl_pos + len(class_decl_match)])
    +    annotation_since = since_annotation.group(1) if since_annotation else ""
    +
    +    # Extract extends/implements
    +    extends_match = re.search(r'\bextends\s+([\w.<>, ]+?)(?:\s+implements|\s*\{)', class_decl_match)
    +    extends_class = extends_match.group(1).strip() if extends_match else ""
    +
    +    implements_match = re.search(r'\bimplements\s+([\w.<>, ]+?)(?:\s*\{|$)', class_decl_match)
    +    implements_interfaces = []
    +    if implements_match:
    +        impl_str = implements_match.group(1).strip().rstrip('{').strip()
    +        implements_interfaces = [i.strip() for i in impl_str.split(',') if i.strip()]
    +
    +    # Parse public methods
    +    public_methods = extract_public_methods(content, class_decl_pos)
    +
    +    # Build the component
    +    component = JavaComponent()
    +    component.name = class_name
    +    component.package = package_name
    +    component.module = module_name
    +    component.component_type = comp_type
    +    component.description = javadoc_info["description"]
    +    component.since_version = javadoc_info["since"] or annotation_since
    +    component.author = javadoc_info["author"]
    +    component.see_refs = javadoc_info["see"]
    +    component.examples = javadoc_info["examples"]
    +    component.extends_class = extends_class
    +    component.implements_interfaces = implements_interfaces
    +    component.declaration_line = class_decl_match.rstrip('{').strip()
    +    component.public_methods = public_methods
    +    component.source_path = os.path.relpath(filepath, PROJECT_ROOT)
    +
    +    return component
    +
    +
    +def extract_public_methods(content, class_start_pos):
    +    """Extract public methods from the class body."""
    +    methods = []
    +    # Only look at content after class declaration
    +    body = content[class_start_pos:]
    +
    +    for match in PUBLIC_METHOD_RE.finditer(body):
    +        javadoc_text = match.group(1)
    +        full_signature = match.group(2)
    +        method_name = match.group(3)
    +
    +        # Skip constructors, getters/setters that are trivial
    +        if method_name in ('toString', 'hashCode', 'equals', 'clone'):
    +            continue
    +
    +        method = JavaMethod()
    +        method.name = method_name
    +        method.signature = full_signature.strip()
    +
    +        if javadoc_text:
    +            method_jd = parse_javadoc(javadoc_text)
    +            method.description = method_jd["description"]
    +            method.since_version = method_jd["since"]
    +            method.examples = method_jd["examples"]
    +
    +        methods.append(method)
    +
    +    return methods[:20]  # Limit to 20 methods per class to keep docs manageable
    +
    +
    +# ──────────────────────────────────────────────
    +# Wiki Page Generator
    +# ──────────────────────────────────────────────
    +
    +def generate_wiki_page(component):
    +    """Generate a Markdown wiki page for a Java component."""
    +    lines = []
    +
    +    # Title
    +    type_label = component.component_type.capitalize()
    +    lines.append(f"# {component.name}")
    +    lines.append("")
    +
    +    # Metadata badge line
    +    badges = []
    +    badges.append(f"**Type:** `{type_label}`")
    +    badges.append(f"**Module:** `{component.module}`")
    +    badges.append(f"**Package:** `{component.package}`")
    +    if component.since_version:
    +        badges.append(f"**Since:** `{component.since_version}`")
    +    lines.append(" | ".join(badges))
    +    lines.append("")
    +
    +    # Source link
    +    lines.append(f"> **Source:** [`{component.source_path}`]"
    +                 f"(https://github.com/microsphere-projects/microsphere-java/blob/main/{component.source_path})")
    +    lines.append("")
    +
    +    # ── Overview ──
    +    lines.append("## Overview")
    +    lines.append("")
    +    if component.description:
    +        desc_md = convert_javadoc_to_markdown(component.description)
    +        lines.append(desc_md)
    +    else:
    +        lines.append(f"`{component.name}` is a {type_label.lower()} in the "
    +                     f"`{component.package}` package of the `{component.module}` module.")
    +    lines.append("")
    +
    +    # Declaration
    +    lines.append("### Declaration")
    +    lines.append("")
    +    lines.append("```java")
    +    lines.append(component.declaration_line)
    +    lines.append("```")
    +    lines.append("")
    +
    +    # ── Author ──
    +    if component.author:
    +        lines.append(f"**Author:** {component.author}")
    +        lines.append("")
    +
    +    # ── Since / Version Info ──
    +    lines.append("## Version Information")
    +    lines.append("")
    +    if component.since_version:
    +        lines.append(f"- **Introduced in:** `{component.since_version}`")
    +    else:
    +        lines.append(f"- **Introduced in:** `{PROJECT_VERSION}` (current)")
    +    lines.append(f"- **Current Project Version:** `{PROJECT_VERSION}`")
    +    lines.append("")
    +
    +    # ── Version Compatibility ──
    +    lines.append("## Version Compatibility")
    +    lines.append("")
    +    lines.append("This component is tested and compatible with the following Java versions:")
    +    lines.append("")
    +    lines.append("| Java Version | Status |")
    +    lines.append("|:---:|:---:|")
    +    for v in JAVA_VERSIONS:
    +        lines.append(f"| Java {v} | ✅ Compatible |")
    +    lines.append("")
    +
    +    # ── Examples ──
    +    has_examples = bool(component.examples)
    +    if not has_examples:
    +        # Check methods for examples
    +        for method in component.public_methods:
    +            if method.examples:
    +                has_examples = True
    +                break
    +
    +    if has_examples:
    +        lines.append("## Examples")
    +        lines.append("")
    +
    +        if component.examples:
    +            for i, example in enumerate(component.examples, 1):
    +                if len(component.examples) > 1:
    +                    lines.append(f"### Example {i}")
    +                    lines.append("")
    +                lines.append("```java")
    +                lines.append(example)
    +                lines.append("```")
    +                lines.append("")
    +
    +        # Method-level examples
    +        method_examples_added = False
    +        for method in component.public_methods:
    +            if method.examples:
    +                if not method_examples_added:
    +                    lines.append(f"### Method Examples")
    +                    lines.append("")
    +                    method_examples_added = True
    +                lines.append(f"#### `{method.name}`")
    +                lines.append("")
    +                for example in method.examples:
    +                    lines.append("```java")
    +                    lines.append(example)
    +                    lines.append("```")
    +                    lines.append("")
    +
    +    # ── Usage Guide ──
    +    lines.append("## Usage")
    +    lines.append("")
    +    lines.append("### Maven Dependency")
    +    lines.append("")
    +    lines.append("Add the following dependency to your `pom.xml`:")
    +    lines.append("")
    +    lines.append("```xml")
    +    lines.append("")
    +    lines.append("    io.github.microsphere-projects")
    +    lines.append(f"    {component.module}")
    +    lines.append(f"    ${{microsphere-java.version}}")
    +    lines.append("")
    +    lines.append("```")
    +    lines.append("")
    +    lines.append("> **Tip:** Use the BOM (`microsphere-java-dependencies`) for consistent version management. "
    +                 "See the [Getting Started](https://github.com/microsphere-projects/microsphere-java#getting-started) guide.")
    +    lines.append("")
    +
    +    # ── Import ──
    +    lines.append("### Import")
    +    lines.append("")
    +    lines.append("```java")
    +    lines.append(f"import {component.fully_qualified_name};")
    +    lines.append("```")
    +    lines.append("")
    +
    +    # ── Public API ──
    +    if component.public_methods:
    +        lines.append("## API Reference")
    +        lines.append("")
    +        lines.append("### Public Methods")
    +        lines.append("")
    +        lines.append("| Method | Description |")
    +        lines.append("|--------|-------------|")
    +        for method in component.public_methods:
    +            desc = method.description.split('\n')[0] if method.description else ""
    +            desc = convert_javadoc_to_markdown(desc)
    +            # Truncate long descriptions for the table
    +            if len(desc) > 120:
    +                desc = desc[:117] + "..."
    +            sig = method.signature.replace('|', '\\|')
    +            lines.append(f"| `{method.name}` | {desc} |")
    +        lines.append("")
    +
    +        # Detailed method descriptions
    +        has_detailed_methods = any(
    +            m.description and len(m.description) > 50 for m in component.public_methods
    +        )
    +        if has_detailed_methods:
    +            lines.append("### Method Details")
    +            lines.append("")
    +            for method in component.public_methods:
    +                if method.description and len(method.description) > 50:
    +                    lines.append(f"#### `{method.name}`")
    +                    lines.append("")
    +                    lines.append(f"```java")
    +                    lines.append(method.signature)
    +                    lines.append("```")
    +                    lines.append("")
    +                    desc_md = convert_javadoc_to_markdown(method.description)
    +                    lines.append(desc_md)
    +                    lines.append("")
    +                    if method.since_version:
    +                        lines.append(f"*Since: {method.since_version}*")
    +                        lines.append("")
    +
    +    # ── See Also ──
    +    if component.see_refs:
    +        lines.append("## See Also")
    +        lines.append("")
    +        for ref in component.see_refs:
    +            ref_clean = ref.strip()
    +            if ref_clean:
    +                lines.append(f"- `{ref_clean}`")
    +        lines.append("")
    +
    +    # ── Footer ──
    +    lines.append("---")
    +    lines.append("")
    +    lines.append(f"*This documentation was auto-generated from the source code of "
    +                 f"[microsphere-java](https://github.com/microsphere-projects/microsphere-java).*")
    +    lines.append("")
    +
    +    return '\n'.join(lines)
    +
    +
    +def generate_home_page(components_by_module):
    +    """Generate the Home (index) wiki page."""
    +    lines = []
    +    lines.append("# Microsphere Java - API Documentation")
    +    lines.append("")
    +    lines.append("Welcome to the **Microsphere Java** wiki! This documentation is auto-generated "
    +                 "from the project source code and provides detailed information about each Java component.")
    +    lines.append("")
    +    lines.append("## Project Information")
    +    lines.append("")
    +    lines.append(f"- **Current Version:** `{PROJECT_VERSION}`")
    +    lines.append(f"- **Java Compatibility:** {', '.join('Java ' + v for v in JAVA_VERSIONS)}")
    +    lines.append("- **License:** Apache License 2.0")
    +    lines.append(f"- **Repository:** [microsphere-projects/microsphere-java]"
    +                 f"(https://github.com/microsphere-projects/microsphere-java)")
    +    lines.append("")
    +
    +    # Table of Contents by module
    +    lines.append("## Modules")
    +    lines.append("")
    +
    +    for module_name, components in components_by_module.items():
    +        lines.append(f"### {module_name}")
    +        lines.append("")
    +
    +        # Group by package
    +        by_package = OrderedDict()
    +        for comp in components:
    +            pkg = comp.package or "(default)"
    +            if pkg not in by_package:
    +                by_package[pkg] = []
    +            by_package[pkg].append(comp)
    +
    +        for pkg, comps in by_package.items():
    +            lines.append(f"**`{pkg}`**")
    +            lines.append("")
    +            for comp in sorted(comps, key=lambda c: c.name):
    +                type_icon = {
    +                    "class": "📦",
    +                    "interface": "🔌",
    +                    "enum": "🔢",
    +                    "annotation": "🏷️",
    +                }.get(comp.component_type, "📄")
    +                wiki_link = comp.wiki_page_name
    +                lines.append(f"- {type_icon} [{comp.name}]({wiki_link}) - "
    +                             f"{comp.component_type.capitalize()}"
    +                             f"{' - Since ' + comp.since_version if comp.since_version else ''}")
    +            lines.append("")
    +
    +    # Quick links
    +    lines.append("## Quick Links")
    +    lines.append("")
    +    lines.append("- [Getting Started](https://github.com/microsphere-projects/microsphere-java#getting-started)")
    +    lines.append("- [Building from Source](https://github.com/microsphere-projects/microsphere-java#building-from-source)")
    +    lines.append("- [Contributing](https://github.com/microsphere-projects/microsphere-java#contributing)")
    +    lines.append("- [JavaDoc](https://javadoc.io/doc/io.github.microsphere-projects)")
    +    lines.append("")
    +    lines.append("---")
    +    lines.append("")
    +    lines.append("*This wiki is auto-generated from the source code of "
    +                 "[microsphere-java](https://github.com/microsphere-projects/microsphere-java). "
    +                 "To update, trigger the `wiki-publish` workflow.*")
    +    lines.append("")
    +
    +    return '\n'.join(lines)
    +
    +
    +def generate_sidebar(components_by_module):
    +    """Generate the _Sidebar wiki page for navigation."""
    +    lines = []
    +    lines.append("**[Home](Home)**")
    +    lines.append("")
    +
    +    for module_name, components in components_by_module.items():
    +        # Shorten module name for sidebar
    +        short_name = module_name.replace("microsphere-", "")
    +        lines.append(f"**{short_name}**")
    +        lines.append("")
    +        for comp in sorted(components, key=lambda c: c.name):
    +            wiki_link = comp.wiki_page_name
    +            lines.append(f"- [{comp.name}]({wiki_link})")
    +        lines.append("")
    +
    +    return '\n'.join(lines)
    +
    +
    +# ──────────────────────────────────────────────
    +# Main
    +# ──────────────────────────────────────────────
    +
    +def discover_java_files(project_root, modules):
    +    """Discover all main Java source files in the given modules."""
    +    java_files = []
    +    for module in modules:
    +        src_dir = os.path.join(project_root, module, SRC_MAIN_JAVA)
    +        if not os.path.isdir(src_dir):
    +            continue
    +        for root, _dirs, files in os.walk(src_dir):
    +            for fname in files:
    +                if fname.endswith('.java') and fname != 'package-info.java' and fname != 'module-info.java':
    +                    java_files.append((os.path.join(root, fname), module))
    +    return java_files
    +
    +
    +def main():
    +    parser = argparse.ArgumentParser(description="Generate wiki documentation for microsphere-java")
    +    parser.add_argument(
    +        "--output", "-o",
    +        default=os.path.join(PROJECT_ROOT, "wiki"),
    +        help="Output directory for generated wiki pages (default: /wiki)",
    +    )
    +    parser.add_argument(
    +        "--project-root",
    +        default=PROJECT_ROOT,
    +        help="Root directory of the microsphere-java project",
    +    )
    +    args = parser.parse_args()
    +
    +    project_root = args.project_root
    +    output_dir = args.output
    +
    +    print(f"Microsphere Java Wiki Documentation Generator")
    +    print(f"  Project root: {project_root}")
    +    print(f"  Output dir:   {output_dir}")
    +    print()
    +
    +    # Discover Java files
    +    java_files = discover_java_files(project_root, MODULES)
    +    print(f"Found {len(java_files)} Java source files across {len(MODULES)} modules")
    +    print()
    +
    +    # Parse all files
    +    components = []
    +    for filepath, module_name in java_files:
    +        component = parse_java_file(filepath, module_name)
    +        if component:
    +            components.append(component)
    +
    +    print(f"Parsed {len(components)} Java components")
    +    print()
    +
    +    # Group by module
    +    components_by_module = OrderedDict()
    +    for module in MODULES:
    +        module_components = [c for c in components if c.module == module]
    +        if module_components:
    +            components_by_module[module] = sorted(module_components, key=lambda c: (c.package, c.name))
    +
    +    # Create output directory
    +    os.makedirs(output_dir, exist_ok=True)
    +
    +    # Generate individual wiki pages
    +    page_count = 0
    +    for module_name, module_components in components_by_module.items():
    +        for comp in module_components:
    +            page_content = generate_wiki_page(comp)
    +            page_filename = f"{comp.wiki_page_name}.md"
    +            page_path = os.path.join(output_dir, page_filename)
    +            with open(page_path, 'w', encoding='utf-8') as f:
    +                f.write(page_content)
    +            page_count += 1
    +
    +    print(f"Generated {page_count} wiki pages")
    +
    +    # Generate Home page
    +    home_content = generate_home_page(components_by_module)
    +    with open(os.path.join(output_dir, "Home.md"), 'w', encoding='utf-8') as f:
    +        f.write(home_content)
    +    print("Generated Home.md")
    +
    +    # Generate Sidebar
    +    sidebar_content = generate_sidebar(components_by_module)
    +    with open(os.path.join(output_dir, "_Sidebar.md"), 'w', encoding='utf-8') as f:
    +        f.write(sidebar_content)
    +    print("Generated _Sidebar.md")
    +
    +    print()
    +    print(f"Wiki documentation generated successfully in: {output_dir}")
    +    print(f"Total pages: {page_count + 2} ({page_count} components + Home + Sidebar)")
    +
    +    return 0
    +
    +
    +if __name__ == "__main__":
    +    sys.exit(main())
    diff --git a/.github/workflows/wiki-publish.yml b/.github/workflows/wiki-publish.yml
    new file mode 100644
    index 000000000..02a20fd7e
    --- /dev/null
    +++ b/.github/workflows/wiki-publish.yml
    @@ -0,0 +1,76 @@
    +name: Generate and Publish Wiki Documentation
    +
    +on:
    +  push:
    +    branches: [ main ]
    +    paths:
    +      - '*/src/main/java/**/*.java'
    +      - '.github/scripts/generate-wiki-docs.py'
    +      - '.github/workflows/wiki-publish.yml'
    +  workflow_dispatch:
    +
    +permissions:
    +  contents: write
    +
    +jobs:
    +  generate-wiki:
    +    name: Generate Wiki Documentation
    +    runs-on: ubuntu-latest
    +
    +    steps:
    +      - name: Checkout source repository
    +        uses: actions/checkout@v4
    +        with:
    +          fetch-depth: 1
    +
    +      - name: Set up Python
    +        uses: actions/setup-python@v5
    +        with:
    +          python-version: '3.x'
    +
    +      - name: Generate wiki pages
    +        run: |
    +          python .github/scripts/generate-wiki-docs.py --output wiki-output
    +          echo "Generated wiki pages:"
    +          ls -la wiki-output/ | head -20
    +          echo "Total pages: $(ls wiki-output/*.md | wc -l)"
    +
    +      - name: Checkout wiki repository
    +        uses: actions/checkout@v4
    +        with:
    +          repository: ${{ github.repository }}.wiki
    +          path: wiki-repo
    +          token: ${{ secrets.GITHUB_TOKEN }}
    +        continue-on-error: true
    +
    +      - name: Initialize wiki repository if needed
    +        run: |
    +          if [ ! -d "wiki-repo/.git" ]; then
    +            echo "Wiki repository not found. Creating initial wiki structure..."
    +            mkdir -p wiki-repo
    +            cd wiki-repo
    +            git init
    +            git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git"
    +          fi
    +
    +      - name: Copy generated pages to wiki
    +        run: |
    +          # Copy all generated markdown files to the wiki repo
    +          cp wiki-output/*.md wiki-repo/
    +          echo "Copied wiki pages to wiki repository"
    +          ls -la wiki-repo/*.md | head -20
    +
    +      - name: Push to wiki
    +        run: |
    +          cd wiki-repo
    +          git config user.name "github-actions[bot]"
    +          git config user.email "github-actions[bot]@users.noreply.github.com"
    +          git add -A
    +          if git diff --cached --quiet; then
    +            echo "No changes to wiki documentation"
    +          else
    +            TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
    +            git commit -m "Update wiki documentation - ${TIMESTAMP}"
    +            git push origin HEAD:master || git push origin HEAD:main
    +            echo "Wiki documentation updated successfully"
    +          fi
    diff --git a/.gitignore b/.gitignore
    index 0d7fad0c8..89351eda5 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -67,4 +67,8 @@ compiler/.gradle/*
     .java-version
     
     # others
    -build.txt
    \ No newline at end of file
    +build.txt
    +
    +# Wiki generated output
    +wiki/
    +wiki-output/
    \ No newline at end of file
    
    From f8bbc5622fbb21d7797c58885184b3c21259a21b Mon Sep 17 00:00:00 2001
    From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
    Date: Sat, 21 Mar 2026 11:06:27 +0000
    Subject: [PATCH 3/3] Optimize @Since annotation search to limit search range
     near class declaration
    
    Co-authored-by: mercyblitz <533114+mercyblitz@users.noreply.github.com>
    Agent-Logs-Url: https://github.com/microsphere-projects/microsphere-java/sessions/7650185e-b104-4ff0-9746-890b795ffe8c
    ---
     .github/scripts/generate-wiki-docs.py | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/.github/scripts/generate-wiki-docs.py b/.github/scripts/generate-wiki-docs.py
    index 23a53a5fb..c8d9c56c2 100755
    --- a/.github/scripts/generate-wiki-docs.py
    +++ b/.github/scripts/generate-wiki-docs.py
    @@ -324,8 +324,9 @@ def parse_java_file(filepath, module_name):
         # Parse the class Javadoc
         javadoc_info = parse_javadoc(class_javadoc)
     
    -    # Check for @Since annotation on the class
    -    since_annotation = SINCE_ANNOTATION_RE.search(content[:class_decl_pos + len(class_decl_match)])
    +    # Check for @Since annotation on the class (limit search to nearby context)
    +    search_start = max(0, class_decl_pos - 2000)
    +    since_annotation = SINCE_ANNOTATION_RE.search(content[search_start:class_decl_pos + len(class_decl_match)])
         annotation_since = since_annotation.group(1) if since_annotation else ""
     
         # Extract extends/implements