Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
335e51c
MCP Phase 0a+1: sub-app control foundation (35→41 tools)
Jul 18, 2026
8596c5c
MCP Phase 2: Data-analyst + Charts control (41→48 tools)
Jul 18, 2026
3897933
MCP Phase 3a: remote gateway core (feature-gated, loopback-only)
Jul 18, 2026
0a802eb
MCP Phase 4: Windows prebuilt sidecar + .mcpb bundle + honest docs
Jul 18, 2026
d17f5d8
v0.1.120: MCP full control — 48 tools + remote gateway + Windows preb…
Jul 18, 2026
f4b54d1
test(mcp): unbuffered stdio in test_mcp_bridge to localize offscreen-…
Jul 19, 2026
95b1950
test(mcp): file-based section localizer for the offscreen-Windows crash
Jul 19, 2026
5a8106e
fix(test): resolve test_mcp_bridge offscreen-Windows teardown crash
Jul 19, 2026
a86d6ad
fix(test): _Exit immediately after qExec — do not touch QSQLITE at sh…
Jul 19, 2026
5f88ca8
fix(test): skip real-QSQLITE sections on Windows to avoid plugin-tear…
Jul 19, 2026
520fe82
fix(ci): sound tolerance for the offscreen-Windows teardown crash
Jul 19, 2026
e905a00
fix(mcp): make the sidecar reach the editor on macOS + close 3-platfo…
Jul 19, 2026
c469389
fix(test): stop the new Windows named-pipe suite from HANGING CI
Jul 20, 2026
96de853
fix(mcp): Windows named-pipe transport DEADLOCKED on every verb — rea…
Jul 20, 2026
8a82334
docs(release): record the Windows/macOS transport fixes + suite count
Jul 20, 2026
8a1ef4d
fix(ci): raise timeouts that were set BELOW the real runtimes
Jul 20, 2026
3f9ee91
fix(ci): set every timeout from MEASURED durations, not guesses
Jul 20, 2026
9705f2c
fix(release-check): WebEngine probe checked a package name that does …
Jul 20, 2026
3717a4e
docs: v0.1.119 was missing from the site entirely + fix two blind gates
Jul 20, 2026
7c4a430
docs: the v0.1.120 release notes described a release we did not build
Jul 20, 2026
690ede3
docs(mcp): document the remote gateway before it can become a false c…
Jul 20, 2026
4bc3798
fix(test): there was never a teardown crash — 3 real Windows failures
Jul 20, 2026
870b969
fix(test): findCard() returned a DISMISSED card — 3 Windows failures
Jul 20, 2026
0afbfe3
test(mcp): end-to-end smoke on all 3 platforms — the check that was m…
Jul 20, 2026
e51ce74
Revert "test(mcp): end-to-end smoke on all 3 platforms"
Jul 20, 2026
67c9256
chore: ignore two stray local scratch files
Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 274 additions & 17 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

40 changes: 39 additions & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ on:
branches: [main]
paths:
- 'rust-core/**'
- 'notepatra-mcp/**'
- '.github/workflows/quality.yml'
pull_request:
branches: [main]
paths:
- 'rust-core/**'
- 'notepatra-mcp/**'
- '.github/workflows/quality.yml'

jobs:
rust-quality:
runs-on: ubuntu-24.04
# HANG CONTAINMENT (v0.1.120). tests/socket_bridge.rs drives REAL unix
# sockets: a SocketEditor regression that stops the client connecting would
# park an accept() forever. That is now bounded in-test (bounded join +
# watchdog abort), but a job ceiling is the outermost net — without one a
# wedge here runs to GitHub's 360-minute default and reports nothing.
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Expand All @@ -31,14 +39,44 @@ jobs:
run: cd rust-core && cargo clippy --all-targets --all-features -- -D warnings

- name: Run tests
timeout-minutes: 15
run: cd rust-core && cargo test --release

# v0.1.118 — same three gates for the MCP sidecar crate.
- name: Check formatting (notepatra-mcp)
run: cd notepatra-mcp && cargo fmt --check

