diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e1c73..db66cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver --- +## [0.1.126] — 2026-08-07 + +**A retest of v0.1.125 confirmed all four Windows fixes and found nine more defects underneath.** The severe one is the same leak from a different door: v0.1.125 taught `search_project` to skip credential files, but `open_file` and `read_tab` were never told, so an agent that asked for `~/.ssh/id_rsa` **by name** still got the whole key back. All nine are fixed. No new editor features. + +### Security +- **The credential deny-list guarded one door out of four.** `PathDenylist::isSecretPath` ran only in `search_project`'s filesystem walk. `open_file` opened `~/.ssh/id_rsa` with `isError:false` and `read_tab` returned the entire private key; `find_in_tab` returned the matching key lines; and `search_project`'s **open-tab leg** read the same file straight out of the buffer while the disk leg refused it. Guarding a discovery path while leaving the direct-request path open is not a guard. All four now route through the same list, and `open_file` refuses *before* the tab exists so the file never reaches the screen either. + +### Fixed +- **`format_json` invented structure and reported success.** The MCP path called the JSON panel's auto-**fixer**: `[1,2` came back as `[1,2]`, `{oops` as `{\n oops`, `not json at all` with its spaces deleted — every one of them `isError:false`. A truncated config file became syntactically valid and semantically invented, so an assistant that formatted and wrote it back fabricated data the user never had. The MCP path now validates strictly first and fails with a parse position, which is what the tool's description has always claimed. The panel keeps its fixer: a human sees that repair and can undo it. +- **A search with zero matches was reported as "No workspace folder is open".** Whether a folder is open is not a fact about the query, but the error fired only when the open tabs happened to miss — so the same tabs answered "not found" for one query and "you have no folder open" for another. Empty results is a successful search: `{results: [], truncated: false}` with the reduced scope carried in a `notice`. +- **`search_project` never sent the `workspace_searched` and `scope` fields v0.1.125 promised.** The C++ bridge had sent them all along; the Rust `SearchResults` struct declared only `results` and `truncated`, so serde discarded the rest before the tool result was built. Two layers, one contract, one of them updated. +- **Without `--socket` the server fabricated everything and said so only in `--help`** — which the consumer, a language model, never reads. Drop the flag from a config and the assistant confidently described three files that did not exist, on POSIX paths, on a Windows box, and reported the editor as 0.1.118. Every mock result now carries a `[MOCK DATA]` content block and `_meta.mock`, `initialize` reports `serverInfo.mock` plus instructions, and the mock reports its own real version. +- **`find_in_tab` rejected a `title` that `read_tab` accepted** with `-32602 unexpected argument`. Two hand-written resolvers on neighbouring tools that had drifted; there is one resolver now. +- **`tools/list` and `prompts/list` accepted an invalid pagination cursor and returned the full list**, which is indistinguishable from "your cursor was valid, here is page 2". This server never issues a `nextCursor`, so any cursor is invalid and gets `-32602`. + +### Changed +- **Tabs have a stable `id`.** Every write verb addressed its target by positional `tab_index`, which re-points at a different document the moment a tab to its left closes — and out-of-range was the *lucky* case, because a shifted-but-valid index wrote silently with only the approval card standing in the way. `list_open_tabs` publishes an `id` that survives reordering, save-as and its neighbours closing, and every verb that names a tab accepts `tab_id`. `tab_index` still works. +- **`read_tab` takes `max_bytes` and always reports `truncated` and `total_chars`.** The 5 MB ceiling was the only bound, so a 2.7 MB buffer came back whole and could fill an assistant's entire context in one call with no way to ask for less — while `search_project` and `run_sql` had been negotiable all along. + +### Internal +- `rust-core` gains `json_parse_error` (strict, non-repairing, RFC 8259) alongside the existing repairing formatter, exported as `npc_json_parse_error`. +- Nine new regression tests, each red-state verified by restoring the pre-fix code rather than negating a condition — including the mock-transport marker, which had to become a separate content block after a text prefix was found to corrupt every JSON-returning tool on the surface. +- **Known gap:** the strict-JSON validator is unit-tested in `rust-core`, but its wiring into `MainWindow`'s `formatText` host lambda is verified by inspection — that lambda is not reachable from the bridge's fake host. + +--- + ## [0.1.125] — 2026-08-07 **A one-word MCP search could return the contents of your SSH private key. Fixed, along with three other defects found by testing the MCP server on Windows — and the Windows binary finally reports its real version after 124 releases claiming 0.1.0.** No new editor features; every change is a fix. diff --git a/CMakeLists.txt b/CMakeLists.txt index a687728..fba4938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(Notepatra VERSION 0.1.125 LANGUAGES CXX) +project(Notepatra VERSION 0.1.126 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index c4c788a..8fb5906 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Not a port. Not a wrapper. Something new — **for everyone**. I asked: **what would a small native code editor look like if it was built today, in 2026, when AI is part of every developer's workflow, and ran natively on Linux + macOS + Windows from one codebase?** -The answer: a tiny native executable — ~12 MB bare (~12.6 MB on Linux x64) on every platform — with a Rust-powered core, Scintilla editing engine, and local-first AI integration (cloud backends optional). v0.1.125 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–43.0 MB on Windows (MSI/zip/setup.exe with bundled Qt DLLs). An editor that can fix your broken JSON with regex in milliseconds — and when regex isn't enough, it asks your AI to figure it out — local by default, six cloud backends one click away when you want a frontier model. No telemetry. No subscription. No mandatory API key. +The answer: a tiny native executable — ~12 MB bare (~12.6 MB on Linux x64) on every platform — with a Rust-powered core, Scintilla editing engine, and local-first AI integration (cloud backends optional). v0.1.126 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–43.0 MB on Windows (MSI/zip/setup.exe with bundled Qt DLLs). An editor that can fix your broken JSON with regex in milliseconds — and when regex isn't enough, it asks your AI to figure it out — local by default, six cloud backends one click away when you want a frontier model. No telemetry. No subscription. No mandatory API key. Notepatra started on Linux — because that's where the gap was. But great tools shouldn't have borders. **Notepatra runs on Linux, Windows, and macOS.** Same codebase. Same features. No one gets left behind. @@ -287,7 +287,7 @@ Full tool reference, Claude Desktop / Agents SDK snippets, security model, and h **Why this hybrid?** - **C++** because Qt and QScintilla are C++ — zero friction for UI - **Rust** because file I/O, text processing, and parsing must never crash — Rust's ownership system guarantees memory safety -- **Result**: the speed of C++, the safety of Rust. The bare executable is **~12 MB** on every platform (~12.6 MB Linux x64, similar on macOS / Windows). Latest v0.1.125 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **43.0 MB** Windows MSI · **32.8 MB** Windows NSIS · **37.4 MB** Windows portable zip. _Installed footprint on Windows is ~75-85 MB after the MSI extracts bundled Qt + QScintilla DLLs — normal for any Qt-based installer._ +- **Result**: the speed of C++, the safety of Rust. The bare executable is **~12 MB** on every platform (~12.6 MB Linux x64, similar on macOS / Windows). Latest v0.1.126 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **43.0 MB** Windows MSI · **32.8 MB** Windows NSIS · **37.4 MB** Windows portable zip. _Installed footprint on Windows is ~75-85 MB after the MSI extracts bundled Qt + QScintilla DLLs — normal for any Qt-based installer._ --- @@ -307,7 +307,7 @@ irm https://notepatra.org/install.ps1 | iex That's it. Auto-detects your OS, downloads the right binary, installs it, adds to PATH, creates shortcuts. -### Or download manually — [Latest release: v0.1.125](https://github.com/singhpratech/notepatra/releases/latest) +### Or download manually — [Latest release: v0.1.126](https://github.com/singhpratech/notepatra/releases/latest) | Platform | Download | Size | What's inside | |---|---|---|---| @@ -332,11 +332,11 @@ For one-time-install-then-every-user-sees-it on a shared machine, or silent push | OS | Artefact | Silent admin install | |---|---|---| -| 🪟 **Windows** | [`notepatra-x.x.x.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-0.1.125.msi /quiet` — installs to `C:\Program Files\Notepatra\`, adds system PATH, registers HKCR file associations, all-users Start Menu. WiX-built, MajorUpgrade-aware, SCCM-friendly. | +| 🪟 **Windows** | [`notepatra-x.x.x.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-0.1.126.msi /quiet` — installs to `C:\Program Files\Notepatra\`, adds system PATH, registers HKCR file associations, all-users Start Menu. WiX-built, MajorUpgrade-aware, SCCM-friendly. | | 🍎 **macOS** | [`Notepatra.dmg`](https://github.com/singhpratech/notepatra/releases/latest) | Mount + `sudo cp -R "/Volumes/Notepatra/Notepatra.app" /Applications/` from a deployment script. Or open the DMG manually and drag to `/Applications` (admin password). Notarised + stapled. | -| 🐧 **Debian / Ubuntu / Mint / Pop!_OS** (x64 + ARM64) | [`notepatra_0.1.125_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.125_amd64.deb` — installs to `/opt/notepatra/` + symlink at `/usr/bin/notepatra`, hicolor icons, `.desktop` registration. ARM64: replace `amd64` → `arm64`. | -| 🐧 **Fedora / RHEL / CentOS Stream / Rocky / Alma** (x64 + ARM64) | [`notepatra-0.1.125-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.125-1.x86_64.rpm` — same layout as the .deb. ARM64: replace `x86_64` → `aarch64`. Bundles QScintilla 2.14.1 alongside the binary because Fedora ships an incompatible packaging. | -| 🐧 **Arch / openSUSE Tumbleweed / Manjaro / EndeavourOS / other glibc 2.38+** | [`Notepatra-0.1.125-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.125-x86_64.AppImage && sudo cp Notepatra-0.1.125-x86_64.AppImage /opt/notepatra.AppImage && sudo ln -s /opt/notepatra.AppImage /usr/local/bin/notepatra`. Requires glibc 2.38+ (Ubuntu 24.04+, Fedora 40+, Arch, Tumbleweed). Older distros: use the .deb / .rpm. | +| 🐧 **Debian / Ubuntu / Mint / Pop!_OS** (x64 + ARM64) | [`notepatra_0.1.126_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.126_amd64.deb` — installs to `/opt/notepatra/` + symlink at `/usr/bin/notepatra`, hicolor icons, `.desktop` registration. ARM64: replace `amd64` → `arm64`. | +| 🐧 **Fedora / RHEL / CentOS Stream / Rocky / Alma** (x64 + ARM64) | [`notepatra-0.1.126-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.126-1.x86_64.rpm` — same layout as the .deb. ARM64: replace `x86_64` → `aarch64`. Bundles QScintilla 2.14.1 alongside the binary because Fedora ships an incompatible packaging. | +| 🐧 **Arch / openSUSE Tumbleweed / Manjaro / EndeavourOS / other glibc 2.38+** | [`Notepatra-0.1.126-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.126-x86_64.AppImage && sudo cp Notepatra-0.1.126-x86_64.AppImage /opt/notepatra.AppImage && sudo ln -s /opt/notepatra.AppImage /usr/local/bin/notepatra`. Requires glibc 2.38+ (Ubuntu 24.04+, Fedora 40+, Arch, Tumbleweed). Older distros: use the .deb / .rpm. | > All artefacts ship with cosign `.sig` + `.pem` for keyless Sigstore verification and SLSA build provenance. See **[Verify your download](#verify-your-download)** below. @@ -346,9 +346,9 @@ For teams that **can't or won't send code to public LLM endpoints** — regulate | OS | Artefact | Silent admin install | |---|---|---| -| 🪟 **Windows** | [`notepatra-local-ai-0.1.125.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.125.msi /quiet` — installs to `C:\Program Files\Notepatra Local AI\`, distinct UpgradeCode so SCCM treats it as its own product. Add/Remove Programs shows "Notepatra Local AI". | -| 🐧 **Debian/Ubuntu x64** | [`notepatra-local-ai_0.1.125_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.125_amd64.deb` | -| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.125_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.125_arm64.deb` | +| 🪟 **Windows** | [`notepatra-local-ai-0.1.126.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.126.msi /quiet` — installs to `C:\Program Files\Notepatra Local AI\`, distinct UpgradeCode so SCCM treats it as its own product. Add/Remove Programs shows "Notepatra Local AI". | +| 🐧 **Debian/Ubuntu x64** | [`notepatra-local-ai_0.1.126_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.126_amd64.deb` | +| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.126_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.126_arm64.deb` | **The binary physically cannot reach `api.openai.com`, `api.anthropic.com`, `openrouter.ai`, `api.mistral.ai`, `generativelanguage.googleapis.com`, or any other public LLM endpoint.** Every `QNetworkAccessManager` request goes through an allowlist that only accepts: @@ -360,7 +360,7 @@ For teams that **can't or won't send code to public LLM endpoints** — regulate Local Ollama, local llama.cpp, self-hosted Ollama on the LAN, and any other OpenAI-compatible server you have installed locally or on your private network — **all continue to work** in the cloud-free build. Only public-cloud LLM endpoints are blocked. The cloud-URL paste box is stripped from the UI as well, so users can't even type a public host. Auditors can confirm by running `strings notepatra | grep -c openai.com` — zero hits. -On Linux the two flavors share the same `notepatra` binary name on disk; `apt` Conflicts ensures only one of `notepatra` / `notepatra-local-ai` is installed at a time, swap transactionally with `sudo apt install ./notepatra-local-ai_0.1.125_amd64.deb`. On Windows the two MSIs are independent products (different UpgradeCode + ProductName + install dir) so they can coexist if needed; admins typically push one or the other based on policy. `notepatra --version` self-identifies the build by name — only the bare lite build carries an edition suffix: `Notepatra Lite v0.1.125` for the lite build and `Notepatra v0.1.125` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.125` / `Notepatra Local AI v0.1.125` for the cloud-free (local-ai) builds; the same name shows in the window title bar and the About dialog. +On Linux the two flavors share the same `notepatra` binary name on disk; `apt` Conflicts ensures only one of `notepatra` / `notepatra-local-ai` is installed at a time, swap transactionally with `sudo apt install ./notepatra-local-ai_0.1.126_amd64.deb`. On Windows the two MSIs are independent products (different UpgradeCode + ProductName + install dir) so they can coexist if needed; admins typically push one or the other based on policy. `notepatra --version` self-identifies the build by name — only the bare lite build carries an edition suffix: `Notepatra Lite v0.1.126` for the lite build and `Notepatra v0.1.126` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.126` / `Notepatra Local AI v0.1.126` for the cloud-free (local-ai) builds; the same name shows in the window title bar and the About dialog. ### Verify your download @@ -622,6 +622,7 @@ Notepatra follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic | Version | Date | Highlights | |---|---|---| +| [**v0.1.126**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.126) | 2026-08-07 | **v0.1.125 taught `search_project` to skip credential files — it never told `open_file` or `read_tab`, so an agent that asked for `~/.ssh/id_rsa` by name still got the whole key back.** A deep retest confirmed all four v0.1.125 Windows fixes on real hardware and found nine more defects underneath. `open_file`, `read_tab`, `find_in_tab` and both legs of `search_project` now check the same deny-list — v0.1.125 had guarded only the filesystem walk, so the same file sitting in an open tab was read straight out of the buffer. **`format_json` invented data and reported success:** it called the JSON panel's auto-*fixer*, so `[1,2` came back as `[1,2]` with `isError:false` — a truncated config file made syntactically valid and semantically invented. The MCP path validates strictly now; the panel keeps its fixer. **Fixed:** a search with zero matches was reported as "No workspace folder is open", which is a different fact about the world · `workspace_searched`/`scope` were sent by the bridge and dropped by the Rust struct, so no client ever saw the fields v0.1.125 promised · without `--socket` every response was fabricated with no marker, so a dropped flag produced an assistant describing three files that do not exist · `find_in_tab` rejected a `title` that `read_tab` accepted · an invalid pagination cursor was ignored and the full list returned. **Changed:** tabs now have a stable `id` — `tab_index` is positional and re-points at a different document when a tab closes, and out-of-range was the *lucky* case · `read_tab` takes `max_bytes` and always reports `truncated` and `total_chars`. | | [**v0.1.125**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.125) | 2026-08-07 | **A one-word MCP search could return the contents of your SSH private key — fixed, along with three other defects found by testing the MCP server on Windows.** No new editor features; every change is a fix. The credential deny-list existed in **two** hand-maintained copies that had drifted in *both* directions (`ai_tools.cpp` had `*.tfvars`/`*.tfstate`/`.pypirc`/the `.env` family; `git_hunk_apply.cpp` had `*.jks` and an unanchored `id_rsa`; neither was a superset) and `search_project` consulted **neither** — so `read_file` refused `~/.ssh/id_rsa` while a one-word search returned its lines. `src/path_denylist.h` is now the single list, built as the union, with all three callers routed through it. **`search_project` also walked the entire home directory:** `FileExplorer`'s constructor set `m_rootPath = QDir::homePath()` as a *display placeholder*, and four guards read it as "the workspace" — including the AI CSV sandbox and a security guard that had therefore never once closed. `workspaceRoot()` now stays empty until a folder is opened, and a workspace-less search reports `workspace_searched` and `scope` so a partial search cannot pass for a complete one. **Fixed:** `goto_line` past EOF reported `{"line": 99999, "ok": true}` from the top of the document, so an assistant aiming at the end of a file wrote at the beginning behind a correct-looking approval card · `insert_text` with `col` but no `line` silently dropped `col` and wrote at the cursor · the sidecar hung on every unrecognised argument (`--version` printed nothing and blocked on stdin; `--sokcet` silently started the **mock** server so the client received fabricated tabs) · the AI workspace root flapped on every Ctrl+Tab, swapping chat history and cancelling pending write approvals · **Windows binaries reported version 0.1.0** to winget, SCCM, Intune and File Properties for 124 releases — the `.rc` is generated from CMake now, with a CI step reading `VersionInfo` back off the shipped `.exe`. Every new test red-state verified by restoring the pre-fix code, not by negating a condition. | | [**v0.1.124**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.124) | 2026-07-28 | **Invisible characters are now visible — 527 of them, where Notepad++ draws 113 — and two Find/Replace bugs that were destroying text are fixed.** A file containing a zero-width space used to look exactly like a file without one: symbol support was only whitespace dots and EOL markers, so every zero-width, bidirectional and exotic-space codepoint was undrawable. **View → Show Symbol** is rebuilt to Notepad++'s eight-item structure in its order (Show Space and Tab · Show End of Line · Show Non-Printing Characters · Show Control Characters & Unicode EOL · Show Every Other Invisible Character · Show All Characters · Show Indent Guide · Show Wrap Symbol). The non-printing (49) and control/Unicode-EOL (64) tables are transcribed from Notepad++'s source byte-for-byte, abbreviations included (`ZWNBSP` for U+FEFF, `OSPM` for U+1680, `SGCI` for U+0099). **"Show Every Other Invisible Character"** adds the 414 codepoints Notepad++'s fixed tables miss — variation selectors, the TAG block U+E0020–E007F, the Hangul fillers, the combining grapheme joiner. Blobs can show codepoints (`U+200B`) instead of abbreviations, and every Show Symbol setting now persists across restarts and applies to all tabs. Defaults match Notepad++ — everything off except the control-character display, so non-printing characters are **off out of the box**. **Fixed:** Replace All silently deleted any replacement containing `$` (handed to the regex engine as a substitution template, so `$100` parsed as capture group 100 and expanded to nothing) · Replace reported replacements it had not made and overwrote unrelated selections · Find in Files' default `*.*` filter skipped extensionless files like `Makefile` and `.gitignore` · Mark All highlighted the wrong spans (offsets computed into a lowercased copy, and lowercasing is not length-preserving in UTF-8) · indent guides rendered as a white-on-black barber pole in Dark and Monokai. Three new suites (75 total Full), each red-state verified. | | [**v0.1.123**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.123) | 2026-07-23 | **The "Save As" dialog is now the native OS file dialog — the Windows Explorer tree everyone knows.** Save As was the only dialog still forced to Qt's own plain "Look in:" list (no folder tree); it now uses the native Windows / macOS / Linux dialog, matching Open, Open Folder, and Save a Copy As, which were already native. The 72-language file-type dropdown is preserved and extensions stay correct (bare name on a Python tab → `.py`; a typed extension is always honoured). It went non-native back in v0.1.88.1 because native dialogs dropped the "which type did you pick" signal and could save the wrong extension — now sidestepped by driving the default extension from the current tab's language instead of the dialog filter. Verified in the running app. | diff --git a/docs/docs.html b/docs/docs.html index d379474..d21f886 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -299,7 +299,7 @@