Community写作与编辑github.com

BESSER-PEARL/BESSER-Skills

为AI编码代理提供BESSER深度知识的Agent技能:涵盖UML类建模、代码生成器、故障排除及贡献者工作流程。

BESSER-Skills 是什么?

BESSER-PEARL/BESSER-Skills 是一个专为AI编码代理设计的技能包,旨在为开发者提供在BESSER开源项目中高效贡献与开发的全面知识。该技能详细描述了如何设置开发环境、新增代码生成器、扩展元模型/子DSL、编写测试、构建文档以及遵循代码风格与提交规范。它特别强调了生成器的确定性和测试的完备性,确保每次运行输出的可预测性与质量。此技能支持Claude Code、Cursor、Codex等多种AI代理平台,但核心指导是通用的,适用于任何希望通过AI辅助参与BESSER项目的开发者。通过本技能,AI代理可以准确理解BESSER的架构约定、贡献流程及常见注意事项,从而自动执行代码生成、测试编写、文档更新等复杂任务,显著降低上手门槛。

兼容平台✓Claude Code~Codex CLI✓Cursor✓Windsurf
npx skills add BESSER-PEARL/BESSER-Skills

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

为BESSER做贡献

本技能涵盖了为BESSER代码库做贡献的程序化工作流程。有关架构细节和代码约定,请同时参考项目自动加载到上下文中的 CLAUDE.md 文件。


开发环境搭建

git clone https://github.com/<你的用户名>/BESSER.git
cd BESSER
python -m venv venv
# Windows: venv\Scripts\activate
# macOS/Linux: source venv/bin/activate
pip install -r requirements.txt
pip install -r docs/requirements.txt  # 用于构建文档
pip install -e .                       # 可编辑安装

验证:python tests/BUML/metamodel/structural/library/library.py

需要 Python 3.10+(Django 5.x 依赖)。


添加新的生成器

这是最常见的贡献类型。要跳过样板代码,请运行内置脚手架:

python scripts/scaffold_generator.py <名称> <BESSER仓库路径>
# 例如:python scripts/scaffold_generator.py graphql ~/code/BESSER

这将写入一个可立即运行的工作存根包(生成器类 + Jinja 模板 + pytest)。您仍然需要实现真正的逻辑、在 Web 编辑器配置中注册生成器并添加文档——这些内容将在下面的步骤中介绍。

如果您更喜欢手动操作,请遵循以下 6 个步骤:

第 1 步:创建生成器包

besser/generators/my_generator/
  __init__.py               # 导出:from .my_generator import MyGenerator
  my_generator.py           # 生成器类
  templates/                # Jinja2 模板
    output_template.py.j2

从现有的简单生成器(例如 python_classes/)复制脚手架。

第 2 步:实现 GeneratorInterface

# besser/generators/my_generator/my_generator.py
import os
from jinja2 import Environment, FileSystemLoader
from besser.generators import GeneratorInterface