# --all-features so the optional "remote" cfg (hmac/sha2/getrandom) is
# linted too — without it the entire remote/ tree is cfg'd out and its
# warnings never reach the gate. The SHIPPED binary stays std-only; the
# feature is a CI/dev-time build only.
- name: Run clippy (notepatra-mcp)
run: cd notepatra-mcp && cargo clippy --all-targets -- -D warnings
run: cd notepatra-mcp && cargo clippy --all-targets --all-features -- -D warnings

- name: Run tests (notepatra-mcp)
timeout-minutes: 10
run: cd notepatra-mcp && cargo test --release

# Same reason as clippy above: the remote-gated tests are compiled out of
# the default run, so they need their own invocation or they rot silently.
- name: Run tests (notepatra-mcp, remote feature)
timeout-minutes: 10
run: cd notepatra-mcp && cargo test --release --features remote

# A watchdog abort DISCARDS libtest's captured output, so the hung test's
# name survives only in its tracker file. `always()` because the run that
# matters is the failing one.
- name: Transport test trackers (localize a hang)
if: always()
run: |
shopt -s nullglob
files=("${TMPDIR:-/tmp}"/np-*-tracker.log)
if [ ${#files[@]} -eq 0 ]; then
echo "no tracker file — no transport suite reached its first test"
else
for f in "${files[@]}"; do
echo "=== $(basename "$f") ==="
cat "$f"
done
echo "=== an ENTER with no matching LEAVE is the test that hung ==="
fi
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,10 @@ eval/data-analyst/.eval_checkpoint.jsonl
eval/data-analyst/report.html
eval/data-analyst/report.json
eval/**/__pycache__/

# Stray local scratch files (pre-existing, 2026-07-09) — an accidental README
# duplicate and an empty saved "new tab". Ignored, not deleted: they are the
# user's local files, and ignoring keeps them out of commits + satisfies the
# release-check "clean tree" gate without touching them.
/README (copy).md
/tests/new 3test.md
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver

---

## [0.1.120] — 2026-07-20

**MCP full control — 48 tools + a remote gateway, and the sidecar now actually works on Windows and macOS. The `notepatra-mcp` sidecar grows from 35 to 48 tools (Read 24 / Act 13 / Write 11), making every sub-app AI-drivable (Diagram, Noter, Data-analyst, Charts) with capability discovery. A new opt-in, loopback-only remote gateway pairs a client over an authenticated channel with fail-closed scopes; writes still require a physical Approve click in the editor. Windows gets a prebuilt signed sidecar zip and a one-click `.mcpb` bundle.**

### Added
- **13 new MCP tools** (new totals: Read 24 / Act 13 / Write 11 = 48):
- **Diagram control:** `create_diagram` (Act), `get_diagram_source` (Read), `set_diagram_source` (Write).
- **Data-analyst:** `list_connections`, `run_query`, `list_tables` (Read), `open_data_analyst` (Act), `export_query_results` (Write) — reaches saved PostgreSQL / MySQL / SQL Server / SQLite / DuckDB connections that `run_sql` cannot.
- **Charts (Full):** `render_chart` (Act), `export_chart` (Write).
- **Noter:** `open_noter` (Act).
- **Discovery:** `list_languages`, `get_capabilities` (Read).
- **Remote gateway** (opt-in, feature-gated `remote`, loopback-only): `serve` / `pair` / `connect` modes; 8-digit HMAC pairing → 256-bit bearer token (SHA-256 stored, `0600`); fail-closed scopes (`read_only` / `read_act` / `write_request`) enforced at dispatch; `tools/list` filtered by scope. The default `cargo install` build stays crypto-free (hmac/sha2 behind the feature); the editor never listens on the network; writes forward to the local Approve card — no remote bypass.
- **Windows prebuilt sidecar** `notepatra-mcp-windows-x64.zip` (signed; no Rust toolchain needed) and a one-click **`.mcpb`** Claude Desktop bundle (`notepatra-mcp.mcpb`), both covered by SHA-256 checksums, cosign signatures, and SLSA provenance.

### Fixed
- **The Windows named-pipe transport deadlocked on every verb — the sidecar had never completed a tool call on Windows.** The pipe was opened without `FILE_FLAG_OVERLAPPED` and a reader thread parked permanently in `ReadFile`; because a synchronous Windows file object serializes I/O, that parked read blocked every outgoing write indefinitely, and the write carried no timeout so the read timeout never engaged. Only the first request *after* the greeting hung — the greeting itself worked, since the bridge speaks unprompted, which is why the transport looked healthy. Reads and writes are now gated on a command channel so a write is only ever issued while the reader is idle, making the deadlock impossible by construction. This also fixes an EOF-starvation and handle/thread leak on teardown (`Drop` closed only the writer), and a latent hang when a >64 KB payload was written to an editor busy showing an approval dialog. Caught by the new Windows named-pipe test suite.
- **The sidecar could not find a running editor on macOS.** The socket path was computed as `$TMPDIR||/tmp` + name, but Qt binds under `NSTemporaryDirectory()` (`/private/var/folders/…/T/`), so the sidecar reported "Notepatra is not running" against a running editor. The editor now publishes its actual bound endpoint to `<config>/mcp-endpoint.json` (atomic, `0600`) and the sidecar dials that first, falling back to the computed guess — so older editors keep working in both directions.
- `.mcpb` bundles selected the wrong binary on two platforms: all of `darwin` mapped to the arm64 build (a hard exec failure on Intel Macs, which Rosetta cannot translate), and `linux-arm64` was packed but had no selector, so ARM64 Linux got the x64 binary. macOS now ships a lipo'd universal binary with per-slice assertions, and Linux dispatches on `uname -m`.

### Changed
- `set_language` resolves case-insensitively + common aliases (`python` → `Python`, `cpp` → `C++`, …) and echoes the canonical name it applied.
- `git_*` and `search_project` fall back to the active file's directory when no workspace folder is open, so Git works on any open repository file.
- The sidecar's Windows named-pipe transport and the remote gateway are now covered by real runtime tests, and `cargo test` (both feature sets) runs on all four platform jobs instead of Linux x64 alone.

### Security
- `run_query` (Read-tier, card-less) reaches saved named connections, so its read-only classifier's file-read denylist now spans SQLite / MySQL (`LOAD_FILE`) / PostgreSQL (`pg_read_server_files`, `pg_ls_dir`, `pg_stat_file`, …) / DuckDB, guarded by a five-engine regression test. Read-only SQL is never an arbitrary-file-read primitive.
- The new write verbs (`set_diagram_source`, `export_query_results`, `export_chart`) go through the same in-editor Approve/Deny card (120 s auto-deny, no headless bypass); the gate lives in the editor process.
- Remote-gateway token files are now ACL-restricted on Windows (`icacls /inheritance:r`), matching the `0600` posture already applied on Unix — previously they inherited broad directory permissions.

## [0.1.119] — 2026-07-18

**MCP depth — 35 tools. The `notepatra-mcp` sidecar grows from 22 to 35 tools in three tiers (Read 18 / Act 9 / Write 8): read-only Git, read-only SQL, Noter reminders and `.npd` validation, plus approval-gated write verbs for creating/appending notes, setting reminders, and exporting diagrams. Windows named-pipe transport is now supported. `run_sql` is SELECT-only and, on the Full/DuckDB edition, engine-sandboxed.**
Expand Down
7 changes: 5 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(Notepatra VERSION 0.1.119 LANGUAGES CXX)
project(Notepatra VERSION 0.1.120 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -2499,13 +2499,16 @@ if(NOTEPATRA_BUILD_TESTS AND EXISTS "${CMAKE_SOURCE_DIR}/test_mcp_bridge.cpp")
# v0.1.119 — validate_npd calls the REAL parse-only entry point
# Npd::parse (pure QtCore, no widgets).
src/diagram/npd_parser.cpp
# phase 2 — run_query/list_tables exercise the REAL classifier + QSQLITE
# runQuery (compiled with NO NOTEPATRA_HAVE_DUCKDB, so no DuckDB link).
src/dbconnections.cpp
)
target_include_directories(test_mcp_bridge PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_compile_definitions(test_mcp_bridge PRIVATE
NOTEPATRA_VERSION="${PROJECT_VERSION}")
target_link_libraries(test_mcp_bridge PRIVATE
Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Concurrent
Qt5::Test)
Qt5::Sql Qt5::Test)
notepatra_add_qt_test(test_mcp_bridge)
set_tests_properties(test_mcp_bridge PROPERTIES LABELS "unit;mcp;ipc")
message(STATUS "Regression test enabled — target: test_mcp_bridge")
Expand Down
Loading
Loading