Cross platform configuration and management application for the Betaflight firmware
This report summarizes scanner evidence for betaflight/betaflight-configurator, a TypeScript project with 3,345 GitHub stars. SynthScan examined 449,826 lines of code across 932 source files, recording 179 pattern matches distributed across 9 syntactic categories. The legacy adjusted pattern signal is 0.5 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 161 finding(s).
Are imports declared and package/API names verified? 3 finding(s).
Are wrappers, guards, and repeated structures necessary? 128 finding(s).
Which changes need focused human review? 179 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 179 distinct pattern matches across 9 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | vite.config.js | 130 | function stripManifestFromSecondaryEntriesPlugin() { | CODE |
| LOW | test/js/serial_backend.test.ts | 296 | function establishVirtualConnection() { | CODE |
| LOW | test/js/osd_preview_limits.test.js | 14 | function buildArtificialHorizonPreview() { | CODE |
| LOW | test/js/osd_preview_limits.test.js | 25 | function buildHorizonSidebarsPreview() { | CODE |
| LOW | test/js/simplifiedTuning.test.ts | 33 | function enableFilterFeatureDetection() { | CODE |
| LOW | test/blackbox-viewer/workspace_io.test.ts | 55 | function createExistingWorkspaceState() { | CODE |
| LOW⚡ | src/composables/useTuningSliders.ts | 101 | export function readGyroFilterSliderPosition(): GyroFilterSliderPosition { | CODE |
| LOW⚡ | src/composables/useTuningSliders.ts | 109 | export function readDTermFilterSliderPosition(): DTermFilterSliderPosition { | CODE |
| LOW | src/composables/useOsdPreview.ts | 166 | function normalizeSelectedPosition(position: number, totalSize: number): number { | CODE |
| LOW | src/composables/useOsdPreview.ts | 322 | export function clampStringPreviewPosition( | CODE |
| LOW | src/composables/useOsdPreview.ts | 347 | export function clampStringArrayPreviewPosition( | CODE |
| LOW | src/composables/useOsdPreview.ts | 379 | export function clampObjectArrayPreviewPosition( | CODE |
| LOW | src/composables/useOsdPreview.ts | 415 | export function clampArrayPreviewPosition( | CODE |
| LOW | src/composables/useFlightPlan.ts | 162 | function validatePositionalWaypoint(waypointData: WaypointInput): boolean { | CODE |
| LOW | src/composables/useMspCliSession.ts | 215 | export function findCliSettingAllowedValues(lines: string[] | null | undefined): string[] | null { | CODE |
| LOW | src/composables/useOsdRuler.ts | 186 | function getHorizontalAxisGeometry(axis: HorizontalAxis, params: RulerLayout, tick: number) { | CODE |
| LOW | src/composables/usePreflight.ts | 860 | function refreshIpGeolocationConsent(): boolean { | CODE |
| LOW | src/composables/usePreflight.ts | 913 | function updateMagneticDeclination() { | CODE |
| LOW | src/composables/useDataflashErase.ts | 42 | export function getDataflashEraseTimeoutMs(totalSize: number): number { | CODE |
| LOW | src/composables/useVtx.ts | 136 | function buildPowerOptionsFromTable(powerLevelList: VtxTablePowerLevel[], count: number) { | CODE |
| LOW | src/composables/useVtx.ts | 156 | function buildPowerOptionsFromRange(range: PowerRange) { | CODE |
| LOW | src/composables/ports/softSerial.ts | 37 | export function unreportedSoftSerialIdentifiers( | CODE |
| LOW | src/composables/ports/portNames.ts | 113 | export function findPortIdentifierByCliName( | CODE |
| LOW | src/js/tab_switch.ts | 162 | export function selectDefaultTabWhenConnected(): void { | CODE |
| LOW | src/js/main.ts | 196 | async function openDeleteAccountFromLink() { | CODE |
| LOW | src/js/serial_backend.ts | 699 | function finishUnexpectedDisconnect() { | CODE |
| LOW | src/js/serial_backend.ts | 715 | function dropStalledRebootConnection() { | CODE |
| LOW | src/js/serial_backend.ts | 853 | function showConnectionFailedDialog(text: string, title = i18n.getMessage("connectionFailedTitle")) { | CODE |
| LOW | src/js/serial_backend.ts | 869 | function showVersionMismatchAndCli(message: string) { | CODE |
| LOW | src/js/serial_backend.ts | 1103 | async function processBuildConfiguration() { | CODE |
| LOW | src/js/serial_backend.ts | 1514 | function clearLiveDataRefreshTimer() { | CODE |
| LOW | src/js/serial_backend.ts | 1521 | function startLiveDataRefreshTimer() { | CODE |
| LOW | src/js/peripherals.ts | 38 | function adjustBoxNameIfPeripheralWithModeID(modeId: number, defaultName: string): string { | CODE |
| LOW | src/js/simplifiedTuning.ts | 102 | function sliderFactorsFromTuningSliders(sliders: TuningSliders): SliderFactors { | CODE |
| LOW | src/js/simplifiedTuning.ts | 148 | export function calculateSimplifiedPidValues(factors: SliderFactors): AxisPidValues[] { | CODE |
| LOW | src/js/simplifiedTuning.ts | 164 | export function calculateSimplifiedGyroFilterValues(multiplier: number): GyroFilterValues { | CODE |
| LOW | src/js/simplifiedTuning.ts | 190 | export function calculateSimplifiedDtermFilterValues(multiplier: number): DtermFilterValues { | CODE |
| LOW⚡ | src/js/simplifiedTuning.ts | 229 | export function applySimplifiedGyroFilters(sliders: TuningSliders = useFlightControllerStore().tuningSliders): void { | CODE |
| LOW⚡ | src/js/simplifiedTuning.ts | 237 | export function applySimplifiedDtermFilters(sliders: TuningSliders = useFlightControllerStore().tuningSliders): void { | CODE |
| LOW⚡ | src/js/simplifiedTuning.ts | 245 | export function validateVirtualSimplifiedTuning(): void { | CODE |
| LOW | src/js/blackbox/spectral_analysis.ts | 581 | function findMaxAchievablePhaseMargin(tf: TransferFunction, openLoop: OpenLoopResponse) { | CODE |
| LOW | src/js/utils/magModelExport.ts | 357 | export function buildCharacterizationModel({ | CODE |
| LOW | src/js/utils/magCharacterizationCompute.ts | 102 | export function isFirmwareCustomMagAlignCapable(versionString: string | null | undefined): boolean { | CODE |
| LOW | src/js/utils/deleteAccountLink.ts | 32 | export function requestDeleteAccountFocus(): void { | CODE |
| LOW | src/js/utils/deleteAccountLink.ts | 39 | export function consumeDeleteAccountFocus(): boolean { | CODE |
| LOW | src/js/utils/debugModes.ts | 231 | export function debugContextFromSysConfig(sysConfig?: DebugSysConfig | null): DebugContext { | CODE |
| LOW | src/js/utils/debugModes.ts | 2266 | export function decodeDebugFieldToFriendly( | CODE |
| LOW | src/js/utils/sphereFit.ts | 396 | export function computeDirectionalCoverage(points: Point3[], center: Point3, minHits = 3): DirectionalCoverage { | CODE |
| LOW⚡ | src/stores/osd.ts | 324 | function setSelectedPreviewProfile(profile: number) { | CODE |
| LOW⚡ | src/stores/osd.ts | 329 | function updateDisplayItemVisibility(itemIndex: number, profileIndex: number, visible: boolean) { | CODE |
| LOW⚡ | src/stores/osd.ts | 335 | function refreshDisplayItemPreview(displayItem: OsdDisplayItem) { | CODE |
| LOW | src/stores/osd.ts | 355 | function syncStoreFromDecodedOsdData() { | CODE |
| LOW | src/stores/presets_helpers.ts | 114 | export function createSecondaryOfficialSource(): PresetSource { | CODE |
| LOW | src/stores/presets_helpers.ts | 147 | export function sanitizeActiveSourceIndexes(storedIndexes: unknown, sourceCount: number): number[] { | CODE |
| LOW | src/stores/presets_helpers.ts | 183 | export function getDefaultFirmwareSelections( | CODE |
| LOW | src/stores/presets_helpers.ts | 280 | export function applySelectedOptionIdsToOptions( | CODE |
| LOW | src/stores/presets_helpers.ts | 314 | export function presetSearchPriorityComparer(entryA: PresetSortEntry, entryB: PresetSortEntry): number { | CODE |
| LOW⚡ | src/stores/presets_helpers.ts | 335 | export function isPresetFitSearchStatuses(preset: Preset, searchParams: PresetSearchParams): boolean { | CODE |
| LOW⚡ | src/stores/presets_helpers.ts | 340 | export function isPresetFitSearchCategories(preset: Preset, searchParams: PresetSearchParams): boolean { | CODE |
| LOW⚡ | src/stores/presets_helpers.ts | 348 | export function isPresetFitSearchKeywords(preset: Preset, searchParams: PresetSearchParams): boolean { | CODE |
| 53 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | test/js/magModelExport.test.ts | 18 | // ── Synthetic inputs ──────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | test/js/magModelExport.test.ts | 73 | // ── Tests ─────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | test/js/magCalibration.invariance.test.ts | 146 | // ── Tests ─────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/useTuningSliders.ts | 34 | // ── Constants ──────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/useTuningSliders.ts | 43 | // ── Types ──────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/useTuningSliders.ts | 68 | // ── Pure utilities ─────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/useTuningSliders.ts | 84 | // ── Initialization helpers (read fcStore.tuningSliders → plain objects) ────────── | COMMENT |
| MEDIUM⚡ | src/composables/useTuningSliders.ts | 117 | // ── MSP actions ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/usePreflight.ts | 414 | // ── Module-scoped singleton state ────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/composables/usePreflight.ts | 1030 | // ── Public API (singleton) ───────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/js/utils/ellipsoidFit.ts | 223 | // ── Internal helpers ────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/js/utils/magCharacterizationCompute.ts | 90 | // ── Firmware version gate ─────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/js/utils/magCharacterizationCompute.ts | 255 | // ── Quality assessment ────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/components/tabs/PreflightTab.vue | 1641 | // ── NOTAM logic ────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/components/tabs/PreflightTab.vue | 1752 | // ── End NOTAM logic ─────────────────────────────────────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | crowdin.yml | 1 | # ID used by the upload action to Crowdin | COMMENT |
| LOW | test/js/webbluetooth.test.ts | 21 | COMMENT | |
| LOW | test/js/tauri_serial.test.js | 1 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | COMMENT |
| LOW | test/js/boardAlignment.test.ts | 41 | // - Drone nose faces AWAY from the user during calibration (+X = forward). | COMMENT |
| LOW | test/js/webserial.test.js | 1 | import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; | COMMENT |
| LOW | test/js/device_handler.test.ts | 21 | COMMENT | |
| LOW | test/js/usbdfu_h7_erase.test.js | 1 | import { beforeEach, describe, expect, it, vi } from "vitest"; | COMMENT |
| LOW | test/js/serial_backend.test.ts | 801 | COMMENT | |
| LOW | test/js/usbdfu_open_failure.test.js | 1 | import { beforeEach, describe, expect, it, vi } from "vitest"; | COMMENT |
| LOW | test/js/usbdfu_stm32c5.test.js | 1 | import { beforeEach, describe, expect, it, vi } from "vitest"; | COMMENT |
| LOW | test/js/serial_send_callback.test.ts | 21 | COMMENT | |
| LOW | test/js/utils/debugFieldAnnotations.test.ts | 101 | COMMENT | |
| LOW | .github/workflows/tauri-release-assets.yml | 1 | name: Release platform assets | COMMENT |
| LOW | .github/workflows/tauri-nightly.yml | 1 | name: Nightly downloads | COMMENT |
| LOW | .github/workflows/android-play-release.yml | 1 | name: Android Play release | COMMENT |
| LOW | src-tauri/Cargo.toml | 21 | serde_json = "1.0" | COMMENT |
| LOW | src-tauri/src/tcp.rs | 1 | //! Raw TCP client commands for the configurator. | COMMENT |
| LOW | src-tauri/src/mdns/mod.rs | 1 | //! mDNS discovery of Betaflight bridges (github.com/betaflight/bridge). | COMMENT |
| LOW | src/composables/adjustments/useAdjustmentsData.ts | 41 | // configurator's adjustmentsFunctionN locale labels reflect the dispatch | COMMENT |
| LOW | src/js/serial_backend.ts | 81 | // Re-entrancy guard for the live-data poller. update_live_status is async and awaits a | COMMENT |
| LOW | src/js/serial_backend.ts | 101 | // from an unexpected one (unplug / FC reboot / BLE drop) so we don't tear down | COMMENT |
| LOW | src/js/serial_backend.ts | 221 | const connectionStore = useConnectionStore(); | COMMENT |
| LOW | src/js/serial_backend.ts | 421 | COMMENT | |
| LOW | src/js/serial_backend.ts | 1601 | } | COMMENT |
| LOW | src/js/serial_backend.ts | 1641 | rebootReconnectTimerId = false; | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 761 | return 1 - lowFreqErrorDb * 0.05; | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 781 | // fragile by a measure this file already computes. | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 801 | const admissibleMax = clamp(requestedGain, GAIN_SCALE_MIN, GAIN_SCALE_MAX); | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 821 | // rather than implied. Judged against the admissible ceiling with a | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 841 | // per-axis, and yaw conventionally runs no D at all, so a per-axis D | COMMENT |
| LOW | src/js/blackbox/spectral_analysis.ts | 861 | // instability/flyaway on arm. So this may only ever tighten filtering. | COMMENT |
| LOW | src/js/utils/connectDeeplink.ts | 21 | COMMENT | |
| LOW | src/js/protocols/usbdfu.ts | 521 | getChipInfo(_interface: number, callback: (chipInfo: ChipInfo, resultCode: number) => void): void { | COMMENT |
| LOW | src/js/protocols/usbdfu.ts | 561 | // "@Option Bytes /0x1FFF7800/01*048 e/0x1FFFF800/01*048 e" | COMMENT |
| LOW | src/js/protocols/usbdfu.ts | 741 | } | COMMENT |
| LOW | …c/components/dialogs/mag-calibration/MagSphereView.vue | 521 | // 1. set(x,-y,-z,w) is the conjugation p·q·p* (p = _BASE_180_X = Rx180) | COMMENT |
| LOW | src/blackbox-viewer/imu.ts | 41 | PITCH = 1, | COMMENT |
| LOW | src/blackbox-viewer/flightlog_parser.ts | 621 | //Predict that this field is the same as the average of the last two history items: | COMMENT |
| LOW | src/blackbox-viewer/leaflet-global.ts | 21 | COMMENT | |
| LOW | src/blackbox-viewer/flight3d/fcAttitude.ts | 21 | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | package-lock.json | 14870 | "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", | CODE |
| MEDIUM | src/js/utils/AutoBackup.ts | 84 | // More robust prompt detection with multiple patterns | COMMENT |
| MEDIUM | src/js/utils/AutoBackup.ts | 291 | // Process and save the output - more robust parsing | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | AGENTS.md | 8 | Standards for AI-assisted contributions to Betaflight Configurator. Adapted from the [awesome-copilot Vue 3 instructions | CODE |
| MEDIUM | AGENTS.md | 17 | ## Architecture guardrails | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/js/localization.ts | 65 | type LocalizedProperty = "innerHTML" | "title" | "value" | "placeholder"; | CODE |
| LOW | src/js/localization.ts | 320 | { attr: "i18n_placeholder", prop: "placeholder" }, | CODE |
| LOW | src/components/user-session/UserSession.stories.ts | 86 | args: { isLoggedIn: true, userEmail: "user@example.com", userName: "Sample User" }, | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | locales/en/messages.json | 6996 | "message": "Here you can configure the values for your Video Transmitter (VTX). You can view and change the tran | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …c/components/dialogs/mag-calibration/MagSphereView.vue | 552 | // Step 2: cancels the conjugation's p* → net Rx180·q. Dots and live-mag | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …c/components/dialogs/mag-calibration/MagSphereView.vue | 552 | // Step 2: cancels the conjugation's p* → net Rx180·q. Dots and live-mag | COMMENT |