class MyGenerator(GeneratorInterface):

    def __init__(self, model, output_dir: str = None):
        super().__init__(model, output_dir)

    def generate(self):
        """从 B-UML 模型生成输出。"""
        templates_path = os.path.join(os.path.dirname(__file__), "templates")
        env = Environment(
            loader=FileSystemLoader(templates_path),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        template = env.get_template("output_template.py.j2")
        output = template.render(
            classes=self.model.get_classes(),
            associations=self.model.associations,
        )
        file_path = self.build_generation_path("my_output.py")
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(output)

关键要求:

  • 调用 super().__init__(model, output_dir) —— 这设置了 self.model 和 self.output_dir。
  • 使用 self.build_generation_path(filename) 获取正确的输出文件路径。
  • 如果只需要目录路径,请使用 self.build_generation_dir()。
  • Jinja2 模板应位于生成器旁边的 templates/ 子目录中。
  • 输出必须是确定性的 —— 相同的模型每次都产生相同的输出(没有时间戳、随机值等)。
  • 可重用的辅助函数放在 besser/utilities/ 中,而不是生成器中。

第 3 步:编写 Jinja2 模板

{# besser/generators/my_generator/templates/output_template.py.j2 #}
# 由 BESSER MyGenerator 生成
{% for cls in classes %}
class {{ cls.name }}:
    {% for attr in cls.attributes %}
    {{ attr.name }}: {{ attr.type.name }}
    {% endfor %}

{% endfor %}

如果生成 JSX/React,请使用非标准分隔符以避免冲突:

env = Environment(
    loader=FileSystemLoader(templates_path),
    variable_start_string="[[",
    variable_end_string="]]",
)

第 4 步:在 Web 编辑器后端注册

将您的生成器添加到 besser/utilities/web_modeling_editor/backend/config/generators.py:

from besser.generators.my_generator import MyGenerator

GENERATORS = {
    # ... 现有生成器 ...
    "my_generator": GeneratorInfo(
        generator_class=MyGenerator,
        output_type="file",           # "file" 表示单个文件,"zip" 表示多个文件
        file_extension=".py",         # 输出的文件扩展名
        category="my_category",       # 分组标签
        requires_class_diagram=True,   # 如果需要 DomainModel 则为 True
    ),
}

如果生成器产生特定的文件名,也需更新同一文件中的 get_filename_for_generator()。

第 5 步:编写测试

创建 tests/generators/my_generator/test_my_generator.py:

import os
import shutil
import pytest
from besser.BUML.metamodel.structural import (
    DomainModel, Class, Property, BinaryAssociation,
    Multiplicity, StringType, IntegerType,
)
from besser.generators.my_generator import MyGenerator


@pytest.fixture
def domain_model():
    """构建一个小型测试模型。"""
    name_prop = Property(name="name", type=StringType)
    age_prop = Property(name="age", type=IntegerType)
    person = Class(name="Person", attributes={name_prop, age_prop})
    model = DomainModel(name="TestModel", types={person})
    return model


@pytest.fixture
def output_dir(tmp_path):
    return str(tmp_path)


def test_generate_creates_output_file(domain_model, output_dir):
    gen = MyGenerator(model=domain_model, output_dir=output_dir)
    gen.generate()
    assert os.path.exists(os.path.join(output_dir, "my_output.py"))


def test_generate_contains_class_name(domain_model, output_dir):
    gen = MyGenerator(model=domain_model, output_dir=output_dir)
    gen.generate()
    with open(os.path.join(output_dir, "my_output.py")) as f:
        content = f.read()
    assert "Person" in content
    assert "name" in content
    assert "age" in content

整个代码库中使用的测试模式:

  • 使用 tmp_path 或 tmpdirfixture 作为输出目录(自动清理)。
  • 构建小型内联模型作为fixture —— 不要依赖外部文件。
  • 测试结构和内容(文件存在,类名存在)和业务逻辑。
  • 对于生成可导入 Python 代码的生成器,使用 importlib 动态导入并测试生成的代码。
  • 如果不使用 pytest 的 tmp fixture,使用 shutil.rmtree() 进行清理。

第 6 步:文档

在 docs/source/generators/my_generator.rst 中添加文档:

MyGenerator
===========

MyGenerator 从 B-UML 领域模型生成……

前提条件
------------

- 已安装 BESSER
- …

用法
-----

.. code-block:: python

   from besser.generators.my_generator import MyGenerator

   gen = MyGenerator(model=my_model, output_dir="./output")
   gen.generate()

输出
------

生成器生成一个包含……的单个文件 ``my_output.py``。

将其添加到 docs/source/generators.rst 的生成器索引中。


添加新的元模型 / 子 DSL

第 1 步:设计元模型

在 besser/BUML/metamodel/your_dsl/ 中创建新类:

besser/BUML/metamodel/your_dsl/
  __init__.py
  your_dsl.py    # 元模型类

遵循现有模式:

  • 扩展 NamedElement 或 Element 作为基类。
  • 使用带有 getter/setter 验证的私有属性。
  • 保持命名与现有元模型包一致。

第 2 步:添加转换器(如果需要 Web 编辑器集成)

转换器在前端 JSON 和 B-UML 对象之间进行转换:

besser/utilities/web_modeling_editor/backend/services/converters/
  json_to_buml/process_your_dsl.py    # JSON → BUML
  buml_to_json/your_dsl_to_json.py    # BUML → JSON

转换器必须是对称的:在一个方向支持的任何功能都必须在另一个方向正常工作。测试往返:JSON → BUML → JSON 应产生原始 JSON。

第 3 步:添加验证

如果元模型具有超出 setter 强制执行的约束:

def validate(self, raise_exception=True):
    errors = []
    warnings = []
    # 检查结构规则
    if some_bad_condition:
        errors.append("描述性错误消息")
    result = {"success": len(errors) == 0, "errors": errors, "warnings": warnings}
    if raise_exception and errors:
        raise ValueError("\n".join(errors))
    return result

第 4 步:接入后端

  • 在 besser/utilities/web_modeling_editor/backend/backend.py 中添加端点。
  • 在 backend/models/ 中添加 Pydantic 请求/响应模型。
  • 如果适用,在 /validate-diagram 下添加验证路由。

第 5 步:编写测试

将测试放在 tests/BUML/metamodel/your_dsl/ 中:

  • 有效模型构建测试。
  • 无效模型测试(期望 ValueError、TypeError)。
  • 如果适用,进行往返转换器测试。

第 6 步:文档

  • 元模型文档:docs/source/buml_language/your_dsl.rst
  • 添加到 docs/source/buml_language.rst 索引。

测试

运行所有测试

python -m pytest

运行特定测试

# 按目录
python -m pytest tests/generators/sqlalchemy/

# 按关键字
python -m pytest -k "test_django"

# 带详细输出
python -m pytest -v tests/BUML/metamodel/structural/

# 在第一次失败时停止
python -m pytest -x

测试约定

  • 测试文件:test_*.py
  • 测试函数:test_*
  • 使用 @pytest.fixture 处理可重用的模型和目录。
  • 使用 tmp_path 作为输出目录(由 pytest 自动清理)。
  • 断言特定内容,而不仅仅是文件存在。
  • 使用 pytest.raises(ValueError) 测试错误条件。

测试什么

对于元模型更改:构造验证、setter 约束、对无效输入的预期错误、validate() 结果。

对于生成器:输出文件存在、输出中的关键内容(类名、关系映射)、边界情况(空模型、深度继承、多对多)。

对于转换器:往返保真度(JSON → BUML → JSON 应等效)。


构建文档

cd docs

# Windows:
make.bat html

# macOS/Linux:
make html

# 查看:
# 在浏览器中打开 docs/build/html/index.html

文档使用带有 Furo 主题的 Sphinx。源文件是位于 docs/source/ 中的 reStructuredText (.rst) 文件。

文档结构

路径内容
docs/source/buml_language/元模型文档
docs/source/generators/生成器文档
docs/source/web_editor.rstWeb 编辑器 API 文档
docs/source/utilities/实用程序文档
docs/source/contributing/贡献者指南
docs/source/releases/发布说明
docs/source/_static/共享图片和资源
docs/source/img/图表和截图

交叉引用

使用 Sphinx 角色进行交叉链接:

  • :doc:\generators/django`` — 链接到另一个文档页面
  • :mod:\besser.generators.django`` — 链接到模块 API
  • :class:\besser.BUML.metamodel.structural.Class`` — 链接到类 API
  • :ref:\label-name`` — 链接到带标签的部分

代码风格

  • PEP 8,4 空格缩进。
  • 120 字符行限制(在 pyproject.toml 的 [tool.pylint.'FORMAT'] 下配置)。
  • 公共 API 的类型提示。
  • PEP 257 文档字符串。
  • 导入顺序:标准库,第三方,本地模块。
  • snake_case 函数/变量,PascalCase 类,UPPER_CASE 常量。
  • 不允许通配符导入,不允许隐式重新导出。
  • 代码检查:pylint(当前未配置 ruff/black/pre-commit hooks)。

提交和 PR 约定

提交消息

使用常规提交:

feat: add Terraform generator for AWS EKS
fix: correct multiplicity parsing for 0..* associations
refactor: extract shared template helpers to besser.utilities
docs: document QiskitGenerator usage
test: add round-trip tests for GUI model converter

拉取请求工作流

  1. 创建主题分支:git checkout -b feature/add-my-generator。
  2. 进行重点明确、逻辑分组的提交。
  3. 在本地运行测试:python -m pytest。
  4. 构建文档:cd docs && make html。
  5. 变基到最新的 master:git fetch origin && git rebase origin/master。
  6. 推送并针对 master 打开 PR。
  7. 填写 PR 模板:描述、执行的测试、额外情况。
  8. 合并需要两位维护者批准(14 天后一个批准即可)。

跨仓库更改(BESSER + 前端)

当更改同时影响 BESSER 和 Web 编辑器前端时:

  1. 在 WME 仓库 中实现并提交 WME 更改。
  2. 在此仓库中实现 BESSER 更改。
  3. 更新子模块指针:
    cd besser/utilities/web_modeling_editor/frontend
    git fetch && git checkout <commit>
    cd ../../../..
    git add besser/utilities/web_modeling_editor/frontend
    
  4. 链接两个 PR,以便审阅者按正确顺序合并。

CI / 发布

  • CI:没有自动化测试工作流 —— 测试在 PR 之前本地运行。
  • 发布:通过创建 GitHub Release 触发。python-publish.yml 工作流使用 PYPI_API_TOKEN 构建并发布到 PyPI。
  • 版本:在 setup.cfg 的 [metadata] version 下定义。
  • 发布说明:作为 RST 文件添加到 docs/source/releases/。

常见贡献陷阱

  1. 不要重复逻辑 —— 共享辅助函数放在 besser/utilities/ 中,而不是单个生成器中。
  2. 保持确定性 —— 生成器必须为相同输入生成相同输出。
  3. 保持转换器对称 —— 如果 JSON → BUML 支持某个功能,BUML → JSON 也必须支持。
  4. 更新文档 —— 任何后端更改可能都需要更新 docs/source/。
  5. 除非明确要求,否则不要接触前端子模块。UI 更改应转到上游 WME 仓库。
  6. 清理资源 —— 始终对临时目录和文件句柄使用 try/finally。
  7. 测试往返 —— 特别是对于转换器(JSON → BUML → JSON 应为恒等)。

相关技能