besser-generators — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited besser-generators (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Every BESSER generator implements the same shape:
class GeneratorInterface(ABC):
def __init__(self, model, output_dir=None): ...
def generate(self): ...If output_dir is omitted, output goes to <cwd>/output/. The directory is created automatically (os.makedirs(..., exist_ok=True)).
Regeneration always overwrites. Every call to generate() replaces the output files entirely. This is by design — the model is the source of truth. Customizations belong in separate files (see "Safe customization" below). Once you have generated, the besser-user skill's "Verification checklist" covers what to check next (files exist, syntax parses, it runs).
Generate the baseline, then build on top. The point of these generators is that you don't hand-write the boilerplate. The output is template-based and deterministic — the same model produces the same code on every run — so regenerate the baseline freely (cheap and identical each time) and spend your effort only where the work is actually custom: extending and overriding in separate files so a re-run never clobbers it. Often the generated code is all you need; when it isn't, the "Safe customization" patterns below are how you build on top without fighting regeneration.
Per-generator detail (inputs, outputs, options, templates, gotchas) lives in references/. Find your target, then read the listed reference:
| Want | Generator | Reference |
|---|---|---|
| Python data classes | PythonGenerator | python-and-data.md |
| Java classes | JavaGenerator | python-and-data.md |
| Pydantic models (with optional OCL→validator) | PydanticGenerator | python-and-data.md |
| JSON Schema (regular or NGSI-LD) | JSONSchemaGenerator | python-and-data.md |
| JSON data from an ObjectModel (instances) | JSONObjectGenerator | python-and-data.md |
| RDF Turtle vocabulary | RDFGenerator | python-and-data.md |
| SQLAlchemy ORM | SQLAlchemyGenerator | persistence.md |
| Raw SQL DDL | SQLGenerator | persistence.md |
| Supabase (Postgres + RLS) schema | SupabaseGenerator | persistence.md |
| FastAPI + ORM + Pydantic stack | BackendGenerator | api-and-web.md |
| Standalone REST API | RESTAPIGenerator | api-and-web.md |
| Django project | DjangoGenerator | api-and-web.md |
| Full-stack web app (React + FastAPI + Docker) | WebAppGenerator | api-and-web.md |
| Flutter mobile app | FlutterGenerator | api-and-web.md |
| Conversational agent / chatbot | BAFGenerator | agents-and-other.md |
| Quantum circuit | QiskitGenerator | agents-and-other.md |
| Terraform infrastructure | TerraformGenerator | agents-and-other.md |
| PyTorch / TensorFlow neural net | PytorchGenerator / TFGenerator | agents-and-other.md |
Not generating, but stuck?
references/debugging.md.By default a generator writes to <cwd>/output/ and produces the single file documented in its reference. The exceptions worth memorizing:
./output_backend/ (not ./output/); emits main_api.py, pydantic_classes.py, sql_alchemy.py.output_dir must be specified; emits frontend/, backend/, docker-compose.yml.myproject/myapp/models.py, …).For the exact output filename of any other generator, see its row in the relevant references/ file.
Since generate() overwrites everything, customizations need a strategy that survives regeneration.
Many "I need to tweak the output" requests are already covered by parameters:
SQLAlchemyGenerator.generate(dbms="postgresql") — switch dialectBackendGenerator(http_methods=["GET", "POST"]) — limit endpointsBackendGenerator(nested_creations=True) — allow nested createsDjangoGenerator(containerization=True) — add Docker supportQiskitGenerator(backend_type="ibm_quantum", shots=2048)BAFGenerator(generation_mode=GenerationMode.CODE_ONLY)Put custom code in separate files that import from generated code. Generated files are overwritten; your extension files are not.
output/
sql_alchemy.py # GENERATED — do not edit
custom_queries.py # YOUR CODE — imports from sql_alchemy.py
custom_endpoints.py # YOUR CODE — imports from main_api.py# custom_queries.py — safe from regeneration
# The generated sql_alchemy.py exposes `Base` and `engine` (not a Session);
# create the Session yourself from SQLAlchemy.
from sqlalchemy.orm import Session
from sql_alchemy import Book, Author, engine
def get_books_by_author(author_name: str):
with Session(engine) as session:
return session.query(Book).join(Author).filter(Author.name == author_name).all()Mount the generated FastAPI app and add your routes on top:
# app.py — YOUR CODE, not generated
from main_api import app
@app.get("/custom/stats")
def custom_stats():
return {"status": "ok"}Often the cleanest fix: define methods on your model classes with MethodImplementationType.BAL or MethodImplementationType.CODE, and BackendGenerator emits matching REST endpoints. Custom logic stays in the model, where regeneration cannot touch it.
from besser.BUML.metamodel.structural import (
Method, Parameter, StringType, MethodImplementationType,
)
# Pass the implementation via the Method constructor: `code` holds the body
# and `implementation_type` selects BAL or CODE. (There is no separate
# MethodImplementation class, and the body is not assigned afterwards.)
search = Method(
name="search_by_title",
parameters=[Parameter(name="keyword", type=StringType)],
type=StringType,
code="return session.query(Book).filter(Book.title.contains(keyword)).all()",
implementation_type=MethodImplementationType.BAL,
)
book.add_method(search)The method body is inserted verbatim into the generated FastAPI handler — it is not parsed or scoped by the metamodel. So any name it references (here session) must actually exist in the generated handler at that point. Treat the body as code you are responsible for: check the generated main_api.py to confirm what is in scope (e.g. the SQLAlchemy session variable name the handler uses) and reference exactly those names.
Copy a generator's template, edit it, and point the generator at your copy. Templates live in besser/generators/{name}/templates/. Powerful, but BESSER updates won't propagate to your overrides — use sparingly.
git diff output/ > my_customizations.patch # save once
cd output && git apply ../my_customizations.patch # reapply after each generateWorks for small, stable changes. For larger customizations, prefer #2 or #4.
will be overwritten next time you generate.
or template overrides almost always suffice.
Separation keeps your code safe across regeneration cycles.
Several generators orchestrate sub-generators (e.g. WebAppGenerator drives ReactGenerator + BackendGenerator + optional BAFGenerator; BackendGenerator drives RESTAPIGenerator + PydanticGenerator + SQLAlchemyGenerator; SQLGenerator drives SQLAlchemyGenerator in a subprocess). When debugging, trace which sub-generator is actually failing — the error rarely lives in the wrapper. The full tree and per-symptom recipes are in references/debugging.md.
A generator only appears in the web editor's "Generate" dropdown once it is registered in besser/utilities/web_modeling_editor/backend/config/generators.py via two hooks: an entry in SUPPORTED_GENERATORS and its filename in get_filename_for_generator(). Both are required for the dropdown to populate. For the annotated GeneratorInfo snippet and the full authoring workflow (package layout, tests, docs, PR), see the besser-dev skill's references/adding-a-generator.md.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.