This file is a merged representation of the entire codebase, combining all repository files into a single document.
Generated by Repomix on: 2025-11-28 18:31:30

# File Summary

## Purpose:

This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.

## File Format:

The content is organized as follows:
1. This summary section
2. Repository information
3. Repository structure
4. Multiple file entries, each consisting of:
   a. A header with the file path (## File: path/to/file)
   b. The full contents of the file in a code block

## Usage Guidelines:

- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.

## Notes:

- Some files may have been excluded based on .gitignore rules and Repomix's
  configuration.
- Binary files are not included in this packed representation. Please refer to
  the Repository Structure section for a complete list of file paths, including
  binary files.

## Additional Information:

For more information about Repomix, visit: https://github.com/andersonby/python-repomix


# Repository Structure

```
.gitignore
ai_md_files_demo
  nb_ai_context_all_docs_and_codes.md
examples
  AiMdGenerator_example.py
git_nb_ai_context.py
nb_ai_context
  ai_md_generator.py
  contrib
    about.md
    gen_github_proj_ai_md.py
  __init__.py
pub_pypi_nb_ai_context.py
pyproject.toml
README.md
repomix-output.md
tests
  ai_codes
    demo_init_extraction.py
    test_file_content_backquotes.py
    test_init_extraction.py
  ai_docs
    CHANGELOG_init_extraction.md
    complete_docstring_extraction.md
    init_extraction_feature.md
    t.md
    UPDATE_SUMMARY.md
```

# Repository Files


## .gitignore

```text
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/

# pixi
#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
#   in the .venv directory. It is recommended not to include this directory in version control.
.pixi

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
#  you could uncomment the following to ignore the entire vscode folder
# .vscode/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Cursor
#  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
#  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
#  refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/


markdown_gen_files_git_ignore/
```

## ai_md_files_demo/nb_ai_context_all_docs_and_codes.md

``````markdown
﻿# markdown content namespace: nb_ai_context project summary 



- `nb_ai_context` is a powerful ai llm context gennerator library ,it is uesd for ai llm and rag
- `AiMdGenerator(...)` is the main class to create ai context for llm.


## 📋 nb_ai_context most core source files metadata (Entry Points)


以下是项目 nb_ai_context 最核心的入口文件的结构化元数据，帮助快速理解项目架构：



### the project nb_ai_context most core source code files as follows: 
- `nb_ai_context/__init__.py`
- `nb_ai_context/ai_md_generator.py`
- `nb_ai_context/contrib/gen_github_proj_ai_md.py`


### 📄 Python File Metadata: `nb_ai_context/__init__.py`

#### 📦 Imports

- `from nb_path import NbPath`
- `from ai_md_generator import AiMdGenerator`
- `from contrib.gen_github_proj_ai_md import gen_github_proj_docs_and_codes_ai_md`
- `from contrib.gen_github_proj_ai_md import gen_github_proj_all_dirs_ai_md`


---




### 📄 Python File Metadata: `nb_ai_context/ai_md_generator.py`

#### 📦 Imports

- `import typing`
- `import os`
- `import fnmatch`
- `import ast`
- `from nb_path import NbPath`
- `import astor`

#### 🏛️ Classes (1)

##### 📌 `class AiMdGenerator(NbPath)`
*Line: 10*

**Docstring:**
`````
An extremely powerful context generator born for AI collaboration.

This class is designed to revolutionize how developers interact with Large Language
Models (LLMs). It intelligently merges multiple project source files into a single,
well-structured, and context-rich Markdown file, providing the AI with a perfect
and comprehensive project snapshot.

The benefits for large AI models are immense:
1.  **Provides a God's-eye View**: Through a file manifest, clear file boundaries,
    and relative paths, the AI can easily construct the project's overall
    architecture and understand file dependencies and relationships, rather than
    fumbling in the dark.
2.  **Ensures Information Integrity and Accuracy**: The AI receives complete,
    unabridged source file content, avoiding the chaos, omissions, or context
    loss caused by manual copy-pasting. This enables it to provide more precise
    analysis and suggestions.
3.  **Enhances Security**: The built-in `use_gitignore` feature is a critical
    security barrier. It automatically ignores files containing sensitive
    information (like API keys or database passwords) such as `.env` or local
    configs, allowing you to share code without fear of accidental leaks.

Its core methods, `merge_from_files` and `merge_from_dir`, offer extreme
flexibility. Combined with the elegant chainable calls of `nb_path`, creating a
high-quality AI context is transformed from a tedious, error-prone manual task
into a single, delightful line of code.

Example:
    >>> # Imagine you want an AI to review your entire project
    >>> project_name = "my_project"
    >>> project_summary = '''
    ... This is an excellent Python project that demonstrates best practices.
    ... It includes comprehensive documentation and well-structured code.
    ... '''
    >>> 
    >>> (
    ...     AiMdGenerator("project_context_for_ai.md")
    ...     .set_project_propery(project_name=project_name, project_root="/path/to/your/project")
    ...     .clear_text()  # Clear the old file
    ...     .add_project_summary(
    ...         project_summary=project_summary,
    ...         # Extract metadata (without full source) from core files first
    ...         most_core_source_code_file_list=[
    ...             "src/main.py",
    ...             "src/api.py",
    ...             "src/models.py",
    ...         ]
    ...     )
    ...     .auto_merge_from_python_project_some_files()  # Auto-include README, setup.py, etc.
    ...     .merge_from_dir(
    ...         relative_dir_name="src", # The main source code directory
    ...         as_title="Project Source Code",
    ...         use_gitignore=True,  # Automatically use .gitignore rules
    ...         should_include_suffixes=[".py", ".md"], # Only include specified file types
    ...         include_ast_metadata=True,  # Include AST metadata for Python files
    ...     )
    ...     .merge_from_dir(
    ...         relative_dir_name="tests", # The tests directory
    ...         as_title="Project Tests",
    ...         use_gitignore=True,
    ...         should_include_suffixes=[".py"],
    ...         excluded_dir_name_list=["tests/temp_files"],
    ...         include_ast_metadata=True,
    ...     )
    ... )
`````

**Public Methods (7):**
- `def set_project_propery(self, project_name: str, project_root: typing.Union[os.PathLike, str]) -> 'AiMdGenerator'`
  - *Sets the project name for the current markdown file.*
- `def add_project_summary(self, project_summary: str, most_core_source_code_file_list: typing.List[str] = None, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  Adds a project summary to the current markdown file.
  
  Args:
      project_summary: 项目概述文本
      project_root: 项目根目录（如果提供了 most_core_source_code_file_list）
      most_core_source_code_file_list: 最核心的源码文件列表（相对路径）
                                       只提取这些文件的 AST 元数据，不包含完整源码
  
  Example:
      >>> (
      ...     AiMdGenerator("output.md")
      ...     .set_project_name("my_project")
      ...     .clear_text()
      ...     .add_project_summary(
      ...         project_summary="这是一个优秀的项目...",
      ...         project_root="/path/to/project",
      ...         most_core_source_code_file_list=["src/main.py", "src/api.py"],
      ...     )
      ... )
  `````
- `def auto_merge_from_python_project_some_files(self, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - *自动合并项目根目录下的 readme.md 或者ReADME.md 以及setup.py 和 pyproject.toml ，如果有就添加*
- `def merge_from_files(self, relative_file_name_list: typing.List[str], as_title: str, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  Merges the content of the given files into the current markdown file.
  the current markdown file will be used to upload to ai model for code review and learning.
  `````
- `def merge_from_dir(self, relative_dir_name: str, as_title: str, project_root: typing.Union[os.PathLike, str] = None, should_include_suffixes: typing.List[str] = [], excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [], use_gitignore: bool = True, dry_run: bool = False, include_ast_metadata: bool = True) -> 'AiMdGenerator'`
  - *Merges the content of the given directory into the current file.*
- `def merge_dir_of_package_examples(self)`
  - *合并包的examples目录到当前markdown文件*
- `def merge_from_files_with_metadata(self, relative_file_name_list: typing.List[str], as_title: str, project_root: typing.Union[os.PathLike, str] = None, include_ast_metadata: bool = True, include_file_text: bool = True) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  合并文件内容到 Markdown，对于 Python 文件会额外生成 AST 元数据
  
  Args:
      project_root: 项目根目录
      relative_file_name_list: 相对文件路径列表
      as_title: 标题
      include_ast_metadata: 是否包含 AST 元数据（仅对 .py 文件）
      include_file_text: 是否包含完整文件源码（False 时只显示元数据）
  `````

**Class Variables (1):**
- `suffix__lang_map = {'.py': 'python', '.md': 'markdown', '.txt': 'text...`


---




### 📄 Python File Metadata: `nb_ai_context/contrib/gen_github_proj_ai_md.py`

#### 📝 Module Docstring

`````
Generate tutorials and code from any GitHub project into a markdown file for AI context

Download GitHub project zip file
https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
https://codeload.github.com/fastapi/sqlmodel/zip/refs/tags/0.0.25

Unzip the file
Generate markdown file
`````

#### 📦 Imports

- `import typing`
- `from nb_path import NbPath`
- `from nb_ai_context import AiMdGenerator`
- `import re`

#### 🔧 Public Functions (3)

- `def extract_repo_name(github_url)`
  - *Line: 18*

- `def gen_github_proj_docs_and_codes_ai_md(github_zip_url: str, output_md_path: str, docs_dir_name: str, codes_dir_name: str, readme_file: str, should_include_suffixes: list = None, excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [])`
  - *Line: 27*
  - **Docstring:**
  `````
  Generate a Markdown document containing README, tutorials, and source code from a GitHub repository URL
  
  Args:
      github_zip_url: GitHub repository zip download link
                     Example: https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
      output_md_path: Output Markdown file path
      readme_file: README file name, default is "README.md"
      docs_dir_name: Documentation directory name, e.g. "docs"
      codes_dir_name: Source code directory name, e.g. "sqlmodel"
      should_include_suffixes: List of file extensions to include,
                              By default includes ['.py', '.java', '.go', '.md', '.html']
  `````

- `def gen_github_proj_all_dirs_ai_md(github_zip_url: str, output_md_path: str, should_include_suffixes: list = None, excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [])`
  - *Line: 99*
  - *生成github的项目根目录下所有文件到markdown，*


---


# markdown content namespace: nb_ai_context Project Root Dir Some Files 


## File Tree


`````

├── README.md
└── pyproject.toml

`````

---


## Included Files


- `README.md`

- `pyproject.toml`


---


--- **start of file: README.md** --- 

`````markdown
# nb_ai_context

[![PyPI version](https://img.shields.io/pypi/v/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![Python versions](https://img.shields.io/pypi/pyversions/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

🚀 **极其强大的 AI 上下文生成器** - 将任意 IT 项目合并生成为1个结构化 Markdown 文档，方便一次上传给ai大模型或者rag知识库。  

**`nb_ai_context` 生成的markdown文件 传给  `google ai studio` ，生成代码质量和准确率，远远吊打在 `cursor` `trae` `qoder` `gemini-code-assit` 等ai ide中写代码。**

<pre style="color: red;font-weight: bold;background-color:yellow;padding: 10px;border-radius: 5px;font-size: 16px;">
本人实测：
将 nb_ai_context 对项目教程+源码生成的单个markdown文件 传给 `google ai studio` 网页版
的1000k上下文+gemini 3.0pro 大模型，再提问让gemini写代码，代码生成的准确率和质量远远吊打在 
cursor trae qoder等ai ide或gemini-code-assit等 编程插件 里面提问写代码。

 `google ai studio` 优点是1000k上下文以及免费。唯一缺点是他在网页生成代码，而不是直接操作
 修改你的本地代码文件。

 `nb_ai_context` 生成的markdown丢给`google ai studio`完爆cursor trae等ide中写代码 
 主要原因是：  
`google ai studio` 网页版是全量阅读推理你上传的文件，而 ai编程ide 对长教程或者源码，
ai agent试图采取关键字搜索匹配再投喂上下文给ai大模型，（有时候模糊搜索的关键字压根搜不到内容，
因为ai造出来的关键字不是完完全全的匹配你的函数名字或者教程内容，所以就搜不到，
例如近义词，你代码用别的单词表示，ai却用自己的单词来表示，靠关键字压根就搜不到内容，agent咋投喂ai大模型？）
ai ide 这种方式简直是管中窥豹 一叶障目不见泰山。

最根本原因还是：  
ai ide 为了减少成本，防止token消耗大，不会全量把源码教程喂给ai大模型，不然的靠用户交的那20美元的月会员，
cursor要亏得裤衩没得穿。100万 tokens上下文输入，calude gemini gpt 价格大约是3美元，相当于20元人民币，
如果问一次就20元人民币，cursor每个月给你用8次就要开始亏血本了，(另外tokens输出价格远高于输入价格），
现在cursor还给你每月用500次，那他只能从节约tokens下手了，具体表现就是cursor不全量把所有教程和源码
发给ai大模型推理。

`nb_ai_context` 对大型编程项目的效果尤为突出，远远吊打 cursor trae qoder gemini-code-assit 等ai ide中写代码。
</pre>

## ✨ 核心特性

### 🎯 为什么需要 nb_ai_context？

当你想让 AI 大模型（如 ChatGPT、Claude）理解你的整个项目时，手动复制粘贴代码既繁琐又容易出错。`nb_ai_context` 彻底解决了这个痛点：

- ✅ **自动化合并** - 一键将整个项目合并为单个 Markdown 文件
- ✅ **结构化输出** - 包含文件树、文件边界、相对路径，AI 能完美理解项目架构
- ✅ **智能元数据提取** - 对 Python 项目提供 AST 语法树解析，自动生成类、函数、方法签名
- ✅ **安全可靠** - 内置 `.gitignore` 支持，自动排除敏感信息（API 密钥、.env 文件等）
- ✅ **灵活可控** - 支持文件过滤、目录排除、自定义文件类型
- ✅ **GitHub 项目支持** - 直接从 GitHub zip 下载地址生成文档

### 💡 **关键优势对比**
| 方案 | 上下文完整性 | Token 消耗 | 安全性 | 成本 |
|------|--------------|------------|--------|------|
| **Cursor/trae/qoder** | ❌ 分段阅读 | ⚠️ 高 | ⚠️ 需手动过滤 | 💰 付费次数限制 |
| **手动复制粘贴** | ❌ 易遗漏 | ❌ 极高 | ❌ 易泄露敏感信息 | ⏰ 耗时 |
| **nb_ai_context** | ✅ 完整结构化 | ✅ AST 元数据优化 | ✅ 自动 `.gitignore` | 🆓 免费工具 |



### 🎁 对 AI 大模型的巨大价值

1. **提供上帝视角** 📐
   - 清晰的文件树和文件边界
   - 完整的相对路径信息
   - AI 能轻松构建项目的整体架构图

2. **确保信息完整性** 📚
   - 未经删减的完整源码
   - 避免手动复制导致的格式混乱
   - 零上下文缺失，AI 分析更精准

3. **增强安全性** 🔒
   - 自动遵循 `.gitignore` 规则
   - 防止敏感信息泄露
   - 放心分享代码给 AI

4. **Python 项目专属优化** 🐍
   - AST 语法树解析
   - 自动提取类、函数、方法签名
   - 参数类型、返回值、装饰器一目了然

## 📦 安装

```bash
pip install nb_ai_context
```

**依赖项：**
- Python >= 3.7
- nb_path
- nb_log

## 🚀 快速开始

### 示例 1：为本地 Python 项目生成 AI 上下文

```python
from nb_ai_context import AiMdGenerator

project_name = "my_awesome_project"
project_root = r"D:\codes\my_awesome_project"

project_summary = f"""
- `{project_name}` 是一个优秀的 Python Web 应用
- 使用 FastAPI 框架
- 包含完整的用户认证和权限管理
"""

(
    AiMdGenerator(rf"D:\ai_docs\{project_name}_for_ai.md")
    .set_project_propery(project_name=project_name, project_root=project_root)
    .clear_text()  # 清空旧内容
    .add_project_summary(
        project_summary=project_summary,
        # 核心文件列表（只提取元数据，不包含完整源码）
        most_core_source_code_file_list=[
            "src/main.py",
            "src/api.py",
            "src/models.py",
        ],
    )
    .auto_merge_from_python_project_some_files()  # 自动包含 README.md、setup.py、pyproject.toml
    .merge_from_dir(
        relative_dir_name="src",
        as_title=f"{project_name} 源代码",
        use_gitignore=True,  # 使用 .gitignore 规则
        should_include_suffixes=[".py", ".md"],  # 只包含指定文件类型
        include_ast_metadata=True,  # 包含 AST 元数据
    )
    .merge_from_dir(
        relative_dir_name="tests",
        as_title=f"{project_name} 测试代码",
        use_gitignore=True,
        should_include_suffixes=[".py"],
        excluded_dir_name_list=["tests/temp"],  # 排除特定目录
        include_ast_metadata=True,
    )
    .show_textfile_info()  # 显示生成的文件信息
)
```

### 示例 2：从 GitHub 项目生成 AI 文档

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md
from nb_path import NbPath

# 生成 SQLModel 项目的文档和代码
gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_docs_and_codes.md",
    readme_file="README.md",
    docs_dir_name="docs",  # 文档目录
    codes_dir_name="sqlmodel",  # 源码目录
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests", "__pycache__"],
    excluded_file_name_list=["test_*.py"],
)
```

### 示例 3：生成 GitHub 项目的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

# 生成整个项目的所有文件
gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_files.md",
    should_include_suffixes=[".py", ".md", ".html"],
    excluded_dir_name_list=["node_modules", ".git", "__pycache__"],
)
```


## 为什么要这个 nb_ai_context ？脱了裤子放屁？让cusror trae 去阅读项目的源码和教程，还能在ide里面生成代码，这不更香吗？(预判质疑)

- 1. 有时候是非ide环境问项目的用法

- 2. cursor 太贵了，经常提问cursor浪费宝贵次数，要把宝贵次数留给真正的代码生成，而不是提问。
     cursor trae 存在上下文太短，调教成不积极阅读全量源码和教程等问题。

- 3. 可以作为 rag 在线知识库使用，让ai大模型阅读生成的markdown，然后回答问题。

- 4. 可以把生成的markdown丢给免费且上下文无敌高达1000k 的 `google ai studio` 去阅读，claude gpt才256k token，国内大模型才128k token，压根不够用，  
     只有  google ai studio + gemini 3.0pro 才能完整阅读项目源码加教程，并且免费使用。 

- 5. `google ai studio` 对于4M 以内的文件，可以一次性全量阅读全文+推理，并且免费使用。
     一般三方包项目的源码加教程远没有达到4M。
     如果是用户自己的真实项目而非框架或者三方包项目，项目中可能包含大量写法模式非常重复的需求代码，代码加教程加起来可能会超过4M，但是 用户项目核心公共入口函数和类的用法的源码 + 教程 绝对不超过1M。

- 6. `nb_ai_context` 支持按文件夹合并和过滤需要合并的文件，并且`nb_ai_context` 支持 `ast` 解析 任何python文件的 类 函数 入参 注释 等 元信息，大幅减少token消耗，让ai抓住重点。  
     `AiMdGenerator.add_project_summary(most_core_source_code_file_list=[...])` 支持添加项目概述和核心文件元数据，让ai快速抓住重点。  
     不管项目多复杂有几百个class类和模块，但真正能作为公共使用入口的模块一般不会超过10个。  
     例如 `funboost` 项目 `python`文件高达几百个，但真正能作为框架使用用户级别的公共使用入口的python文件也就三四个，其他的python文件压根没机会被终端用户直接import并使用。


- 10. 最最主要的原因！！！ cursor trae 内置的大模型都不乖乖阅读所有源码和教程，即使你明确发出指令强迫他阅读所有教程和源码，他也会偷懒不读，只读部分代码，导致幻觉率极高。
      为什么 cursor trae 里面的大模型不全量阅读呢，因为他早就预判了有流氓玩家恶意滥用大模型算力， 如果你让cursor + claude 阅读c盘下的所有文件，cursor和claude 会这么乖吗？
      c盘下的文本文件高达几百亿行，如果读完一次就会消耗价值几十万美元的tokens，大模型厂商亏得裤衩没得穿，你才20美元的包月会员，cursor和claude厂家不被你薅秃噜皮？
      所以不管用户怎么强迫cursor  trae去阅读所有源码和教程，cursor 和 claude 和trae都不会听你的话。

- 11. trae 和 cursor 被调教限制了， trae一次最多阅读200行，qoder一次最多阅读1000行，对超长教程，需要分多次阅读，导致幻觉率极高，你让这种 ai ide + 大模型去总结 项目的中心思想非常不靠谱。 
      并且分段阅读交互次数高达几十次，太费等待时间了。
      并且 ai ide里面阅读长文件会分批阅读直到20次就会自动停止，例如文件有10000行，trae一次阅读200行，需要50次交互，但ai ide设置了最多交互20次就会自动停止；这是因为担心流氓恶意用户让他阅读1亿行的文件导致自己血亏，所以最多分段阅读20次就会停止。

- 12. 解决 10和11的痛点问题， 只有 `google ai studio` 才能做到 ， 或者使用rag也勉强可以。
      把文档上传给 `google ai studio` 网页版的1000k上下文的gemini pro 这种方式最强， 其次是 `rag`方式。
      对 4M以内的文档（大约就是1000k tokens）， 推荐使用  `google ai studio`，不要使用 `rag` 方式。
      超过4M文档的上下文，突破了gemini 1000k token 上限，无法使用，此时需要使用 `rag`方式。

- 13. 一些网址：
       google ai studio 网址  [https://aistudio.google.com/](https://aistudio.google.com/)    
       智谱清言 ai智能体网址，可以支持免费rag  [chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh](chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh)    
       腾讯ima知识库 [ima.qq.com](https://ima.qq.com/)    



## 📖 核心 API 详解

### AiMdGenerator 类

`AiMdGenerator` 是核心类，继承自 `NbPath`，支持优雅的链式调用。

#### 主要方法

##### 1. `set_project_propery(project_name, project_root)`
设置项目名称和根目录（必须首先调用）

```python
.set_project_propery(
    project_name="my_project",
    project_root=r"D:\codes\my_project"
)
```

##### 2. `add_project_summary(project_summary, most_core_source_code_file_list)`
添加项目概述和核心文件元数据

```python
.add_project_summary(
    project_summary="项目描述...",
    most_core_source_code_file_list=[
        "src/main.py",  # 只提取这些文件的 AST 元数据，不包含完整源码
        "src/api.py",
    ]
)
```

##### 3. `auto_merge_from_python_project_some_files()`
自动合并项目根目录的关键文件（README.md、setup.py、pyproject.toml）

```python
.auto_merge_from_python_project_some_files()
```

##### 4. `merge_from_files(relative_file_name_list, as_title)`
合并指定文件列表

```python
.merge_from_files(
    relative_file_name_list=["docs/intro.md", "docs/api.md"],
    as_title="项目文档"
)
```

##### 5. `merge_from_dir()` ⭐ 最强大的方法
合并整个目录的文件

```python
.merge_from_dir(
    relative_dir_name="src",           # 相对目录名
    as_title="源代码",                  # 章节标题
    should_include_suffixes=[".py"],   # 包含的文件类型
    excluded_dir_name_list=[],         # 排除的目录列表
    excluded_file_name_list=[],        # 排除的文件列表
    use_gitignore=True,                # 使用 .gitignore 规则
    include_ast_metadata=True,         # 包含 Python AST 元数据
)
```

**参数说明：**
- `relative_dir_name`: 相对于项目根目录的路径
- `as_title`: 在生成的 Markdown 中的章节标题
- `should_include_suffixes`: 只包含这些后缀的文件（留空则包含所有文本文件）
- `excluded_dir_name_list`: 要排除的目录列表
- `excluded_file_name_list`: 要排除的文件列表
- `use_gitignore`: 是否遵循 `.gitignore` 规则（强烈推荐开启）
- `include_ast_metadata`: 对 Python 文件是否生成 AST 元数据
- `dry_run`: 预览模式，只显示将要包含的文件，不实际生成

##### 6. `merge_from_files_with_metadata()`
带元数据的文件合并（更高级的控制）

```python
.merge_from_files_with_metadata(
    relative_file_name_list=["src/main.py"],
    as_title="核心代码",
    include_ast_metadata=True,   # 是否包含 AST 元数据
    include_file_text=True,      # 是否包含完整源码
)
```

##### 7. `show_textfile_info()`
显示生成文件的统计信息

```python
.show_textfile_info()  # 打印文件大小、行数等信息
```

### GitHub 项目处理函数

#### `gen_github_proj_docs_and_codes_ai_md()`
生成 GitHub 项目的 README、文档和源码

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md

gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output.md",
    readme_file="README.md",
    docs_dir_name="docs",
    codes_dir_name="src",
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests"],
    excluded_file_name_list=["test_*.py"],
)
```

#### `gen_github_proj_all_dirs_ai_md()`
生成 GitHub 项目根目录下的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output_all.md",
    should_include_suffixes=[".py", ".java", ".go", ".md"],
    excluded_dir_name_list=["node_modules", ".git"],
)
```

## 🐍 Python AST 元数据提取

对于 Python 文件，`nb_ai_context` 会自动提取以下元数据：

### 提取内容
- ✅ **模块文档字符串**
- ✅ **导入语句** (import / from import)
- ✅ **类定义**
  - 类名、继承关系、装饰器
  - 类文档字符串
  - 公有方法（含参数类型、返回值、装饰器）
  - 属性 (@property)
  - 类变量
- ✅ **顶级函数**
  - 函数签名（参数类型、默认值、返回值）
  - 装饰器
  - 文档字符串
  - 异步函数标识

### 元数据示例输出

```markdown
### 📄 Python File Metadata: `src/main.py`

#### 📝 Module Docstring
```
这是主模块，包含应用的入口点
```

#### 📦 Imports
- `import os`
- `from typing import List, Optional`
- `from fastapi import FastAPI`

#### 🏛️ Classes (2)

##### 📌 `class UserService(BaseService)`
*Line: 15*

**Docstring:**
```
用户服务类，处理用户相关的业务逻辑
```

**Public Methods (3):**
- `def get_user(user_id: int) -> Optional[User]`
  - *获取用户信息*
- `async def create_user(name: str, email: str) -> User`
  - *创建新用户*
- `def update_user(user_id: int, **kwargs) -> bool`

**Properties (1):**
- `@property user_count -> int`

#### 🔧 Public Functions (2)
- `async def startup_event() -> None`
  - *Line: 120*
  - *应用启动事件处理*
```

## 🎨 生成的 Markdown 结构

生成的 Markdown 文档结构清晰，包含：

```markdown
# markdown content namespace: 项目概述

项目描述...

## 📋 Core Source Files Metadata (Entry Points)
核心文件的 AST 元数据（不含源码）

---

# markdown content namespace: 项目根目录文件

## File Tree
```
├── README.md
├── setup.py
└── pyproject.toml
```

## Included Files
- `README.md`
- `setup.py`
- `pyproject.toml`

---

--- **start of file: README.md** ---
（文件内容）
--- **end of file: README.md** ---

---

# markdown content namespace: 源代码

## File Tree
（文件树）

## Included Files
（文件列表）

--- **start of file: src/main.py** ---

### 📄 Python File Metadata: `src/main.py`
（AST 元数据）

```python
（完整源码）
```

--- **end of file: src/main.py** ---
```

## 🔧 高级用法

### 1. 预览模式（Dry Run）

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        dry_run=True,  # 只预览，不实际生成
    )
)
```

### 2. 只提取元数据，不包含源码

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_files_with_metadata(
        relative_file_name_list=["src/main.py"],
        as_title="核心代码架构",
        include_ast_metadata=True,  # 包含元数据
        include_file_text=False,    # 不包含源码
    )
)
```

### 3. 支持的文件类型

`AiMdGenerator` 支持多种编程语言的语法高亮：

```python
suffix__lang_map = {
    ".py": "python",
    ".java": "java",
    ".go": "go",
    ".js": "javascript",
    ".ts": "typescript",
    ".jsx": "javascript",
    ".tsx": "typescript",
    ".vue": "vue",
    ".php": "php",
    ".c": "c",
    ".cpp": "cpp",
    ".cs": "csharp",
    ".sql": "sql",
    ".sh": "shell",
    ".ps1": "powershell",
    ".md": "markdown",
    ".json": "json",
    ".yaml": "yaml",
    ".xml": "xml",
    ".html": "html",
    ".css": "css",
    # ... 更多
}
```

## 🎯 实际应用场景

### 1. AI 代码审查
生成完整项目上下文，让 AI 全面审查代码质量、安全漏洞、性能问题。

### 2. 技术文档生成
快速为新团队成员生成项目概览文档，配合 AI 解释代码逻辑。

### 3. 知识库构建
将项目内容导入 RAG 知识库，实现智能代码搜索和问答。

### 4. 项目迁移和重构
让 AI 理解整个项目后，提供重构建议或迁移到新框架。

### 5. 学习开源项目
下载 GitHub 项目后生成结构化文档，配合 AI 快速理解项目架构。

## 🔒 安全性

### `.gitignore` 支持

设置 `use_gitignore=True` 时，会自动遵循项目的 `.gitignore` 规则：

```python
.merge_from_dir(
    relative_dir_name="src",
    use_gitignore=True,  # 自动排除 .env、*.log 等敏感文件
)
```

### 自动排除
- 自动排除以 `.` 开头的隐藏目录（如 `.git`、`.venv`）
- 支持手动排除特定目录和文件

## 📊 性能特性

- ✅ 高效的文件遍历和过滤
- ✅ 智能文本文件检测
- ✅ 支持大型项目（数千个文件）
- ✅ 链式调用，代码简洁优雅

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT License

## 🔗 相关链接

- **GitHub**: https://github.com/ydf0509/nb_ai_context
- **PyPI**: https://pypi.org/project/nb_ai_context/
- **Issues**: https://github.com/ydf0509/nb_ai_context/issues

## 💡 常见问题

### Q: 生成的 Markdown 文件太大怎么办？
A: 使用 `should_include_suffixes` 限制文件类型，或使用 `excluded_dir_name_list` 排除不重要的目录（如 `node_modules`、`venv`）。

### Q: 如何只生成代码结构，不包含源码？
A: 使用 `merge_from_files_with_metadata()` 方法，设置 `include_file_text=False`。

### Q: 支持哪些编程语言？
A: 所有文本文件都支持，但 Python 文件有特殊的 AST 元数据提取支持。



## 🌟 Star History

如果这个项目对你有帮助，请给个 Star ⭐️！

---

**nb_ai_context** - 让 AI 真正理解你的代码 🚀

`````

