bluetooth mesh chat, IRC vibes
This report summarizes scanner evidence for permissionlesstech/bitchat, a Swift project with 37,023 GitHub stars. SynthScan examined 170,836 lines of code across 614 source files, recording 429 pattern matches distributed across 6 syntactic categories. The legacy adjusted pattern signal is 2.9 (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? 423 finding(s).
Are imports declared and package/API names verified? 6 finding(s).
Are wrappers, guards, and repeated structures necessary? 20 finding(s).
Which changes need focused human review? 429 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 429 distinct pattern matches across 6 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 | .periphery.yml | 1 | # Periphery dead-code scan configuration (https://github.com/peripheryapp/periphery) | COMMENT |
| LOW | bitchat/ViewModels/ChatLiveVoiceCoordinator.swift | 681 | COMMENT | |
| LOW | bitchat/ViewModels/ChatOutgoingCoordinator.swift | 41 | /// coordinator never reads it.) | COMMENT |
| LOW | …at/ViewModels/ChatPrivateConversationCoordinator.swift | 1 | import BitFoundation | COMMENT |
| LOW | …at/ViewModels/ChatPrivateConversationCoordinator.swift | 101 | /// errors about a DM belong in that DM, not on the active timeline. | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 1 | // | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 21 | /// - Command processing | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 41 | /// - Supports verified fingerprints for secure communication | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 61 | /// - SwiftUI automatically optimizes UI updates | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 221 | COMMENT | |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 601 | /// Note: when this runs after a store `migrateConversation`, the store has | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModel.swift | 1701 | COMMENT | |
| LOW | bitchat/ViewModels/NostrInboundPipeline.swift | 21 | func displayNameForNostrPubkey(_ pubkeyHex: String) -> String | COMMENT |
| LOW | bitchat/ViewModels/NostrInboundPipeline.swift | 81 | @discardableResult | COMMENT |
| LOW | bitchat/ViewModels/NostrInboundPipeline.swift | 341 | } | COMMENT |
| LOW | bitchat/ViewModels/PublicMessagePipeline.swift | 1 | // | COMMENT |
| LOW | bitchat/ViewModels/GeoChannelCoordinator.swift | 1 | // | COMMENT |
| LOW | bitchat/ViewModels/ChatPeerIdentityCoordinator.swift | 1 | import BitFoundation | COMMENT |
| LOW | bitchat/ViewModels/ChatPeerIdentityCoordinator.swift | 101 | func sendFavoriteNotificationViaNostr(noisePublicKey: Data, isFavorite: Bool) | COMMENT |
| LOW | bitchat/ViewModels/ChatGroupCoordinator.swift | 521 | COMMENT | |
| LOW | bitchat/ViewModels/ChatMediaTransferCoordinator.swift | 141 | } | COMMENT |
| LOW | bitchat/ViewModels/ChatNostrCoordinator.swift | 21 | func sendGeohashDeliveryAck(for messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity | COMMENT |
| LOW | …hat/ViewModels/ChatPublicConversationCoordinator.swift | 1 | import BitFoundation | COMMENT |
| LOW | …hat/ViewModels/ChatPublicConversationCoordinator.swift | 101 | extension ChatViewModel: ChatPublicConversationContext { | COMMENT |
| LOW | …hat/ViewModels/ChatPublicConversationCoordinator.swift | 421 | /// rate limit; low/no-PoW events keep the strict limits so old clients | COMMENT |
| LOW | bitchat/ViewModels/ChatVerificationCoordinator.swift | 61 | func privateMediaPeerDidAuthenticate(_ peerID: PeerID) | COMMENT |
| LOW | bitchat/ViewModels/ChatLifecycleCoordinator.swift | 61 | COMMENT | |
| LOW | bitchat/ViewModels/ChatTransportEventCoordinator.swift | 81 | func handleVoiceFramePayload(from peerID: PeerID, payload: Data, timestamp: Date) | COMMENT |
| LOW | bitchat/ViewModels/ChatViewModelBootstrapper.swift | 221 | /// mesh messages, persisted across restarts) as dimmed "heard here | COMMENT |
| LOW | bitchat/ViewModels/ChatDeliveryCoordinator.swift | 1 | import BitFoundation | COMMENT |
| LOW | bitchat/ViewModels/ChatDeliveryCoordinator.swift | 41 | func notifyUIChanged() | COMMENT |
| LOW | bitchat/ViewModels/ChatPeerListCoordinator.swift | 1 | import BitFoundation | COMMENT |
| LOW | bitchat/App/AppRuntime.swift | 461 | COMMENT | |
| LOW | bitchat/App/NearbyNotesCounter.swift | 1 | // | COMMENT |
| LOW | bitchat/App/PrivacyScreen.swift | 1 | // | COMMENT |
| LOW | bitchat/App/ConversationStore.swift | 1 | // | COMMENT |
| LOW | bitchat/App/ConversationStore.swift | 341 | // MARK: Selection state | COMMENT |
| LOW | bitchat/App/ConversationStore.swift | 361 | /// message into both the stable-key and ephemeral-peer chats | COMMENT |
| LOW | bitchat/App/ConversationStore.swift | 441 | /// republish and `.statusChanged` event - otherwise a view observing the | COMMENT |
| LOW | bitchat/App/ConversationStore.swift | 701 | COMMENT | |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 1 | // | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 21 | /// - Thread-safe access patterns | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 41 | /// - No cloud sync or backup | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 61 | /// | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 81 | /// - Users can disable all persistence | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 161 | // In-memory state | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 181 | // `.profraw`, deadlocking the process at teardown on the constrained CI | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 241 | if !keyIsEphemeral { | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 321 | } | COMMENT |
| LOW | bitchat/Identity/SecureIdentityStateManager.swift | 361 | /// TOFU signing-key pinning: once a signing key has been persisted for a | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 1 | // | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 21 | /// The layers are separate concerns, but they are not independent: the network | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 41 | /// | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 61 | /// 2. **Casual**: Basic interaction history | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 81 | /// // When peer connects with new ID | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 141 | /// `vouched` mid-ladder cannot corrupt previously persisted values — every | COMMENT |
| LOW | bitchat/Identity/IdentityModels.swift | 201 | // Fingerprint -> when we verified it (orders outgoing vouch batches; | COMMENT |
| LOW | bitchat/Features/voice/PTTBurstPlayer.swift | 1 | // | COMMENT |
| LOW | bitchat/Features/voice/PTTBurstPlayer.swift | 141 | } | COMMENT |
| LOW | bitchat/Features/voice/VoiceCaptureSession.swift | 1 | // | COMMENT |
| 320 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | bitchat/Services/BLE/BLEAnnounceHandler.swift | 248 | // Persist cryptographic identity and signing key for robust offline | COMMENT |
| MEDIUM | …tFoundation/Sources/BitFoundation/BinaryProtocol.swift | 258 | // Try decode as-is first (robust when padding wasn't applied) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 81 | #expect(harness.lines.isEmpty) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 94 | #expect(harness.lines.isEmpty) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 103 | #expect(harness.lines.isEmpty) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 106 | #expect(harness.lines.count == 1) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 107 | #expect(harness.lines[0].geohash == "9q8yy") | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 108 | #expect(harness.lines[0].content.contains("road closed")) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 109 | #expect(harness.lines[0].content.contains("@alice")) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 122 | #expect(harness.lines.isEmpty) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 135 | #expect(harness.lines.count == 1) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 136 | #expect(harness.lines[0].content.contains("3")) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 137 | #expect(harness.pendingFlushes.isEmpty) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 149 | #expect(harness.lines.count == 2) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 150 | #expect(Set(harness.lines.map(\.geohash)) == ["9q8yy", ""]) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 164 | #expect(harness.lines.count == 1) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 165 | #expect(!harness.lines[0].content.contains("2")) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 192 | #expect(harness.pendingFlushes.count == 1) | COMMENT |
| MEDIUM | bitchatTests/Services/BoardAlertsModelTests.swift | 198 | #expect(harness.lines.isEmpty) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 211 | #expect(harness.lines.count == 1) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 212 | #expect(harness.lines[0].content.count < 200) | COMMENT |
| MEDIUM⚡ | bitchatTests/Services/BoardAlertsModelTests.swift | 213 | #expect(harness.lines[0].content.contains("…")) | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/tests/test_fetch_georelays_workflow.py | 15 | def test_write_capable_checkout_action_is_immutable(self) -> None: | CODE |
| LOW | scripts/tests/test_fetch_georelays_workflow.py | 21 | def test_pr_failure_has_single_issue_fallback_with_review_metadata(self) -> None: | CODE |
| LOW | scripts/tests/test_fetch_georelays_workflow.py | 45 | def test_obsolete_review_state_is_cleaned_without_pushing_main(self) -> None: | CODE |
| LOW | scripts/tests/test_fetch_georelays_workflow.py | 53 | def test_workflow_runs_all_validator_tests(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 15 | def test_validates_and_deduplicates_secure_relay_addresses(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 29 | def test_rejects_insecure_or_non_host_relay_urls(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 53 | def test_rejects_malformed_rows_and_unsafe_coordinates(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 71 | def test_accepts_ascii_coordinate_forms_supported_by_swift_double(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 85 | def test_rejects_conflicts_limits_and_large_baseline_deltas(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 117 | def test_update_requires_exact_normalized_baseline_entry_overlap(self) -> None: | CODE |
| LOW | scripts/tests/test_validate_georelays.py | 157 | def test_cli_copies_only_validated_data_and_emits_review_metadata(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 33 | def test_runs_on_untrusted_pull_request_trigger(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 38 | def test_no_job_holds_a_write_scope(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 43 | def test_checkout_does_not_persist_credentials(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 46 | def test_relay_data_changes_trigger_the_workflow(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 52 | def test_gate_checks_out_the_reviewed_base(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 57 | def test_gate_runs_nothing_the_pull_request_wrote(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 66 | def test_gate_pins_both_inputs_to_the_event(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 74 | def test_gate_validation_cannot_write_to_the_reviewed_file(self) -> None: | CODE |
| LOW⚡ | scripts/tests/test_georelay_pr_workflow.py | 79 | def test_proposed_validator_is_exercised_against_the_real_directory(self) -> None: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | bitchat/_PreviewHelpers/BitchatMessage+Preview.swift | 16 | sender: "John Doe", | CODE |
| LOW | bitchat/_PreviewHelpers/BitchatMessage+Preview.swift | 22 | recipientNickname: "Jane Doe", | CODE |
| LOW | bitchat/Views/Components/DeliveryStatusView.swift | 141 | .delivered(to: "John Doe", at: Date()), | CODE |
| LOW | bitchat/Views/Components/DeliveryStatusView.swift | 142 | .read(by: "Jane Doe", at: Date()), | CODE |
| LOW | bitchatTests/ProtocolContractTests.swift | 107 | #expect(preview.sender == "John Doe") | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | bitchat/Services/VerificationService.swift | 120 | // MARK: - Noise payloads (scaffold only) | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/validate_georelays.py | 4 | CODE |