Automated pre-submission checks for GNOME Shell extensions, built from analysis of real EGO review decisions. ego-lint catches the mechanical issues that cause the most common rejections — so extensions arrive cleaner and reviewers spend less time on round-trips.
Fully deterministic: bash + python + YAML rules. No AI at runtime, no network access, no dependencies beyond coreutils.
git clone https://github.com/ZviBaratz/gnome-extension-reviewer.git
cd gnome-extension-reviewer
./ego-lint /path/to/your-extension@usernameExit code 0 = no blocking issues. Exit code 1 = blocking issues that will likely cause rejection.
Run ./ego-lint --help for the full check list and options.
unzip extension.zip -d /tmp/some-extension@author
./ego-lint /tmp/some-extension@author --show allTry it on a bundled test fixture:
./ego-lint tests/fixtures/lifecycle-imbalance@test --show all
Runs in ~2 seconds on typical extensions, ~4 seconds on large ones (11 modules). No network access.
================================================================
ego-lint — GNOME Shell Extension Compliance Checker
================================================================
Extension: /path/to/my-extension@username
[PASS] file-structure/extension.js extension.js exists
[PASS] file-structure/metadata.json metadata.json exists
[PASS] license License file found (appears GPL-compatible)
[FAIL] R-DEPR-04 extension.js:4: Legacy imports.* syntax; use ESM imports for GNOME 45+
[FAIL] R-DEPR-05 extension.js:2: ExtensionUtils removed in GNOME 45+; use Extension base class
[WARN] R-SEC-17 lib/controller.js:10: Writing to /tmp is insecure; use GLib.get_tmp_dir()
[PASS] metadata/required-fields All required fields present
[FAIL] metadata/session-modes session-modes ["user"] is redundant and MUST be dropped
[WARN] metadata/shell-version-current shell-version does not include GNOME 49
[PASS] lifecycle/enable-disable enable() and disable() both defined
[WARN] lifecycle/file-monitor-cleanup File monitor created but no .cancel() found
[WARN] resource-tracking/orphan-signal lib/manager.js:9 — this._handlerId not cleaned up in destroy()
[PASS] quality/code-provenance provenance-score=1; signals=[consistent-naming-style]
...
----------------------------------------------------------------
Results: 196 checks — 167 passed, 3 failed, 4 warnings, 22 skipped
----------------------------------------------------------------
200+ checks across 14 categories — 64 blocking rules (FAIL) and 60 advisory rules (WARN), plus structural checks.
Full check categories (14)
| Category | Checks |
|---|---|
| Metadata | UUID format/match, required fields, shell-version format, session-modes, GNOME trademark, donations |
| Imports | GTK/Gdk/Adw banned in extension.js; Clutter/Meta/St/Shell banned in prefs.js; transitive dependency analysis |
| Schema | Schema ID matches metadata, path format, glib-compile-schemas dry-run |
| Lifecycle | enable/disable symmetry, signal cleanup, timeout removal, InjectionManager, D-Bus unexport, widget destroy, settings null, subprocess cancellation |
| Async | _destroyed guards, cancellable usage, per-call cancellable verification |
| GObject | GObject.registerClass patterns, GTypeName validation |
| Resources | Cross-file resource graph (signals, timeouts, widgets, D-Bus, file monitors, GSettings), orphan detection |
| Security | Subprocess validation, pkexec targets, clipboard/network disclosure, /tmp writes, telemetry, curl/gsettings spawn |
| Deprecated | Mainloop, Lang, ByteArray, ExtensionUtils, Tweener, legacy imports.* syntax |
| Web APIs | setTimeout, setInterval, fetch, XMLHttpRequest, WebSocket, localStorage |
| Version Compat | GNOME 44–50 migration rules (version-gated, only fire for declared shell-versions) |
| CSS | Unscoped class names, !important usage, GNOME Shell theme class overrides |
| Code Quality | AI slop detection (try-catch density, impossible states, empty catches, obfuscation, code provenance scoring) |
| Package | Forbidden files in zip, required files, compiled schemas for GNOME 45+ |
| Preferences | ExtensionPreferences base class, GTK4/Adwaita patterns, memory leak detection |
See rules/patterns.yaml for the full list with rationale.
ego-lint uses a three-tier rule system:
- Tier 1 — Pattern rules (
rules/patterns.yaml): 124 regex-based rules in declarative YAML, with version-gating, guard patterns, and file-level suppression - Tier 2 — Structural checks (Python/bash scripts): Cross-file resource graph analysis, lifecycle symmetry verification, import BFS, AI slop heuristics
- Tier 3 — Semantic checklists (Markdown): Applied by Claude during
ego-review— lifecycle, security, code quality, AI slop, licensing, accessibility
Adding a new Tier 1 check is 4 lines of YAML:
- id: R-DEPR-08
pattern: "\\bnew\\s+Lang\\.Class\\b"
scope: ["*.js"]
severity: blocking
message: "Lang.Class is deprecated; use standard ES6 classes"
category: deprecatedego-lint does not:
- Make approval/rejection decisions
- Use AI inference or network access at runtime
- Check logic correctness or functionality
- Replace human review judgment
See docs/ARCHITECTURE.md for the full architecture, including why AI is only in Tier 3.
Run ego-lint before submitting to EGO — it catches the mechanical issues that cause the most common rejections.
CI integration: Pure bash + python, exits 0/1, no network access. Tested against 186 fixtures with 495 assertions. See docs/ci-integration.md for GitHub Actions and GitLab CI examples.
All rules: rules/patterns.yaml — 124 pattern rules with rationale, plus rules reference for the full R-XXXX-NN catalog.
This tool encodes the mechanical checks you already do by hand — import segregation, lifecycle symmetry, metadata validation, resource cleanup — into automated, reproducible rules. The rules are grounded in real EGO review analysis and designed to be co-owned: adding a new check is 4 lines of YAML.
- Run
./ego-linton an extension from your review queue - Compare findings against your own review notes
- If something is wrong: open an issue
- If something is missing: add a 4-line YAML rule
You are invited to shape the rules, adjust severity, and add checks for rejection patterns you see often.
ego-lint is the standalone core — deterministic, no dependencies, works in CI. The Claude Code skills extend it with AI-powered analysis for developers who want deeper review coverage:
| Skill | Description |
|---|---|
ego-review |
Multi-phase code review applying 6 semantic checklists (lifecycle, security, code quality, AI slop, licensing, accessibility) |
ego-simulate |
Estimates review readiness using rejection taxonomy and published review criteria |
ego-scaffold |
Generates EGO-compliant extension boilerplate from templates |
ego-submit |
Full pipeline: lint → review → package validation → readiness report |
claude plugins add github:ZviBaratz/gnome-extension-reviewerThe four skills above use Claude to analyze extension source code via Anthropic's API. ego-lint itself makes no API calls — it's the same deterministic tool whether or not you use the plugin.
- Claude Code wrote the code — scripts, rules, tests, and docs were developed using Claude Code (Anthropic's AI coding tool). Every design decision was human-reviewed. The AI slop detection rules are based on patterns observed in real EGO rejections of AI-generated submissions.
- Research was AI-assisted — Discourse mining, guideline extraction, cross-source synthesis, and gap analysis were performed with Claude Code and verified against real EGO reviews on extensions.gnome.org, gjs.guide requirements, and GNOME Shell GitLab history. Regression-tested against a real 11-module extension as baseline.
- ego-lint itself is AI-free — The output artifact is deterministic bash + python + YAML. No API calls. No network access. No model inference. AI was the development tool, not the runtime tool.
The rules and checks are grounded in analysis of real EGO review behavior — not just the official documentation.
- Analyzed 9 real EGO reviews on extensions.gnome.org by active reviewers
- Identified 26 real-world findings including 8 unwritten rules not in official docs
- Cross-referenced gjs.guide guidelines (109 extracted requirements) with actual reviewer behavior
- Traced GNOME Shell guideline evolution across versions 44–50 via GitLab history
- Reverse-engineered patterns from 5 popular approved extensions
- Regression-tested all checks against hara-hachi-bu (an 11-module power management extension whose initial EGO submission was rejected) as baseline
Key unwritten rules discovered:
- No "GNOME" in UUID, extension name, or schema ID (trademark)
shell-versionmust be major-only for GNOME 40+ (no minor versions like "45.1")- D-Bus interfaces must be unexported in
disable() destroy()must be followed bynullassignment- No compiled schemas in package for GNOME 45+
- Timeout IDs must be removed before reassignment
- Subprocesses must have cancellation path in
disable() - No
convenience.jspatterns
Full research: docs/research/ | Coverage gaps: docs/research/gap-analysis.md | Field testing: field-tests/ — 10 real-world extensions tested with TP/FP classification and calibration lessons
- Polkit action ID validation (verify
.policyfile whenpkexecis used) - Schema filename validation (ensure
.gschema.xmlfilename matches schema ID) - Module-scope mutable state detection (
Map/Setat module level) - Per-extension configuration (
.ego-lint.ymlfor rule overrides)
Full gap list: docs/research/gap-analysis.md
- Does not guarantee EGO approval — use as guidance, not certification
- Rules are based primarily on active EGO reviewer patterns; individual reviewers may have different preferences
- Some checks are heuristic (AI slop detection, code quality scoring) and may produce false positives
- Per-line
_async()cancellable check is a heuristic — somenullcancellable calls are valid - Full gap list: docs/research/gap-analysis.md
| Problem | Fix |
|---|---|
python3: command not found |
sudo apt install python3 (Ubuntu/Debian) or sudo dnf install python3 (Fedora) |
| Schema check skipped | Install glib-compile-schemas: sudo apt install libglib2.0-dev-bin or sudo dnf install glib2-devel |
| ESLint check skipped | Install Node.js: sudo apt install nodejs npm or sudo dnf install nodejs npm |
zipinfo: not found |
sudo apt install unzip or sudo dnf install unzip |
- Required: bash, python3
- Optional: npm/node (ESLint checks), glib-compile-schemas (schema validation), zipinfo/unzip (package checks)
- Add rules, report false positives: CONTRIBUTING.md
- Governance and rule decisions: GOVERNANCE.md
- Join the conversation: #extensions:gnome.org on Matrix
- Report issues: GitHub Issues — false positives in blocking rules are high priority