--- **end of file: README.md** --- 

---


--- **start of file: pyproject.toml** --- 

`````text
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "nb_ai_context"
version = "1.5"
description = "非常强力的对任意it项目生成ai上下文单个markdown文档，丢给大模型或者知识库，非常强力，大模型幻觉大幅度减少。尤其是对python项目还有专门的ast语法树解析"
keywords = ["ai", "ai context", "llm", "rag", "python","ast","ast metadata","ai context generator","markdown"]
authors = [
    {name = "ydf0509", email = "ydf0509@xxx.com"},
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
]
dependencies = [
    "typing_extensions>=3.7; python_version < '3.8'",
    "nb_path",
    "nb_log",
]

[project.optional-dependencies]
test = [
    "pytest>=6.0",
    "pytest-asyncio>=0.14",
]
dev = [
    "black>=21.0",
    "flake8>=3.8",
    "mypy>=0.812",
]

[project.urls]
Homepage = "https://github.com/ydf0509/nb_ai_context"
Repository = "https://github.com/ydf0509/nb_ai_context"
Issues = "https://github.com/ydf0509/nb_ai_context/issues"

[tool.setuptools.packages.find]
where = ["."]
include = ["nb_ai_context*"]

[tool.setuptools.package-data]
nb_ai_context = ["py.typed", "contrib/README.md"]

`````

--- **end of file: pyproject.toml** --- 

---

# markdown content namespace: nb_ai_context examples 


## File Tree


`````

└── examples
    └── AiMdGenerator_example.py

`````

---


## Included Files


- `examples/AiMdGenerator_example.py`


---


--- **start of file: examples/AiMdGenerator_example.py** --- 


### 📄 Python File Metadata: `examples/AiMdGenerator_example.py`

#### 📦 Imports

- `from nb_ai_context import AiMdGenerator`


---

`````python
from nb_ai_context import AiMdGenerator


project_name = "nb_ai_context"
project_root = rf"D:\codes\{project_name}"

project_summary = f"""
- `{project_name}` is a powerful ai llm context gennerator library ,it is uesd for ai llm and rag
- `AiMdGenerator(...)` is the main class to create ai context for llm.
"""


(
    AiMdGenerator(
        rf"D:\codes\nb_ai_context\ai_md_files_demo\{project_name}_all_docs_and_codes.md"
    )
    .set_project_propery(project_name=project_name, project_root=project_root)
    .ensure_parent()
    .clear_text()
    .add_project_summary(
        project_summary=project_summary,
        most_core_source_code_file_list=[
            "nb_ai_context/__init__.py",
            "nb_ai_context/ai_md_generator.py",
            "nb_ai_context/contrib/gen_github_proj_ai_md.py",
        ],
    )
    .auto_merge_from_python_project_some_files()
    .show_textfile_info()
    .merge_from_dir(
        relative_dir_name='examples',
        use_gitignore=True,
        as_title=f"{project_name} examples",
        # 只包含 .py 和 .md 文件
        should_include_suffixes=[".py", ".md"],
        # 排除 __pycache__ 目录和特定的测试文件
        excluded_dir_name_list=[],
        include_ast_metadata=True,
    )
    .merge_from_dir(
        relative_dir_name=project_name,
        use_gitignore=True,
        as_title=f"{project_name} codes",
        # 只包含 .py 和 .md 文件
        should_include_suffixes=[".py", ".md"],
        # 排除 __pycache__ 目录和特定的测试文件
        excluded_dir_name_list=[],
        include_ast_metadata=True,
    )
    .show_textfile_info()
)

`````

--- **end of file: examples/AiMdGenerator_example.py** --- 

---

# markdown content namespace: nb_ai_context codes 


## File Tree


`````

└── nb_ai_context
    ├── __init__.py
    ├── ai_md_generator.py
    └── contrib
        ├── about.md
        └── gen_github_proj_ai_md.py

`````

---


## Included Files


- `nb_ai_context/ai_md_generator.py`

- `nb_ai_context/__init__.py`

- `nb_ai_context/contrib/about.md`

- `nb_ai_context/contrib/gen_github_proj_ai_md.py`


---


--- **start of file: nb_ai_context/ai_md_generator.py** --- 


### 📄 Python File Metadata: `nb_ai_context/ai_md_generator.py`

#### 📦 Imports

- `import typing`
- `import os`
- `import fnmatch`
- `import ast`
- `from nb_path import NbPath`
- `import astor`

#### 🏛️ Classes (1)

##### 📌 `class AiMdGenerator(NbPath)`
*Line: 10*

**Docstring:**
`````
An extremely powerful context generator born for AI collaboration.

This class is designed to revolutionize how developers interact with Large Language
Models (LLMs). It intelligently merges multiple project source files into a single,
well-structured, and context-rich Markdown file, providing the AI with a perfect
and comprehensive project snapshot.

The benefits for large AI models are immense:
1.  **Provides a God's-eye View**: Through a file manifest, clear file boundaries,
    and relative paths, the AI can easily construct the project's overall
    architecture and understand file dependencies and relationships, rather than
    fumbling in the dark.
2.  **Ensures Information Integrity and Accuracy**: The AI receives complete,
    unabridged source file content, avoiding the chaos, omissions, or context
    loss caused by manual copy-pasting. This enables it to provide more precise
    analysis and suggestions.
3.  **Enhances Security**: The built-in `use_gitignore` feature is a critical
    security barrier. It automatically ignores files containing sensitive
    information (like API keys or database passwords) such as `.env` or local
    configs, allowing you to share code without fear of accidental leaks.

Its core methods, `merge_from_files` and `merge_from_dir`, offer extreme
flexibility. Combined with the elegant chainable calls of `nb_path`, creating a
high-quality AI context is transformed from a tedious, error-prone manual task
into a single, delightful line of code.

Example:
    >>> # Imagine you want an AI to review your entire project
    >>> project_name = "my_project"
    >>> project_summary = '''
    ... This is an excellent Python project that demonstrates best practices.
    ... It includes comprehensive documentation and well-structured code.
    ... '''
    >>> 
    >>> (
    ...     AiMdGenerator("project_context_for_ai.md")
    ...     .set_project_propery(project_name=project_name, project_root="/path/to/your/project")
    ...     .clear_text()  # Clear the old file
    ...     .add_project_summary(
    ...         project_summary=project_summary,
    ...         # Extract metadata (without full source) from core files first
    ...         most_core_source_code_file_list=[
    ...             "src/main.py",
    ...             "src/api.py",
    ...             "src/models.py",
    ...         ]
    ...     )
    ...     .auto_merge_from_python_project_some_files()  # Auto-include README, setup.py, etc.
    ...     .merge_from_dir(
    ...         relative_dir_name="src", # The main source code directory
    ...         as_title="Project Source Code",
    ...         use_gitignore=True,  # Automatically use .gitignore rules
    ...         should_include_suffixes=[".py", ".md"], # Only include specified file types
    ...         include_ast_metadata=True,  # Include AST metadata for Python files
    ...     )
    ...     .merge_from_dir(
    ...         relative_dir_name="tests", # The tests directory
    ...         as_title="Project Tests",
    ...         use_gitignore=True,
    ...         should_include_suffixes=[".py"],
    ...         excluded_dir_name_list=["tests/temp_files"],
    ...         include_ast_metadata=True,
    ...     )
    ... )
`````

**Public Methods (7):**
- `def set_project_propery(self, project_name: str, project_root: typing.Union[os.PathLike, str]) -> 'AiMdGenerator'`
  - *Sets the project name for the current markdown file.*
- `def add_project_summary(self, project_summary: str, most_core_source_code_file_list: typing.List[str] = None, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  Adds a project summary to the current markdown file.
  
  Args:
      project_summary: 项目概述文本
      project_root: 项目根目录（如果提供了 most_core_source_code_file_list）
      most_core_source_code_file_list: 最核心的源码文件列表（相对路径）
                                       只提取这些文件的 AST 元数据，不包含完整源码
  
  Example:
      >>> (
      ...     AiMdGenerator("output.md")
      ...     .set_project_name("my_project")
      ...     .clear_text()
      ...     .add_project_summary(
      ...         project_summary="这是一个优秀的项目...",
      ...         project_root="/path/to/project",
      ...         most_core_source_code_file_list=["src/main.py", "src/api.py"],
      ...     )
      ... )
  `````
