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
21 changes: 21 additions & 0 deletions .github/workflows/features-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: features-drift

on:
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Check .revengai/features.json is up to date
run: python scripts/emit_features.py --check
58 changes: 58 additions & 0 deletions .revengai/features.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"features": {
"ai_decompilation_summary": {
"status": "yes"
},
"analyse_binary": {
"status": "yes"
},
"apply_existing_analysis": {
"status": "yes"
},
"binary_auto_analysis": {
"status": "yes"
},
"binary_upload": {
"note": "No explicit command or feature to upload binaries. Binaries are uploaded on creation of a new analysis only.",
"status": "yes"
},
"check_analysis_status": {
"status": "yes"
},
"comment_sync": {
"status": "partial"
},
"data_types_sync": {
"status": "yes"
},
"fs_binary_filter": {
"status": "yes"
},
"fs_collection_filter": {
"status": "yes"
},
"fs_debug_filter": {
"status": "yes"
},
"fs_nns_filter": {
"status": "partial"
},
"fs_similarity_filter": {
"status": "yes"
},
"function_decompilation": {
"status": "yes"
},
"rename_from_similar_function": {
"status": "yes"
},
"search": {
"status": "yes"
},
"upload_function_names": {
"status": "yes"
}
},
"plugin": "binary_ninja",
"schema_version": 1
}
43 changes: 43 additions & 0 deletions docs/FEATURE_STANDARD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Feature manifest

This repo publishes `.revengai/features.json`, a machine-readable manifest listing which
RevEng.AI features this plugin supports. It is generated from code and committed so it is
available at every release tag.

## Source of truth

Feature support is declared in [`scripts/emit_features.py`](../scripts/emit_features.py):

- `FEATURE_CLASSES` maps the plugin's `Feature` classes (registered in
`reai_toolkit/revengai.py`) to a `feature_id` + status.
- `EXTRA` declares features not backed by a single `Feature` class (binary upload, the
function-search filters, status checking, comment/data-type sync, etc.).
- `build_manifest()` merges the two.

`.revengai/features.json` is generated from this script — never edit the JSON by hand. The
script lives in `scripts/` (not in the `reai_toolkit` package) so it is build tooling and
is not shipped with the plugin.

## Status values

`yes`, `partial`, `poc`, `wip`, `planned`, `absent`. A feature may carry an optional `note`
(<=200 chars).

Feature ids are a shared vocabulary used consistently across the RevEng.AI plugins; reuse
an existing id, or coordinate with the team before introducing a new one.

## Regenerating

```sh
python scripts/emit_features.py # rewrite .revengai/features.json
python scripts/emit_features.py --check # exit non-zero if it is stale
```

Standard library only (no `binaryninja` imports), so it runs in plain CI without Binary
Ninja.

## CI

`.github/workflows/features-drift.yml` runs the `--check` on every pull request and fails
if the committed `.revengai/features.json` does not match the script. When you add or
change a feature: update `scripts/emit_features.py`, run the emitter, and commit both.
74 changes: 74 additions & 0 deletions scripts/emit_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import argparse
import json
import sys
from pathlib import Path

PLUGIN = "binary_ninja"

FEATURE_CLASSES = {
"UploadFeature": ("analyse_binary", "yes"),
"AutoUnstripFeature": ("binary_auto_analysis", "yes"),
"ChooseSourceFeature": ("apply_existing_analysis", "yes"),
"MatchCurrentFunctionFeature": ("rename_from_similar_function", "yes"),
"AIDecompilerFeature": ("function_decompilation", "yes"),
}

EXTRA = {
"binary_upload": {
"status": "yes",
"note": "No explicit command or feature to upload binaries. Binaries are uploaded on creation of a new analysis only.",
},
"check_analysis_status": {"status": "yes"},
"fs_collection_filter": {"status": "yes"},
"fs_binary_filter": {"status": "yes"},
"fs_debug_filter": {"status": "yes"},
"fs_nns_filter": {"status": "partial"},
"fs_similarity_filter": {"status": "yes"},
"upload_function_names": {"status": "yes"},
"data_types_sync": {"status": "yes"},
"comment_sync": {"status": "partial"},
"search": {"status": "yes"},
"ai_decompilation_summary": {"status": "yes"},
}

MANIFEST_PATH = Path(__file__).resolve().parent.parent / ".revengai" / "features.json"


def build_manifest():
features = {}
for _feature_class, (feature_id, status) in FEATURE_CLASSES.items():
features.setdefault(feature_id, {"status": status})
for feature_id, entry in EXTRA.items():
features.setdefault(feature_id, dict(entry))
return {"schema_version": 1, "plugin": PLUGIN, "features": features}


def serialize():
return json.dumps(build_manifest(), indent=2, sort_keys=True) + "\n"


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
args = parser.parse_args()

content = serialize()
if args.check:
current = MANIFEST_PATH.read_text() if MANIFEST_PATH.exists() else ""
if current != content:
print(
"features.json is out of date; run: python scripts/emit_features.py",
file=sys.stderr,
)
return 1
print("features.json is up to date.")
return 0

MANIFEST_PATH.parent.mkdir(parents=True, exist_ok=True)
MANIFEST_PATH.write_text(content)
print(f"wrote {MANIFEST_PATH}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading