Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
test:
name: Go tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
# Current Go: older toolchains produce macOS binaries that new macOS refuses to load.
go-version: stable
- run: go vet ./...
- run: go test -race ./...
if: runner.os != 'Windows'
- run: go test ./...
if: runner.os == 'Windows'

integration:
name: Lua, Luau and end-to-end tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
# Current Go: older toolchains produce macOS binaries that new macOS refuses to load.
go-version: stable
- name: Install Lua 5.1 and the official Luau toolchain
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq lua5.1
curl -sSL -o /tmp/luau.zip https://github.com/luau-lang/luau/releases/latest/download/luau-ubuntu.zip
mkdir -p "$HOME/luau" && unzip -q /tmp/luau.zip -d "$HOME/luau"
echo "$HOME/luau" >> "$GITHUB_PATH"
- name: Rename safety checked by executing code (Lua 5.1 + Luau VM)
run: go test ./internal/luau -run 'Execution' -v
- name: Client compiles as Luau
run: luau-compile --null client/betterdecompiler.lua
- name: Client unit tests
run: lua5.1 client/tests/run.lua
- name: End-to-end (server + mock AI provider + Lua client)
run: scripts/e2e.sh
- name: Cross-compile every release target
run: scripts/build.sh ci
73 changes: 73 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Release

# Publishes a GitHub release with binaries for every platform. Start it by:
# - pushing a tag: git tag v2.0.1 && git push origin v2.0.1
# - pushing a commit titled: "Release v2.0.1" (no tag push needed)
# - running it from the Actions tab: Release -> Run workflow (enter the version)
# In the last two cases the tag is created on the commit being released.

on:
push:
tags: ["v*"]
branches: ["**"]
workflow_dispatch:
inputs:
tag:
description: Version to publish, e.g. v2.0.1
required: true

permissions:
contents: write

jobs:
release:
if: >-
github.event_name == 'workflow_dispatch' ||
github.ref_type == 'tag' ||
startsWith(github.event.head_commit.message, 'Release v')
runs-on: ubuntu-latest
steps:
- name: Resolve version
env:
EVENT: ${{ github.event_name }}
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
INPUT_TAG: ${{ github.event.inputs.tag }}
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
if [ "$EVENT" = workflow_dispatch ]; then tag="$INPUT_TAG"
elif [ "$REF_TYPE" = tag ]; then tag="$REF_NAME"
else tag="$(printf '%s\n' "$COMMIT_MSG" | head -n1 | awk '{print $2}')"; fi
if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$'; then
echo "::error::'$tag' is not a version like v2.0.1"; exit 1
fi
echo "TAG=$tag" >> "$GITHUB_ENV"
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
# Current Go: older toolchains produce macOS binaries that new macOS refuses to load.
go-version: stable
- name: Test
run: |
go vet ./...
go test ./...
- name: Build all platforms
run: scripts/build.sh "$TAG"
- name: Release notes
run: |
awk -v tag="$TAG" '
$0 ~ "^## \\[" tag "\\]" {on=1; next}
on && /^## \[/ {exit}
on {print}
' CHANGELOG.md > notes.md
[ -s notes.md ] || echo "See CHANGELOG.md" > notes.md
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
gh release upload "$TAG" dist/*.zip dist/*.tar.gz dist/betterdecompiler.lua dist/SHA256SUMS.txt --clobber
else
gh release create "$TAG" dist/*.zip dist/*.tar.gz dist/betterdecompiler.lua dist/SHA256SUMS.txt \
--title "BetterDecompiler $TAG" --notes-file notes.md --latest --target "$GITHUB_SHA"
fi
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/dist/
/betterdecompiler
/betterdecompiler.exe
/mockllm
*.test
*.out
.DS_Store
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Changelog

## [v2.0.0]

A complete rewrite of BetterDecompiler.

### Highlights

- **Use your own API key** with Anthropic, OpenAI, Google Gemini, OpenRouter, Groq, DeepSeek, Mistral or xAI,
any OpenAI-, Anthropic- or Gemini-compatible endpoint (Azure, proxies, self-hosted), or free local models via
Ollama and LM Studio. The unofficial g4f proxy is gone.
- **Offline mode**: no key needed. Names are inferred from Roblox API usage (services, `WaitForChild` names,
`Instance.new`, event callbacks, loops, constructors).
- **Rename mode (new default)**: the AI only proposes names and comments as compact JSON, and a scope-aware Luau
parser applies them. Every rename is verified so the code's behavior cannot change. It is faster and much cheaper
than having the AI re-type the whole script.
- **Rewrite mode** is still available for full AI rewrites. Its output is checked for syntax errors.
- **One self-contained program** for Windows, macOS (Intel and Apple Silicon) and Linux (x64, ARM64, ARMv7). No
Python or pip.
- **Built-in web UI**, a **CLI** for files, folders and stdin, and a **JSON HTTP API**.
- **New executor client** (`betterdecompiler.lua`) that hooks the global `decompile`, so Dex and other tools get
cleaned output automatically. It supports `request`, `http_request`, `syn.request`, `http.request` and
`fluxus.request`, keeps the v1 `decompile(script, enabled, url)` signature, and always falls back to raw output on
errors.

### Speed and reliability

- Results are cached in memory and on disk; identical concurrent requests share one AI call.
- Large scripts are split at top-level statements and processed in parallel.
- Retries with exponential backoff (honoring `Retry-After`), timeouts, and a configurable provider fallback chain
that ends with the offline renamer.
- Connections to providers are pooled and reused.

### Security

- The server listens on `127.0.0.1` by default, rejects cross-site browser requests and DNS-rebinding attempts,
and supports an optional access token.
- API keys are stored in a config file readable only by you, or referenced from environment variables.

### Compatibility

- The v1 `POST /fix_script` endpoint is still supported, so `dex_betterdecompiler.lua` and v1 clients work with the
v2 server unchanged. The default port is still 5000.
- `flask_ai_server.py` and `main.lua` were removed; `client/betterdecompiler.lua` replaces `main.lua`.
Loading
Loading