- `def auto_merge_from_python_project_some_files(self, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - *自动合并项目根目录下的 readme.md 或者ReADME.md 以及setup.py 和 pyproject.toml ，如果有就添加*
- `def merge_from_files(self, relative_file_name_list: typing.List[str], as_title: str, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  Merges the content of the given files into the current markdown file.
  the current markdown file will be used to upload to ai model for code review and learning.
  `````
- `def merge_from_dir(self, relative_dir_name: str, as_title: str, project_root: typing.Union[os.PathLike, str] = None, should_include_suffixes: typing.List[str] = [], excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [], use_gitignore: bool = True, dry_run: bool = False, include_ast_metadata: bool = True) -> 'AiMdGenerator'`
  - *Merges the content of the given directory into the current file.*
- `def merge_dir_of_package_examples(self)`
  - *合并包的examples目录到当前markdown文件*
- `def merge_from_files_with_metadata(self, relative_file_name_list: typing.List[str], as_title: str, project_root: typing.Union[os.PathLike, str] = None, include_ast_metadata: bool = True, include_file_text: bool = True) -> 'AiMdGenerator'`
  - **Docstring:**
  `````
  合并文件内容到 Markdown，对于 Python 文件会额外生成 AST 元数据
  
  Args:
      project_root: 项目根目录
      relative_file_name_list: 相对文件路径列表
      as_title: 标题
      include_ast_metadata: 是否包含 AST 元数据（仅对 .py 文件）
      include_file_text: 是否包含完整文件源码（False 时只显示元数据）
  `````

**Class Variables (1):**
- `suffix__lang_map = {'.py': 'python', '.md': 'markdown', '.txt': 'text...`


---

`````python
import typing
import os
import fnmatch
import ast

from nb_path import NbPath

FILE_CONTENT_BACKQUOTES = "`````"  # 不用反三引号是为了避免与被合并的本身就是.md文件的里面的反三引号冲突，导致文件块提前判断结束

class AiMdGenerator(NbPath):
    """
    An extremely powerful context generator born for AI collaboration.

    This class is designed to revolutionize how developers interact with Large Language
    Models (LLMs). It intelligently merges multiple project source files into a single,
    well-structured, and context-rich Markdown file, providing the AI with a perfect
    and comprehensive project snapshot.

    The benefits for large AI models are immense:
    1.  **Provides a God's-eye View**: Through a file manifest, clear file boundaries,
        and relative paths, the AI can easily construct the project's overall
        architecture and understand file dependencies and relationships, rather than
        fumbling in the dark.
    2.  **Ensures Information Integrity and Accuracy**: The AI receives complete,
        unabridged source file content, avoiding the chaos, omissions, or context
        loss caused by manual copy-pasting. This enables it to provide more precise
        analysis and suggestions.
    3.  **Enhances Security**: The built-in `use_gitignore` feature is a critical
        security barrier. It automatically ignores files containing sensitive
        information (like API keys or database passwords) such as `.env` or local
        configs, allowing you to share code without fear of accidental leaks.

    Its core methods, `merge_from_files` and `merge_from_dir`, offer extreme
    flexibility. Combined with the elegant chainable calls of `nb_path`, creating a
    high-quality AI context is transformed from a tedious, error-prone manual task
    into a single, delightful line of code.

    Example:
        >>> # Imagine you want an AI to review your entire project
        >>> project_name = "my_project"
        >>> project_summary = '''
        ... This is an excellent Python project that demonstrates best practices.
        ... It includes comprehensive documentation and well-structured code.
        ... '''
        >>> 
        >>> (
        ...     AiMdGenerator("project_context_for_ai.md")
        ...     .set_project_propery(project_name=project_name, project_root="/path/to/your/project")
        ...     .clear_text()  # Clear the old file
        ...     .add_project_summary(
        ...         project_summary=project_summary,
        ...         # Extract metadata (without full source) from core files first
        ...         most_core_source_code_file_list=[
        ...             "src/main.py",
        ...             "src/api.py",
        ...             "src/models.py",
        ...         ]
        ...     )
        ...     .auto_merge_from_python_project_some_files()  # Auto-include README, setup.py, etc.
        ...     .merge_from_dir(
        ...         relative_dir_name="src", # The main source code directory
        ...         as_title="Project Source Code",
        ...         use_gitignore=True,  # Automatically use .gitignore rules
        ...         should_include_suffixes=[".py", ".md"], # Only include specified file types
        ...         include_ast_metadata=True,  # Include AST metadata for Python files
        ...     )
        ...     .merge_from_dir(
        ...         relative_dir_name="tests", # The tests directory
        ...         as_title="Project Tests",
        ...         use_gitignore=True,
        ...         should_include_suffixes=[".py"],
        ...         excluded_dir_name_list=["tests/temp_files"],
        ...         include_ast_metadata=True,
        ...     )
        ... )
    """

    """cn description
    一个极其强大的、为 AI 协作而生的上下文生成器。

    此类旨在彻底改变开发者与大语言模型（LLM）的交互方式。它能够智能地将多个项目源文件
    合并成一个结构清晰、上下文丰富的单一 Markdown 文件，从而为 AI 提供一个完美、全面的项目快照。

    对 AI 大模型的好处是巨大的：
    1.  **提供上帝视角**：通过文件清单、清晰的文件边界和相对路径，AI 能够轻松构建出项目的
        整体架构，理解文件间的依赖和引用关系，而不是盲人摸象。
    2.  **确保信息的完整与准确**：AI 得到的是未经删减的、完整的源文件内容，避免了因手动
        复制粘贴导致的格式混乱、内容遗漏或上下文缺失，从而能给出更精准的分析和建议。
    3.  **提升安全性**：内置的 `use_gitignore` 功能是一道关键的安全屏障。它能自动忽略
        `.env`、本地配置等包含敏感信息（如 API 密钥、数据库密码）的文件，让你在分享代码
        时无需担心意外泄露秘密。

    其核心方法 `merge_from_files` 和 `merge_from_dir` 提供了极高的灵活性，结合 `nb_path`
    优雅的链式调用，使得创建一个高质量的 AI 上下文从繁琐、易错的手工劳动，变成了一行
    赏心悦目的代码。

    """

    suffix__lang_map = {
        ".py": "python",
        ".md": "markdown",
        ".txt": "text",
        ".json": "json",
        ".yaml": "yaml",
        ".yml": "yaml",
        ".xml": "xml",
        ".html": "html",
        ".css": "css",
        ".js": "javascript",
        ".ts": "typescript",
        ".jsx": "javascript",
        ".tsx": "typescript",
        ".vue": "vue",
        ".php": "php",
        ".java": "java",
        ".c": "c",
        ".cpp": "cpp",
        ".h": "h",
        ".hpp": "hpp",
        ".cs": "csharp",
        ".vb": "vb",
        ".sql": "sql",
        ".bat": "batch",
        ".sh": "shell",
        ".ps1": "powershell",
        ".psm1": "powershell",
        ".psd1": "powershell",
        ".pssc": "powershell",
        ".psscx": "powershell",
    }

    def set_project_propery(self, project_name: str, project_root: typing.Union[os.PathLike, str] ) -> "AiMdGenerator":
        """Sets the project name for the current markdown file."""
        self.project_name = project_name
        self.project_root = project_root
        return self
    
    def _check_project_name(self) -> "AiMdGenerator":
        """Checks if the project name is set."""
        if not hasattr(self, 'project_name'):
            raise ValueError("Project name is not set. Please call set_project_name() first.")
        return self

    def add_project_summary(
        self, 
        project_summary: str, 
        most_core_source_code_file_list: typing.List[str] = None,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
        """
        Adds a project summary to the current markdown file.
        
        Args:
            project_summary: 项目概述文本
            project_root: 项目根目录（如果提供了 most_core_source_code_file_list）
            most_core_source_code_file_list: 最核心的源码文件列表（相对路径）
                                             只提取这些文件的 AST 元数据，不包含完整源码
        
        Example:
            >>> (
            ...     AiMdGenerator("output.md")
            ...     .set_project_name("my_project")
            ...     .clear_text()
            ...     .add_project_summary(
            ...         project_summary="这是一个优秀的项目...",
            ...         project_root="/path/to/project",
            ...         most_core_source_code_file_list=["src/main.py", "src/api.py"],
            ...     )
            ... )
        """
        self._check_project_name()
        project_root =  project_root or self.project_root 
        str_list = [f"# markdown content namespace: {self.project_name} project summary \n\n"]
        str_list.append(project_summary)
        # str_list.append("\n---\n\n")
        
        # 如果提供了核心文件列表，提取它们的元数据（不包含源码）
        if most_core_source_code_file_list and project_root:
            # str_list.append("\n---\n\n")
            str_list.append(f"\n## 📋 {self.project_name} most core source files metadata (Entry Points)\n\n")
            str_list.append(f"以下是项目 {self.project_name} 最核心的入口文件的结构化元数据，帮助快速理解项目架构：\n\n")
            most_core_source_code_file_list_str = ''
            for relative_file_name in most_core_source_code_file_list:
                most_core_source_code_file_list_str += f"- `{relative_file_name}`\n"
            str_list.append(f'\n### the project {self.project_name} most core source code files as follows: \n{most_core_source_code_file_list_str}')
            
            
            project_root_path = NbPath(project_root).resolve()
            
            for relative_file_name in most_core_source_code_file_list:
                file = (project_root_path / relative_file_name).resolve()
                if not file.exists():
                    raise FileNotFoundError(f"File {file} not found.")

                if file.is_file() and file.is_text() and file.suffix == ".py":
                    relative_file_name_posix = file.relative_to(project_root_path).as_posix()
                    
                    self.logger.info(f"提取核心文件元数据（无源码）: {relative_file_name_posix}")
                    
                    # 只提取元数据，不包含源码
                    metadata = self._parse_python_file_ast(file)
                    metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                    str_list.append(metadata_md)
                    str_list.append("\n")
        
        self.append_text('\n'.join(str_list))
        return self

    def _generate_markdown_header(self, as_title: str, file_text_list: list) -> list:
        """生成包含文件树和文件列表的 Markdown 头部"""
        str_list = [f"# markdown content namespace: {as_title} \n\n"]

        # 1. 生成文件树
        str_list.append("## File Tree\n\n")
        str_list.append(f"{FILE_CONTENT_BACKQUOTES}\n")
        tree = {}
        sorted_paths = sorted([item[1] for item in file_text_list])
        for path in sorted_paths:
            parts = path.split('/')
            current_level = tree
            for part in parts:
                if part not in current_level:
                    current_level[part] = {}
                current_level = current_level[part]

        def format_tree(node, prefix=""):
            lines = []
            entries = sorted(node.keys())
            for i, entry in enumerate(entries):
                connector = "├── " if i < len(entries) - 1 else "└── "
                lines.append(f"{prefix}{connector}{entry}")
                if node[entry]:
                    extension = "│   " if i < len(entries) - 1 else "    "
                    lines.extend(format_tree(node[entry], prefix + extension))
            return lines

        str_list.extend(format_tree(tree))
        str_list.append(f"\n{FILE_CONTENT_BACKQUOTES}\n\n---\n\n")

        # 2. 生成文件列表
        str_list.append("## Included Files\n\n")
        for _, relative_file_name_posix, _, _ in file_text_list:
            str_list.append(f"- `{relative_file_name_posix}`\n")
        str_list.append("\n---\n\n")

        return str_list

    def auto_merge_from_python_project_some_files(self, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator':
        """自动合并项目根目录下的 readme.md 或者ReADME.md 以及setup.py 和 pyproject.toml ，如果有就添加"""
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_merge_list = []
        
        # 定义要查找的根目录文件
        root_files_to_check = [
            "README.md",
            # "readme.md",
            "setup.py",
            "pyproject.toml"
        ]
        
        # 检查每个文件是否存在，如果存在则添加到合并列表中
        project_root_path = NbPath(project_root).resolve()
        for filename in root_files_to_check:
            file_path = project_root_path / filename
            if file_path.is_file() and file_path.is_text():
                file_merge_list.append(filename)
                
        self.merge_from_files(file_merge_list, f"{self.project_name} Project Root Dir Some Files",project_root, )
        return self

    def merge_from_files(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
        """Merges the content of the given files into the current markdown file.
        the current markdown file will be used to upload to ai model for code review and learning.
        """
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_text_list = []
        project_root_path = NbPath(project_root).resolve()
        for relative_file_name in relative_file_name_list:
            file = (project_root_path / relative_file_name).resolve()
            if not file.exists():
                raise FileNotFoundError(f"File {file} not found.")
            if file.is_file() and file.is_text():
                relative_file_name_posix = file.relative_to(
                    project_root_path
                ).as_posix()
                try:
                    text = file.read_text()
                except Exception as e:
                    self.logger.error(f"Error reading file {file}: {e}")
                    text = ""
                file_text_list.append(
                    [file, relative_file_name_posix, file.suffix, text]
                )
                self.logger.debug(f"need merged file: {file}")
            else:
                raise ValueError(f"File {file} is not a text file.")
        str_list = []
        if file_text_list:
            # 调用新函数生成头部
            str_list.extend(self._generate_markdown_header(as_title, file_text_list))


        for file, relative_file_name_posix, suffix, text in file_text_list:
            # 2. Remove the debug print statement.
            # print(f'file: {file}, relative_file_name_posix: {relative_file_name_posix}, suffix: {suffix}, text: {text}')
            str_list.append(f"--- **start of file: {relative_file_name_posix}** --- \n")
            # 3. Handle .md files separately to ensure their content is rendered correctly.
            #    Other file types are wrapped in code blocks.
            # if suffix == ".md":
            #     str_list.append(text + "\n")
            # else:
            #     lang = self.suffix__lang_map.get(suffix, "text")
            #     str_list.append(f"{FILE_CONTENT_BACKQUOTES}{lang}\n{text}\n{FILE_CONTENT_BACKQUOTES}\n")
            lang = self.suffix__lang_map.get(suffix, "text")
            str_list.append(f"{FILE_CONTENT_BACKQUOTES}{lang}\n{text}\n{FILE_CONTENT_BACKQUOTES}\n")

            str_list.append(f"--- **end of file: {relative_file_name_posix}** --- \n")
            str_list.append("---\n\n")

        # with self.open(mode="a", encoding="utf-8") as f:
        #     f.write("\n".join(str_list))
        self.append_text('\n'.join(str_list))
        self.ensure_utf8_bom()
        return self
        
        
    def merge_from_dir(
        self,
        relative_dir_name: str,
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        should_include_suffixes: typing.List[str] = [],
        excluded_dir_name_list: typing.List[str] = [],
        excluded_file_name_list: typing.List[str] = [],
        use_gitignore: bool = True,
        dry_run: bool = False,
        include_ast_metadata: bool = True,
    ) -> "AiMdGenerator":
        """Merges the content of the given directory into the current file."""
        project_root =  project_root or self.project_root
        project_root_path = NbPath(project_root).resolve()
        target_dir_path = (project_root_path / relative_dir_name).resolve()
        if not target_dir_path.exists():
            raise FileNotFoundError(f"Directory {target_dir_path} not found.")

        # Use sets for efficient lookups
        excluded_dir_paths = {
            (project_root_path / d).resolve() for d in excluded_dir_name_list
        }
        excluded_file_paths = {
            (project_root_path / f).resolve() for f in excluded_file_name_list
        }

        ignore_patterns = []
        if use_gitignore:
            try:
                gitignore_path = project_root_path.find_git_root() / ".gitignore"
                if gitignore_path.is_file():
                    self.logger.debug(f"Using .gitignore rules from: {gitignore_path}")
                    with open(gitignore_path, "r", encoding="utf-8") as f:
                        for line in f:
                            line = line.strip()
                            if line and not line.startswith("#"):
                                # Gitignore patterns always use forward slashes.
                                # We will compare against the posix version of the relative path
                                # for cross-platform reliability.
                                ignore_patterns.append(line)
            except FileNotFoundError:
                self.logger.warning("use_gitignore is True, but no .git/ or .gitignore file found.")

        relative_paths_to_include = []
        for path_obj in target_dir_path.rglob("*"):
            # Automatically exclude directories starting with a dot at the project root
            try:
                first_part = path_obj.relative_to(project_root_path).parts[0]
                if first_part.startswith('.'):
                    continue
            except (ValueError, IndexError):
                continue
            # Check if the path is within any of the excluded directories
            is_in_excluded_dir = any(
                path_obj == excluded_dir or excluded_dir in path_obj.parents
                for excluded_dir in excluded_dir_paths
            )
            if is_in_excluded_dir:
                continue

            # Check if the path matches any gitignore patterns.
            # Convert the relative path to a posix-style string for reliable matching.
            relative_to_root = path_obj.relative_to(project_root_path)
            relative_posix_path = relative_to_root.as_posix()
            # Use fnmatch for robust gitignore-style pattern matching.
            is_ignored = False
            for p in ignore_patterns:
                # If a pattern does not contain a slash, it matches in any directory.
                # e.g., 'test_git_ignore1.py' should match 'nb_path/example_dir/test_git_ignore1.py'
                if '/' not in p.strip('/'):
                    p_glob = f"**/{p.strip('/')}"
                else:
                    p_glob = p
                if fnmatch.fnmatch(relative_posix_path, p_glob) or fnmatch.fnmatch(relative_posix_path, p):
                    is_ignored = True
                    break
            if is_ignored:
                self.logger.debug(f"Ignoring {relative_to_root} due to .gitignore rule.")
                continue

            if path_obj.is_file():
                # Check if the file itself is excluded
                if path_obj.resolve() in excluded_file_paths:
                    continue
                # Check if the file is a text file
                if not path_obj.is_text():
                    continue
                # Check if the suffix is in the inclusion list (if the list is not empty)
                if (
                    should_include_suffixes
                    and path_obj.suffix not in should_include_suffixes
                ):
                    continue
                relative_paths_to_include.append(
                    path_obj.relative_to(project_root_path).as_posix()
                )

        if dry_run:
            print("\n--- [DRY RUN] AiMdGenerator Execution Plan ---")
            print(f"\n✅ {len(relative_paths_to_include)} files would be INCLUDED in '{self.name}':")
            for p in sorted(relative_paths_to_include):
                print(f"  - {p}")
            print("\n--- End of DRY RUN ---")
            return self
        else:
            # 使用带元数据的方法
            return self.merge_from_files_with_metadata(
                 
                relative_paths_to_include, 
                as_title,
                project_root=project_root,
                include_ast_metadata=include_ast_metadata
            )

    def merge_dir_of_package_examples(self):
        """合并包的examples目录到当前markdown文件"""
        self._check_project_name()
        self.merge_from_dir(
            relative_dir_name=self.project_name,
            use_gitignore=True,
            as_title=f"{self.project_name} codes",
            # 只包含 .py 和 .md 文件
            should_include_suffixes=[".py", ".md"],
            # 排除 __pycache__ 目录和特定的测试文件
            excluded_dir_name_list=[],
        ).merge_from_dir(
            relative_dir_name="examples",
            use_gitignore=True,
            as_title=f"{self.project_name} examples",
            # 只包含 .py 和 .md 文件
            should_include_suffixes=[".py", ".md"],
            # 排除 __pycache__ 目录和特定的测试文件
            excluded_dir_name_list=[],
            include_ast_metadata=False,
        )
        return self

    def _ast_to_source(self, node) -> str:
        """将 AST 节点转换为源代码字符串，兼容 Python 3.7+"""
        if node is None:
            return ""
        try:
            # Python 3.9+ 支持 ast.unparse
            if hasattr(ast, 'unparse'):
                return ast.unparse(node)
            else:
                # Python 3.7/3.8 的回退方案
                # 尝试使用 astor
                try:
                    import astor
                    return astor.to_source(node).strip()
                except ImportError:
                    pass
                
                # 简单的手工处理常见情况
                if isinstance(node, ast.Name):
                    return node.id
                elif isinstance(node, ast.Constant):
                    return repr(node.value)
                elif isinstance(node, ast.Attribute):
                    value = self._ast_to_source(node.value)
                    return f"{value}.{node.attr}"
                elif isinstance(node, ast.Subscript):
                    value = self._ast_to_source(node.value)
                    slice_val = self._ast_to_source(node.slice)
                    return f"{value}[{slice_val}]"
                elif isinstance(node, (ast.List, ast.Tuple)):
                    elts = [self._ast_to_source(e) for e in node.elts]
                    if isinstance(node, ast.List):
                        return f"[{', '.join(elts)}]"
                    else:
                        return f"({', '.join(elts)})"
                else:
                    # 对于复杂类型，返回类型名称
                    return node.__class__.__name__
        except Exception:
            return ""

    def _parse_type_annotation(self, annotation) -> str:
        """解析类型注解，返回字符串表示"""
        return self._ast_to_source(annotation)

    def _extract_function_metadata(self, node: typing.Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> dict:
        """提取函数/方法的元数据"""
        metadata = {
            "name": node.name,
            "type": "async_function" if isinstance(node, ast.AsyncFunctionDef) else "function",
            "lineno": node.lineno,
            "docstring": ast.get_docstring(node) or "",
            "parameters": [],
            "return_type": self._parse_type_annotation(node.returns),
            "decorators": [self._ast_to_source(dec) for dec in node.decorator_list],
            "is_public": not node.name.startswith("_"),
        }

        # 提取参数信息
        for arg in node.args.args:
            param_info = {
                "name": arg.arg,
                "type": self._parse_type_annotation(arg.annotation),
                "default": None,
            }
            metadata["parameters"].append(param_info)

        # 处理默认参数
        defaults = node.args.defaults
        if defaults:
            # 默认值从后往前对应参数
            num_defaults = len(defaults)
            for i, default in enumerate(defaults):
                param_idx = len(metadata["parameters"]) - num_defaults + i
                if param_idx >= 0:
                    try:
                        metadata["parameters"][param_idx]["default"] = self._ast_to_source(default)
                    except Exception:
                        metadata["parameters"][param_idx]["default"] = "<complex_default>"

        # 处理 *args 和 **kwargs
        if node.args.vararg:
            metadata["parameters"].append({
                "name": f"*{node.args.vararg.arg}",
                "type": self._parse_type_annotation(node.args.vararg.annotation),
                "default": None,
            })
        if node.args.kwarg:
            metadata["parameters"].append({
                "name": f"**{node.args.kwarg.arg}",
                "type": self._parse_type_annotation(node.args.kwarg.annotation),
                "default": None,
            })

        return metadata

    def _extract_class_metadata(self, node: ast.ClassDef) -> dict:
        """提取类的元数据"""
        metadata = {
            "name": node.name,
            "type": "class",
            "lineno": node.lineno,
            "docstring": ast.get_docstring(node) or "",
            "bases": [self._ast_to_source(base) for base in node.bases],
            "decorators": [self._ast_to_source(dec) for dec in node.decorator_list],
            "methods": [],
            "properties": [],
            "class_variables": [],
            "is_public": not node.name.startswith("_"),
        }

        # 遍历类的成员
        for item in node.body:
            if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
                method_info = self._extract_function_metadata(item)
                
                # 检查是否是 property
                is_property = any("property" in dec for dec in method_info["decorators"])
                if is_property:
                    metadata["properties"].append(method_info)
                else:
                    metadata["methods"].append(method_info)
            
            elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
                # 类变量（带类型注解）
                value_str = ""
                if item.value:
                    try:
                        value_str = self._ast_to_source(item.value)
                        # 限制值的长度
                        if len(value_str) > 50:
                            value_str = value_str[:50] + "..."
                    except Exception:
                        value_str = "<value>"
                
                metadata["class_variables"].append({
                    "name": item.target.id,
                    "type": self._parse_type_annotation(item.annotation),
                    "value": value_str,
                    "lineno": item.lineno,
                })
            elif isinstance(item, ast.Assign):
                # 类变量（无类型注解）
                for target in item.targets:
                    if isinstance(target, ast.Name):
                        # 提取值
                        value_str = ""
                        if item.value:
                            try:
                                value_str = self._ast_to_source(item.value)
                                # 限制值的长度
                                if len(value_str) > 50:
                                    value_str = value_str[:50] + "..."
                            except Exception:
                                value_str = "<value>"
                        
                        metadata["class_variables"].append({
                            "name": target.id,
                            "type": "",
                            "value": value_str,
                            "lineno": item.lineno,
                        })

        return metadata

    def _parse_python_file_ast(self, file_path: NbPath) -> dict:
        """解析 Python 文件的 AST，提取所有元数据"""
        try:
            source_code = file_path.read_text(encoding="utf-8")
            # 移除 BOM (Byte Order Mark) 字符，如果存在的话
            # BOM 是 U+FEFF，在 UTF-8 编码中是 \ufeff
            if source_code.startswith('\ufeff'):
                source_code = source_code[1:]
                self.logger.debug(f"Removed BOM from file: {file_path}")
            tree = ast.parse(source_code, filename=str(file_path))
        except Exception as e:
            self.logger.error(f"Failed to parse Python file {file_path}: {e}")
            return {
                "error": str(e),
                "classes": [],
                "functions": [],
                "imports": [],
                "module_docstring": "",
            }

        metadata = {
            "file": str(file_path),
            "module_docstring": ast.get_docstring(tree) or "",
            "classes": [],
            "functions": [],
            "imports": [],
            "constants": [],
        }

        # 遍历模块级别的节点
        for node in ast.walk(tree):
            # 只处理模块级别的定义（通过检查父节点）
            if isinstance(node, ast.ClassDef):
                # 检查是否是顶级类（不在其他类内部）
                parent = None
                for potential_parent in ast.walk(tree):
                    if isinstance(potential_parent, ast.ClassDef) and node in ast.walk(potential_parent) and node != potential_parent:
                        parent = potential_parent
                        break
                if parent is None:  # 顶级类
                    metadata["classes"].append(self._extract_class_metadata(node))

            elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                # 检查是否是顶级函数（不在类内部）
                parent_class = None
                for potential_parent in ast.walk(tree):
                    if isinstance(potential_parent, ast.ClassDef) and node in ast.walk(potential_parent):
                        parent_class = potential_parent
                        break
                if parent_class is None:  # 顶级函数
                    metadata["functions"].append(self._extract_function_metadata(node))

            elif isinstance(node, (ast.Import, ast.ImportFrom)):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        metadata["imports"].append({
                            "type": "import",
                            "module": alias.name,
                            "alias": alias.asname,
                            "lineno": node.lineno,
                        })
                else:  # ImportFrom
                    module = node.module or ""
                    for alias in node.names:
                        metadata["imports"].append({
                            "type": "from_import",
                            "module": module,
                            "name": alias.name,
                            "alias": alias.asname,
                            "lineno": node.lineno,
                        })

        return metadata

    def _format_py_metadata_as_markdown(self, metadata: dict, relative_file_name: str) -> str:
        """将 Python 文件元数据格式化为 Markdown"""
        lines = []
        lines.append(f"\n### 📄 Python File Metadata: `{relative_file_name}`\n")

        # 模块文档字符串
        if metadata.get("module_docstring"):
            lines.append("#### 📝 Module Docstring\n")
            lines.append(FILE_CONTENT_BACKQUOTES)
            lines.append(metadata["module_docstring"])
            lines.append(f"{FILE_CONTENT_BACKQUOTES}\n")

        # 导入信息
        if metadata.get("imports"):
            lines.append("#### 📦 Imports\n")
            for imp in metadata["imports"]:  # 显示所有 imports，不再限制数量
                if imp["type"] == "import":
                    alias_str = f" as {imp['alias']}" if imp['alias'] else ""
                    lines.append(f"- `import {imp['module']}{alias_str}`")
                else:
                    alias_str = f" as {imp['alias']}" if imp['alias'] else ""
                    lines.append(f"- `from {imp['module']} import {imp['name']}{alias_str}`")
            lines.append("")

        # 类信息
        if metadata.get("classes"):
            lines.append(f"#### 🏛️ Classes ({len(metadata['classes'])})\n")
            for cls in metadata["classes"]:
                # 只显示公有类或所有类（根据需要）
                class_header = f"##### 📌 `class {cls['name']}"
                if cls["bases"]:
                    class_header += f"({', '.join(cls['bases'])})"
                class_header += "`"
                lines.append(class_header)
                lines.append(f"*Line: {cls['lineno']}*\n")
                
                if cls["docstring"]:
                    # 显示完整的类文档字符串
                    docstring_lines = cls["docstring"].split("\n")
                    lines.append("**Docstring:**")
                    lines.append(FILE_CONTENT_BACKQUOTES)
                    lines.extend(docstring_lines)
                    lines.append(f"{FILE_CONTENT_BACKQUOTES}\n")

                # 首先单独显示 __init__ 方法（非常重要）
                init_method = None
                for method in cls["methods"]:
                    if method["name"] == "__init__":
                        init_method = method
                        break
                
                if init_method:
                    lines.append("**🔧 Constructor (`__init__`):**")
                    params_str = self._format_parameters(init_method["parameters"])
                    lines.append(f"- `def __init__({params_str})`")
                    
                    # 显示 __init__ 的完整文档字符串
                    if init_method["docstring"]:
                        lines.append("  - **Docstring:**")
                        lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                        for doc_line in init_method["docstring"].split("\n"):
                            lines.append(f"  {doc_line}")
                        lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                    
                    # 显示每个参数的详细信息
                    if init_method["parameters"]:
                        lines.append("  - **Parameters:**")
                        for param in init_method["parameters"]:
                            param_name = param["name"]
                            param_type = f": {param['type']}" if param["type"] else ""
                            param_default = f" = {param['default']}" if param["default"] else ""
                            lines.append(f"    - `{param_name}{param_type}{param_default}`")
                    lines.append("")

                # 公有方法（排除 __init__）
                public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
                if public_methods:
                    lines.append(f"**Public Methods ({len(public_methods)}):**")
                    for method in public_methods:
                        params_str = self._format_parameters(method["parameters"])
                        return_str = f" -> {method['return_type']}" if method["return_type"] else ""
                        async_str = "async " if method["type"] == "async_function" else ""
                        
                        decorators_str = ""
                        if method["decorators"]:
                            decorators_str = " " + " ".join([f"`{d}`" for d in method["decorators"]])
                        
                        lines.append(f"- `{async_str}def {method['name']}({params_str}){return_str}`{decorators_str}")
                        
                        # 显示完整的文档字符串
                        if method["docstring"]:
                            # 如果文档字符串只有一行，用简短格式显示
                            docstring_lines = method["docstring"].split("\n")
                            if len(docstring_lines) == 1:
                                lines.append(f"  - *{method['docstring'].strip()}*")
                            else:
                                # 多行文档字符串,用代码块格式显示
                                lines.append("  - **Docstring:**")
                                lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                                for doc_line in docstring_lines:
                                    lines.append(f"  {doc_line}")
                                lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                    lines.append("")

                # Properties
                if cls["properties"]:
                    lines.append(f"**Properties ({len(cls['properties'])}):**")
                    for prop in cls["properties"]:
                        return_str = f" -> {prop['return_type']}" if prop["return_type"] else ""
                        lines.append(f"- `@property {prop['name']}{return_str}`")
                    lines.append("")

                # 类变量
                if cls["class_variables"]:
                    lines.append(f"**Class Variables ({len(cls['class_variables'])}):**")
                    for var in cls["class_variables"]:
                        type_str = f": {var['type']}" if var["type"] else ""
                        value_str = f" = {var['value']}" if var.get("value") else ""
                        lines.append(f"- `{var['name']}{type_str}{value_str}`")
                    lines.append("")

        # 顶级函数
        if metadata.get("functions"):
            public_functions = [f for f in metadata["functions"] if f["is_public"]]
            if public_functions:
                lines.append(f"#### 🔧 Public Functions ({len(public_functions)})\n")
                for func in public_functions:
                    params_str = self._format_parameters(func["parameters"])
                    return_str = f" -> {func['return_type']}" if func["return_type"] else ""
                    async_str = "async " if func["type"] == "async_function" else ""
                    
                    decorators_str = ""
                    if func["decorators"]:
                        decorators_str = " " + " ".join([f"`{d}`" for d in func["decorators"]])
                    
                    lines.append(f"- `{async_str}def {func['name']}({params_str}){return_str}`{decorators_str}")
                    lines.append(f"  - *Line: {func['lineno']}*")
                    
                    if func["docstring"]:
                        # 如果文档字符串只有一行，用简短格式显示
                        docstring_lines = func["docstring"].split("\n")
                        if len(docstring_lines) == 1:
                            lines.append(f"  - *{func['docstring'].strip()}*")
                        else:
                            # 多行文档字符串,用代码块格式显示
                            lines.append("  - **Docstring:**")
                            lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                            for doc_line in docstring_lines:
                                lines.append(f"  {doc_line}")
                            lines.append(f"  {FILE_CONTENT_BACKQUOTES}")
                    lines.append("")

        lines.append("\n---\n")
        return "\n".join(lines)

    def _format_parameters(self, parameters: list) -> str:
        """格式化函数参数列表"""
        param_strs = []
        for param in parameters:
            param_str = param["name"]
            if param["type"]:
                param_str += f": {param['type']}"
            if param["default"]:
                param_str += f" = {param['default']}"
            param_strs.append(param_str)
        return ", ".join(param_strs)

    def merge_from_files_with_metadata(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        include_ast_metadata: bool = True,
        include_file_text: bool = True,
    ) -> "AiMdGenerator":
        """
        合并文件内容到 Markdown，对于 Python 文件会额外生成 AST 元数据
        
        Args:
            project_root: 项目根目录
            relative_file_name_list: 相对文件路径列表
            as_title: 标题
            include_ast_metadata: 是否包含 AST 元数据（仅对 .py 文件）
            include_file_text: 是否包含完整文件源码（False 时只显示元数据）
        """
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_text_list = []
        project_root_path = NbPath(project_root).resolve()
        
        for relative_file_name in relative_file_name_list:
            file = (project_root_path / relative_file_name).resolve()
            if not file.exists():
                raise FileNotFoundError(f"File {file} not found.")
            if file.is_file() and file.is_text():
                relative_file_name_posix = file.relative_to(project_root_path).as_posix()
                try:
                    text = file.read_text()
                except Exception as e:
                    self.logger.error(f"Error reading file {file}: {e}")
                    text = ""
                
                file_text_list.append([file, relative_file_name_posix, file.suffix, text])
                self.logger.debug(f"need merged file: {file}")
            else:
                raise ValueError(f"File {file} is not a text file.")
        
        str_list = []
        if file_text_list:
            str_list.extend(self._generate_markdown_header(as_title, file_text_list))

        for file, relative_file_name_posix, suffix, text in file_text_list:
            # 如果不包含文件内容，只输出元数据（仅对 Python 文件）
            if not include_file_text:
                if suffix == ".py" and include_ast_metadata:
                    # 只显示元数据，不显示源码
                    metadata = self._parse_python_file_ast(file)
                    metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                    str_list.append(metadata_md)
                    str_list.append("\n")
                # 非 Python 文件跳过
                continue
            
            # 正常流程：包含文件内容
            str_list.append(f"--- **start of file: {relative_file_name_posix}** --- \n")
            
            # 对于 Python 文件，添加 AST 元数据
            if suffix == ".py" and include_ast_metadata:
                metadata = self._parse_python_file_ast(file)
                metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                str_list.append(metadata_md)
            
            # 添加完整的文件内容
            if suffix == ".md":
                str_list.append(text + "\n")
            else:
                lang = self.suffix__lang_map.get(suffix, "text")
                str_list.append(f"{FILE_CONTENT_BACKQUOTES}{lang}\n{text}\n{FILE_CONTENT_BACKQUOTES}\n")

            str_list.append(f"--- **end of file: {relative_file_name_posix}** --- \n")
            str_list.append("---\n\n")

        self.append_text('\n'.join(str_list))
        self.ensure_utf8_bom()
        return self

`````

--- **end of file: nb_ai_context/ai_md_generator.py** --- 

---


--- **start of file: nb_ai_context/__init__.py** --- 


### 📄 Python File Metadata: `nb_ai_context/__init__.py`

#### 📦 Imports

- `from nb_path import NbPath`
- `from ai_md_generator import AiMdGenerator`
- `from contrib.gen_github_proj_ai_md import gen_github_proj_docs_and_codes_ai_md`
- `from contrib.gen_github_proj_ai_md import gen_github_proj_all_dirs_ai_md`


---

`````python

from nb_path import NbPath
from .ai_md_generator import AiMdGenerator

from .contrib.gen_github_proj_ai_md import gen_github_proj_docs_and_codes_ai_md,gen_github_proj_all_dirs_ai_md
`````

--- **end of file: nb_ai_context/__init__.py** --- 

---


--- **start of file: nb_ai_context/contrib/about.md** --- 



some contribution about nb_path

--- **end of file: nb_ai_context/contrib/about.md** --- 

---


--- **start of file: nb_ai_context/contrib/gen_github_proj_ai_md.py** --- 


### 📄 Python File Metadata: `nb_ai_context/contrib/gen_github_proj_ai_md.py`

#### 📝 Module Docstring

`````
Generate tutorials and code from any GitHub project into a markdown file for AI context

Download GitHub project zip file
https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
https://codeload.github.com/fastapi/sqlmodel/zip/refs/tags/0.0.25

Unzip the file
Generate markdown file
`````

#### 📦 Imports

- `import typing`
- `from nb_path import NbPath`
- `from nb_ai_context import AiMdGenerator`
- `import re`

#### 🔧 Public Functions (3)

- `def extract_repo_name(github_url)`
  - *Line: 18*

- `def gen_github_proj_docs_and_codes_ai_md(github_zip_url: str, output_md_path: str, docs_dir_name: str, codes_dir_name: str, readme_file: str, should_include_suffixes: list = None, excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [])`
  - *Line: 27*
  - **Docstring:**
  `````
  Generate a Markdown document containing README, tutorials, and source code from a GitHub repository URL
  
  Args:
      github_zip_url: GitHub repository zip download link
                     Example: https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
      output_md_path: Output Markdown file path
      readme_file: README file name, default is "README.md"
      docs_dir_name: Documentation directory name, e.g. "docs"
      codes_dir_name: Source code directory name, e.g. "sqlmodel"
      should_include_suffixes: List of file extensions to include,
                              By default includes ['.py', '.java', '.go', '.md', '.html']
  `````

- `def gen_github_proj_all_dirs_ai_md(github_zip_url: str, output_md_path: str, should_include_suffixes: list = None, excluded_dir_name_list: typing.List[str] = [], excluded_file_name_list: typing.List[str] = [])`
  - *Line: 99*
  - *生成github的项目根目录下所有文件到markdown，*


---

`````python
"""
Generate tutorials and code from any GitHub project into a markdown file for AI context

Download GitHub project zip file
https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
https://codeload.github.com/fastapi/sqlmodel/zip/refs/tags/0.0.25

Unzip the file
Generate markdown file
"""

import typing
from nb_path import NbPath
from nb_ai_context import AiMdGenerator
import re


def extract_repo_name(github_url):
    # Match GitHub codeload URL pattern
    pattern = r"https://codeload\.github\.com/[^/]+/([^/]+)"
    match = re.search(pattern, github_url)
    if match:
        return match.group(1)
    return None


def gen_github_proj_docs_and_codes_ai_md(
    github_zip_url: str,
    output_md_path: str,
    docs_dir_name: str,
    codes_dir_name: str,
    readme_file: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    Generate a Markdown document containing README, tutorials, and source code from a GitHub repository URL

    Args:
        github_zip_url: GitHub repository zip download link
                       Example: https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
        output_md_path: Output Markdown file path
        readme_file: README file name, default is "README.md"
        docs_dir_name: Documentation directory name, e.g. "docs"
        codes_dir_name: Source code directory name, e.g. "sqlmodel"
        should_include_suffixes: List of file extensions to include,
                                By default includes ['.py', '.java', '.go', '.md', '.html']
    """
    project_name = extract_repo_name(github_zip_url)
    if should_include_suffixes is None:
        should_include_suffixes = [".py", ".java", ".go", ".md", ".html"]

    with NbPath.tempdir() as temp_dir:
        # Download zip file
        zip_file = temp_dir / "repo.zip"
        zip_file.download_from_url(github_zip_url)

        # Unzip the file
        unzip_dir = zip_file.unzip_to(temp_dir / "unzip")
        project_root = list(unzip_dir.iterdir())[
            0
        ]  # Get the project root directory after extraction

        print(f"tempdir github project_root: {project_root}")

        # Create AiMdGenerator instance and chain calls

        md_generator = (
            AiMdGenerator(output_md_path)
            .set_project_propery(project_name=project_name, project_root=project_root)
            .clear_text()
            .merge_from_files(
                relative_file_name_list=[readme_file],
                as_title=f"{project_name}  README",
            )
            .merge_from_dir(
                relative_dir_name=docs_dir_name,
                as_title=f"{project_name}  Documentation",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .merge_from_dir(
                relative_dir_name=codes_dir_name,
                as_title=f"{project_name}  Source Code",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .show_textfile_info()
        )

        return md_generator


def gen_github_proj_all_dirs_ai_md(
    github_zip_url: str,
    output_md_path: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    生成github的项目根目录下所有文件到markdown，
    """
    project_name = extract_repo_name(github_zip_url)
    if should_include_suffixes is None:
        should_include_suffixes = [".py", ".java", ".go", ".md", ".html"]

    with NbPath.tempdir() as temp_dir:
        # Download zip file
        zip_file = temp_dir / "repo.zip"
        zip_file.download_from_url(github_zip_url)

        # Unzip the file
        unzip_dir = zip_file.unzip_to(temp_dir / "unzip")
        project_root = list(unzip_dir.iterdir())[
            0
        ]  # Get the project root directory after extraction

        print(f"tempdir github project_root: {project_root}")

        # Create AiMdGenerator instance and chain calls

        md_generator = (
            AiMdGenerator(output_md_path)
            .set_project_propery(project_name=project_name, project_root=project_root)
            .clear_text()
            .merge_from_dir(
                relative_dir_name=".",
                as_title=f"{project_name}  all files",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .show_textfile_info()
        )

        return md_generator


if __name__ == "__main__":
    # Example: Generate documentation for sqlmodel project
    gen_github_proj_docs_and_codes_ai_md(
        github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
        output_md_path=NbPath(
            r"D:\codes\nb_ai_context\markdown_gen_files_git_ignore\ai_md_files\other_peoples"
        )
        .joinpath("sqlmodel_all_docs_and_codes.md")
        .ensure_parent(),
        readme_file="README.md",
        docs_dir_name="docs",
        codes_dir_name="sqlmodel",
        should_include_suffixes=[
            ".py",
            ".md",
        ],
    )

    gen_github_proj_all_dirs_ai_md(
        github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
        output_md_path=NbPath(
            r"D:\codes\nb_ai_context\markdown_gen_files_git_ignore\ai_md_files\other_peoples"
        )
        .joinpath("sqlmodel_all_dirs.md")
        .ensure_parent(),
    )

`````

--- **end of file: nb_ai_context/contrib/gen_github_proj_ai_md.py** --- 

---
``````

## examples/AiMdGenerator_example.py

```python
from nb_ai_context import AiMdGenerator
⋮----
project_name = "nb_ai_context"
project_root = rf"D:\codes\{project_name}"
⋮----
project_summary = f"""
- `{project_name}` is a powerful ai llm context gennerator library ,it is uesd for ai llm and rag
- `AiMdGenerator(...)` is the main class to create ai context for llm.
"""
```

## git_nb_ai_context.py

```python
import subprocess
import time
⋮----
def getstatusoutput(cmd):
⋮----
def do_cmd(cmd_strx):
⋮----
t0 = time.time()
```

## nb_ai_context/ai_md_generator.py

``````python
import typing
import os
import fnmatch
import ast
from nb_path import NbPath
⋮----
FILE_CONTENT_BACKQUOTES = "`````"
⋮----
class AiMdGenerator(NbPath):
    """
    An extremely powerful context generator born for AI collaboration.

    This class is designed to revolutionize how developers interact with Large Language
    Models (LLMs). It intelligently merges multiple project source files into a single,
    well-structured, and context-rich Markdown file, providing the AI with a perfect
    and comprehensive project snapshot.

    The benefits for large AI models are immense:
    1.  **Provides a God's-eye View**: Through a file manifest, clear file boundaries,
        and relative paths, the AI can easily construct the project's overall
        architecture and understand file dependencies and relationships, rather than
        fumbling in the dark.
    2.  **Ensures Information Integrity and Accuracy**: The AI receives complete,
        unabridged source file content, avoiding the chaos, omissions, or context
        loss caused by manual copy-pasting. This enables it to provide more precise
        analysis and suggestions.
    3.  **Enhances Security**: The built-in `use_gitignore` feature is a critical
        security barrier. It automatically ignores files containing sensitive
        information (like API keys or database passwords) such as `.env` or local
        configs, allowing you to share code without fear of accidental leaks.

    Its core methods, `merge_from_files` and `merge_from_dir`, offer extreme
    flexibility. Combined with the elegant chainable calls of `nb_path`, creating a
    high-quality AI context is transformed from a tedious, error-prone manual task
    into a single, delightful line of code.

    Example:
        >>> # Imagine you want an AI to review your entire project
        >>> project_name = "my_project"
        >>> project_summary = '''
        ... This is an excellent Python project that demonstrates best practices.
        ... It includes comprehensive documentation and well-structured code.
        ... '''
        >>> 
        >>> (
        ...     AiMdGenerator("project_context_for_ai.md")
        ...     .set_project_propery(project_name=project_name, project_root="/path/to/your/project")
        ...     .clear_text()  # Clear the old file
        ...     .add_project_summary(
        ...         project_summary=project_summary,
        ...         # Extract metadata (without full source) from core files first
        ...         most_core_source_code_file_list=[
        ...             "src/main.py",
        ...             "src/api.py",
        ...             "src/models.py",
        ...         ]
        ...     )
        ...     .auto_merge_from_python_project_some_files()  # Auto-include README, setup.py, etc.
        ...     .merge_from_dir(
        ...         relative_dir_name="src", # The main source code directory
        ...         as_title="Project Source Code",
        ...         use_gitignore=True,  # Automatically use .gitignore rules
        ...         should_include_suffixes=[".py", ".md"], # Only include specified file types
        ...         include_ast_metadata=True,  # Include AST metadata for Python files
        ...     )
        ...     .merge_from_dir(
        ...         relative_dir_name="tests", # The tests directory
        ...         as_title="Project Tests",
        ...         use_gitignore=True,
        ...         should_include_suffixes=[".py"],
        ...         excluded_dir_name_list=["tests/temp_files"],
        ...         include_ast_metadata=True,
        ...     )
        ... )
    """
    """cn description
    一个极其强大的、为 AI 协作而生的上下文生成器。

    此类旨在彻底改变开发者与大语言模型（LLM）的交互方式。它能够智能地将多个项目源文件
    合并成一个结构清晰、上下文丰富的单一 Markdown 文件，从而为 AI 提供一个完美、全面的项目快照。

    对 AI 大模型的好处是巨大的：
    1.  **提供上帝视角**：通过文件清单、清晰的文件边界和相对路径，AI 能够轻松构建出项目的
        整体架构，理解文件间的依赖和引用关系，而不是盲人摸象。
    2.  **确保信息的完整与准确**：AI 得到的是未经删减的、完整的源文件内容，避免了因手动
        复制粘贴导致的格式混乱、内容遗漏或上下文缺失，从而能给出更精准的分析和建议。
    3.  **提升安全性**：内置的 `use_gitignore` 功能是一道关键的安全屏障。它能自动忽略
        `.env`、本地配置等包含敏感信息（如 API 密钥、数据库密码）的文件，让你在分享代码
        时无需担心意外泄露秘密。

    其核心方法 `merge_from_files` 和 `merge_from_dir` 提供了极高的灵活性，结合 `nb_path`
    优雅的链式调用，使得创建一个高质量的 AI 上下文从繁琐、易错的手工劳动，变成了一行
    赏心悦目的代码。

    """
⋮----
def set_project_propery(self, project_name: str, project_root: typing.Union[os.PathLike, str] ) -> "AiMdGenerator":
    """Sets the project name for the current markdown file."""
⋮----
def _check_project_name(self) -> "AiMdGenerator":
    """Checks if the project name is set."""
⋮----
def add_project_summary(
        self, 
        project_summary: str, 
        most_core_source_code_file_list: typing.List[str] = None,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
    """
        Adds a project summary to the current markdown file.
        
        Args:
            project_summary: 项目概述文本
            project_root: 项目根目录（如果提供了 most_core_source_code_file_list）
            most_core_source_code_file_list: 最核心的源码文件列表（相对路径）
                                             只提取这些文件的 AST 元数据，不包含完整源码
        
        Example:
            >>> (
            ...     AiMdGenerator("output.md")
            ...     .set_project_name("my_project")
            ...     .clear_text()
            ...     .add_project_summary(
            ...         project_summary="这是一个优秀的项目...",
            ...         project_root="/path/to/project",
            ...         most_core_source_code_file_list=["src/main.py", "src/api.py"],
            ...     )
            ... )
        """
⋮----
def _generate_markdown_header(self, as_title: str, file_text_list: list) -> list:
    """生成包含文件树和文件列表的 Markdown 头部"""
def format_tree(node, prefix=""):
⋮----
def auto_merge_from_python_project_some_files(self, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator':
    """自动合并项目根目录下的 readme.md 或者ReADME.md 以及setup.py 和 pyproject.toml ，如果有就添加"""
⋮----
def merge_from_files(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
    """Merges the content of the given files into the current markdown file.
        the current markdown file will be used to upload to ai model for code review and learning.
        """
⋮----
def merge_from_dir(
        self,
        relative_dir_name: str,
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        should_include_suffixes: typing.List[str] = [],
        excluded_dir_name_list: typing.List[str] = [],
        excluded_file_name_list: typing.List[str] = [],
        use_gitignore: bool = True,
        dry_run: bool = False,
        include_ast_metadata: bool = True,
    ) -> "AiMdGenerator":
    """Merges the content of the given directory into the current file."""
⋮----
def merge_dir_of_package_examples(self):
    """合并包的examples目录到当前markdown文件"""
⋮----
def _ast_to_source(self, node) -> str:
    """将 AST 节点转换为源代码字符串，兼容 Python 3.7+"""
⋮----
import astor
⋮----
def _parse_type_annotation(self, annotation) -> str:
    """解析类型注解，返回字符串表示"""
⋮----
def _extract_function_metadata(self, node: typing.Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> dict:
    """提取函数/方法的元数据"""
⋮----
def _extract_class_metadata(self, node: ast.ClassDef) -> dict:
    """提取类的元数据"""
⋮----
def _parse_python_file_ast(self, file_path: NbPath) -> dict:
    """解析 Python 文件的 AST，提取所有元数据"""
⋮----
def _format_py_metadata_as_markdown(self, metadata: dict, relative_file_name: str) -> str:
    """将 Python 文件元数据格式化为 Markdown"""
⋮----
def _format_parameters(self, parameters: list) -> str:
    """格式化函数参数列表"""
⋮----
def merge_from_files_with_metadata(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        include_ast_metadata: bool = True,
        include_file_text: bool = True,
    ) -> "AiMdGenerator":
    """
        合并文件内容到 Markdown，对于 Python 文件会额外生成 AST 元数据
        
        Args:
            project_root: 项目根目录
            relative_file_name_list: 相对文件路径列表
            as_title: 标题
            include_ast_metadata: 是否包含 AST 元数据（仅对 .py 文件）
            include_file_text: 是否包含完整文件源码（False 时只显示元数据）
        """
``````

## nb_ai_context/contrib/about.md

```markdown
some contribution about nb_path
```

## nb_ai_context/contrib/gen_github_proj_ai_md.py

```python
import typing
from nb_path import NbPath
from nb_ai_context import AiMdGenerator
import re
⋮----
def extract_repo_name(github_url):
⋮----
def gen_github_proj_docs_and_codes_ai_md(
    github_zip_url: str,
    output_md_path: str,
    docs_dir_name: str,
    codes_dir_name: str,
    readme_file: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    Generate a Markdown document containing README, tutorials, and source code from a GitHub repository URL

    Args:
        github_zip_url: GitHub repository zip download link
                       Example: https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
        output_md_path: Output Markdown file path
        readme_file: README file name, default is "README.md"
        docs_dir_name: Documentation directory name, e.g. "docs"
        codes_dir_name: Source code directory name, e.g. "sqlmodel"
        should_include_suffixes: List of file extensions to include,
                                By default includes ['.py', '.java', '.go', '.md', '.html']
    """
⋮----
def gen_github_proj_all_dirs_ai_md(
    github_zip_url: str,
    output_md_path: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    生成github的项目根目录下所有文件到markdown，
    """
```

## nb_ai_context/__init__.py

```python
from nb_path import NbPath
from .ai_md_generator import AiMdGenerator
from .contrib.gen_github_proj_ai_md import gen_github_proj_docs_and_codes_ai_md,gen_github_proj_all_dirs_ai_md
```

## pub_pypi_nb_ai_context.py

```python
import os
import sys
import shutil
import time
import git_nb_ai_context
⋮----
t_start = time.time()
⋮----
t_end = time.time()
```

## pyproject.toml

```text
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "nb_ai_context"
version = "1.5"
description = "非常强力的对任意it项目生成ai上下文单个markdown文档，丢给大模型或者知识库，非常强力，大模型幻觉大幅度减少。尤其是对python项目还有专门的ast语法树解析"
keywords = ["ai", "ai context", "llm", "rag", "python","ast","ast metadata","ai context generator","markdown"]
authors = [
    {name = "ydf0509", email = "ydf0509@xxx.com"},
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
]
dependencies = [
    "typing_extensions>=3.7; python_version < '3.8'",
    "nb_path",
    "nb_log",
]

[project.optional-dependencies]
test = [
    "pytest>=6.0",
    "pytest-asyncio>=0.14",
]
dev = [
    "black>=21.0",
    "flake8>=3.8",
    "mypy>=0.812",
]

[project.urls]
Homepage = "https://github.com/ydf0509/nb_ai_context"
Repository = "https://github.com/ydf0509/nb_ai_context"
Issues = "https://github.com/ydf0509/nb_ai_context/issues"

[tool.setuptools.packages.find]
where = ["."]
include = ["nb_ai_context*"]

[tool.setuptools.package-data]
nb_ai_context = ["py.typed", "contrib/README.md"]
```

## README.md

````markdown
# nb_ai_context

[![PyPI version](https://img.shields.io/pypi/v/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![Python versions](https://img.shields.io/pypi/pyversions/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

🚀 **极其强大的 AI 上下文生成器** - 将任意 IT 项目合并生成为1个结构化 Markdown 文档，方便一次上传给ai大模型或者rag知识库。  

**`nb_ai_context` 生成的markdown文件 传给  `google ai studio` ，生成代码质量和准确率，远远吊打在 `cursor` `trae` `qoder` `gemini-code-assit` 等ai ide中写代码。**

<pre style="color: red;font-weight: bold;background-color:yellow;padding: 10px;border-radius: 5px;font-size: 16px;">
本人实测：
将 nb_ai_context 对项目教程+源码生成的单个markdown文件 传给 `google ai studio` 网页版
的1000k上下文+gemini 3.0pro 大模型，再提问让gemini写代码，代码生成的准确率和质量远远吊打在 
cursor trae qoder等ai ide或gemini-code-assit等 编程插件 里面提问写代码。

 `google ai studio` 优点是1000k上下文以及免费。唯一缺点是他在网页生成代码，而不是直接操作
 修改你的本地代码文件。

 `nb_ai_context` 生成的markdown丢给`google ai studio`完爆cursor trae等ide中写代码 
 主要原因是：  
`google ai studio` 网页版是全量阅读推理你上传的文件，而 ai编程ide 对长教程或者源码，
ai agent试图采取关键字搜索匹配再投喂上下文给ai大模型，（有时候模糊搜索的关键字压根搜不到内容，
因为ai造出来的关键字不是完完全全的匹配你的函数名字或者教程内容，所以就搜不到，
例如近义词，你代码用别的单词表示，ai却用自己的单词来表示，靠关键字压根就搜不到内容，agent咋投喂ai大模型？）
ai ide 这种方式简直是管中窥豹 一叶障目不见泰山。

最根本原因还是：  
ai ide 为了减少成本，防止token消耗大，不会全量把源码教程喂给ai大模型，不然的靠用户交的那20美元的月会员，
cursor要亏得裤衩没得穿。100万 tokens上下文输入，calude gemini gpt 价格大约是3美元，相当于20元人民币，
如果问一次就20元人民币，cursor每个月给你用8次就要开始亏血本了，(另外tokens输出价格远高于输入价格），
现在cursor还给你每月用500次，那他只能从节约tokens下手了，具体表现就是cursor不全量把所有教程和源码
发给ai大模型推理。

`nb_ai_context` 对大型编程项目的效果尤为突出，远远吊打 cursor trae qoder gemini-code-assit 等ai ide中写代码。
</pre>

## ✨ 核心特性

### 🎯 为什么需要 nb_ai_context？

当你想让 AI 大模型（如 ChatGPT、Claude）理解你的整个项目时，手动复制粘贴代码既繁琐又容易出错。`nb_ai_context` 彻底解决了这个痛点：

- ✅ **自动化合并** - 一键将整个项目合并为单个 Markdown 文件
- ✅ **结构化输出** - 包含文件树、文件边界、相对路径，AI 能完美理解项目架构
- ✅ **智能元数据提取** - 对 Python 项目提供 AST 语法树解析，自动生成类、函数、方法签名
- ✅ **安全可靠** - 内置 `.gitignore` 支持，自动排除敏感信息（API 密钥、.env 文件等）
- ✅ **灵活可控** - 支持文件过滤、目录排除、自定义文件类型
- ✅ **GitHub 项目支持** - 直接从 GitHub zip 下载地址生成文档

### 💡 **关键优势对比**
| 方案 | 上下文完整性 | Token 消耗 | 安全性 | 成本 |
|------|--------------|------------|--------|------|
| **Cursor/trae/qoder** | ❌ 分段阅读 | ⚠️ 高 | ⚠️ 需手动过滤 | 💰 付费次数限制 |
| **手动复制粘贴** | ❌ 易遗漏 | ❌ 极高 | ❌ 易泄露敏感信息 | ⏰ 耗时 |
| **nb_ai_context** | ✅ 完整结构化 | ✅ AST 元数据优化 | ✅ 自动 `.gitignore` | 🆓 免费工具 |



### 🎁 对 AI 大模型的巨大价值

1. **提供上帝视角** 📐
   - 清晰的文件树和文件边界
   - 完整的相对路径信息
   - AI 能轻松构建项目的整体架构图

2. **确保信息完整性** 📚
   - 未经删减的完整源码
   - 避免手动复制导致的格式混乱
   - 零上下文缺失，AI 分析更精准

3. **增强安全性** 🔒
   - 自动遵循 `.gitignore` 规则
   - 防止敏感信息泄露
   - 放心分享代码给 AI

4. **Python 项目专属优化** 🐍
   - AST 语法树解析
   - 自动提取类、函数、方法签名
   - 参数类型、返回值、装饰器一目了然

## 📦 安装

```bash
pip install nb_ai_context
```

**依赖项：**
- Python >= 3.7
- nb_path
- nb_log

## 🚀 快速开始

### 示例 1：为本地 Python 项目生成 AI 上下文

```python
from nb_ai_context import AiMdGenerator

project_name = "my_awesome_project"
project_root = r"D:\codes\my_awesome_project"

project_summary = f"""
- `{project_name}` 是一个优秀的 Python Web 应用
- 使用 FastAPI 框架
- 包含完整的用户认证和权限管理
"""

(
    AiMdGenerator(rf"D:\ai_docs\{project_name}_for_ai.md")
    .set_project_propery(project_name=project_name, project_root=project_root)
    .clear_text()  # 清空旧内容
    .add_project_summary(
        project_summary=project_summary,
        # 核心文件列表（只提取元数据，不包含完整源码）
        most_core_source_code_file_list=[
            "src/main.py",
            "src/api.py",
            "src/models.py",
        ],
    )
    .auto_merge_from_python_project_some_files()  # 自动包含 README.md、setup.py、pyproject.toml
    .merge_from_dir(
        relative_dir_name="src",
        as_title=f"{project_name} 源代码",
        use_gitignore=True,  # 使用 .gitignore 规则
        should_include_suffixes=[".py", ".md"],  # 只包含指定文件类型
        include_ast_metadata=True,  # 包含 AST 元数据
    )
    .merge_from_dir(
        relative_dir_name="tests",
        as_title=f"{project_name} 测试代码",
        use_gitignore=True,
        should_include_suffixes=[".py"],
        excluded_dir_name_list=["tests/temp"],  # 排除特定目录
        include_ast_metadata=True,
    )
    .show_textfile_info()  # 显示生成的文件信息
)
```

### 示例 2：从 GitHub 项目生成 AI 文档

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md
from nb_path import NbPath

# 生成 SQLModel 项目的文档和代码
gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_docs_and_codes.md",
    readme_file="README.md",
    docs_dir_name="docs",  # 文档目录
    codes_dir_name="sqlmodel",  # 源码目录
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests", "__pycache__"],
    excluded_file_name_list=["test_*.py"],
)
```

### 示例 3：生成 GitHub 项目的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

# 生成整个项目的所有文件
gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_files.md",
    should_include_suffixes=[".py", ".md", ".html"],
    excluded_dir_name_list=["node_modules", ".git", "__pycache__"],
)
```


## 为什么要这个 nb_ai_context ？脱了裤子放屁？让cusror trae 去阅读项目的源码和教程，还能在ide里面生成代码，这不更香吗？(预判质疑)

- 1. 有时候是非ide环境问项目的用法

- 2. cursor 太贵了，经常提问cursor浪费宝贵次数，要把宝贵次数留给真正的代码生成，而不是提问。
     cursor trae 存在上下文太短，调教成不积极阅读全量源码和教程等问题。

- 3. 可以作为 rag 在线知识库使用，让ai大模型阅读生成的markdown，然后回答问题。

- 4. 可以把生成的markdown丢给免费且上下文无敌高达1000k 的 `google ai studio` 去阅读，claude gpt才256k token，国内大模型才128k token，压根不够用，  
     只有  google ai studio + gemini 3.0pro 才能完整阅读项目源码加教程，并且免费使用。 

- 5. `google ai studio` 对于4M 以内的文件，可以一次性全量阅读全文+推理，并且免费使用。
     一般三方包项目的源码加教程远没有达到4M。
     如果是用户自己的真实项目而非框架或者三方包项目，项目中可能包含大量写法模式非常重复的需求代码，代码加教程加起来可能会超过4M，但是 用户项目核心公共入口函数和类的用法的源码 + 教程 绝对不超过1M。

- 6. `nb_ai_context` 支持按文件夹合并和过滤需要合并的文件，并且`nb_ai_context` 支持 `ast` 解析 任何python文件的 类 函数 入参 注释 等 元信息，大幅减少token消耗，让ai抓住重点。  
     `AiMdGenerator.add_project_summary(most_core_source_code_file_list=[...])` 支持添加项目概述和核心文件元数据，让ai快速抓住重点。  
     不管项目多复杂有几百个class类和模块，但真正能作为公共使用入口的模块一般不会超过10个。  
     例如 `funboost` 项目 `python`文件高达几百个，但真正能作为框架使用用户级别的公共使用入口的python文件也就三四个，其他的python文件压根没机会被终端用户直接import并使用。


- 10. 最最主要的原因！！！ cursor trae 内置的大模型都不乖乖阅读所有源码和教程，即使你明确发出指令强迫他阅读所有教程和源码，他也会偷懒不读，只读部分代码，导致幻觉率极高。
      为什么 cursor trae 里面的大模型不全量阅读呢，因为他早就预判了有流氓玩家恶意滥用大模型算力， 如果你让cursor + claude 阅读c盘下的所有文件，cursor和claude 会这么乖吗？
      c盘下的文本文件高达几百亿行，如果读完一次就会消耗价值几十万美元的tokens，大模型厂商亏得裤衩没得穿，你才20美元的包月会员，cursor和claude厂家不被你薅秃噜皮？
      所以不管用户怎么强迫cursor  trae去阅读所有源码和教程，cursor 和 claude 和trae都不会听你的话。

- 11. trae 和 cursor 被调教限制了， trae一次最多阅读200行，qoder一次最多阅读1000行，对超长教程，需要分多次阅读，导致幻觉率极高，你让这种 ai ide + 大模型去总结 项目的中心思想非常不靠谱。 
      并且分段阅读交互次数高达几十次，太费等待时间了。
      并且 ai ide里面阅读长文件会分批阅读直到20次就会自动停止，例如文件有10000行，trae一次阅读200行，需要50次交互，但ai ide设置了最多交互20次就会自动停止；这是因为担心流氓恶意用户让他阅读1亿行的文件导致自己血亏，所以最多分段阅读20次就会停止。

- 12. 解决 10和11的痛点问题， 只有 `google ai studio` 才能做到 ， 或者使用rag也勉强可以。
      把文档上传给 `google ai studio` 网页版的1000k上下文的gemini pro 这种方式最强， 其次是 `rag`方式。
      对 4M以内的文档（大约就是1000k tokens）， 推荐使用  `google ai studio`，不要使用 `rag` 方式。
      超过4M文档的上下文，突破了gemini 1000k token 上限，无法使用，此时需要使用 `rag`方式。

- 13. 一些网址：
       google ai studio 网址  [https://aistudio.google.com/](https://aistudio.google.com/)    
       智谱清言 ai智能体网址，可以支持免费rag  [chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh](chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh)    
       腾讯ima知识库 [ima.qq.com](https://ima.qq.com/)    



## 📖 核心 API 详解

### AiMdGenerator 类

`AiMdGenerator` 是核心类，继承自 `NbPath`，支持优雅的链式调用。

#### 主要方法

##### 1. `set_project_propery(project_name, project_root)`
设置项目名称和根目录（必须首先调用）

```python
.set_project_propery(
    project_name="my_project",
    project_root=r"D:\codes\my_project"
)
```

##### 2. `add_project_summary(project_summary, most_core_source_code_file_list)`
添加项目概述和核心文件元数据

```python
.add_project_summary(
    project_summary="项目描述...",
    most_core_source_code_file_list=[
        "src/main.py",  # 只提取这些文件的 AST 元数据，不包含完整源码
        "src/api.py",
    ]
)
```

##### 3. `auto_merge_from_python_project_some_files()`
自动合并项目根目录的关键文件（README.md、setup.py、pyproject.toml）

```python
.auto_merge_from_python_project_some_files()
```

##### 4. `merge_from_files(relative_file_name_list, as_title)`
合并指定文件列表

```python
.merge_from_files(
    relative_file_name_list=["docs/intro.md", "docs/api.md"],
    as_title="项目文档"
)
```

##### 5. `merge_from_dir()` ⭐ 最强大的方法
合并整个目录的文件

```python
.merge_from_dir(
    relative_dir_name="src",           # 相对目录名
    as_title="源代码",                  # 章节标题
    should_include_suffixes=[".py"],   # 包含的文件类型
    excluded_dir_name_list=[],         # 排除的目录列表
    excluded_file_name_list=[],        # 排除的文件列表
    use_gitignore=True,                # 使用 .gitignore 规则
    include_ast_metadata=True,         # 包含 Python AST 元数据
)
```

**参数说明：**
- `relative_dir_name`: 相对于项目根目录的路径
- `as_title`: 在生成的 Markdown 中的章节标题
- `should_include_suffixes`: 只包含这些后缀的文件（留空则包含所有文本文件）
- `excluded_dir_name_list`: 要排除的目录列表
- `excluded_file_name_list`: 要排除的文件列表
- `use_gitignore`: 是否遵循 `.gitignore` 规则（强烈推荐开启）
- `include_ast_metadata`: 对 Python 文件是否生成 AST 元数据
- `dry_run`: 预览模式，只显示将要包含的文件，不实际生成

##### 6. `merge_from_files_with_metadata()`
带元数据的文件合并（更高级的控制）

```python
.merge_from_files_with_metadata(
    relative_file_name_list=["src/main.py"],
    as_title="核心代码",
    include_ast_metadata=True,   # 是否包含 AST 元数据
    include_file_text=True,      # 是否包含完整源码
)
```

##### 7. `show_textfile_info()`
显示生成文件的统计信息

```python
.show_textfile_info()  # 打印文件大小、行数等信息
```

### GitHub 项目处理函数

#### `gen_github_proj_docs_and_codes_ai_md()`
生成 GitHub 项目的 README、文档和源码

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md

gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output.md",
    readme_file="README.md",
    docs_dir_name="docs",
    codes_dir_name="src",
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests"],
    excluded_file_name_list=["test_*.py"],
)
```

#### `gen_github_proj_all_dirs_ai_md()`
生成 GitHub 项目根目录下的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output_all.md",
    should_include_suffixes=[".py", ".java", ".go", ".md"],
    excluded_dir_name_list=["node_modules", ".git"],
)
```

## 🐍 Python AST 元数据提取

对于 Python 文件，`nb_ai_context` 会自动提取以下元数据：

### 提取内容
- ✅ **模块文档字符串**
- ✅ **导入语句** (import / from import)
- ✅ **类定义**
  - 类名、继承关系、装饰器
  - 类文档字符串
  - 公有方法（含参数类型、返回值、装饰器）
  - 属性 (@property)
  - 类变量
- ✅ **顶级函数**
  - 函数签名（参数类型、默认值、返回值）
  - 装饰器
  - 文档字符串
  - 异步函数标识

### 元数据示例输出

```markdown
### 📄 Python File Metadata: `src/main.py`

#### 📝 Module Docstring
```
这是主模块，包含应用的入口点
```

#### 📦 Imports
- `import os`
- `from typing import List, Optional`
- `from fastapi import FastAPI`

#### 🏛️ Classes (2)

##### 📌 `class UserService(BaseService)`
*Line: 15*

**Docstring:**
```
用户服务类，处理用户相关的业务逻辑
```

**Public Methods (3):**
- `def get_user(user_id: int) -> Optional[User]`
  - *获取用户信息*
- `async def create_user(name: str, email: str) -> User`
  - *创建新用户*
- `def update_user(user_id: int, **kwargs) -> bool`

**Properties (1):**
- `@property user_count -> int`

#### 🔧 Public Functions (2)
- `async def startup_event() -> None`
  - *Line: 120*
  - *应用启动事件处理*
```

## 🎨 生成的 Markdown 结构

生成的 Markdown 文档结构清晰，包含：

```markdown
# markdown content namespace: 项目概述

项目描述...

## 📋 Core Source Files Metadata (Entry Points)
核心文件的 AST 元数据（不含源码）

---

# markdown content namespace: 项目根目录文件

## File Tree
```
├── README.md
├── setup.py
└── pyproject.toml
```

## Included Files
- `README.md`
- `setup.py`
- `pyproject.toml`

---

--- **start of file: README.md** ---
（文件内容）
--- **end of file: README.md** ---

---

# markdown content namespace: 源代码

## File Tree
（文件树）

## Included Files
（文件列表）

--- **start of file: src/main.py** ---

### 📄 Python File Metadata: `src/main.py`
（AST 元数据）

```python
（完整源码）
```

--- **end of file: src/main.py** ---
```

## 🔧 高级用法

### 1. 预览模式（Dry Run）

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        dry_run=True,  # 只预览，不实际生成
    )
)
```

### 2. 只提取元数据，不包含源码

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_files_with_metadata(
        relative_file_name_list=["src/main.py"],
        as_title="核心代码架构",
        include_ast_metadata=True,  # 包含元数据
        include_file_text=False,    # 不包含源码
    )
)
```

### 3. 支持的文件类型

`AiMdGenerator` 支持多种编程语言的语法高亮：

```python
suffix__lang_map = {
    ".py": "python",
    ".java": "java",
    ".go": "go",
    ".js": "javascript",
    ".ts": "typescript",
    ".jsx": "javascript",
    ".tsx": "typescript",
    ".vue": "vue",
    ".php": "php",
    ".c": "c",
    ".cpp": "cpp",
    ".cs": "csharp",
    ".sql": "sql",
    ".sh": "shell",
    ".ps1": "powershell",
    ".md": "markdown",
    ".json": "json",
    ".yaml": "yaml",
    ".xml": "xml",
    ".html": "html",
    ".css": "css",
    # ... 更多
}
```

## 🎯 实际应用场景

### 1. AI 代码审查
生成完整项目上下文，让 AI 全面审查代码质量、安全漏洞、性能问题。

### 2. 技术文档生成
快速为新团队成员生成项目概览文档，配合 AI 解释代码逻辑。

### 3. 知识库构建
将项目内容导入 RAG 知识库，实现智能代码搜索和问答。

### 4. 项目迁移和重构
让 AI 理解整个项目后，提供重构建议或迁移到新框架。

### 5. 学习开源项目
下载 GitHub 项目后生成结构化文档，配合 AI 快速理解项目架构。

## 🔒 安全性

### `.gitignore` 支持

设置 `use_gitignore=True` 时，会自动遵循项目的 `.gitignore` 规则：

```python
.merge_from_dir(
    relative_dir_name="src",
    use_gitignore=True,  # 自动排除 .env、*.log 等敏感文件
)
```

### 自动排除
- 自动排除以 `.` 开头的隐藏目录（如 `.git`、`.venv`）
- 支持手动排除特定目录和文件

## 📊 性能特性

- ✅ 高效的文件遍历和过滤
- ✅ 智能文本文件检测
- ✅ 支持大型项目（数千个文件）
- ✅ 链式调用，代码简洁优雅

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT License

## 🔗 相关链接

- **GitHub**: https://github.com/ydf0509/nb_ai_context
- **PyPI**: https://pypi.org/project/nb_ai_context/
- **Issues**: https://github.com/ydf0509/nb_ai_context/issues

## 💡 常见问题

### Q: 生成的 Markdown 文件太大怎么办？
A: 使用 `should_include_suffixes` 限制文件类型，或使用 `excluded_dir_name_list` 排除不重要的目录（如 `node_modules`、`venv`）。

### Q: 如何只生成代码结构，不包含源码？
A: 使用 `merge_from_files_with_metadata()` 方法，设置 `include_file_text=False`。

### Q: 支持哪些编程语言？
A: 所有文本文件都支持，但 Python 文件有特殊的 AST 元数据提取支持。



## 🌟 Star History

如果这个项目对你有帮助，请给个 Star ⭐️！

---

**nb_ai_context** - 让 AI 真正理解你的代码 🚀
````

## repomix-output.md

`````markdown
This file is a merged representation of the entire codebase, combining all repository files into a single document.
Generated by Repomix on: 2025-11-28 10:18:51

# File Summary

## Purpose:

This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.

## File Format:

The content is organized as follows:
1. This summary section
2. Repository information
3. Repository structure
4. Multiple file entries, each consisting of:
   a. A header with the file path (## File: path/to/file)
   b. The full contents of the file in a code block

## Usage Guidelines:

- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.

## Notes:

- Some files may have been excluded based on .gitignore rules and Repomix's
  configuration.
- Binary files are not included in this packed representation. Please refer to
  the Repository Structure section for a complete list of file paths, including
  binary files.

## Additional Information:

For more information about Repomix, visit: https://github.com/andersonby/python-repomix


# Repository Structure

```
.gitignore
examples
  AiMdGenerator_example.py
git_nb_ai_context.py
nb_ai_context
  ai_md_generator.py
  contrib
    about.md
    gen_github_proj_ai_md.py
  __init__.py
pub_pypi_nb_ai_context.py
pyproject.toml
README.md
tests
  ai_codes
    demo_init_extraction.py
    test_init_extraction.py
  ai_docs
    CHANGELOG_init_extraction.md
    complete_docstring_extraction.md
    init_extraction_feature.md
    UPDATE_SUMMARY.md
```

# Repository Files


## .gitignore

```text
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/

# pixi
#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
#   in the .venv directory. It is recommended not to include this directory in version control.
.pixi

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
#  you could uncomment the following to ignore the entire vscode folder
# .vscode/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Cursor
#  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
#  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
#  refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/


markdown_gen_files_git_ignore/
```

## examples/AiMdGenerator_example.py

```python
from nb_ai_context import AiMdGenerator


project_name = "nb_ai_context"
project_root = rf"D:\codes\{project_name}"

project_summary = f"""
- `{project_name}` is a powerful ai llm context gennerator library ,it is uesd for ai llm and rag
- `AiMdGenerator(...)` is the main class to create ai context for llm.
"""


(
    AiMdGenerator(
        rf"D:\codes\nb_ai_context\markdown_gen_files_git_ignore\ai_md_files\{project_name}_all_docs_and_codes.md"
    )
    .set_project_propery(project_name=project_name, project_root=project_root)
    .clear_text()
    .add_project_summary(
        project_summary=project_summary,
        most_core_source_code_file_list=[
            "nb_ai_context/__init__.py",
            "nb_ai_context/ai_md_generator.py",
            "nb_ai_context/contrib/gen_github_proj_ai_md.py",
        ],
    )
    .auto_merge_from_python_project_some_files()
    .show_textfile_info()
    .merge_from_dir(
        relative_dir_name='examples',
        use_gitignore=True,
        as_title=f"{project_name} examples",
        # 只包含 .py 和 .md 文件
        should_include_suffixes=[".py", ".md"],
        # 排除 __pycache__ 目录和特定的测试文件
        excluded_dir_name_list=[],
        include_ast_metadata=True,
    )
    .merge_from_dir(
        relative_dir_name=project_name,
        use_gitignore=True,
        as_title=f"{project_name} codes",
        # 只包含 .py 和 .md 文件
        should_include_suffixes=[".py", ".md"],
        # 排除 __pycache__ 目录和特定的测试文件
        excluded_dir_name_list=[],
        include_ast_metadata=True,
    )
    .show_textfile_info()
)
```

## git_nb_ai_context.py

```python
import subprocess

import time

def getstatusoutput(cmd):
    try:
        data = subprocess.check_output(cmd, shell=True, universal_newlines=True,
                                       stderr=subprocess.STDOUT, encoding='utf8')  # Must set encoding to utf8 to avoid errors.
        exitcode = 0
    except subprocess.CalledProcessError as ex:
        data = ex.output
        exitcode = ex.returncode
    if data[-1:] == '\n':
        data = data[:-1]
    return exitcode, data

def do_cmd(cmd_strx):
    print(f'exec {cmd_strx}')
    retx = getstatusoutput(cmd_strx)
    print(retx[0])

    print(retx[1], '\n')
    return retx

t0 = time.time()

do_cmd('git pull')

do_cmd('git diff')

do_cmd('git add .')

do_cmd('git commit -m commit')



do_cmd('git push origin')

# print(subprocess.getstatusoutput('git push github'))
print(f'spend_time {time.time() - t0}')


if __name__ == '__main__':

    time.sleep(100000)
```

## nb_ai_context/ai_md_generator.py

````python
import typing
import os
import fnmatch
import ast

from nb_path import NbPath

class AiMdGenerator(NbPath):
    """
    An extremely powerful context generator born for AI collaboration.

    This class is designed to revolutionize how developers interact with Large Language
    Models (LLMs). It intelligently merges multiple project source files into a single,
    well-structured, and context-rich Markdown file, providing the AI with a perfect
    and comprehensive project snapshot.

    The benefits for large AI models are immense:
    1.  **Provides a God's-eye View**: Through a file manifest, clear file boundaries,
        and relative paths, the AI can easily construct the project's overall
        architecture and understand file dependencies and relationships, rather than
        fumbling in the dark.
    2.  **Ensures Information Integrity and Accuracy**: The AI receives complete,
        unabridged source file content, avoiding the chaos, omissions, or context
        loss caused by manual copy-pasting. This enables it to provide more precise
        analysis and suggestions.
    3.  **Enhances Security**: The built-in `use_gitignore` feature is a critical
        security barrier. It automatically ignores files containing sensitive
        information (like API keys or database passwords) such as `.env` or local
        configs, allowing you to share code without fear of accidental leaks.

    Its core methods, `merge_from_files` and `merge_from_dir`, offer extreme
    flexibility. Combined with the elegant chainable calls of `nb_path`, creating a
    high-quality AI context is transformed from a tedious, error-prone manual task
    into a single, delightful line of code.

    Example:
        >>> # Imagine you want an AI to review your entire project
        >>> project_name = "my_project"
        >>> project_summary = '''
        ... This is an excellent Python project that demonstrates best practices.
        ... It includes comprehensive documentation and well-structured code.
        ... '''
        >>> 
        >>> (
        ...     AiMdGenerator("project_context_for_ai.md")
        ...     .set_project_propery(project_name=project_name, project_root="/path/to/your/project")
        ...     .clear_text()  # Clear the old file
        ...     .add_project_summary(
        ...         project_summary=project_summary,
        ...         # Extract metadata (without full source) from core files first
        ...         most_core_source_code_file_list=[
        ...             "src/main.py",
        ...             "src/api.py",
        ...             "src/models.py",
        ...         ]
        ...     )
        ...     .auto_merge_from_python_project_some_files()  # Auto-include README, setup.py, etc.
        ...     .merge_from_dir(
        ...         relative_dir_name="src", # The main source code directory
        ...         as_title="Project Source Code",
        ...         use_gitignore=True,  # Automatically use .gitignore rules
        ...         should_include_suffixes=[".py", ".md"], # Only include specified file types
        ...         include_ast_metadata=True,  # Include AST metadata for Python files
        ...     )
        ...     .merge_from_dir(
        ...         relative_dir_name="tests", # The tests directory
        ...         as_title="Project Tests",
        ...         use_gitignore=True,
        ...         should_include_suffixes=[".py"],
        ...         excluded_dir_name_list=["tests/temp_files"],
        ...         include_ast_metadata=True,
        ...     )
        ... )
    """

    """cn description
    一个极其强大的、为 AI 协作而生的上下文生成器。

    此类旨在彻底改变开发者与大语言模型（LLM）的交互方式。它能够智能地将多个项目源文件
    合并成一个结构清晰、上下文丰富的单一 Markdown 文件，从而为 AI 提供一个完美、全面的项目快照。

    对 AI 大模型的好处是巨大的：
    1.  **提供上帝视角**：通过文件清单、清晰的文件边界和相对路径，AI 能够轻松构建出项目的
        整体架构，理解文件间的依赖和引用关系，而不是盲人摸象。
    2.  **确保信息的完整与准确**：AI 得到的是未经删减的、完整的源文件内容，避免了因手动
        复制粘贴导致的格式混乱、内容遗漏或上下文缺失，从而能给出更精准的分析和建议。
    3.  **提升安全性**：内置的 `use_gitignore` 功能是一道关键的安全屏障。它能自动忽略
        `.env`、本地配置等包含敏感信息（如 API 密钥、数据库密码）的文件，让你在分享代码
        时无需担心意外泄露秘密。

    其核心方法 `merge_from_files` 和 `merge_from_dir` 提供了极高的灵活性，结合 `nb_path`
    优雅的链式调用，使得创建一个高质量的 AI 上下文从繁琐、易错的手工劳动，变成了一行
    赏心悦目的代码。

    """

    suffix__lang_map = {
        ".py": "python",
        ".md": "markdown",
        ".txt": "text",
        ".json": "json",
        ".yaml": "yaml",
        ".yml": "yaml",
        ".xml": "xml",
        ".html": "html",
        ".css": "css",
        ".js": "javascript",
        ".ts": "typescript",
        ".jsx": "javascript",
        ".tsx": "typescript",
        ".vue": "vue",
        ".php": "php",
        ".java": "java",
        ".c": "c",
        ".cpp": "cpp",
        ".h": "h",
        ".hpp": "hpp",
        ".cs": "csharp",
        ".vb": "vb",
        ".sql": "sql",
        ".bat": "batch",
        ".sh": "shell",
        ".ps1": "powershell",
        ".psm1": "powershell",
        ".psd1": "powershell",
        ".pssc": "powershell",
        ".psscx": "powershell",
    }

    def set_project_propery(self, project_name: str, project_root: typing.Union[os.PathLike, str] ) -> "AiMdGenerator":
        """Sets the project name for the current markdown file."""
        self.project_name = project_name
        self.project_root = project_root
        return self
    
    def _check_project_name(self) -> "AiMdGenerator":
        """Checks if the project name is set."""
        if not hasattr(self, 'project_name'):
            raise ValueError("Project name is not set. Please call set_project_name() first.")
        return self

    def add_project_summary(
        self, 
        project_summary: str, 
        most_core_source_code_file_list: typing.List[str] = None,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
        """
        Adds a project summary to the current markdown file.
        
        Args:
            project_summary: 项目概述文本
            project_root: 项目根目录（如果提供了 most_core_source_code_file_list）
            most_core_source_code_file_list: 最核心的源码文件列表（相对路径）
                                             只提取这些文件的 AST 元数据，不包含完整源码
        
        Example:
            >>> (
            ...     AiMdGenerator("output.md")
            ...     .set_project_name("my_project")
            ...     .clear_text()
            ...     .add_project_summary(
            ...         project_summary="这是一个优秀的项目...",
            ...         project_root="/path/to/project",
            ...         most_core_source_code_file_list=["src/main.py", "src/api.py"],
            ...     )
            ... )
        """
        self._check_project_name()
        project_root =  project_root or self.project_root 
        str_list = [f"# markdown content namespace: {self.project_name} project summary \n\n"]
        str_list.append(project_summary)
        # str_list.append("\n---\n\n")
        
        # 如果提供了核心文件列表，提取它们的元数据（不包含源码）
        if most_core_source_code_file_list and project_root:
            # str_list.append("\n---\n\n")
            str_list.append("\n## 📋 Core Source Files Metadata (Entry Points)\n\n")
            str_list.append("以下是项目最核心的入口文件的结构化元数据，帮助快速理解项目架构：\n\n")
            most_core_source_code_file_list_str = ''
            for relative_file_name in most_core_source_code_file_list:
                most_core_source_code_file_list_str += f"- `{relative_file_name}`\n"
            str_list.append(f'\n### the project {self.project_name} most core source code files as follows: \n{most_core_source_code_file_list_str}')
            
            
            project_root_path = NbPath(project_root).resolve()
            
            for relative_file_name in most_core_source_code_file_list:
                file = (project_root_path / relative_file_name).resolve()
                if not file.exists():
                    raise FileNotFoundError(f"File {file} not found.")

                if file.is_file() and file.is_text() and file.suffix == ".py":
                    relative_file_name_posix = file.relative_to(project_root_path).as_posix()
                    
                    self.logger.info(f"提取核心文件元数据（无源码）: {relative_file_name_posix}")
                    
                    # 只提取元数据，不包含源码
                    metadata = self._parse_python_file_ast(file)
                    metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                    str_list.append(metadata_md)
                    str_list.append("\n")
        
        self.append_text('\n'.join(str_list))
        return self

    def _generate_markdown_header(self, as_title: str, file_text_list: list) -> list:
        """生成包含文件树和文件列表的 Markdown 头部"""
        str_list = [f"# markdown content namespace: {as_title} \n\n"]

        # 1. 生成文件树
        str_list.append("## File Tree\n\n")
        str_list.append("```\n")
        tree = {}
        sorted_paths = sorted([item[1] for item in file_text_list])
        for path in sorted_paths:
            parts = path.split('/')
            current_level = tree
            for part in parts:
                if part not in current_level:
                    current_level[part] = {}
                current_level = current_level[part]

        def format_tree(node, prefix=""):
            lines = []
            entries = sorted(node.keys())
            for i, entry in enumerate(entries):
                connector = "├── " if i < len(entries) - 1 else "└── "
                lines.append(f"{prefix}{connector}{entry}")
                if node[entry]:
                    extension = "│   " if i < len(entries) - 1 else "    "
                    lines.extend(format_tree(node[entry], prefix + extension))
            return lines

        str_list.extend(format_tree(tree))
        str_list.append("\n```\n\n---\n\n")

        # 2. 生成文件列表
        str_list.append("## Included Files\n\n")
        for _, relative_file_name_posix, _, _ in file_text_list:
            str_list.append(f"- `{relative_file_name_posix}`\n")
        str_list.append("\n---\n\n")

        return str_list

    def auto_merge_from_python_project_some_files(self, project_root: typing.Union[os.PathLike, str] = None) -> 'AiMdGenerator':
        """自动合并项目根目录下的 readme.md 或者ReADME.md 以及setup.py 和 pyproject.toml ，如果有就添加"""
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_merge_list = []
        
        # 定义要查找的根目录文件
        root_files_to_check = [
            "README.md",
            # "readme.md",
            "setup.py",
            "pyproject.toml"
        ]
        
        # 检查每个文件是否存在，如果存在则添加到合并列表中
        project_root_path = NbPath(project_root).resolve()
        for filename in root_files_to_check:
            file_path = project_root_path / filename
            if file_path.is_file() and file_path.is_text():
                file_merge_list.append(filename)
                
        self.merge_from_files(file_merge_list, f"{self.project_name} Project Root Dir Some Files",project_root, )
        return self

    def merge_from_files(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
    ) -> "AiMdGenerator":
        """Merges the content of the given files into the current markdown file.
        the current markdown file will be used to upload to ai model for code review and learning.
        """
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_text_list = []
        project_root_path = NbPath(project_root).resolve()
        for relative_file_name in relative_file_name_list:
            file = (project_root_path / relative_file_name).resolve()
            if not file.exists():
                raise FileNotFoundError(f"File {file} not found.")
            if file.is_file() and file.is_text():
                relative_file_name_posix = file.relative_to(
                    project_root_path
                ).as_posix()
                try:
                    text = file.read_text()
                except Exception as e:
                    self.logger.error(f"Error reading file {file}: {e}")
                    text = ""
                file_text_list.append(
                    [file, relative_file_name_posix, file.suffix, text]
                )
                self.logger.debug(f"need merged file: {file}")
            else:
                raise ValueError(f"File {file} is not a text file.")
        str_list = []
        if file_text_list:
            # 调用新函数生成头部
            str_list.extend(self._generate_markdown_header(as_title, file_text_list))


        for file, relative_file_name_posix, suffix, text in file_text_list:
            # 2. Remove the debug print statement.
            # print(f'file: {file}, relative_file_name_posix: {relative_file_name_posix}, suffix: {suffix}, text: {text}')
            str_list.append(f"--- **start of file: {relative_file_name_posix}** --- \n")
            # 3. Handle .md files separately to ensure their content is rendered correctly.
            #    Other file types are wrapped in code blocks.
            if suffix == ".md":
                str_list.append(text + "\n")
            else:
                lang = self.suffix__lang_map.get(suffix, "text")
                str_list.append(f"``{lang}\n{text}\n```\n")

            str_list.append(f"--- **end of file: {relative_file_name_posix}** --- \n")
            str_list.append("---\n\n")

        # with self.open(mode="a", encoding="utf-8") as f:
        #     f.write("\n".join(str_list))
        self.append_text('\n'.join(str_list))
        self.ensure_utf8_bom()
        return self
        
        
    def merge_from_dir(
        self,
        relative_dir_name: str,
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        should_include_suffixes: typing.List[str] = [],
        excluded_dir_name_list: typing.List[str] = [],
        excluded_file_name_list: typing.List[str] = [],
        use_gitignore: bool = True,
        dry_run: bool = False,
        include_ast_metadata: bool = True,
    ) -> "AiMdGenerator":
        """Merges the content of the given directory into the current file."""
        project_root =  project_root or self.project_root
        project_root_path = NbPath(project_root).resolve()
        target_dir_path = (project_root_path / relative_dir_name).resolve()
        if not target_dir_path.exists():
            raise FileNotFoundError(f"Directory {target_dir_path} not found.")

        # Use sets for efficient lookups
        excluded_dir_paths = {
            (project_root_path / d).resolve() for d in excluded_dir_name_list
        }
        excluded_file_paths = {
            (project_root_path / f).resolve() for f in excluded_file_name_list
        }

        ignore_patterns = []
        if use_gitignore:
            try:
                gitignore_path = project_root_path.find_git_root() / ".gitignore"
                if gitignore_path.is_file():
                    self.logger.debug(f"Using .gitignore rules from: {gitignore_path}")
                    with open(gitignore_path, "r", encoding="utf-8") as f:
                        for line in f:
                            line = line.strip()
                            if line and not line.startswith("#"):
                                # Gitignore patterns always use forward slashes.
                                # We will compare against the posix version of the relative path
                                # for cross-platform reliability.
                                ignore_patterns.append(line)
            except FileNotFoundError:
                self.logger.warning("use_gitignore is True, but no .git/ or .gitignore file found.")

        relative_paths_to_include = []
        for path_obj in target_dir_path.rglob("*"):
            # Automatically exclude directories starting with a dot at the project root
            try:
                first_part = path_obj.relative_to(project_root_path).parts[0]
                if first_part.startswith('.'):
                    continue
            except (ValueError, IndexError):
                continue
            # Check if the path is within any of the excluded directories
            is_in_excluded_dir = any(
                path_obj == excluded_dir or excluded_dir in path_obj.parents
                for excluded_dir in excluded_dir_paths
            )
            if is_in_excluded_dir:
                continue

            # Check if the path matches any gitignore patterns.
            # Convert the relative path to a posix-style string for reliable matching.
            relative_to_root = path_obj.relative_to(project_root_path)
            relative_posix_path = relative_to_root.as_posix()
            # Use fnmatch for robust gitignore-style pattern matching.
            is_ignored = False
            for p in ignore_patterns:
                # If a pattern does not contain a slash, it matches in any directory.
                # e.g., 'test_git_ignore1.py' should match 'nb_path/example_dir/test_git_ignore1.py'
                if '/' not in p.strip('/'):
                    p_glob = f"**/{p.strip('/')}"
                else:
                    p_glob = p
                if fnmatch.fnmatch(relative_posix_path, p_glob) or fnmatch.fnmatch(relative_posix_path, p):
                    is_ignored = True
                    break
            if is_ignored:
                self.logger.debug(f"Ignoring {relative_to_root} due to .gitignore rule.")
                continue

            if path_obj.is_file():
                # Check if the file itself is excluded
                if path_obj.resolve() in excluded_file_paths:
                    continue
                # Check if the file is a text file
                if not path_obj.is_text():
                    continue
                # Check if the suffix is in the inclusion list (if the list is not empty)
                if (
                    should_include_suffixes
                    and path_obj.suffix not in should_include_suffixes
                ):
                    continue
                relative_paths_to_include.append(
                    path_obj.relative_to(project_root_path).as_posix()
                )

        if dry_run:
            print("\n--- [DRY RUN] AiMdGenerator Execution Plan ---")
            print(f"\n✅ {len(relative_paths_to_include)} files would be INCLUDED in '{self.name}':")
            for p in sorted(relative_paths_to_include):
                print(f"  - {p}")
            print("\n--- End of DRY RUN ---")
            return self
        else:
            # 使用带元数据的方法
            return self.merge_from_files_with_metadata(
                 
                relative_paths_to_include, 
                as_title,
                project_root=project_root,
                include_ast_metadata=include_ast_metadata
            )

    def merge_dir_of_package_examples(self):
        """合并包的examples目录到当前markdown文件"""
        self._check_project_name()
        self.merge_from_dir(
            relative_dir_name=self.project_name,
            use_gitignore=True,
            as_title=f"{self.project_name} codes",
            # 只包含 .py 和 .md 文件
            should_include_suffixes=[".py", ".md"],
            # 排除 __pycache__ 目录和特定的测试文件
            excluded_dir_name_list=[],
        ).merge_from_dir(
            relative_dir_name="examples",
            use_gitignore=True,
            as_title=f"{self.project_name} examples",
            # 只包含 .py 和 .md 文件
            should_include_suffixes=[".py", ".md"],
            # 排除 __pycache__ 目录和特定的测试文件
            excluded_dir_name_list=[],
            include_ast_metadata=False,
        )
        return self

    def _ast_to_source(self, node) -> str:
        """将 AST 节点转换为源代码字符串，兼容 Python 3.7+"""
        if node is None:
            return ""
        try:
            # Python 3.9+ 支持 ast.unparse
            if hasattr(ast, 'unparse'):
                return ast.unparse(node)
            else:
                # Python 3.7/3.8 的回退方案
                # 尝试使用 astor
                try:
                    import astor
                    return astor.to_source(node).strip()
                except ImportError:
                    pass
                
                # 简单的手工处理常见情况
                if isinstance(node, ast.Name):
                    return node.id
                elif isinstance(node, ast.Constant):
                    return repr(node.value)
                elif isinstance(node, ast.Attribute):
                    value = self._ast_to_source(node.value)
                    return f"{value}.{node.attr}"
                elif isinstance(node, ast.Subscript):
                    value = self._ast_to_source(node.value)
                    slice_val = self._ast_to_source(node.slice)
                    return f"{value}[{slice_val}]"
                elif isinstance(node, (ast.List, ast.Tuple)):
                    elts = [self._ast_to_source(e) for e in node.elts]
                    if isinstance(node, ast.List):
                        return f"[{', '.join(elts)}]"
                    else:
                        return f"({', '.join(elts)})"
                else:
                    # 对于复杂类型，返回类型名称
                    return node.__class__.__name__
        except Exception:
            return ""

    def _parse_type_annotation(self, annotation) -> str:
        """解析类型注解，返回字符串表示"""
        return self._ast_to_source(annotation)

    def _extract_function_metadata(self, node: typing.Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> dict:
        """提取函数/方法的元数据"""
        metadata = {
            "name": node.name,
            "type": "async_function" if isinstance(node, ast.AsyncFunctionDef) else "function",
            "lineno": node.lineno,
            "docstring": ast.get_docstring(node) or "",
            "parameters": [],
            "return_type": self._parse_type_annotation(node.returns),
            "decorators": [self._ast_to_source(dec) for dec in node.decorator_list],
            "is_public": not node.name.startswith("_"),
        }

        # 提取参数信息
        for arg in node.args.args:
            param_info = {
                "name": arg.arg,
                "type": self._parse_type_annotation(arg.annotation),
                "default": None,
            }
            metadata["parameters"].append(param_info)

        # 处理默认参数
        defaults = node.args.defaults
        if defaults:
            # 默认值从后往前对应参数
            num_defaults = len(defaults)
            for i, default in enumerate(defaults):
                param_idx = len(metadata["parameters"]) - num_defaults + i
                if param_idx >= 0:
                    try:
                        metadata["parameters"][param_idx]["default"] = self._ast_to_source(default)
                    except Exception:
                        metadata["parameters"][param_idx]["default"] = "<complex_default>"

        # 处理 *args 和 **kwargs
        if node.args.vararg:
            metadata["parameters"].append({
                "name": f"*{node.args.vararg.arg}",
                "type": self._parse_type_annotation(node.args.vararg.annotation),
                "default": None,
            })
        if node.args.kwarg:
            metadata["parameters"].append({
                "name": f"**{node.args.kwarg.arg}",
                "type": self._parse_type_annotation(node.args.kwarg.annotation),
                "default": None,
            })

        return metadata

    def _extract_class_metadata(self, node: ast.ClassDef) -> dict:
        """提取类的元数据"""
        metadata = {
            "name": node.name,
            "type": "class",
            "lineno": node.lineno,
            "docstring": ast.get_docstring(node) or "",
            "bases": [self._ast_to_source(base) for base in node.bases],
            "decorators": [self._ast_to_source(dec) for dec in node.decorator_list],
            "methods": [],
            "properties": [],
            "class_variables": [],
            "is_public": not node.name.startswith("_"),
        }

        # 遍历类的成员
        for item in node.body:
            if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
                method_info = self._extract_function_metadata(item)
                
                # 检查是否是 property
                is_property = any("property" in dec for dec in method_info["decorators"])
                if is_property:
                    metadata["properties"].append(method_info)
                else:
                    metadata["methods"].append(method_info)
            
            elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
                # 类变量（带类型注解）
                value_str = ""
                if item.value:
                    try:
                        value_str = self._ast_to_source(item.value)
                        # 限制值的长度
                        if len(value_str) > 50:
                            value_str = value_str[:50] + "..."
                    except Exception:
                        value_str = "<value>"
                
                metadata["class_variables"].append({
                    "name": item.target.id,
                    "type": self._parse_type_annotation(item.annotation),
                    "value": value_str,
                    "lineno": item.lineno,
                })
            elif isinstance(item, ast.Assign):
                # 类变量（无类型注解）
                for target in item.targets:
                    if isinstance(target, ast.Name):
                        # 提取值
                        value_str = ""
                        if item.value:
                            try:
                                value_str = self._ast_to_source(item.value)
                                # 限制值的长度
                                if len(value_str) > 50:
                                    value_str = value_str[:50] + "..."
                            except Exception:
                                value_str = "<value>"
                        
                        metadata["class_variables"].append({
                            "name": target.id,
                            "type": "",
                            "value": value_str,
                            "lineno": item.lineno,
                        })

        return metadata

    def _parse_python_file_ast(self, file_path: NbPath) -> dict:
        """解析 Python 文件的 AST，提取所有元数据"""
        try:
            source_code = file_path.read_text(encoding="utf-8")
            # 移除 BOM (Byte Order Mark) 字符，如果存在的话
            # BOM 是 U+FEFF，在 UTF-8 编码中是 \ufeff
            if source_code.startswith('\ufeff'):
                source_code = source_code[1:]
                self.logger.debug(f"Removed BOM from file: {file_path}")
            tree = ast.parse(source_code, filename=str(file_path))
        except Exception as e:
            self.logger.error(f"Failed to parse Python file {file_path}: {e}")
            return {
                "error": str(e),
                "classes": [],
                "functions": [],
                "imports": [],
                "module_docstring": "",
            }

        metadata = {
            "file": str(file_path),
            "module_docstring": ast.get_docstring(tree) or "",
            "classes": [],
            "functions": [],
            "imports": [],
            "constants": [],
        }

        # 遍历模块级别的节点
        for node in ast.walk(tree):
            # 只处理模块级别的定义（通过检查父节点）
            if isinstance(node, ast.ClassDef):
                # 检查是否是顶级类（不在其他类内部）
                parent = None
                for potential_parent in ast.walk(tree):
                    if isinstance(potential_parent, ast.ClassDef) and node in ast.walk(potential_parent) and node != potential_parent:
                        parent = potential_parent
                        break
                if parent is None:  # 顶级类
                    metadata["classes"].append(self._extract_class_metadata(node))

            elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                # 检查是否是顶级函数（不在类内部）
                parent_class = None
                for potential_parent in ast.walk(tree):
                    if isinstance(potential_parent, ast.ClassDef) and node in ast.walk(potential_parent):
                        parent_class = potential_parent
                        break
                if parent_class is None:  # 顶级函数
                    metadata["functions"].append(self._extract_function_metadata(node))

            elif isinstance(node, (ast.Import, ast.ImportFrom)):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        metadata["imports"].append({
                            "type": "import",
                            "module": alias.name,
                            "alias": alias.asname,
                            "lineno": node.lineno,
                        })
                else:  # ImportFrom
                    module = node.module or ""
                    for alias in node.names:
                        metadata["imports"].append({
                            "type": "from_import",
                            "module": module,
                            "name": alias.name,
                            "alias": alias.asname,
                            "lineno": node.lineno,
                        })

        return metadata

    def _format_py_metadata_as_markdown(self, metadata: dict, relative_file_name: str) -> str:
        """将 Python 文件元数据格式化为 Markdown"""
        lines = []
        lines.append(f"\n### 📄 Python File Metadata: `{relative_file_name}`\n")

        # 模块文档字符串
        if metadata.get("module_docstring"):
            lines.append("#### 📝 Module Docstring\n")
            lines.append("```")
            lines.append(metadata["module_docstring"])
            lines.append("```\n")

        # 导入信息
        if metadata.get("imports"):
            lines.append("#### 📦 Imports\n")
            for imp in metadata["imports"]:  # 显示所有 imports，不再限制数量
                if imp["type"] == "import":
                    alias_str = f" as {imp['alias']}" if imp['alias'] else ""
                    lines.append(f"- `import {imp['module']}{alias_str}`")
                else:
                    alias_str = f" as {imp['alias']}" if imp['alias'] else ""
                    lines.append(f"- `from {imp['module']} import {imp['name']}{alias_str}`")
            lines.append("")

        # 类信息
        if metadata.get("classes"):
            lines.append(f"#### 🏛️ Classes ({len(metadata['classes'])})\n")
            for cls in metadata["classes"]:
                # 只显示公有类或所有类（根据需要）
                class_header = f"##### 📌 `class {cls['name']}"
                if cls["bases"]:
                    class_header += f"({', '.join(cls['bases'])})"
                class_header += "`"
                lines.append(class_header)
                lines.append(f"*Line: {cls['lineno']}*\n")
                
                if cls["docstring"]:
                    # 显示完整的类文档字符串
                    docstring_lines = cls["docstring"].split("\n")
                    lines.append("**Docstring:**")
                    lines.append("```")
                    lines.extend(docstring_lines)
                    lines.append("```\n")

                # 首先单独显示 __init__ 方法（非常重要）
                init_method = None
                for method in cls["methods"]:
                    if method["name"] == "__init__":
                        init_method = method
                        break
                
                if init_method:
                    lines.append("**🔧 Constructor (`__init__`):**")
                    params_str = self._format_parameters(init_method["parameters"])
                    lines.append(f"- `def __init__({params_str})`")
                    
                    # 显示 __init__ 的完整文档字符串
                    if init_method["docstring"]:
                        lines.append("  - **Docstring:**")
                        lines.append("  ```")
                        for doc_line in init_method["docstring"].split("\n"):
                            lines.append(f"  {doc_line}")
                        lines.append("  ```")
                    
                    # 显示每个参数的详细信息
                    if init_method["parameters"]:
                        lines.append("  - **Parameters:**")
                        for param in init_method["parameters"]:
                            param_name = param["name"]
                            param_type = f": {param['type']}" if param["type"] else ""
                            param_default = f" = {param['default']}" if param["default"] else ""
                            lines.append(f"    - `{param_name}{param_type}{param_default}`")
                    lines.append("")

                # 公有方法（排除 __init__）
                public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
                if public_methods:
                    lines.append(f"**Public Methods ({len(public_methods)}):**")
                    for method in public_methods:
                        params_str = self._format_parameters(method["parameters"])
                        return_str = f" -> {method['return_type']}" if method["return_type"] else ""
                        async_str = "async " if method["type"] == "async_function" else ""
                        
                        decorators_str = ""
                        if method["decorators"]:
                            decorators_str = " " + " ".join([f"`{d}`" for d in method["decorators"]])
                        
                        lines.append(f"- `{async_str}def {method['name']}({params_str}){return_str}`{decorators_str}")
                        
                        # 显示完整的文档字符串
                        if method["docstring"]:
                            # 如果文档字符串只有一行，用简短格式显示
                            docstring_lines = method["docstring"].split("\n")
                            if len(docstring_lines) == 1:
                                lines.append(f"  - *{method['docstring'].strip()}*")
                            else:
                                # 多行文档字符串，用代码块格式显示
                                lines.append("  - **Docstring:**")
                                lines.append("  ```")
                                for doc_line in docstring_lines:
                                    lines.append(f"  {doc_line}")
                                lines.append("  ```")
                    lines.append("")

                # Properties
                if cls["properties"]:
                    lines.append(f"**Properties ({len(cls['properties'])}):**")
                    for prop in cls["properties"]:
                        return_str = f" -> {prop['return_type']}" if prop["return_type"] else ""
                        lines.append(f"- `@property {prop['name']}{return_str}`")
                    lines.append("")

                # 类变量
                if cls["class_variables"]:
                    lines.append(f"**Class Variables ({len(cls['class_variables'])}):**")
                    for var in cls["class_variables"]:
                        type_str = f": {var['type']}" if var["type"] else ""
                        value_str = f" = {var['value']}" if var.get("value") else ""
                        lines.append(f"- `{var['name']}{type_str}{value_str}`")
                    lines.append("")

        # 顶级函数
        if metadata.get("functions"):
            public_functions = [f for f in metadata["functions"] if f["is_public"]]
            if public_functions:
                lines.append(f"#### 🔧 Public Functions ({len(public_functions)})\n")
                for func in public_functions:
                    params_str = self._format_parameters(func["parameters"])
                    return_str = f" -> {func['return_type']}" if func["return_type"] else ""
                    async_str = "async " if func["type"] == "async_function" else ""
                    
                    decorators_str = ""
                    if func["decorators"]:
                        decorators_str = " " + " ".join([f"`{d}`" for d in func["decorators"]])
                    
                    lines.append(f"- `{async_str}def {func['name']}({params_str}){return_str}`{decorators_str}")
                    lines.append(f"  - *Line: {func['lineno']}*")
                    
                    if func["docstring"]:
                        # 如果文档字符串只有一行，用简短格式显示
                        docstring_lines = func["docstring"].split("\n")
                        if len(docstring_lines) == 1:
                            lines.append(f"  - *{func['docstring'].strip()}*")
                        else:
                            # 多行文档字符串，用代码块格式显示
                            lines.append("  - **Docstring:**")
                            lines.append("  ```")
                            for doc_line in docstring_lines:
                                lines.append(f"  {doc_line}")
                            lines.append("  ```")
                    lines.append("")

        lines.append("\n---\n")
        return "\n".join(lines)

    def _format_parameters(self, parameters: list) -> str:
        """格式化函数参数列表"""
        param_strs = []
        for param in parameters:
            param_str = param["name"]
            if param["type"]:
                param_str += f": {param['type']}"
            if param["default"]:
                param_str += f" = {param['default']}"
            param_strs.append(param_str)
        return ", ".join(param_strs)

    def merge_from_files_with_metadata(
        self,
        relative_file_name_list: typing.List[str],
        as_title: str,
        project_root: typing.Union[os.PathLike, str] = None,
        include_ast_metadata: bool = True,
        include_file_text: bool = True,
    ) -> "AiMdGenerator":
        """
        合并文件内容到 Markdown，对于 Python 文件会额外生成 AST 元数据
        
        Args:
            project_root: 项目根目录
            relative_file_name_list: 相对文件路径列表
            as_title: 标题
            include_ast_metadata: 是否包含 AST 元数据（仅对 .py 文件）
            include_file_text: 是否包含完整文件源码（False 时只显示元数据）
        """
        self._check_project_name()
        project_root =  project_root or self.project_root
        file_text_list = []
        project_root_path = NbPath(project_root).resolve()
        
        for relative_file_name in relative_file_name_list:
            file = (project_root_path / relative_file_name).resolve()
            if not file.exists():
                raise FileNotFoundError(f"File {file} not found.")
            if file.is_file() and file.is_text():
                relative_file_name_posix = file.relative_to(project_root_path).as_posix()
                try:
                    text = file.read_text()
                except Exception as e:
                    self.logger.error(f"Error reading file {file}: {e}")
                    text = ""
                
                file_text_list.append([file, relative_file_name_posix, file.suffix, text])
                self.logger.debug(f"need merged file: {file}")
            else:
                raise ValueError(f"File {file} is not a text file.")
        
        str_list = []
        if file_text_list:
            str_list.extend(self._generate_markdown_header(as_title, file_text_list))

        for file, relative_file_name_posix, suffix, text in file_text_list:
            # 如果不包含文件内容，只输出元数据（仅对 Python 文件）
            if not include_file_text:
                if suffix == ".py" and include_ast_metadata:
                    # 只显示元数据，不显示源码
                    metadata = self._parse_python_file_ast(file)
                    metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                    str_list.append(metadata_md)
                    str_list.append("\n")
                # 非 Python 文件跳过
                continue
            
            # 正常流程：包含文件内容
            str_list.append(f"--- **start of file: {relative_file_name_posix}** --- \n")
            
            # 对于 Python 文件，添加 AST 元数据
            if suffix == ".py" and include_ast_metadata:
                metadata = self._parse_python_file_ast(file)
                metadata_md = self._format_py_metadata_as_markdown(metadata, relative_file_name_posix)
                str_list.append(metadata_md)
            
            # 添加完整的文件内容
            if suffix == ".md":
                str_list.append(text + "\n")
            else:
                lang = self.suffix__lang_map.get(suffix, "text")
                str_list.append(f"```{lang}\n{text}\n```\n")

            str_list.append(f"--- **end of file: {relative_file_name_posix}** --- \n")
            str_list.append("---\n\n")

        self.append_text('\n'.join(str_list))
        self.ensure_utf8_bom()
        return self
````

## nb_ai_context/contrib/about.md

```markdown
some contribution about nb_path
```

## nb_ai_context/contrib/gen_github_proj_ai_md.py

```python
"""
Generate tutorials and code from any GitHub project into a markdown file for AI context

Download GitHub project zip file
https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
https://codeload.github.com/fastapi/sqlmodel/zip/refs/tags/0.0.25

Unzip the file
Generate markdown file
"""

import typing
from nb_path import NbPath
from nb_ai_context import AiMdGenerator
import re


def extract_repo_name(github_url):
    # Match GitHub codeload URL pattern
    pattern = r"https://codeload\.github\.com/[^/]+/([^/]+)"
    match = re.search(pattern, github_url)
    if match:
        return match.group(1)
    return None


def gen_github_proj_docs_and_codes_ai_md(
    github_zip_url: str,
    output_md_path: str,
    docs_dir_name: str,
    codes_dir_name: str,
    readme_file: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    Generate a Markdown document containing README, tutorials, and source code from a GitHub repository URL

    Args:
        github_zip_url: GitHub repository zip download link
                       Example: https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main
        output_md_path: Output Markdown file path
        readme_file: README file name, default is "README.md"
        docs_dir_name: Documentation directory name, e.g. "docs"
        codes_dir_name: Source code directory name, e.g. "sqlmodel"
        should_include_suffixes: List of file extensions to include,
                                By default includes ['.py', '.java', '.go', '.md', '.html']
    """
    project_name = extract_repo_name(github_zip_url)
    if should_include_suffixes is None:
        should_include_suffixes = [".py", ".java", ".go", ".md", ".html"]

    with NbPath.tempdir() as temp_dir:
        # Download zip file
        zip_file = temp_dir / "repo.zip"
        zip_file.download_from_url(github_zip_url)

        # Unzip the file
        unzip_dir = zip_file.unzip_to(temp_dir / "unzip")
        project_root = list(unzip_dir.iterdir())[
            0
        ]  # Get the project root directory after extraction

        print(f"tempdir github project_root: {project_root}")

        # Create AiMdGenerator instance and chain calls

        md_generator = (
            AiMdGenerator(output_md_path)
            .set_project_propery(project_name=project_name, project_root=project_root)
            .clear_text()
            .merge_from_files(
                relative_file_name_list=[readme_file],
                as_title=f"{project_name}  README",
            )
            .merge_from_dir(
                relative_dir_name=docs_dir_name,
                as_title=f"{project_name}  Documentation",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .merge_from_dir(
                relative_dir_name=codes_dir_name,
                as_title=f"{project_name}  Source Code",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .show_textfile_info()
        )

        return md_generator


def gen_github_proj_all_dirs_ai_md(
    github_zip_url: str,
    output_md_path: str,
    should_include_suffixes: list = None,
    excluded_dir_name_list: typing.List[str] = [],
    excluded_file_name_list: typing.List[str] = [],
):
    """
    生成github的项目根目录下所有文件到markdown，
    """
    project_name = extract_repo_name(github_zip_url)
    if should_include_suffixes is None:
        should_include_suffixes = [".py", ".java", ".go", ".md", ".html"]

    with NbPath.tempdir() as temp_dir:
        # Download zip file
        zip_file = temp_dir / "repo.zip"
        zip_file.download_from_url(github_zip_url)

        # Unzip the file
        unzip_dir = zip_file.unzip_to(temp_dir / "unzip")
        project_root = list(unzip_dir.iterdir())[
            0
        ]  # Get the project root directory after extraction

        print(f"tempdir github project_root: {project_root}")

        # Create AiMdGenerator instance and chain calls

        md_generator = (
            AiMdGenerator(output_md_path)
            .set_project_propery(project_name=project_name, project_root=project_root)
            .clear_text()
            .merge_from_dir(
                relative_dir_name=".",
                as_title=f"{project_name}  all files",
                should_include_suffixes=should_include_suffixes,
                use_gitignore=False,
                excluded_dir_name_list=excluded_dir_name_list,
                excluded_file_name_list=excluded_file_name_list,
            )
            .show_textfile_info()
        )

        return md_generator


if __name__ == "__main__":
    # Example: Generate documentation for sqlmodel project
    gen_github_proj_docs_and_codes_ai_md(
        github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
        output_md_path=NbPath(
            r"D:\codes\nb_ai_context\markdown_gen_files_git_ignore\ai_md_files\other_peoples"
        )
        .joinpath("sqlmodel_all_docs_and_codes.md")
        .ensure_parent(),
        readme_file="README.md",
        docs_dir_name="docs",
        codes_dir_name="sqlmodel",
        should_include_suffixes=[
            ".py",
            ".md",
        ],
    )

    gen_github_proj_all_dirs_ai_md(
        github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
        output_md_path=NbPath(
            r"D:\codes\nb_ai_context\markdown_gen_files_git_ignore\ai_md_files\other_peoples"
        )
        .joinpath("sqlmodel_all_dirs.md")
        .ensure_parent(),
    )
```

## nb_ai_context/__init__.py

```python
from nb_path import NbPath
from .ai_md_generator import AiMdGenerator

from .contrib.gen_github_proj_ai_md import gen_github_proj_docs_and_codes_ai_md,gen_github_proj_all_dirs_ai_md
```

## pub_pypi_nb_ai_context.py

```python
import os
import sys
import shutil
import time
import git_nb_ai_context

# sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

t_start = time.time()
os.system("python -m build")
t_end = time.time()
print(f"build time: {t_end - t_start}")

t_start = time.time()
os.system("twine upload dist/*")
t_end = time.time()
print(f"upload time: {t_end - t_start}")

if os.path.exists("dist"):
    shutil.rmtree("dist")


print(time.strftime("%Y-%m-%d %H:%M:%S"))

time.sleep(1000000)
```

## pyproject.toml

```text
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "nb_ai_context"
version = "1.5"
description = "非常强力的对任意it项目生成ai上下文单个markdown文档，丢给大模型或者知识库，非常强力，大模型幻觉大幅度减少。尤其是对python项目还有专门的ast语法树解析"
keywords = ["ai", "ai context", "llm", "rag", "python","ast","ast metadata","ai context generator","markdown"]
authors = [
    {name = "ydf0509", email = "ydf0509@xxx.com"},
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
]
dependencies = [
    "typing_extensions>=3.7; python_version < '3.8'",
    "nb_path",
    "nb_log",
]

[project.optional-dependencies]
test = [
    "pytest>=6.0",
    "pytest-asyncio>=0.14",
]
dev = [
    "black>=21.0",
    "flake8>=3.8",
    "mypy>=0.812",
]

[project.urls]
Homepage = "https://github.com/ydf0509/nb_ai_context"
Repository = "https://github.com/ydf0509/nb_ai_context"
Issues = "https://github.com/ydf0509/nb_ai_context/issues"

[tool.setuptools.packages.find]
where = ["."]
include = ["nb_ai_context*"]

[tool.setuptools.package-data]
nb_ai_context = ["py.typed", "contrib/README.md"]
```

## README.md

````markdown
# nb_ai_context

[![PyPI version](https://img.shields.io/pypi/v/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![Python versions](https://img.shields.io/pypi/pyversions/nb_ai_context.svg)](https://pypi.org/project/nb_ai_context/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

🚀 **极其强大的 AI 上下文生成器** - 将任意 IT 项目合并生成为1个结构化 Markdown 文档，方便一次上传给ai大模型或者rag知识库。  

**`nb_ai_context` 生成的markdown文件 传给  `google ai studio` ，生成代码质量和准确率，远远吊打在 `cursor` `trae` `qoder` `gemini-code-assit` 等ai ide中写代码。**

<pre style="color: red;font-weight: bold;background-color:yellow;padding: 10px;border-radius: 5px;font-size: 16px;">
本人实测：
将 nb_ai_context 对项目教程+源码生成的单个markdown文件 传给 `google ai studio` 网页版
的1000k上下文+gemini 3.0pro 大模型，再提问让gemini写代码，代码生成的准确率和质量远远吊打在 
cursor trae qoder等ai ide或gemini-code-assit等 编程插件 里面提问写代码。

 `google ai studio` 优点是1000k上下文以及免费。唯一缺点是他在网页生成代码，而不是直接操作
 修改你的本地代码文件。

 `nb_ai_context` 生成的markdown丢给`google ai studio`完爆cursor trae等ide中写代码 
 主要原因是：  
`google ai studio` 网页版是全量阅读推理你上传的文件，而 ai编程ide 对长教程或者源码，
ai agent试图采取关键字搜索匹配再投喂上下文给ai大模型，（有时候模糊搜索的关键字压根搜不到内容，
因为ai造出来的关键字不是完完全全的匹配你的函数名字或者教程内容，所以就搜不到，
例如近义词，你代码用别的单词表示，ai却用自己的单词来表示，靠关键字压根就搜不到内容，agent咋投喂ai大模型？）
ai ide 这种方式简直是管中窥豹 一叶障目不见泰山。

最根本原因还是：  
ai ide 为了减少成本，防止token消耗大，不会全量把源码教程喂给ai大模型，不然的靠用户交的那20美元的月会员，
cursor要亏得裤衩没得穿。100万 tokens上下文输入，calude gemini gpt 价格大约是3美元，相当于20元人民币，
如果问一次就20元人民币，cursor每个月给你用8次就要开始亏血本了，(另外tokens输出价格远高于输入价格），
现在cursor还给你每月用500次，那他只能从节约tokens下手了，具体表现就是cursor不全量把所有教程和源码
发给ai大模型推理。

`nb_ai_context` 对大型编程项目的效果尤为突出，远远吊打 cursor trae qoder gemini-code-assit 等ai ide中写代码。
</pre>

## ✨ 核心特性

### 🎯 为什么需要 nb_ai_context？

当你想让 AI 大模型（如 ChatGPT、Claude）理解你的整个项目时，手动复制粘贴代码既繁琐又容易出错。`nb_ai_context` 彻底解决了这个痛点：

- ✅ **自动化合并** - 一键将整个项目合并为单个 Markdown 文件
- ✅ **结构化输出** - 包含文件树、文件边界、相对路径，AI 能完美理解项目架构
- ✅ **智能元数据提取** - 对 Python 项目提供 AST 语法树解析，自动生成类、函数、方法签名
- ✅ **安全可靠** - 内置 `.gitignore` 支持，自动排除敏感信息（API 密钥、.env 文件等）
- ✅ **灵活可控** - 支持文件过滤、目录排除、自定义文件类型
- ✅ **GitHub 项目支持** - 直接从 GitHub zip 下载地址生成文档

### 💡 **关键优势对比**
| 方案 | 上下文完整性 | Token 消耗 | 安全性 | 成本 |
|------|--------------|------------|--------|------|
| **Cursor/trae/qoder** | ❌ 分段阅读 | ⚠️ 高 | ⚠️ 需手动过滤 | 💰 付费次数限制 |
| **手动复制粘贴** | ❌ 易遗漏 | ❌ 极高 | ❌ 易泄露敏感信息 | ⏰ 耗时 |
| **nb_ai_context** | ✅ 完整结构化 | ✅ AST 元数据优化 | ✅ 自动 `.gitignore` | 🆓 免费工具 |



### 🎁 对 AI 大模型的巨大价值

1. **提供上帝视角** 📐
   - 清晰的文件树和文件边界
   - 完整的相对路径信息
   - AI 能轻松构建项目的整体架构图

2. **确保信息完整性** 📚
   - 未经删减的完整源码
   - 避免手动复制导致的格式混乱
   - 零上下文缺失，AI 分析更精准

3. **增强安全性** 🔒
   - 自动遵循 `.gitignore` 规则
   - 防止敏感信息泄露
   - 放心分享代码给 AI

4. **Python 项目专属优化** 🐍
   - AST 语法树解析
   - 自动提取类、函数、方法签名
   - 参数类型、返回值、装饰器一目了然

## 📦 安装

```bash
pip install nb_ai_context
```

**依赖项：**
- Python >= 3.7
- nb_path
- nb_log

## 🚀 快速开始

### 示例 1：为本地 Python 项目生成 AI 上下文

```python
from nb_ai_context import AiMdGenerator

project_name = "my_awesome_project"
project_root = r"D:\codes\my_awesome_project"

project_summary = f"""
- `{project_name}` 是一个优秀的 Python Web 应用
- 使用 FastAPI 框架
- 包含完整的用户认证和权限管理
"""

(
    AiMdGenerator(rf"D:\ai_docs\{project_name}_for_ai.md")
    .set_project_propery(project_name=project_name, project_root=project_root)
    .clear_text()  # 清空旧内容
    .add_project_summary(
        project_summary=project_summary,
        # 核心文件列表（只提取元数据，不包含完整源码）
        most_core_source_code_file_list=[
            "src/main.py",
            "src/api.py",
            "src/models.py",
        ],
    )
    .auto_merge_from_python_project_some_files()  # 自动包含 README.md、setup.py、pyproject.toml
    .merge_from_dir(
        relative_dir_name="src",
        as_title=f"{project_name} 源代码",
        use_gitignore=True,  # 使用 .gitignore 规则
        should_include_suffixes=[".py", ".md"],  # 只包含指定文件类型
        include_ast_metadata=True,  # 包含 AST 元数据
    )
    .merge_from_dir(
        relative_dir_name="tests",
        as_title=f"{project_name} 测试代码",
        use_gitignore=True,
        should_include_suffixes=[".py"],
        excluded_dir_name_list=["tests/temp"],  # 排除特定目录
        include_ast_metadata=True,
    )
    .show_textfile_info()  # 显示生成的文件信息
)
```

### 示例 2：从 GitHub 项目生成 AI 文档

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md
from nb_path import NbPath

# 生成 SQLModel 项目的文档和代码
gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_docs_and_codes.md",
    readme_file="README.md",
    docs_dir_name="docs",  # 文档目录
    codes_dir_name="sqlmodel",  # 源码目录
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests", "__pycache__"],
    excluded_file_name_list=["test_*.py"],
)
```

### 示例 3：生成 GitHub 项目的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

# 生成整个项目的所有文件
gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/fastapi/sqlmodel/zip/refs/heads/main",
    output_md_path=r"D:\ai_docs\sqlmodel_all_files.md",
    should_include_suffixes=[".py", ".md", ".html"],
    excluded_dir_name_list=["node_modules", ".git", "__pycache__"],
)
```


## 为什么要这个 nb_ai_context ？脱了裤子放屁？让cusror trae 去阅读项目的源码和教程，还能在ide里面生成代码，这不更香吗？(预判质疑)

- 1. 有时候是非ide环境问项目的用法

- 2. cursor 太贵了，经常提问cursor浪费宝贵次数，要把宝贵次数留给真正的代码生成，而不是提问。
     cursor trae 存在上下文太短，调教成不积极阅读全量源码和教程等问题。

- 3. 可以作为 rag 在线知识库使用，让ai大模型阅读生成的markdown，然后回答问题。

- 4. 可以把生成的markdown丢给免费且上下文无敌高达1000k 的 `google ai studio` 去阅读，claude gpt才256k token，国内大模型才128k token，压根不够用，  
     只有  google ai studio + gemini 3.0pro 才能完整阅读项目源码加教程，并且免费使用。 

- 5. `google ai studio` 对于4M 以内的文件，可以一次性全量阅读全文+推理，并且免费使用。
     一般三方包项目的源码加教程远没有达到4M。
     如果是用户自己的真实项目而非框架或者三方包项目，项目中可能包含大量写法模式非常重复的需求代码，代码加教程加起来可能会超过4M，但是 用户项目核心公共入口函数和类的用法的源码 + 教程 绝对不超过1M。

- 6. `nb_ai_context` 支持按文件夹合并和过滤需要合并的文件，并且`nb_ai_context` 支持 `ast` 解析 任何python文件的 类 函数 入参 注释 等 元信息，大幅减少token消耗，让ai抓住重点。  
     `AiMdGenerator.add_project_summary(most_core_source_code_file_list=[...])` 支持添加项目概述和核心文件元数据，让ai快速抓住重点。  
     不管项目多复杂有几百个class类和模块，但真正能作为公共使用入口的模块一般不会超过10个。  
     例如 `funboost` 项目 `python`文件高达几百个，但真正能作为框架使用用户级别的公共使用入口的python文件也就三四个，其他的python文件压根没机会被终端用户直接import并使用。


- 10. 最最主要的原因！！！ cursor trae 内置的大模型都不乖乖阅读所有源码和教程，即使你明确发出指令强迫他阅读所有教程和源码，他也会偷懒不读，只读部分代码，导致幻觉率极高。
      为什么 cursor trae 里面的大模型不全量阅读呢，因为他早就预判了有流氓玩家恶意滥用大模型算力， 如果你让cursor + claude 阅读c盘下的所有文件，cursor和claude 会这么乖吗？
      c盘下的文本文件高达几百亿行，如果读完一次就会消耗价值几十万美元的tokens，大模型厂商亏得裤衩没得穿，你才20美元的包月会员，cursor和claude厂家不被你薅秃噜皮？
      所以不管用户怎么强迫cursor  trae去阅读所有源码和教程，cursor 和 claude 和trae都不会听你的话。

- 11. trae 和 cursor 被调教限制了， trae一次最多阅读200行，qoder一次最多阅读1000行，对超长教程，需要分多次阅读，导致幻觉率极高，你让这种 ai ide + 大模型去总结 项目的中心思想非常不靠谱。 
      并且分段阅读交互次数高达几十次，太费等待时间了。
      并且 ai ide里面阅读长文件会分批阅读直到20次就会自动停止，例如文件有10000行，trae一次阅读200行，需要50次交互，但ai ide设置了最多交互20次就会自动停止；这是因为担心流氓恶意用户让他阅读1亿行的文件导致自己血亏，所以最多分段阅读20次就会停止。

- 12. 解决 10和11的痛点问题， 只有 `google ai studio` 才能做到 ， 或者使用rag也勉强可以。
      把文档上传给 `google ai studio` 网页版的1000k上下文的gemini pro 这种方式最强， 其次是 `rag`方式。
      对 4M以内的文档（大约就是1000k tokens）， 推荐使用  `google ai studio`，不要使用 `rag` 方式。
      超过4M文档的上下文，突破了gemini 1000k token 上限，无法使用，此时需要使用 `rag`方式。

- 13. 一些网址：
       google ai studio 网址  [https://aistudio.google.com/](https://aistudio.google.com/)    
       智谱清言 ai智能体网址，可以支持免费rag  [chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh](chatglm.cn/main/gdetail/69168f75e8e6a00da25f55cc?lang=zh)    
       腾讯ima知识库 [ima.qq.com](https://ima.qq.com/)    



## 📖 核心 API 详解

### AiMdGenerator 类

`AiMdGenerator` 是核心类，继承自 `NbPath`，支持优雅的链式调用。

#### 主要方法

##### 1. `set_project_propery(project_name, project_root)`
设置项目名称和根目录（必须首先调用）

```python
.set_project_propery(
    project_name="my_project",
    project_root=r"D:\codes\my_project"
)
```

##### 2. `add_project_summary(project_summary, most_core_source_code_file_list)`
添加项目概述和核心文件元数据

```python
.add_project_summary(
    project_summary="项目描述...",
    most_core_source_code_file_list=[
        "src/main.py",  # 只提取这些文件的 AST 元数据，不包含完整源码
        "src/api.py",
    ]
)
```

##### 3. `auto_merge_from_python_project_some_files()`
自动合并项目根目录的关键文件（README.md、setup.py、pyproject.toml）

```python
.auto_merge_from_python_project_some_files()
```

##### 4. `merge_from_files(relative_file_name_list, as_title)`
合并指定文件列表

```python
.merge_from_files(
    relative_file_name_list=["docs/intro.md", "docs/api.md"],
    as_title="项目文档"
)
```

##### 5. `merge_from_dir()` ⭐ 最强大的方法
合并整个目录的文件

```python
.merge_from_dir(
    relative_dir_name="src",           # 相对目录名
    as_title="源代码",                  # 章节标题
    should_include_suffixes=[".py"],   # 包含的文件类型
    excluded_dir_name_list=[],         # 排除的目录列表
    excluded_file_name_list=[],        # 排除的文件列表
    use_gitignore=True,                # 使用 .gitignore 规则
    include_ast_metadata=True,         # 包含 Python AST 元数据
)
```

**参数说明：**
- `relative_dir_name`: 相对于项目根目录的路径
- `as_title`: 在生成的 Markdown 中的章节标题
- `should_include_suffixes`: 只包含这些后缀的文件（留空则包含所有文本文件）
- `excluded_dir_name_list`: 要排除的目录列表
- `excluded_file_name_list`: 要排除的文件列表
- `use_gitignore`: 是否遵循 `.gitignore` 规则（强烈推荐开启）
- `include_ast_metadata`: 对 Python 文件是否生成 AST 元数据
- `dry_run`: 预览模式，只显示将要包含的文件，不实际生成

##### 6. `merge_from_files_with_metadata()`
带元数据的文件合并（更高级的控制）

```python
.merge_from_files_with_metadata(
    relative_file_name_list=["src/main.py"],
    as_title="核心代码",
    include_ast_metadata=True,   # 是否包含 AST 元数据
    include_file_text=True,      # 是否包含完整源码
)
```

##### 7. `show_textfile_info()`
显示生成文件的统计信息

```python
.show_textfile_info()  # 打印文件大小、行数等信息
```

### GitHub 项目处理函数

#### `gen_github_proj_docs_and_codes_ai_md()`
生成 GitHub 项目的 README、文档和源码

```python
from nb_ai_context import gen_github_proj_docs_and_codes_ai_md

gen_github_proj_docs_and_codes_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output.md",
    readme_file="README.md",
    docs_dir_name="docs",
    codes_dir_name="src",
    should_include_suffixes=[".py", ".md"],
    excluded_dir_name_list=["tests"],
    excluded_file_name_list=["test_*.py"],
)
```

#### `gen_github_proj_all_dirs_ai_md()`
生成 GitHub 项目根目录下的所有文件

```python
from nb_ai_context import gen_github_proj_all_dirs_ai_md

gen_github_proj_all_dirs_ai_md(
    github_zip_url="https://codeload.github.com/owner/repo/zip/refs/heads/main",
    output_md_path="output_all.md",
    should_include_suffixes=[".py", ".java", ".go", ".md"],
    excluded_dir_name_list=["node_modules", ".git"],
)
```

## 🐍 Python AST 元数据提取

对于 Python 文件，`nb_ai_context` 会自动提取以下元数据：

### 提取内容
- ✅ **模块文档字符串**
- ✅ **导入语句** (import / from import)
- ✅ **类定义**
  - 类名、继承关系、装饰器
  - 类文档字符串
  - 公有方法（含参数类型、返回值、装饰器）
  - 属性 (@property)
  - 类变量
- ✅ **顶级函数**
  - 函数签名（参数类型、默认值、返回值）
  - 装饰器
  - 文档字符串
  - 异步函数标识

### 元数据示例输出

```markdown
### 📄 Python File Metadata: `src/main.py`

#### 📝 Module Docstring
```
这是主模块，包含应用的入口点
```

#### 📦 Imports
- `import os`
- `from typing import List, Optional`
- `from fastapi import FastAPI`

#### 🏛️ Classes (2)

##### 📌 `class UserService(BaseService)`
*Line: 15*

**Docstring:**
```
用户服务类，处理用户相关的业务逻辑
```

**Public Methods (3):**
- `def get_user(user_id: int) -> Optional[User]`
  - *获取用户信息*
- `async def create_user(name: str, email: str) -> User`
  - *创建新用户*
- `def update_user(user_id: int, **kwargs) -> bool`

**Properties (1):**
- `@property user_count -> int`

#### 🔧 Public Functions (2)
- `async def startup_event() -> None`
  - *Line: 120*
  - *应用启动事件处理*
```

## 🎨 生成的 Markdown 结构

生成的 Markdown 文档结构清晰，包含：

```markdown
# markdown content namespace: 项目概述

项目描述...

## 📋 Core Source Files Metadata (Entry Points)
核心文件的 AST 元数据（不含源码）

---

# markdown content namespace: 项目根目录文件

## File Tree
```
├── README.md
├── setup.py
└── pyproject.toml
```

## Included Files
- `README.md`
- `setup.py`
- `pyproject.toml`

---

--- **start of file: README.md** ---
（文件内容）
--- **end of file: README.md** ---

---

# markdown content namespace: 源代码

## File Tree
（文件树）

## Included Files
（文件列表）

--- **start of file: src/main.py** ---

### 📄 Python File Metadata: `src/main.py`
（AST 元数据）

```python
（完整源码）
```

--- **end of file: src/main.py** ---
```

## 🔧 高级用法

### 1. 预览模式（Dry Run）

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        dry_run=True,  # 只预览，不实际生成
    )
)
```

### 2. 只提取元数据，不包含源码

```python
(
    AiMdGenerator("output.md")
    .set_project_propery("my_project", r"D:\codes\my_project")
    .merge_from_files_with_metadata(
        relative_file_name_list=["src/main.py"],
        as_title="核心代码架构",
        include_ast_metadata=True,  # 包含元数据
        include_file_text=False,    # 不包含源码
    )
)
```

### 3. 支持的文件类型

`AiMdGenerator` 支持多种编程语言的语法高亮：

```python
suffix__lang_map = {
    ".py": "python",
    ".java": "java",
    ".go": "go",
    ".js": "javascript",
    ".ts": "typescript",
    ".jsx": "javascript",
    ".tsx": "typescript",
    ".vue": "vue",
    ".php": "php",
    ".c": "c",
    ".cpp": "cpp",
    ".cs": "csharp",
    ".sql": "sql",
    ".sh": "shell",
    ".ps1": "powershell",
    ".md": "markdown",
    ".json": "json",
    ".yaml": "yaml",
    ".xml": "xml",
    ".html": "html",
    ".css": "css",
    # ... 更多
}
```

## 🎯 实际应用场景

### 1. AI 代码审查
生成完整项目上下文，让 AI 全面审查代码质量、安全漏洞、性能问题。

### 2. 技术文档生成
快速为新团队成员生成项目概览文档，配合 AI 解释代码逻辑。

### 3. 知识库构建
将项目内容导入 RAG 知识库，实现智能代码搜索和问答。

### 4. 项目迁移和重构
让 AI 理解整个项目后，提供重构建议或迁移到新框架。

### 5. 学习开源项目
下载 GitHub 项目后生成结构化文档，配合 AI 快速理解项目架构。

## 🔒 安全性

### `.gitignore` 支持

设置 `use_gitignore=True` 时，会自动遵循项目的 `.gitignore` 规则：

```python
.merge_from_dir(
    relative_dir_name="src",
    use_gitignore=True,  # 自动排除 .env、*.log 等敏感文件
)
```

### 自动排除
- 自动排除以 `.` 开头的隐藏目录（如 `.git`、`.venv`）
- 支持手动排除特定目录和文件

## 📊 性能特性

- ✅ 高效的文件遍历和过滤
- ✅ 智能文本文件检测
- ✅ 支持大型项目（数千个文件）
- ✅ 链式调用，代码简洁优雅

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT License

## 🔗 相关链接

- **GitHub**: https://github.com/ydf0509/nb_ai_context
- **PyPI**: https://pypi.org/project/nb_ai_context/
- **Issues**: https://github.com/ydf0509/nb_ai_context/issues

## 💡 常见问题

### Q: 生成的 Markdown 文件太大怎么办？
A: 使用 `should_include_suffixes` 限制文件类型，或使用 `excluded_dir_name_list` 排除不重要的目录（如 `node_modules`、`venv`）。

### Q: 如何只生成代码结构，不包含源码？
A: 使用 `merge_from_files_with_metadata()` 方法，设置 `include_file_text=False`。

### Q: 支持哪些编程语言？
A: 所有文本文件都支持，但 Python 文件有特殊的 AST 元数据提取支持。



## 🌟 Star History

如果这个项目对你有帮助，请给个 Star ⭐️！

---

**nb_ai_context** - 让 AI 真正理解你的代码 🚀
````

## tests/ai_codes/demo_init_extraction.py

```python
"""
演示 __init__ 方法参数提取功能的示例代码
"""
from typing import Optional, List, Dict


class DatabaseConnection:
    """数据库连接类
    
    管理数据库连接池和查询操作
    """
    
    def __init__(
        self,
        host: str,
        port: int = 3306,
        username: str = "root",
        password: str = "",
        database: Optional[str] = None,
        charset: str = "utf8mb4",
        pool_size: int = 10,
        timeout: float = 30.0,
        ssl_config: Optional[Dict[str, str]] = None,
        auto_commit: bool = True,
        *args,
        **kwargs
    ):
        """初始化数据库连接
        
        Args:
            host: 数据库主机地址
            port: 数据库端口号，默认 3306
            username: 用户名，默认 root
            password: 密码
            database: 数据库名称（可选）
            charset: 字符集，默认 utf8mb4
            pool_size: 连接池大小
            timeout: 连接超时时间（秒）
            ssl_config: SSL 配置字典
            auto_commit: 是否自动提交事务
            *args: 额外的位置参数
            **kwargs: 额外的关键字参数
        """
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.database = database
        self.charset = charset
        self.pool_size = pool_size
        self.timeout = timeout
        self.ssl_config = ssl_config or {}
        self.auto_commit = auto_commit
    
    def connect(self) -> bool:
        """建立数据库连接"""
        return True
    
    def execute(self, query: str, params: Optional[tuple] = None) -> List[Dict]:
        """执行 SQL 查询"""
        return []
    
    def close(self):
        """关闭数据库连接"""
        pass
    
    @property
    def is_connected(self) -> bool:
        """检查是否已连接"""
        return True


class SimpleLogger:
    """简单的日志类（没有 __init__ 参数）"""
    
    def log(self, message: str):
        """记录日志"""
        print(message)
    
    def error(self, message: str):
        """记录错误"""
        print(f"ERROR: {message}")


# 使用 AiMdGenerator 生成这个文件的 AST 元数据
if __name__ == "__main__":
    from nb_ai_context import AiMdGenerator
    from nb_path import NbPath
    
    # 获取当前文件路径
    current_file = NbPath(__file__)
    project_root = current_file.parent.parent.parent  # 回到项目根目录
    
    output_path = current_file.parent.parent / "ai_docs" / "demo_init_extraction_output.md"
    output_path.ensure_parent()
    
    print(f"当前文件: {current_file}")
    print(f"项目根目录: {project_root}")
    print(f"输出路径: {output_path}")
    
    # 生成 Markdown
    (
        AiMdGenerator(str(output_path))
        .set_project_propery(
            project_name="demo",
            project_root=str(project_root)
        )
        .clear_text()
        .merge_from_files(
            relative_file_name_list=[str(current_file.relative_to(project_root))],
            as_title="演示 __init__ 提取功能",
        )
        .show_textfile_info()
    )
    
    print(f"\n✅ 已生成 Markdown 文件: {output_path}")
    print(f"\n请查看文件内容，可以看到：")
    print("  1. DatabaseConnection 类的 __init__ 方法被单独突出显示")
    print("  2. 所有参数（包括类型、默认值）都被详细列出")
    print("  3. 支持复杂类型如 Optional[Dict[str, str]]")
    print("  4. 支持 *args 和 **kwargs")
    print("  5. SimpleLogger 类（没有自定义 __init__）也能正常处理")
    
    # 读取并显示部分内容
    content = output_path.read_text(encoding="utf-8")
    
    # 查找并显示 __init__ 部分
    if "🔧 Constructor (`__init__`):" in content:
        print("\n" + "="*80)
        print("生成的 __init__ 元数据预览：")
        print("="*80)
        
        start_idx = content.index("🔧 Constructor (`__init__`):")
        # 找到下一个 ** 开头的部分（Public Methods 或 Properties）
        end_markers = ["**Public Methods", "**Properties", "**Class Variables"]
        end_idx = len(content)
        for marker in end_markers:
            if marker in content[start_idx:]:
                potential_end = start_idx + content[start_idx:].index(marker)
                if potential_end < end_idx:
                    end_idx = potential_end
        
        print(content[start_idx:end_idx])
    else:
        print("\n⚠️ 未找到 __init__ 构造器信息")
```

## tests/ai_codes/test_init_extraction.py

````python
"""
测试 __init__ 方法参数提取功能
"""
from nb_ai_context import AiMdGenerator
from nb_path import NbPath
import tempfile
import os


# 创建一个测试用的 Python 文件
test_code = '''
"""测试模块"""
from typing import Optional, List


class UserService:
    """用户服务类
    
    这是一个处理用户相关业务的服务类
    """
    
    def __init__(
        self, 
        database_url: str,
        timeout: int = 30,
        max_connections: int = 10,
        enable_cache: bool = True,
        allowed_domains: Optional[List[str]] = None,
        **kwargs
    ):
        """初始化用户服务
        
        Args:
            database_url: 数据库连接URL
            timeout: 超时时间（秒）
            max_connections: 最大连接数
            enable_cache: 是否启用缓存
            allowed_domains: 允许的域名列表
        """
        self.database_url = database_url
        self.timeout = timeout
        self.max_connections = max_connections
        self.enable_cache = enable_cache
        self.allowed_domains = allowed_domains or []
    
    def get_user(self, user_id: int) -> dict:
        """获取用户信息"""
        pass
    
    def create_user(self, name: str, email: str) -> bool:
        """创建新用户"""
        pass
    
    @property
    def connection_count(self) -> int:
        """当前连接数"""
        return 0


class SimpleClass:
    """一个没有 __init__ 方法的简单类"""
    
    def do_something(self):
        """做一些事情"""
        pass
'''


def test_init_extraction():
    """测试 __init__ 方法提取"""
    
    # 创建临时目录和文件
    with tempfile.TemporaryDirectory() as temp_dir:
        # 创建测试文件
        test_file = os.path.join(temp_dir, "test_service.py")
        with open(test_file, "w", encoding="utf-8") as f:
            f.write(test_code)
        
        # 生成 AI Markdown
        output_path = os.path.join(temp_dir, "test_output.md")
        
        (
            AiMdGenerator(output_path)
            .set_project_propery(
                project_name="test_project",
                project_root=temp_dir
            )
            .clear_text()
            .merge_from_files(
                relative_file_name_list=["test_service.py"],
                as_title="测试代码",
            )
            .show_textfile_info()
        )
        
        # 读取生成的内容
        with open(output_path, "r", encoding="utf-8") as f:
            content = f.read()
        
        print("\n" + "="*80)
        print("生成的 Markdown 内容（AST 元数据部分）：")
        print("="*80)
        
        # 只打印元数据部分
        if "### 📄 Python File Metadata:" in content:
            metadata_start = content.index("### 📄 Python File Metadata:")
            metadata_end = content.index("```python", metadata_start) if "```python" in content[metadata_start:] else len(content)
            print(content[metadata_start:metadata_end])
        
        # 验证关键内容
        assert "🔧 Constructor (`__init__`):" in content, "缺少 __init__ 构造器标题"
        assert "database_url: str" in content, "缺少 database_url 参数"
        assert "timeout: int = 30" in content, "缺少 timeout 参数及默认值"
        assert "enable_cache: bool = True" in content, "缺少 enable_cache 参数及默认值"
        assert "Optional[List[str]]" in content, "缺少复杂类型注解"
        assert "**kwargs" in content, "缺少 kwargs 参数"
        
        print("\n✅ 所有测试通过！__init__ 方法参数提取成功！")
        print("\n关键特性验证：")
        print("  ✓ __init__ 方法被单独显示")
        print("  ✓ 参数类型注解正确提取")
        print("  ✓ 默认值正确显示")
        print("  ✓ 复杂类型（Optional[List[str]]）正确解析")
        print("  ✓ **kwargs 等特殊参数正确处理")
        print("  ✓ 没有 __init__ 的类也能正常处理")


if __name__ == "__main__":
    test_init_extraction()
````

## tests/ai_docs/CHANGELOG_init_extraction.md

````markdown
# 更新日志 - __init__ 方法参数提取功能

## 📅 更新时间
2025-11-21

## 🎯 更新内容

### 新增功能：突出显示类的 `__init__` 构造方法

在 `AiMdGenerator` 生成的 Python 文件 AST 元数据中，类的 `__init__` 方法现在会被**单独提取并详细展示**。

## 🔧 技术修改

### 修改文件
- `nb_ai_context/ai_md_generator.py`

### 修改位置
- 方法：`_format_py_metadata_as_markdown`
- 行数：约 730-760 行

### 修改内容

#### 1. 查找 __init__ 方法
```python
# 首先单独显示 __init__ 方法（非常重要）
init_method = None
for method in cls["methods"]:
    if method["name"] == "__init__":
        init_method = method
        break
```

#### 2. 单独显示构造器信息
```python
if init_method:
    lines.append("**🔧 Constructor (`__init__`):**")
    params_str = self._format_parameters(init_method["parameters"])
    lines.append(f"- `def __init__({params_str})`")
    
    # 显示 __init__ 的文档字符串
    if init_method["docstring"]:
        first_line = init_method["docstring"].split("\n")[0].strip()
        if first_line:
            lines.append(f"  - *{first_line}*")
    
    # 显示每个参数的详细信息
    if init_method["parameters"]:
        lines.append("  - **Parameters:**")
        for param in init_method["parameters"]:
            param_name = param["name"]
            param_type = f": {param['type']}" if param["type"] else ""
            param_default = f" = {param['default']}" if param["default"] else ""
            lines.append(f"    - `{param_name}{param_type}{param_default}`")
    lines.append("")
```

#### 3. 从公有方法列表中排除 __init__
```python
# 公有方法（排除 __init__）
public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
```

## 📊 输出格式示例

### 修改前
```markdown
**Public Methods (5):**
- `def __init__(database_url: str, timeout: int = 30)`
- `def get_user(user_id: int) -> dict`
- `def create_user(name: str, email: str) -> bool`
- `def update_user(user_id: int) -> bool`
- `def delete_user(user_id: int) -> bool`
```

### 修改后
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, **kwargs)`
  - *初始化数据库服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `**kwargs`

**Public Methods (4):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
- `def update_user(user_id: int) -> bool`
  - *更新用户信息*
- `def delete_user(user_id: int) -> bool`
  - *删除用户*
```

## ✨ 功能特性

### 支持的参数类型
- ✅ 位置参数：`param`
- ✅ 带类型注解的参数：`param: str`
- ✅ 带默认值的参数：`param: int = 10`
- ✅ 可选类型：`param: Optional[str] = None`
- ✅ 复杂类型：`param: Optional[Dict[str, List[int]]] = None`
- ✅ 可变位置参数：`*args`
- ✅ 可变关键字参数：`**kwargs`

### 显示内容
1. **完整方法签名** - 一行显示所有参数
2. **文档字符串** - 显示 docstring 的第一行
3. **参数详细列表** - 每个参数单独一行，包含：
   - 参数名
   - 类型注解（如果有）
   - 默认值（如果有）

## 💡 为什么这个改进很重要？

### 对开发者的价值
1. **快速理解** - 一眼看出如何创建类的实例
2. **准确使用** - 知道哪些参数必需，哪些可选
3. **类型安全** - 清楚每个参数的类型约束
4. **默认配置** - 了解默认值，减少不必要的参数传递

### 对 AI 大模型的价值
1. **更好的代码生成** - AI 能准确生成类的实例化代码
2. **减少幻觉** - 明确的参数信息减少 AI 的猜测
3. **理解项目架构** - 构造函数参数反映了类的依赖关系
4. **提升准确性** - 完整的类型信息帮助 AI 理解代码意图

## 🔄 兼容性说明

- ✅ **向后兼容** - 不影响现有功能
- ✅ **优雅降级** - 如果类没有 `__init__` 方法，不会显示构造器部分
- ✅ **无破坏性** - 不改变其他方法的显示方式
- ✅ **适用所有场景** - 对所有 Python 类都生效

## 📝 使用示例

### 示例代码
```python
from nb_ai_context import AiMdGenerator

(
    AiMdGenerator("output.md")
    .set_project_propery(
        project_name="my_project",
        project_root=r"D:\codes\my_project"
    )
    .clear_text()
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        should_include_suffixes=[".py"],
        include_ast_metadata=True,  # 开启 AST 元数据提取
    )
)
```

生成的 Markdown 中，每个 Python 类的 `__init__` 方法都会被特别突出显示。

## 🎯 实际应用场景

### 1. API 文档生成
快速生成类的构造函数文档，方便用户查阅。

### 2. 代码审查
审查者能快速了解类的初始化需求和依赖。

### 3. AI 辅助编程
AI 能准确理解如何实例化类，生成正确的代码。

### 4. 新人上手
新团队成员能快速理解项目中各个类的使用方式。

### 5. 重构参考
重构时能清楚看到类的构造依赖，避免遗漏。

## 🚀 下一步计划

可能的增强方向：
- [ ] 支持提取 `@dataclass` 的字段信息
- [ ] 支持提取 `@attrs` 装饰器类的属性
- [ ] 支持显示 `__new__` 方法（如果有）
- [ ] 支持提取类的继承链中所有 `__init__` 参数

## 📞 反馈

如有任何问题或建议，请访问：
- GitHub Issues: https://github.com/ydf0509/nb_ai_context/issues

---

**nb_ai_context** - 让 AI 真正理解你的代码 🚀
````

## tests/ai_docs/complete_docstring_extraction.md

````markdown
# 完整文档字符串提取功能

## 📅 更新时间
2025-11-21

## 🎯 问题描述

之前的版本中，AST 元数据提取时对文档字符串（docstring）进行了截断处理：
- **类的 docstring**：只显示前 3 行，后面用 `...` 省略
- **`__init__` 方法的 docstring**：只显示第 1 行
- **普通方法的 docstring**：只显示第 1 行
- **顶级函数的 docstring**：只显示第 1 行

这导致很多重要的文档信息丢失，特别是对于像 funboost 这样有详细文档的项目。

## ✨ 解决方案

现在所有文档字符串都会**完整显示**：

### 1. 类的 docstring - 完整显示

```python
# 修改前
docstring_lines = cls["docstring"].split("\n")[:3]  # 只取前3行
if len(cls["docstring"].split("\n")) > 3:
    lines.append("...")

# 修改后
docstring_lines = cls["docstring"].split("\n")  # 显示所有行
```

### 2. `__init__` 方法的 docstring - 完整显示

```python
# 修改前
first_line = init_method["docstring"].split("\n")[0].strip()
if first_line:
    lines.append(f"  - *{first_line}*")

# 修改后
if init_method["docstring"]:
    lines.append("  - **Docstring:**")
    lines.append("  ```")
    for doc_line in init_method["docstring"].split("\n"):
        lines.append(f"  {doc_line}")
    lines.append("  ```")
```

### 3. 普通方法的 docstring - 智能显示

```python
# 新逻辑：根据行数智能选择格式
if method["docstring"]:
    docstring_lines = method["docstring"].split("\n")
    if len(docstring_lines) == 1:
        # 单行文档：简短格式
        lines.append(f"  - *{method['docstring'].strip()}*")
    else:
        # 多行文档：代码块格式
        lines.append("  - **Docstring:**")
        lines.append("  ```")
        for doc_line in docstring_lines:
            lines.append(f"  {doc_line}")
        lines.append("  ```")
```

### 4. 顶级函数的 docstring - 智能显示

与普通方法相同的逻辑。

## 📊 效果对比

### 修改前 ❌

```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
...
```

**🔧 Constructor (`__init__`):**
- `def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - *@boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。*
```

### 修改后 ✅

```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

Booster 是把Consumer 和 Publisher的方法集为一体。
```

**🔧 Constructor (`__init__`):**
- `def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - **Docstring:**
  ```
  @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
  pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.
  (高版本的pycharm pydantic是内置支持代码补全的,由此可见,pydantic太好了,pycharm官方都来支持)
  
  强烈建议所有入参放在 BoosterParams() 中,不要直接在BoosterParams之外传参.现在是兼容老的直接在@boost中传参方式.
  建议不要给第一个入参queue_name传递字符串，而是永远传递BoosterParams类型， 例如 @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  
  ```python
  # @boost('queue_test_f01', qps=0.2, ) # 老的入参方式
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, )) # 新的入参方式
  def f(a, b):
      print(a + b)
  
  for i in range(10, 20):
      f.pub(dict(a=i, b=i * 2))
      f.push(i, i * 2)
  f.consume()
  ```
  
  @boost('queue_test_f01', qps=0.2, ) 
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  @Booster(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  @BoosterParams(queue_name='queue_test_f01', qps=0.2, )
  以上4种写法等效。
  ```
  - **Parameters:**
    - `self`
    - `queue_name: typing.Union[BoosterParams, str] = None`
    - `**kwargs`
```

## 🔧 技术实现

### 修改文件
- `nb_ai_context/ai_md_generator.py`

### 修改的位置

1. **类的 docstring**（约 720-728 行）
   - 移除了 `[:3]` 切片
   - 移除了 `...` 省略标记

2. **`__init__` 方法的 docstring**（约 738-752 行）
   - 从单行显示改为完整多行代码块显示
   - 添加 "Docstring:" 标题
   - 保持适当的缩进

3. **普通方法的 docstring**（约 771-783 行）
   - 添加智能判断逻辑
   - 单行：简短格式 `*text*`
   - 多行：代码块格式

4. **顶级函数的 docstring**（约 819-833 行）
   - 与普通方法相同的智能判断逻辑

## ✅ 优势

### 1. 信息完整性
- ✅ 不再截断重要的文档信息
- ✅ 完整保留作者的意图和说明
- ✅ 特别适合有详细文档的项目（如 funboost）

### 2. AI 理解能力提升
- ✅ AI 能获取完整的使用说明
- ✅ 能看到完整的示例代码
- ✅ 理解多种使用方式和注意事项

### 3. 格式优化
- ✅ 单行文档：保持简洁，用斜体显示
- ✅ 多行文档：使用代码块，保持格式
- ✅ 适当的缩进，保持层次结构清晰

## 📝 实际案例

### 案例：funboost 的 Booster 类

原始代码：
```python
class Booster:
    """
    funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
    这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
    一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

    pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

    Booster 是把Consumer 和 Publisher的方法集为一体。
    """
    
    def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs):
        """
        @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
        pydatinc pycharm编程代码补全,请安装 pydantic插件...
        
        强烈建议所有入参放在 BoosterParams() 中...
        
        ```python
        # 示例代码
        @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
        def f(a, b):
            print(a + b)
        ```
        
        以上4种写法等效。
        """
        pass
```

现在生成的 AST 元数据会**完整显示**：
- 所有 7 行的类文档字符串
- `__init__` 方法中包含示例代码的完整文档字符串

## 🎯 适用场景

特别适合以下类型的项目：
1. **框架类项目** - 如 funboost、FastAPI 等，需要详细的使用说明
2. **库项目** - 有详细的 API 文档和示例
3. **教学项目** - 代码中包含大量注释和说明
4. **企业项目** - 有规范的文档字符串标准

## 🔄 兼容性

- ✅ 完全向后兼容
- ✅ 单行文档字符串保持简洁格式
- ✅ 多行文档字符串使用代码块格式
- ✅ 不影响其他功能

## 💡 最佳实践

### 编写文档字符串时的建议

1. **第一行应该是简短的概述**
   ```python
   """简短的功能描述
   
   详细的说明...
   """
   ```

2. **包含示例代码**
   ```python
   """初始化配置
   
   Example:
       ```python
       config = Config(host="localhost", port=3306)
       ```
   """
   ```

3. **说明参数和返回值**
   ```python
   """执行查询
   
   Args:
       query: SQL 查询语句
       params: 查询参数
       
   Returns:
       查询结果列表
   """
   ```

现在这些详细的文档都会被完整提取到 AI 上下文中！

---

**nb_ai_context** - 完整、准确地为 AI 提供代码上下文 🚀
````

## tests/ai_docs/init_extraction_feature.md

````markdown
# __init__ 方法参数提取功能说明

## 🎯 功能描述

现在 `AiMdGenerator` 在提取 Python 类的 AST 元数据时，会**单独突出显示**类的 `__init__` 构造方法及其所有参数信息。

## ✨ 为什么这很重要？

类的 `__init__` 方法定义了如何创建类的实例，其参数列表是理解如何使用该类的关键：

- ✅ 知道需要传递哪些参数
- ✅ 了解参数的类型注解
- ✅ 查看哪些参数有默认值
- ✅ 快速理解类的初始化逻辑

## 📊 修改前后对比

### 修改前 ❌

`__init__` 方法混在普通的公有方法列表中，不够突出：

```markdown
**Public Methods (5):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10)`
- `def get_user(user_id: int) -> dict`
- `def create_user(name: str, email: str) -> bool`
- `def update_user(user_id: int, **kwargs) -> bool`
- `def delete_user(user_id: int) -> bool`
```

### 修改后 ✅

`__init__` 方法被单独提取，并详细显示每个参数：

```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, allowed_domains: Optional[List[str]] = None, **kwargs)`
  - *初始化用户服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `allowed_domains: Optional[List[str]] = None`
    - `**kwargs`

**Public Methods (4):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
- `def update_user(user_id: int, **kwargs) -> bool`
  - *更新用户信息*
- `def delete_user(user_id: int) -> bool`
  - *删除用户*
```

## 🔧 技术实现

修改位置：`nb_ai_context/ai_md_generator.py` 的 `_format_py_metadata_as_markdown` 方法

### 核心逻辑

1. **查找 __init__ 方法**
   ```python
   init_method = None
   for method in cls["methods"]:
       if method["name"] == "__init__":
           init_method = method
           break
   ```

2. **单独显示构造器**
   - 使用专门的标题 `🔧 Constructor (\`__init__\`):`
   - 显示完整的方法签名
   - 显示文档字符串
   - **逐个列出每个参数及其类型和默认值**

3. **从普通方法中排除**
   ```python
   public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
   ```

## 📝 完整示例

假设有以下 Python 类：

```python
from typing import Optional, List

class UserService:
    """用户服务类"""
    
    def __init__(
        self, 
        database_url: str,
        timeout: int = 30,
        max_connections: int = 10,
        enable_cache: bool = True,
        allowed_domains: Optional[List[str]] = None,
        **kwargs
    ):
        """初始化用户服务
        
        Args:
            database_url: 数据库连接URL
            timeout: 超时时间（秒）
            max_connections: 最大连接数
            enable_cache: 是否启用缓存
            allowed_domains: 允许的域名列表
        """
        self.database_url = database_url
        self.timeout = timeout
    
    def get_user(self, user_id: int) -> dict:
        """获取用户信息"""
        pass
    
    def create_user(self, name: str, email: str) -> bool:
        """创建新用户"""
        pass
```

### 生成的 AST 元数据 Markdown

```markdown
### 📄 Python File Metadata: `src/user_service.py`

#### 📦 Imports
- `from typing import Optional, List`

#### 🏛️ Classes (1)

##### 📌 `class UserService`
*Line: 3*

**Docstring:**
```
用户服务类
```

**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, allowed_domains: Optional[List[str]] = None, **kwargs)`
  - *初始化用户服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `allowed_domains: Optional[List[str]] = None`
    - `**kwargs`

**Public Methods (2):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
```

## ✅ 优势总结

1. **更清晰** - `__init__` 作为类的构造器，被特别突出显示
2. **更详细** - 每个参数单独一行，类型和默认值一目了然
3. **更易读** - AI 和人类都能快速理解如何实例化这个类
4. **更专业** - 符合 API 文档的标准格式

## 🎯 对 AI 大模型的价值

当 AI 看到这样的元数据时，它能立即理解：

- 这个类需要什么参数才能创建实例
- 哪些参数是必需的，哪些是可选的
- 参数的类型约束是什么
- 有哪些默认配置

这大大提升了 AI 生成正确代码的能力！

## 🔄 兼容性

- ✅ 如果类没有 `__init__` 方法，不会显示构造器部分
- ✅ 不影响其他方法的显示
- ✅ 完全向后兼容
- ✅ 支持所有类型的参数（位置参数、关键字参数、*args、**kwargs）
````

## tests/ai_docs/UPDATE_SUMMARY.md

````markdown
# nb_ai_context 更新摘要

## 📅 更新日期
2025-11-21

## 🎯 本次更新的两个重要功能

### 功能 1️⃣：突出显示类的 `__init__` 构造方法 ⭐⭐⭐⭐⭐

#### 问题
之前 `__init__` 方法混在普通公有方法列表中，不够突出。

#### 解决方案
- ✅ `__init__` 方法被单独提取，使用专门的标题 `🔧 Constructor (\`__init__\`):`
- ✅ 显示完整的方法签名
- ✅ 显示完整的文档字符串
- ✅ **逐个列出每个参数**，包括类型注解和默认值
- ✅ 支持所有参数类型：位置参数、关键字参数、`*args`、`**kwargs`
- ✅ 从公有方法列表中排除，避免重复

#### 效果
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, **kwargs)`
  - **Docstring:**
  ```
  初始化数据库服务
  
  Args:
      database_url: 数据库连接URL
      timeout: 超时时间（秒）
      max_connections: 最大连接数
  ```
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `**kwargs`

**Public Methods (4):**  👈 注意：__init__ 已被排除，数量减少了
- `def get_user(user_id: int) -> dict`
- ...
```

---

### 功能 2️⃣：完整显示文档字符串 ⭐⭐⭐⭐⭐

#### 问题
之前版本对文档字符串进行了截断：
- 类的 docstring：只显示前 3 行
- `__init__` 方法：只显示第 1 行
- 普通方法：只显示第 1 行
- 顶级函数：只显示第 1 行

这导致**大量重要文档信息丢失**！

#### 解决方案

##### 1. 类的 docstring - **完整显示**
```python
# 之前：只显示前3行
docstring_lines = cls["docstring"].split("\n")[:3]

# 现在：显示所有行
docstring_lines = cls["docstring"].split("\n")
```

##### 2. `__init__` 方法的 docstring - **完整显示**
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(...)`
  - **Docstring:**
  ```
  完整的文档字符串
  包括所有行
  包括示例代码
  包括注意事项
  ```
  - **Parameters:**
    - ...
```

##### 3. 普通方法的 docstring - **智能显示**
- 单行文档：保持简洁 `*简短描述*`
- 多行文档：使用代码块格式，完整显示

##### 4. 顶级函数的 docstring - **智能显示**
- 与普通方法相同的逻辑

#### 效果对比

**修改前** ❌
```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法...
...  👈 后面的重要信息都被截断了！
```

**🔧 Constructor (`__init__`):**
- `def __init__(...)`
  - *@boost 这是funboost框架最重要的一个函数*  👈 只有第一行
```

**修改后** ✅
```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

Booster 是把Consumer 和 Publisher的方法集为一体。
```
👆 所有信息都完整保留！

**🔧 Constructor (`__init__`):**
- `def __init__(queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - **Docstring:**
  ```
  @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
  pydatinc pycharm编程代码补全,请安装 pydantic插件...
  
  强烈建议所有入参放在 BoosterParams() 中...
  
  ```python
  # 示例代码
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  def f(a, b):
      print(a + b)
  ```
  
  以上4种写法等效。
  ```
  👆 包括示例代码在内的所有文档都完整显示！
  - **Parameters:**
    - `queue_name: typing.Union[BoosterParams, str] = None`
    - `**kwargs`
```

---

## 🎯 为什么这两个功能非常重要？

### 对开发者的价值
1. **快速理解类的使用方式** - `__init__` 参数一目了然
2. **完整的文档信息** - 不再错过重要的使用说明和示例
3. **更好的代码审查** - 完整的上下文信息
4. **新人友好** - 详细的文档帮助快速上手

### 对 AI 大模型的价值
1. **准确的代码生成** - 知道如何正确实例化类
2. **减少幻觉** - 完整的文档信息减少 AI 的猜测
3. **理解项目架构** - 从构造函数参数理解类的依赖关系
4. **完整的示例代码** - 学习项目的最佳实践

### 特别适合的项目类型
- ✅ **框架类项目**：如 funboost、FastAPI 等
- ✅ **库项目**：有详细的 API 文档
- ✅ **教学项目**：代码中包含大量注释和说明
- ✅ **企业项目**：有规范的文档字符串标准

---

## 🔧 技术实现

### 修改文件
`nb_ai_context/ai_md_generator.py`

### 修改的方法
`_format_py_metadata_as_markdown()`

### 修改的行数区间
- 720-726 行：类的 docstring
- 735-756 行：`__init__` 方法的提取和显示
- 759 行：从公有方法中排除 `__init__`
- 773-785 行：普通方法的 docstring
- 819-833 行：顶级函数的 docstring

---

## ✅ 兼容性保证

- ✅ **完全向后兼容** - 不影响现有功能
- ✅ **优雅降级** - 如果类没有 `__init__`，不显示构造器部分
- ✅ **智能格式化** - 单行/多行文档自动选择合适的显示格式
- ✅ **无破坏性** - 不改变其他部分的行为

---

## 📊 实际应用场景

### 场景 1：理解框架的核心类
```python
# funboost 的 Booster 类
# 现在能看到完整的 7 行类文档 + __init__ 的详细使用说明（包括4种等效写法）
```

### 场景 2：学习正确的使用方式
```python
# 从 __init__ 参数列表看出：
# - 哪些参数是必需的
# - 哪些参数有默认值
# - 参数的类型约束
# - 从文档字符串看到示例代码
```

### 场景 3：AI 代码生成
```python
# AI 现在能看到完整信息：
# - 构造函数的所有参数
# - 每个参数的类型和默认值
# - 详细的使用说明和示例
# → 生成的代码更准确！
```

---

## 🚀 使用方式不变

```python
from nb_ai_context import AiMdGenerator

(
    AiMdGenerator("output.md")
    .set_project_propery(
        project_name="my_project",
        project_root=r"D:\codes\my_project"
    )
    .clear_text()
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        should_include_suffixes=[".py"],
        include_ast_metadata=True,  # 开启 AST 元数据提取
    )
)
```

生成的 Markdown 文件中，每个类的 `__init__` 方法都会被突出显示，所有文档字符串都会完整保留！

---

## 📝 相关文档

- `tests/ai_docs/init_extraction_feature.md` - __init__ 提取功能详细说明
- `tests/ai_docs/CHANGELOG_init_extraction.md` - __init__ 功能更新日志
- `tests/ai_docs/complete_docstring_extraction.md` - 完整文档字符串提取说明
- `tests/ai_codes/demo_init_extraction.py` - 演示示例代码

---

**nb_ai_context** - 让 AI 获得完整、准确的代码上下文 🚀

现在支持：
- ✅ 突出显示 `__init__` 构造方法
- ✅ 完整显示所有文档字符串
- ✅ 智能格式化输出
- ✅ 完美支持复杂项目（如 funboost）
````

## Statistics

- Total Files: 16
- Total Characters: 97240
- Total Tokens: 0
`````

## tests/ai_codes/demo_init_extraction.py

```python
from typing import Optional, List, Dict
⋮----
class DatabaseConnection:
    """数据库连接类
    
    管理数据库连接池和查询操作
    """
⋮----
def __init__(
        self,
        host: str,
        port: int = 3306,
        username: str = "root",
        password: str = "",
        database: Optional[str] = None,
        charset: str = "utf8mb4",
        pool_size: int = 10,
        timeout: float = 30.0,
        ssl_config: Optional[Dict[str, str]] = None,
        auto_commit: bool = True,
        *args,
        **kwargs
    ):
    """初始化数据库连接
        
        Args:
            host: 数据库主机地址
            port: 数据库端口号，默认 3306
            username: 用户名，默认 root
            password: 密码
            database: 数据库名称（可选）
            charset: 字符集，默认 utf8mb4
            pool_size: 连接池大小
            timeout: 连接超时时间（秒）
            ssl_config: SSL 配置字典
            auto_commit: 是否自动提交事务
            *args: 额外的位置参数
            **kwargs: 额外的关键字参数
        """
⋮----
def connect(self) -> bool:
    """建立数据库连接"""
⋮----
def execute(self, query: str, params: Optional[tuple] = None) -> List[Dict]:
    """执行 SQL 查询"""
⋮----
def close(self):
    """关闭数据库连接"""
⋮----
@property
⋮----
@property
    def is_connected(self) -> bool:
    """检查是否已连接"""
⋮----
class SimpleLogger:
    """简单的日志类（没有 __init__ 参数）"""
⋮----
def log(self, message: str):
    """记录日志"""
⋮----
def error(self, message: str):
    """记录错误"""
⋮----
from nb_ai_context import AiMdGenerator
from nb_path import NbPath
```

## tests/ai_codes/test_file_content_backquotes.py

```python
import sys
import os
⋮----
from nb_ai_context.ai_md_generator import AiMdGenerator, FILE_CONTENT_BACKQUOTES
⋮----
def test_file_content_backquotes():
    """测试 FILE_CONTENT_BACKQUOTES 是否正确定义"""
⋮----
def test_markdown_generation():
    """测试生成的 Markdown 是否使用了 FILE_CONTENT_BACKQUOTES"""
```

## tests/ai_codes/test_init_extraction.py

```python
from nb_ai_context import AiMdGenerator
from nb_path import NbPath
import tempfile
import os
⋮----
test_code = '''
"""测试模块"""
from typing import Optional, List


class UserService:
    """用户服务类
    
    这是一个处理用户相关业务的服务类
    """
    
    def __init__(
        self, 
        database_url: str,
        timeout: int = 30,
        max_connections: int = 10,
        enable_cache: bool = True,
        allowed_domains: Optional[List[str]] = None,
        **kwargs
    ):
        """初始化用户服务
        
        Args:
            database_url: 数据库连接URL
            timeout: 超时时间（秒）
            max_connections: 最大连接数
            enable_cache: 是否启用缓存
            allowed_domains: 允许的域名列表
        """
        self.database_url = database_url
        self.timeout = timeout
        self.max_connections = max_connections
        self.enable_cache = enable_cache
        self.allowed_domains = allowed_domains or []
    
    def get_user(self, user_id: int) -> dict:
        """获取用户信息"""
        pass
    
    def create_user(self, name: str, email: str) -> bool:
        """创建新用户"""
        pass
    
    @property
    def connection_count(self) -> int:
        """当前连接数"""
        return 0


class SimpleClass:
    """一个没有 __init__ 方法的简单类"""
    
    def do_something(self):
        """做一些事情"""
        pass
'''
⋮----
def test_init_extraction():
    """测试 __init__ 方法提取"""
```

## tests/ai_docs/CHANGELOG_init_extraction.md

````markdown
# 更新日志 - __init__ 方法参数提取功能

## 📅 更新时间
2025-11-21

## 🎯 更新内容

### 新增功能：突出显示类的 `__init__` 构造方法

在 `AiMdGenerator` 生成的 Python 文件 AST 元数据中，类的 `__init__` 方法现在会被**单独提取并详细展示**。

## 🔧 技术修改

### 修改文件
- `nb_ai_context/ai_md_generator.py`

### 修改位置
- 方法：`_format_py_metadata_as_markdown`
- 行数：约 730-760 行

### 修改内容

#### 1. 查找 __init__ 方法
```python
# 首先单独显示 __init__ 方法（非常重要）
init_method = None
for method in cls["methods"]:
    if method["name"] == "__init__":
        init_method = method
        break
```

#### 2. 单独显示构造器信息
```python
if init_method:
    lines.append("**🔧 Constructor (`__init__`):**")
    params_str = self._format_parameters(init_method["parameters"])
    lines.append(f"- `def __init__({params_str})`")
    
    # 显示 __init__ 的文档字符串
    if init_method["docstring"]:
        first_line = init_method["docstring"].split("\n")[0].strip()
        if first_line:
            lines.append(f"  - *{first_line}*")
    
    # 显示每个参数的详细信息
    if init_method["parameters"]:
        lines.append("  - **Parameters:**")
        for param in init_method["parameters"]:
            param_name = param["name"]
            param_type = f": {param['type']}" if param["type"] else ""
            param_default = f" = {param['default']}" if param["default"] else ""
            lines.append(f"    - `{param_name}{param_type}{param_default}`")
    lines.append("")
```

#### 3. 从公有方法列表中排除 __init__
```python
# 公有方法（排除 __init__）
public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
```

## 📊 输出格式示例

### 修改前
```markdown
**Public Methods (5):**
- `def __init__(database_url: str, timeout: int = 30)`
- `def get_user(user_id: int) -> dict`
- `def create_user(name: str, email: str) -> bool`
- `def update_user(user_id: int) -> bool`
- `def delete_user(user_id: int) -> bool`
```

### 修改后
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, **kwargs)`
  - *初始化数据库服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `**kwargs`

**Public Methods (4):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
- `def update_user(user_id: int) -> bool`
  - *更新用户信息*
- `def delete_user(user_id: int) -> bool`
  - *删除用户*
```

## ✨ 功能特性

### 支持的参数类型
- ✅ 位置参数：`param`
- ✅ 带类型注解的参数：`param: str`
- ✅ 带默认值的参数：`param: int = 10`
- ✅ 可选类型：`param: Optional[str] = None`
- ✅ 复杂类型：`param: Optional[Dict[str, List[int]]] = None`
- ✅ 可变位置参数：`*args`
- ✅ 可变关键字参数：`**kwargs`

### 显示内容
1. **完整方法签名** - 一行显示所有参数
2. **文档字符串** - 显示 docstring 的第一行
3. **参数详细列表** - 每个参数单独一行，包含：
   - 参数名
   - 类型注解（如果有）
   - 默认值（如果有）

## 💡 为什么这个改进很重要？

### 对开发者的价值
1. **快速理解** - 一眼看出如何创建类的实例
2. **准确使用** - 知道哪些参数必需，哪些可选
3. **类型安全** - 清楚每个参数的类型约束
4. **默认配置** - 了解默认值，减少不必要的参数传递

### 对 AI 大模型的价值
1. **更好的代码生成** - AI 能准确生成类的实例化代码
2. **减少幻觉** - 明确的参数信息减少 AI 的猜测
3. **理解项目架构** - 构造函数参数反映了类的依赖关系
4. **提升准确性** - 完整的类型信息帮助 AI 理解代码意图

## 🔄 兼容性说明

- ✅ **向后兼容** - 不影响现有功能
- ✅ **优雅降级** - 如果类没有 `__init__` 方法，不会显示构造器部分
- ✅ **无破坏性** - 不改变其他方法的显示方式
- ✅ **适用所有场景** - 对所有 Python 类都生效

## 📝 使用示例

### 示例代码
```python
from nb_ai_context import AiMdGenerator

(
    AiMdGenerator("output.md")
    .set_project_propery(
        project_name="my_project",
        project_root=r"D:\codes\my_project"
    )
    .clear_text()
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        should_include_suffixes=[".py"],
        include_ast_metadata=True,  # 开启 AST 元数据提取
    )
)
```

生成的 Markdown 中，每个 Python 类的 `__init__` 方法都会被特别突出显示。

## 🎯 实际应用场景

### 1. API 文档生成
快速生成类的构造函数文档，方便用户查阅。

### 2. 代码审查
审查者能快速了解类的初始化需求和依赖。

### 3. AI 辅助编程
AI 能准确理解如何实例化类，生成正确的代码。

### 4. 新人上手
新团队成员能快速理解项目中各个类的使用方式。

### 5. 重构参考
重构时能清楚看到类的构造依赖，避免遗漏。

## 🚀 下一步计划

可能的增强方向：
- [ ] 支持提取 `@dataclass` 的字段信息
- [ ] 支持提取 `@attrs` 装饰器类的属性
- [ ] 支持显示 `__new__` 方法（如果有）
- [ ] 支持提取类的继承链中所有 `__init__` 参数

## 📞 反馈

如有任何问题或建议，请访问：
- GitHub Issues: https://github.com/ydf0509/nb_ai_context/issues

---

**nb_ai_context** - 让 AI 真正理解你的代码 🚀
````

## tests/ai_docs/complete_docstring_extraction.md

````markdown
# 完整文档字符串提取功能

## 📅 更新时间
2025-11-21

## 🎯 问题描述

之前的版本中，AST 元数据提取时对文档字符串（docstring）进行了截断处理：
- **类的 docstring**：只显示前 3 行，后面用 `...` 省略
- **`__init__` 方法的 docstring**：只显示第 1 行
- **普通方法的 docstring**：只显示第 1 行
- **顶级函数的 docstring**：只显示第 1 行

这导致很多重要的文档信息丢失，特别是对于像 funboost 这样有详细文档的项目。

## ✨ 解决方案

现在所有文档字符串都会**完整显示**：

### 1. 类的 docstring - 完整显示

```python
# 修改前
docstring_lines = cls["docstring"].split("\n")[:3]  # 只取前3行
if len(cls["docstring"].split("\n")) > 3:
    lines.append("...")

# 修改后
docstring_lines = cls["docstring"].split("\n")  # 显示所有行
```

### 2. `__init__` 方法的 docstring - 完整显示

```python
# 修改前
first_line = init_method["docstring"].split("\n")[0].strip()
if first_line:
    lines.append(f"  - *{first_line}*")

# 修改后
if init_method["docstring"]:
    lines.append("  - **Docstring:**")
    lines.append("  ```")
    for doc_line in init_method["docstring"].split("\n"):
        lines.append(f"  {doc_line}")
    lines.append("  ```")
```

### 3. 普通方法的 docstring - 智能显示

```python
# 新逻辑：根据行数智能选择格式
if method["docstring"]:
    docstring_lines = method["docstring"].split("\n")
    if len(docstring_lines) == 1:
        # 单行文档：简短格式
        lines.append(f"  - *{method['docstring'].strip()}*")
    else:
        # 多行文档：代码块格式
        lines.append("  - **Docstring:**")
        lines.append("  ```")
        for doc_line in docstring_lines:
            lines.append(f"  {doc_line}")
        lines.append("  ```")
```

### 4. 顶级函数的 docstring - 智能显示

与普通方法相同的逻辑。

## 📊 效果对比

### 修改前 ❌

```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
...
```

**🔧 Constructor (`__init__`):**
- `def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - *@boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。*
```

### 修改后 ✅

```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

Booster 是把Consumer 和 Publisher的方法集为一体。
```

**🔧 Constructor (`__init__`):**
- `def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - **Docstring:**
  ```
  @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
  pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.
  (高版本的pycharm pydantic是内置支持代码补全的,由此可见,pydantic太好了,pycharm官方都来支持)
  
  强烈建议所有入参放在 BoosterParams() 中,不要直接在BoosterParams之外传参.现在是兼容老的直接在@boost中传参方式.
  建议不要给第一个入参queue_name传递字符串，而是永远传递BoosterParams类型， 例如 @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  
  ```python
  # @boost('queue_test_f01', qps=0.2, ) # 老的入参方式
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, )) # 新的入参方式
  def f(a, b):
      print(a + b)
  
  for i in range(10, 20):
      f.pub(dict(a=i, b=i * 2))
      f.push(i, i * 2)
  f.consume()
  ```
  
  @boost('queue_test_f01', qps=0.2, ) 
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  @Booster(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  @BoosterParams(queue_name='queue_test_f01', qps=0.2, )
  以上4种写法等效。
  ```
  - **Parameters:**
    - `self`
    - `queue_name: typing.Union[BoosterParams, str] = None`
    - `**kwargs`
```

## 🔧 技术实现

### 修改文件
- `nb_ai_context/ai_md_generator.py`

### 修改的位置

1. **类的 docstring**（约 720-728 行）
   - 移除了 `[:3]` 切片
   - 移除了 `...` 省略标记

2. **`__init__` 方法的 docstring**（约 738-752 行）
   - 从单行显示改为完整多行代码块显示
   - 添加 "Docstring:" 标题
   - 保持适当的缩进

3. **普通方法的 docstring**（约 771-783 行）
   - 添加智能判断逻辑
   - 单行：简短格式 `*text*`
   - 多行：代码块格式

4. **顶级函数的 docstring**（约 819-833 行）
   - 与普通方法相同的智能判断逻辑

## ✅ 优势

### 1. 信息完整性
- ✅ 不再截断重要的文档信息
- ✅ 完整保留作者的意图和说明
- ✅ 特别适合有详细文档的项目（如 funboost）

### 2. AI 理解能力提升
- ✅ AI 能获取完整的使用说明
- ✅ 能看到完整的示例代码
- ✅ 理解多种使用方式和注意事项

### 3. 格式优化
- ✅ 单行文档：保持简洁，用斜体显示
- ✅ 多行文档：使用代码块，保持格式
- ✅ 适当的缩进，保持层次结构清晰

## 📝 实际案例

### 案例：funboost 的 Booster 类

原始代码：
```python
class Booster:
    """
    funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
    这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
    一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

    pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

    Booster 是把Consumer 和 Publisher的方法集为一体。
    """
    
    def __init__(self, queue_name: typing.Union[BoosterParams, str] = None, **kwargs):
        """
        @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
        pydatinc pycharm编程代码补全,请安装 pydantic插件...
        
        强烈建议所有入参放在 BoosterParams() 中...
        
        ```python
        # 示例代码
        @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
        def f(a, b):
            print(a + b)
        ```
        
        以上4种写法等效。
        """
        pass
```

现在生成的 AST 元数据会**完整显示**：
- 所有 7 行的类文档字符串
- `__init__` 方法中包含示例代码的完整文档字符串

## 🎯 适用场景

特别适合以下类型的项目：
1. **框架类项目** - 如 funboost、FastAPI 等，需要详细的使用说明
2. **库项目** - 有详细的 API 文档和示例
3. **教学项目** - 代码中包含大量注释和说明
4. **企业项目** - 有规范的文档字符串标准

## 🔄 兼容性

- ✅ 完全向后兼容
- ✅ 单行文档字符串保持简洁格式
- ✅ 多行文档字符串使用代码块格式
- ✅ 不影响其他功能

## 💡 最佳实践

### 编写文档字符串时的建议

1. **第一行应该是简短的概述**
   ```python
   """简短的功能描述
   
   详细的说明...
   """
   ```

2. **包含示例代码**
   ```python
   """初始化配置
   
   Example:
       ```python
       config = Config(host="localhost", port=3306)
       ```
   """
   ```

3. **说明参数和返回值**
   ```python
   """执行查询
   
   Args:
       query: SQL 查询语句
       params: 查询参数
       
   Returns:
       查询结果列表
   """
   ```

现在这些详细的文档都会被完整提取到 AI 上下文中！

---

**nb_ai_context** - 完整、准确地为 AI 提供代码上下文 🚀
````

## tests/ai_docs/init_extraction_feature.md

````markdown
# __init__ 方法参数提取功能说明

## 🎯 功能描述

现在 `AiMdGenerator` 在提取 Python 类的 AST 元数据时，会**单独突出显示**类的 `__init__` 构造方法及其所有参数信息。

## ✨ 为什么这很重要？

类的 `__init__` 方法定义了如何创建类的实例，其参数列表是理解如何使用该类的关键：

- ✅ 知道需要传递哪些参数
- ✅ 了解参数的类型注解
- ✅ 查看哪些参数有默认值
- ✅ 快速理解类的初始化逻辑

## 📊 修改前后对比

### 修改前 ❌

`__init__` 方法混在普通的公有方法列表中，不够突出：

```markdown
**Public Methods (5):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10)`
- `def get_user(user_id: int) -> dict`
- `def create_user(name: str, email: str) -> bool`
- `def update_user(user_id: int, **kwargs) -> bool`
- `def delete_user(user_id: int) -> bool`
```

### 修改后 ✅

`__init__` 方法被单独提取，并详细显示每个参数：

```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, allowed_domains: Optional[List[str]] = None, **kwargs)`
  - *初始化用户服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `allowed_domains: Optional[List[str]] = None`
    - `**kwargs`

**Public Methods (4):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
- `def update_user(user_id: int, **kwargs) -> bool`
  - *更新用户信息*
- `def delete_user(user_id: int) -> bool`
  - *删除用户*
```

## 🔧 技术实现

修改位置：`nb_ai_context/ai_md_generator.py` 的 `_format_py_metadata_as_markdown` 方法

### 核心逻辑

1. **查找 __init__ 方法**
   ```python
   init_method = None
   for method in cls["methods"]:
       if method["name"] == "__init__":
           init_method = method
           break
   ```

2. **单独显示构造器**
   - 使用专门的标题 `🔧 Constructor (\`__init__\`):`
   - 显示完整的方法签名
   - 显示文档字符串
   - **逐个列出每个参数及其类型和默认值**

3. **从普通方法中排除**
   ```python
   public_methods = [m for m in cls["methods"] if m["is_public"] and m["name"] != "__init__"]
   ```

## 📝 完整示例

假设有以下 Python 类：

```python
from typing import Optional, List

class UserService:
    """用户服务类"""
    
    def __init__(
        self, 
        database_url: str,
        timeout: int = 30,
        max_connections: int = 10,
        enable_cache: bool = True,
        allowed_domains: Optional[List[str]] = None,
        **kwargs
    ):
        """初始化用户服务
        
        Args:
            database_url: 数据库连接URL
            timeout: 超时时间（秒）
            max_connections: 最大连接数
            enable_cache: 是否启用缓存
            allowed_domains: 允许的域名列表
        """
        self.database_url = database_url
        self.timeout = timeout
    
    def get_user(self, user_id: int) -> dict:
        """获取用户信息"""
        pass
    
    def create_user(self, name: str, email: str) -> bool:
        """创建新用户"""
        pass
```

### 生成的 AST 元数据 Markdown

```markdown
### 📄 Python File Metadata: `src/user_service.py`

#### 📦 Imports
- `from typing import Optional, List`

#### 🏛️ Classes (1)

##### 📌 `class UserService`
*Line: 3*

**Docstring:**
```
用户服务类
```

**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, enable_cache: bool = True, allowed_domains: Optional[List[str]] = None, **kwargs)`
  - *初始化用户服务*
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `enable_cache: bool = True`
    - `allowed_domains: Optional[List[str]] = None`
    - `**kwargs`

**Public Methods (2):**
- `def get_user(user_id: int) -> dict`
  - *获取用户信息*
- `def create_user(name: str, email: str) -> bool`
  - *创建新用户*
```

## ✅ 优势总结

1. **更清晰** - `__init__` 作为类的构造器，被特别突出显示
2. **更详细** - 每个参数单独一行，类型和默认值一目了然
3. **更易读** - AI 和人类都能快速理解如何实例化这个类
4. **更专业** - 符合 API 文档的标准格式

## 🎯 对 AI 大模型的价值

当 AI 看到这样的元数据时，它能立即理解：

- 这个类需要什么参数才能创建实例
- 哪些参数是必需的，哪些是可选的
- 参数的类型约束是什么
- 有哪些默认配置

这大大提升了 AI 生成正确代码的能力！

## 🔄 兼容性

- ✅ 如果类没有 `__init__` 方法，不会显示构造器部分
- ✅ 不影响其他方法的显示
- ✅ 完全向后兼容
- ✅ 支持所有类型的参数（位置参数、关键字参数、*args、**kwargs）
````

## tests/ai_docs/t.md

``````markdown
## 1 二级别

aaa
`````md
# 1  一级别

内容111

````python
print("hello")
````

## 1.2
嘿嘿
`````

<div >
# 1 2323
</div>
[nb_ai_context_all_docs_and_codes.md](../../markdown_gen_files_git_ignore/ai_md_files/nb_ai_context_all_docs_and_codes.md)


<project_context project_name="nb_ai_context">

    <project_summary>
        # markdown content namespace: nb_ai_context project summary
        - nb_ai_context is a powerful...
    </project_summary>[nb_ai_context_all_docs_and_codes.md](../../markdown_gen_files_git_ignore/ai_md_files/nb_ai_context_all_docs_and_codes.md)

    <core_metadata>
        ## 📋 Core Source Files Metadata
        ...
    </core_metadata>

    <project_structure>
        ## File Tree
        ```
        ├── README.md
        └── src/
        ```
        ## Included Files
        - README.md
        - src/main.py
    </project_structure>

    <repository_files>
        
        <div class="markdown-file" file_path="README.md">
            --- **start of file: README.md** ---
            # Title
            ...
            --- **end of file: README.md** ---
        </div>

        <file path="src/main.py">
            <ast_metadata>
                ### 📄 Python File Metadata...
                class AiMdGenerator...
            </ast_metadata>
            
            <content>
                --- **start of file: src/main.py** ---
                ```python
                import os
                ...
                ```
                --- **end of file: src/main.py** ---
            </content>
        </file>

    </repository_files>

</project_context>
``````

## tests/ai_docs/UPDATE_SUMMARY.md

````markdown
# nb_ai_context 更新摘要

## 📅 更新日期
2025-11-21

## 🎯 本次更新的两个重要功能

### 功能 1️⃣：突出显示类的 `__init__` 构造方法 ⭐⭐⭐⭐⭐

#### 问题
之前 `__init__` 方法混在普通公有方法列表中，不够突出。

#### 解决方案
- ✅ `__init__` 方法被单独提取，使用专门的标题 `🔧 Constructor (\`__init__\`):`
- ✅ 显示完整的方法签名
- ✅ 显示完整的文档字符串
- ✅ **逐个列出每个参数**，包括类型注解和默认值
- ✅ 支持所有参数类型：位置参数、关键字参数、`*args`、`**kwargs`
- ✅ 从公有方法列表中排除，避免重复

#### 效果
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(database_url: str, timeout: int = 30, max_connections: int = 10, **kwargs)`
  - **Docstring:**
  ```
  初始化数据库服务
  
  Args:
      database_url: 数据库连接URL
      timeout: 超时时间（秒）
      max_connections: 最大连接数
  ```
  - **Parameters:**
    - `database_url: str`
    - `timeout: int = 30`
    - `max_connections: int = 10`
    - `**kwargs`

**Public Methods (4):**  👈 注意：__init__ 已被排除，数量减少了
- `def get_user(user_id: int) -> dict`
- ...
```

---

### 功能 2️⃣：完整显示文档字符串 ⭐⭐⭐⭐⭐

#### 问题
之前版本对文档字符串进行了截断：
- 类的 docstring：只显示前 3 行
- `__init__` 方法：只显示第 1 行
- 普通方法：只显示第 1 行
- 顶级函数：只显示第 1 行

这导致**大量重要文档信息丢失**！

#### 解决方案

##### 1. 类的 docstring - **完整显示**
```python
# 之前：只显示前3行
docstring_lines = cls["docstring"].split("\n")[:3]

# 现在：显示所有行
docstring_lines = cls["docstring"].split("\n")
```

##### 2. `__init__` 方法的 docstring - **完整显示**
```markdown
**🔧 Constructor (`__init__`):**
- `def __init__(...)`
  - **Docstring:**
  ```
  完整的文档字符串
  包括所有行
  包括示例代码
  包括注意事项
  ```
  - **Parameters:**
    - ...
```

##### 3. 普通方法的 docstring - **智能显示**
- 单行文档：保持简洁 `*简短描述*`
- 多行文档：使用代码块格式，完整显示

##### 4. 顶级函数的 docstring - **智能显示**
- 与普通方法相同的逻辑

#### 效果对比

**修改前** ❌
```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法...
...  👈 后面的重要信息都被截断了！
```

**🔧 Constructor (`__init__`):**
- `def __init__(...)`
  - *@boost 这是funboost框架最重要的一个函数*  👈 只有第一行
```

**修改后** ✅
```markdown
**Docstring:**
```
funboost极其重视代码能在pycharm下自动补全。元编程经常造成在pycharm下代码无法自动补全提示，主要是实现代码补全难。
这种__call__写法在pycahrm下 不仅能补全消费函数的 push consume等方法，也能补全函数本身的入参，一举两得。代码能自动补全很重要。
一个函数fun被 boost装饰器装饰后， isinstance(fun,Booster) 为True.

pydatinc pycharm编程代码补全,请安装 pydantic插件, 在pycharm的  file -> settings -> Plugins -> 输入 pydantic 搜索,点击安装 pydantic 插件.

Booster 是把Consumer 和 Publisher的方法集为一体。
```
👆 所有信息都完整保留！

**🔧 Constructor (`__init__`):**
- `def __init__(queue_name: typing.Union[BoosterParams, str] = None, **kwargs)`
  - **Docstring:**
  ```
  @boost 这是funboost框架最重要的一个函数，必须看懂BoosterParams里面的入参有哪些。
  pydatinc pycharm编程代码补全,请安装 pydantic插件...
  
  强烈建议所有入参放在 BoosterParams() 中...
  
  ```python
  # 示例代码
  @boost(BoosterParams(queue_name='queue_test_f01', qps=0.2, ))
  def f(a, b):
      print(a + b)
  ```
  
  以上4种写法等效。
  ```
  👆 包括示例代码在内的所有文档都完整显示！
  - **Parameters:**
    - `queue_name: typing.Union[BoosterParams, str] = None`
    - `**kwargs`
```

---

## 🎯 为什么这两个功能非常重要？

### 对开发者的价值
1. **快速理解类的使用方式** - `__init__` 参数一目了然
2. **完整的文档信息** - 不再错过重要的使用说明和示例
3. **更好的代码审查** - 完整的上下文信息
4. **新人友好** - 详细的文档帮助快速上手

### 对 AI 大模型的价值
1. **准确的代码生成** - 知道如何正确实例化类
2. **减少幻觉** - 完整的文档信息减少 AI 的猜测
3. **理解项目架构** - 从构造函数参数理解类的依赖关系
4. **完整的示例代码** - 学习项目的最佳实践

### 特别适合的项目类型
- ✅ **框架类项目**：如 funboost、FastAPI 等
- ✅ **库项目**：有详细的 API 文档
- ✅ **教学项目**：代码中包含大量注释和说明
- ✅ **企业项目**：有规范的文档字符串标准

---

## 🔧 技术实现

### 修改文件
`nb_ai_context/ai_md_generator.py`

### 修改的方法
`_format_py_metadata_as_markdown()`

### 修改的行数区间
- 720-726 行：类的 docstring
- 735-756 行：`__init__` 方法的提取和显示
- 759 行：从公有方法中排除 `__init__`
- 773-785 行：普通方法的 docstring
- 819-833 行：顶级函数的 docstring

---

## ✅ 兼容性保证

- ✅ **完全向后兼容** - 不影响现有功能
- ✅ **优雅降级** - 如果类没有 `__init__`，不显示构造器部分
- ✅ **智能格式化** - 单行/多行文档自动选择合适的显示格式
- ✅ **无破坏性** - 不改变其他部分的行为

---

## 📊 实际应用场景

### 场景 1：理解框架的核心类
```python
# funboost 的 Booster 类
# 现在能看到完整的 7 行类文档 + __init__ 的详细使用说明（包括4种等效写法）
```

### 场景 2：学习正确的使用方式
```python
# 从 __init__ 参数列表看出：
# - 哪些参数是必需的
# - 哪些参数有默认值
# - 参数的类型约束
# - 从文档字符串看到示例代码
```

### 场景 3：AI 代码生成
```python
# AI 现在能看到完整信息：
# - 构造函数的所有参数
# - 每个参数的类型和默认值
# - 详细的使用说明和示例
# → 生成的代码更准确！
```

---

## 🚀 使用方式不变

```python
from nb_ai_context import AiMdGenerator

(
    AiMdGenerator("output.md")
    .set_project_propery(
        project_name="my_project",
        project_root=r"D:\codes\my_project"
    )
    .clear_text()
    .merge_from_dir(
        relative_dir_name="src",
        as_title="源代码",
        should_include_suffixes=[".py"],
        include_ast_metadata=True,  # 开启 AST 元数据提取
    )
)
```

生成的 Markdown 文件中，每个类的 `__init__` 方法都会被突出显示，所有文档字符串都会完整保留！

---

## 📝 相关文档

- `tests/ai_docs/init_extraction_feature.md` - __init__ 提取功能详细说明
- `tests/ai_docs/CHANGELOG_init_extraction.md` - __init__ 功能更新日志
- `tests/ai_docs/complete_docstring_extraction.md` - 完整文档字符串提取说明
- `tests/ai_codes/demo_init_extraction.py` - 演示示例代码

---

**nb_ai_context** - 让 AI 获得完整、准确的代码上下文 🚀

现在支持：
- ✅ 突出显示 `__init__` 构造方法
- ✅ 完整显示所有文档字符串
- ✅ 智能格式化输出
- ✅ 完美支持复杂项目（如 funboost）
````

## Statistics

- Total Files: 20
- Total Characters: 239953
- Total Tokens: 0
