diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..faa467f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an improvement to InputPilot +title: "" +labels: enhancement +--- + +## What problem would this solve? + + + +## What would you expect to happen? + +## Your setup + +- macOS version: +- Keyboards involved (model, USB/Bluetooth/built-in): +- Input sources you switch between: + +## Anything you have tried + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9faf7bd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +All notable changes to InputPilot are documented here. This project follows [Semantic Versioning](https://semver.org). + +## [1.0.1] — 2026-08-18 + +### Fixed + +- **First launch now asks for Input Monitoring.** 1.0.0 only *checked* the permission and never requested it, so a new user saw nothing at all: no prompt, no window, and — because macOS does not list an app that has never requested access — no way to grant it by hand either. A welcome window now explains what InputPilot does, why the permission is needed, and that it never reads typed text, with a button that triggers the system prompt. +- The welcome window also covers the state right after a grant, where macOS reports the permission as granted but only applies it on the next launch, by offering to quit and reopen instead of leaving the app looking permitted but inert. +- "Fix in Settings…" in the conflict warning did nothing. It used `SettingsLink`, which targets a `Settings` scene the app does not declare. + +### Changed + +- The release pipeline now derives versions from the project file and refuses to build when the marketing version does not match the requested release, or when the build number is not greater than the one the published appcast advertises. Sparkle compares build numbers, so the previous behaviour could have shipped an update that was never offered. + +## [1.0.0] — 2026-08-18 + +First public release. + +### Added + +- Per-keyboard input source mapping, with per-device and global fallbacks. +- Undo for the last automatic switch, and pause for 15 or 60 minutes. +- Conflict detection for mappings pointing at removed or disabled input sources. +- Debounce (400 ms) and cooldown (1.5 s) to prevent flapping between keyboards. +- Optional switching on modifier-only key presses, off by default, so ⌘-Tab does not change the layout. +- In-app debug log with copy and export, containing device and status information only. +- Automatic updates via Sparkle, over a signed appcast. + +### Fixed + +Issues found in the pre-release audit and fixed before shipping: + +- Switch decisions and undo read the current input source fresh instead of a cache that could be six seconds stale, so an input source changed outside the app no longer suppresses a switch or corrupts what undo restores. +- Undecodable mapping data is preserved under a `.corrupted` backup key and logged, instead of being silently replaced on the next write — previously one decode failure could destroy every stored mapping. +- Keyboards without vendor or product IDs are no longer treated as one shared device. +- Unplugging the active keyboard now clears it from the display. + +### Security + +- Signed with a Developer ID certificate and notarized by Apple; both the app and the DMG carry a stapled notarization ticket, so first launch works offline. + +[1.0.1]: https://github.com/LucaGerlich/InputPilot/releases/tag/v1.0.1 +[1.0.0]: https://github.com/LucaGerlich/InputPilot/releases/tag/v1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a8247f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +Thanks for taking an interest in InputPilot. Bug reports, ideas, and pull requests are all welcome. + +## Reporting bugs + +Open an issue with the [bug report template](https://github.com/LucaGerlich/InputPilot/issues/new/choose). The single most useful thing you can attach is an exported debug log: menu bar icon → **Open Debug** → reproduce the problem → **Export…**. It contains device and status information only, never anything you typed. + +Please include your macOS version, the InputPilot version (Settings → About), and which keyboards are involved — device-identity behaviour varies a lot between USB, Bluetooth, and built-in keyboards. + +## Development setup + +You need **Xcode 26** or newer. + +```bash +xcodebuild -scheme InputPilot -destination 'platform=macOS' build test +``` + +One thing that will confuse you otherwise: an app launched from Xcode runs under the debugger, and macOS attributes Input Monitoring to **Xcode**, not to InputPilot. Anything touching the permission flow has to be tested with a build launched from Finder. + +## Pull requests + +- Branch from `main`, keep commits small and coherent, and use `feat:` / `fix:` / `docs:` / `chore:` prefixes. +- CI runs build and tests on every pull request; it must be green. +- Add tests for logic changes. `AppState` and the services are protocol-injected precisely so behaviour can be tested without HID hardware — see `InputPilotTests/ServiceMocks.swift`. +- Some areas cannot be covered by automated tests: the permission flow, the release pipeline, and anything TCC-related. If you touch those, say in the pull request how you verified it manually. + +## Things worth knowing before you change them + +**Device identity.** Keyboards are matched on vendor, product, transport, built-in status, and normalized product name, with location as a tie-breaker. `KeyboardDeviceKey.id` is the key under which mappings are persisted, so changing how it is composed silently breaks every existing user's configuration. + +**Switch timing.** Debounce and cooldown live in `SwitchController` behind an injectable clock. Changes there should come with tests using `ControlledClock` rather than sleeps. + +**Privacy.** The HID callback deliberately extracts only the device and a modifier flag. Please keep it that way; see [SECURITY.md](SECURITY.md). + +## Releases + +Releases are cut by the maintainer with `Scripts/release.sh`, which requires a Developer ID certificate, notarytool credentials, and the Sparkle signing key. Contributors do not need any of that. diff --git a/README.md b/README.md index ac59b37..9bddfe1 100644 --- a/README.md +++ b/README.md @@ -1,298 +1,133 @@ # InputPilot -InputPilot is a macOS menu bar app built with Swift/SwiftUI that detects the active keyboard and automatically switches to the matching input source (keyboard layout). - -This README reflects the current state of the `main` branch. - -## Table of Contents - -- [What InputPilot Does](#what-inputpilot-does) -- [Core Features](#core-features) -- [Privacy and Security](#privacy-and-security) -- [Installation](#installation) -- [Requirements](#requirements) -- [Build and Run](#build-and-run) -- [How to Use](#how-to-use) -- [Auto-Switch Logic (Important)](#auto-switch-logic-important) -- [Persisted Data](#persisted-data) -- [Debugging](#debugging) -- [Tests](#tests) -- [Project Structure](#project-structure) -- [Architecture](#architecture) -- [Troubleshooting](#troubleshooting) -- [Known Limitations](#known-limitations) -- [Releasing](#releasing) -- [License](#license) - -## What InputPilot Does - -Typical use case: you use multiple keyboards (for example an internal MacBook keyboard and an external keyboard), and you want the input source to follow the keyboard you are actively using. - -InputPilot does this by: - -1. Detecting the keyboard that produced the latest key event via HID. -2. Looking up the configured input source for that device. -3. Switching input source using Carbon/TIS. - -## Core Features - -- MenuBarExtra UI with live status. -- Input Monitoring permission flow: - - check status - - request permission - - open System Settings directly -- HID keyboard monitoring: - - start/stop with error handling - - keyDown detection without logging typed characters - - modifier-only handling for anti-flapping behavior -- Input source service (Carbon TIS): - - list enabled/all sources - - read current source - - select source by ID -- Per-device mappings. -- Per-device fallback input source. -- Global fallback input source. -- Undo for the last auto-switch action. -- Debounce + cooldown via `SwitchController`. -- Conflict detection: - - detects mappings that target missing/disabled sources - - surfaces conflicts in menu and settings -- Debug window with ring-buffer logs: - - live view - - copy to clipboard - - export as `.txt` -- Quit action directly in the menu. -- No external dependencies. - -## Privacy and Security - -- No keylogging. -- No typed text is stored. -- No keycodes or app content are stored. -- HID events are only used for device/event classification and switching logic. -- Debug logs contain technical status/error information only. -- Exported logs are sanitized (sensitive tokens are redacted). - -## Installation - -1. Download the latest `InputPilot.dmg` from the [GitHub Releases page](https://github.com/LucaGerlich/InputPilot/releases). -2. Open the DMG and drag `InputPilot.app` into `Applications`. -3. Launch InputPilot. A keyboard icon appears in the menu bar (there is no Dock icon). -4. Grant **Input Monitoring** permission when prompted (System Settings → Privacy & Security → Input Monitoring). InputPilot needs it to detect *which* keyboard produced a key event — it never reads or stores what you type. -5. Press a key on each keyboard you want to configure, then open `Settings…` and assign an input source per keyboard. - -If macOS reports the app as damaged or from an unidentified developer, the build you downloaded is not the notarized release — download only from the official Releases page. +[![Latest release](https://img.shields.io/github/v/release/LucaGerlich/InputPilot?label=download)](https://github.com/LucaGerlich/InputPilot/releases/latest) +[![macOS 13+](https://img.shields.io/badge/macOS-13%2B-black)](https://github.com/LucaGerlich/InputPilot/releases/latest) +[![CI](https://github.com/LucaGerlich/InputPilot/actions/workflows/ci.yml/badge.svg)](https://github.com/LucaGerlich/InputPilot/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE) -## Requirements - -- macOS 13.0 (Ventura) or newer -- To build from source: Xcode 26+ (the project uses the Xcode 16+ project format and a Swift 6.2 toolchain) - -## Build and Run - -1. Open the project: - - `InputPilot.xcodeproj` -2. Select the `InputPilot` scheme. -3. Run the app. -4. Grant Input Monitoring permission when prompted. +**Type on the right keyboard, get the right language.** InputPilot is a small macOS menu bar app that notices which keyboard you are typing on and switches the input source to match. -CLI build: - -```bash -xcodebuild -scheme InputPilot -destination 'platform=macOS' build -``` +If you keep a German external keyboard next to a US MacBook keyboard, you already know the problem: every switch between them means fixing the layout by hand. Map each keyboard once and InputPilot does it for you. -## How to Use +## Install -### Initial Setup +1. Download **`InputPilot-x.y.z.dmg`** from the [latest release](https://github.com/LucaGerlich/InputPilot/releases/latest). +2. Open the DMG and drag InputPilot into Applications. +3. Launch it. A keyboard icon appears in the menu bar — there is no Dock icon and no main window. +4. A welcome window asks for **Input Monitoring**. Grant it, then quit and reopen InputPilot if macOS asks. +5. Press a key on each keyboard you want to configure, then open **Settings** from the menu bar icon and pick an input source per keyboard. -1. Launch the app (keyboard icon in the menu bar). -2. If permission is missing in the menu: - - click `Request Permission` - - if needed, click `Open Input Monitoring Settings` -3. Press at least one key on each keyboard you want to configure so the device is detected. -4. Open `Settings…` and configure a mapping for each device. +The app is signed with a Developer ID certificate and notarized by Apple, so it opens without Gatekeeper warnings. It updates itself through [Sparkle](https://sparkle-project.org); you can also check manually from the menu bar. -### Key Menu Actions +> **If InputPilot does not appear under Input Monitoring** after you allow the prompt, add it by hand: System Settings → Privacy & Security → Input Monitoring → **+** → pick InputPilot in Applications, then switch it on. Some Macs do not populate that list on their own. -- `Auto-Switch` on/off -- `Pause 15 min` / `Pause 60 min` / `Resume` -- `Last switch` + `Undo` -- `Open Debug` -- `Quit InputPilot` +## Why it needs Input Monitoring -### Settings Overview +To tell your keyboards apart, InputPilot has to see that *a* key was pressed and on *which* device. macOS puts that behind the Input Monitoring permission. -- `Auto-Switch`: pause state, last action, latest error -- `Input Monitoring`: permission and active device/source status -- `Input Sources`: current source and ID -- `Fallbacks`: global fallback and quick action to use current source -- `Conflicts`: invalid mappings with `Fix...` action -- `Keyboard Device Mappings`: mapping, per-device fallback, forget device +It never reads what you type. The HID callback extracts exactly two things — the device that sent the event, and whether the key was a modifier — and nothing else is kept. There is no key code, no character, no text, anywhere in the app. Nothing is sent off your Mac: the only network traffic is the update check, and the only third-party dependency is the Sparkle updater. -## Auto-Switch Logic (Important) +See [SECURITY.md](SECURITY.md) for the full privacy statement and how to report a vulnerability. -### Target Source Resolution Order +## Features -1. Device mapping -2. Per-device fallback -3. Global fallback -4. Otherwise no action +- **Per-keyboard input sources** — map each keyboard to the layout you want. +- **Fallbacks** — a per-device fallback for when a mapping breaks, and a global fallback for unmapped keyboards. +- **Undo** — revert the last automatic switch from the menu. +- **Pause** — silence auto-switching for 15 or 60 minutes when it would get in the way. +- **Conflict detection** — spots mappings that point at an input source you have since removed or disabled, and offers to fix them. +- **Stable device identity** — keyboards are matched on vendor, product, transport and name, so unplugging and replugging (or moving to another USB port) keeps your mapping. +- **No flapping** — a 400 ms debounce and a 1.5 s cooldown stop rapid switching, and modifier-only presses (a lone ⇧ or ⌘ during ⌘-Tab) are ignored by default. +- **Debug log** — an in-app log window you can copy or export when reporting a bug. It contains device and status information only, never typed text. -Auto-switch is active only when `isAutoSwitchActive == true`: +## How it works -- `autoSwitchEnabled == true` -- not paused (`pauseUntil` is `nil` or in the past) +1. A HID callback reports which keyboard produced the latest key press. +2. InputPilot resolves the target input source for that device, in order: **device mapping → per-device fallback → global fallback**. If none apply, it does nothing. +3. After the debounce settles, it switches the input source through the Text Input Sources (TIS) API. -### Stabilization Against Flapping +Auto-switching only runs when it is enabled and not paused. -- Debounce: `400ms` (default) -- Cooldown after successful switch: `1500ms` -- Modifier-only key presses (a lone Shift/Cmd/Option, e.g. during Cmd+Tab) do not trigger switching by default. The Settings toggle "Switch on modifier-only key presses" opts back in. +## Settings -### Fingerprint and Matching +| Section | What it does | +|---|---| +| Auto-Switch | Pause state, modifier-only opt-in, last action, last error | +| Input Monitoring | Permission status and the currently active keyboard | +| Input Sources | The current input source and its identifier | +| Fallbacks | Global fallback, plus a shortcut to adopt the current source | +| Conflicts | Mappings pointing at missing or disabled sources, with a fix action | +| Keyboard Device Mappings | Per-keyboard input source, per-device fallback, forget device | -- Primary match key: `vendorId + productId + transport + isBuiltIn (+ normalized productName)` -- `locationId` is used as a hint/tie-breaker -- Goal: stable behavior across port changes and varying HID metadata - -## Persisted Data - -InputPilot stores the following in `UserDefaults`: - -- auto-switch enabled flag -- modifier-only switching opt-in flag -- pause-until timestamp -- global fallback input source ID -- device mappings (including per-device fallback) -- migration flag for mapping schema (legacy -> v2) -- if mapping data ever fails to decode, the raw payload is preserved under a `.corrupted` backup key instead of being overwritten +## Troubleshooting -Not persisted: +**Nothing switches.** Check the menu bar icon: it shows whether the permission is granted and whether the monitor is running. Auto-switch must be enabled and not paused, and the target input source must still be enabled in System Settings → Keyboard → Input Sources. -- `lastAction` (runtime only) -- debug log ring buffer (runtime only) +**Permission granted but nothing happens.** macOS applies Input Monitoring on the next launch. Quit InputPilot and open it again. -## Debugging +**A keyboard is not detected.** Press a key on it — mouse movement is not enough. InputPilot only learns about a keyboard once it produces a key event. -`Open Debug` in the menu opens a dedicated window with: +**It switches too eagerly.** Turn off "Switch on modifier-only key presses" in Settings if you turned it on. For unusual setups such as a KVM, give each device an explicit mapping or fallback. -- log list (newest first) -- level (`INFO`, `WARN`, `ERROR`) -- category and timestamp -- `Copy to Clipboard` -- `Export…` +**Reporting a bug.** Open the debug log from the menu bar, reproduce the problem, export the log, and attach it to a [new issue](https://github.com/LucaGerlich/InputPilot/issues/new/choose). The log is safe to share. -Recommended issue workflow: +## What it stores -1. Open Debug window. -2. Reproduce the issue. -3. Export logs. -4. Inspect relevant error lines. +Settings live in `UserDefaults`: the auto-switch and modifier-only flags, the pause timestamp, the global fallback, your device mappings, and a schema migration flag. The last switch and the debug log are in memory only and disappear when you quit. If stored mappings ever fail to decode, the raw data is preserved under a `.corrupted` key rather than being overwritten. -## Tests +## Requirements -Test framework: Swift Testing (`import Testing`) +macOS 13.0 (Ventura) or newer, Apple silicon or Intel. -Covered areas: +--- -- `AppState` auto-switch behavior including pause/resume/undo -- `SwitchController` debounce/cooldown behavior -- `MappingStore` roundtrip, conflicts, migration -- `DebugLogService` ring buffer and privacy sanitization +## Development -Run tests: +Building from source needs **Xcode 26** or newer (the project uses the Xcode 16+ project format and a Swift 6.2 toolchain). ```bash +git clone https://github.com/LucaGerlich/InputPilot.git +cd InputPilot +xcodebuild -scheme InputPilot -destination 'platform=macOS' build xcodebuild -scheme InputPilot -destination 'platform=macOS' test ``` -## Project Structure +Open `InputPilot.xcodeproj` and press ⌘R to run. Note that an app launched from Xcode runs under the debugger, and macOS attributes Input Monitoring to Xcode rather than to the app — to test the permission flow, run a built copy from Finder instead. + +### Architecture + +`AppState` is the orchestrator: it owns the published UI state and makes every switch decision. Everything it touches sits behind a protocol (`PermissionServicing`, `HIDKeyboardMonitoring`, `InputSourceServicing`, `MappingStoring`, `ClockProviding`, `DebugLogServicing`), so the logic is testable without HID hardware or a real input source. `SwitchController` isolates debounce and cooldown behind an injectable clock. Persistence is deliberately plain `UserDefaults` behind small stores. ```text InputPilot/ - App/ - AppState.swift - InputPilotApp.swift - Services/ - PermissionService.swift - HIDKeyboardMonitor.swift - InputSourceService.swift - SwitchController.swift - DebugLogService.swift - ServiceProtocols.swift - Models/ - ActiveKeyboardDevice.swift - KeyboardFingerprint.swift - KeyboardDeviceKey.swift - KeyboardEventKind.swift - InputSourceInfo.swift - InputStatusSnapshot.swift - MappingConflict.swift - SwitchAction.swift - Persistence/ - MappingStore.swift - AppSettingsStore.swift - UI/ - MenuBarMenuView.swift - SettingsView.swift - AboutSection.swift - DebugLogView.swift -Scripts/ - release.sh - ExportOptions.plist + App/ AppState (orchestration), InputPilotApp (scenes) + Services/ HID monitoring, input sources, permissions, updates, logging + Models/ Device identity, fingerprints, events, snapshots + Persistence/ Mapping and settings stores + UI/ Menu bar, settings, welcome, debug log +Scripts/ release.sh, ExportOptions.plist +Config/ AppInfo.plist (privacy strings, Sparkle feed) +docs/ Engineering notes ``` -## Architecture - -- `AppState` is the central orchestrator (UI state + switch decisions). -- Services are abstracted behind protocols (`PermissionServicing`, `HIDKeyboardMonitoring`, `InputSourceServicing`, `MappingStoring`, `ClockProviding`, `DebugLogServicing`). -- `SwitchController` encapsulates debounce/cooldown independent of UI. -- Persistence is intentionally lightweight (`UserDefaults` via stores). - -## Troubleshooting - -### "Permission granted, but monitor does not start" - -- Verify Input Monitoring permission in macOS Privacy settings. -- Restart the app. -- Check logs for `kIOReturnNotPermitted` or `kIOReturnNotPrivileged`. - -### No devices are detected - -- Confirm Input Monitoring is actually `granted`. -- Press a key on the target keyboard (mouse movement is not enough). -- Check `Status` and `Active Keyboard Device` in the menu. +### Tests -### Input source is not switching +Swift Testing (`import Testing`). Covered: auto-switch behaviour including pause, resume and undo; failure paths such as denied or revoked permission, a HID monitor that will not start, and a failing input-source switch; debounce and cooldown under a controlled clock; mapping persistence, migration and corrupt-data recovery; and the debug log's ring buffer and redaction. -- Verify the device mapping in Settings. -- Ensure the target source is enabled and selectable. -- Check `Conflicts` for `missing/disabled`. -- Verify pause state and `Auto-Switch` toggle. +### Releasing -### Too many switches - -- Debounce/cooldown is active; inspect logs for edge cases. -- For unstable setups (for example KVM), configure explicit mapping/fallback. - -## Known Limitations - -- macOS only. -- Input Monitoring permission is required. -- Detection depends on keyboard events; no key event means no active-device update. -- No cloud sync/profile/hotkey management in the current `main` branch. - -## Releasing - -Maintainer workflow (requires a Developer ID Application certificate, notarytool credentials, and Sparkle EdDSA keys): +Maintainers only; needs a Developer ID certificate, notarytool credentials, and the Sparkle signing key. ```bash -Scripts/release.sh 1.0.0 +Scripts/release.sh 1.0.2 ``` -The script archives a Release build, exports with Developer ID, notarizes and staples, packages a DMG, and generates the Sparkle appcast. Attach the DMG and `appcast.xml` to a tagged GitHub Release. +The script verifies versions, archives, exports with Developer ID, notarizes and staples both the app and the DMG, checks Gatekeeper, and generates the appcast. It then prints the remaining steps: tag, publish the release with the DMG attached, and commit the updated `appcast.xml`. + +## Contributing + +Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Changes to the release pipeline or the permission flow should say how they were verified, since neither is covered by automated tests. ## License -InputPilot is licensed under the [Apache License 2.0](LICENSE). +[Apache License 2.0](LICENSE) © 2026 Luca Gerlich diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..096fbc0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,38 @@ +# Security and Privacy + +InputPilot asks for Input Monitoring, the permission that lets an app see keyboard events. That is a lot of trust to ask for, so here is exactly what the app does with it. + +## What InputPilot reads + +To decide which input source to activate, InputPilot needs to know **which keyboard** produced the most recent key press. Its HID callback takes two things from each event: + +- the device that sent it (vendor ID, product ID, transport, product name, location) +- whether the key was a modifier + +Nothing else leaves that callback. No key code, no character, no typed text exists anywhere in the app — not in memory, not on disk, not in the debug log. + +## What InputPilot stores + +Settings and keyboard mappings live in `UserDefaults`: which input source belongs to which keyboard, your fallbacks, the pause timestamp, and a schema migration flag. That is all. + +The debug log is an in-memory ring buffer of at most 200 entries, holding device identifiers, permission state, input source names, and errors. It is never written to disk unless you explicitly export it, and export runs the text through a redaction pass that strips any field resembling key codes, typed text, characters, or app contents — defence in depth for a log that should never contain them in the first place. + +## What leaves your Mac + +Update checks against the appcast at `raw.githubusercontent.com`, and the download itself when you accept an update. Nothing else. There is no telemetry, no analytics, and no crash reporter. + +Updates are verified twice: an EdDSA signature over the appcast enclosure, and Apple's notarization of the downloaded app. + +## Supply chain + +One third-party dependency: [Sparkle](https://github.com/sparkle-project/Sparkle), the standard macOS updater, pinned to a major version via Swift Package Manager. Releases are built locally, signed with a Developer ID certificate, and notarized by Apple. Signing keys are never stored in the repository or in CI — the CI workflow builds and tests only, and cannot produce a signed artifact. + +## Supported versions + +Fixes go into the latest release. Please update before reporting a problem. + +## Reporting a vulnerability + +Report privately through [GitHub Security Advisories](https://github.com/LucaGerlich/InputPilot/security/advisories/new) rather than a public issue. Please include the version, your macOS version, and how to reproduce the problem. You can expect an initial response within a week. + +If you find that InputPilot handles keystroke data in any way this document does not describe, treat it as a security issue and report it that way. diff --git a/AUDIT.md b/docs/pre-release-audit.md similarity index 95% rename from AUDIT.md rename to docs/pre-release-audit.md index 58b841f..6ea5ece 100644 --- a/AUDIT.md +++ b/docs/pre-release-audit.md @@ -1,5 +1,7 @@ # InputPilot Production-Readiness Audit +> **Historical record.** This is the pre-release audit carried out on 2026-08-18, before InputPilot 1.0.0 shipped. Every finding below is resolved; it is kept for transparency about what was checked and what was found. Current issues live in the [issue tracker](https://github.com/LucaGerlich/InputPilot/issues), and released changes in [CHANGELOG.md](../CHANGELOG.md). + Audited: 2026-08-18 · Scope: full repo · Method: 4 specialized reviews (correctness/concurrency, security/privacy, distribution, tests) + empirical verification Verification baseline at audit time: **build ✓ · all 30 tests pass ✓ · no third-party dependencies · security review clean (no keystroke logging, no network calls)**