diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81c936b..481959f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,8 @@ concurrency: jobs: build-linux: runs-on: ubuntu-24.04 + # Fail fast instead of burning the 360-minute default on a wedge. + timeout-minutes: 120 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -67,13 +69,35 @@ jobs: cd build QT_QPA_PLATFORM=offscreen ctest --output-on-failure - # v0.1.118 — MCP sidecar. x64 is the canonical unit-test run (arm and - # macOS jobs only build it); ships as notepatra-mcp-linux-x64.tar.gz. + # v0.1.118 — MCP sidecar; ships as notepatra-mcp-linux-x64.tar.gz. + # + # ORDERING LAW (all four platform jobs): `cargo test --features remote` + # REBUILDS target/release/notepatra-mcp WITH remote compiled in, because + # cargo unifies the bin's features when it builds the integration tests + # that depend on it. So the LAST cargo invocation before the upload must + # be a plain `cargo build --release`, and we then assert the flavor — + # otherwise we would silently ship a crypto-linked sidecar. - name: Build and test MCP server + timeout-minutes: 25 run: | cd notepatra-mcp - cargo build --release cargo test --release + cargo test --release --features remote + cargo build --release + # Flavor assertion. `serve` is remote-only: a std-only build prints + # "built without remote support" and exits 2 immediately. + # + # MUST be time-capped and backgrounded, NOT `OUT=$(... serve)`: on a + # remote-ENABLED build `serve` binds a port and blocks forever, so a + # command substitution would wait on an EOF that never comes and hang + # the job — in precisely the failure case this exists to catch. A + # hang is not a red state. Capped at 10s, then killed, then we judge + # by what it printed. flavor.txt 2>&1 & PID=$! + for _ in $(seq 1 20); do kill -0 $PID 2>/dev/null || break; sleep 0.5; done + kill -9 $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + grep -q "built without remote support" flavor.txt || { echo "::error::shipped sidecar unexpectedly has remote support compiled in"; cat flavor.txt; exit 1; } - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 @@ -193,6 +217,8 @@ jobs: build-linux-arm: runs-on: ubuntu-24.04-arm + # Fail fast instead of burning the 360-minute default on a wedge. + timeout-minutes: 120 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -245,9 +271,24 @@ jobs: name: notepatra-linux-arm64 path: build/notepatra - # v0.1.118 — MCP sidecar (build only; unit tests run in the x64 job). - - name: Build MCP server - run: cd notepatra-mcp && cargo build --release + # v0.1.118 — MCP sidecar. Runs the full default + remote suites natively + # on arm64 rather than trusting the x64 job: pointer-width and alignment + # differences are exactly what a cross-arch suite exists to catch. + # Same ordering law as the x64 job — plain build LAST, then assert flavor. + - name: Build and test MCP server + timeout-minutes: 25 + run: | + cd notepatra-mcp + cargo test --release + cargo test --release --features remote + cargo build --release + # Time-capped flavor assertion — see the x64 job for why this cannot + # be a plain command substitution. + ./target/release/notepatra-mcp serve flavor.txt 2>&1 & PID=$! + for _ in $(seq 1 20); do kill -0 $PID 2>/dev/null || break; sleep 0.5; done + kill -9 $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + grep -q "built without remote support" flavor.txt || { echo "::error::shipped sidecar unexpectedly has remote support compiled in"; cat flavor.txt; exit 1; } - name: Upload MCP server artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 @@ -336,6 +377,8 @@ jobs: build-macos-arm: runs-on: macos-14 + # Fail fast instead of burning the 360-minute default on a wedge. + timeout-minutes: 120 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -432,6 +475,8 @@ jobs: echo "✓ macOS Full binary built (DuckDB engine; WebEngine N/A on brew qt@5)" - name: Build and run regression suite + # ctest's blanket TIMEOUT 600 is per-TEST, not per-step: a systemic wedge across ~48 tests still runs for hours without this. + timeout-minutes: 75 run: | # Reuse the same Qt5/QScintilla discovery the main build did by # reconfiguring the existing build tree with -DBUILD_TESTING=ON. @@ -871,11 +916,42 @@ jobs: name: notepatra-macos-arm64-full path: build-full/Notepatra-full.dmg - # v0.1.118 — MCP sidecar (build only; unit tests run in the linux x64 - # job). No Windows MCP artifact yet — the named-pipe transport is a - # stub, so shipping a binary there would be dishonest. - - name: Build MCP server - run: cd notepatra-mcp && cargo build --release + # v0.1.118 — MCP sidecar. As of v0.1.119 ALL FOUR platform jobs run both + # the default (std-only) and the remote-feature suites natively; there is + # no longer a single "canonical" test job. macOS additionally lipo's an + # arm64+x86_64 universal binary, because .mcpb declares ONE `darwin` entry + # and Claude Desktop on Intel Macs would otherwise get an unrunnable + # slice. The universal binary is consumed ONLY by the .mcpb — the + # standalone notepatra-mcp-macos-arm64.tar.gz stays arm64-thin so the + # released filename keeps meaning what it says. + # + # Same ordering law as the linux jobs: `cargo test --features remote` + # rebuilds the bin WITH remote, so the plain `cargo build --release` must + # come last, and the flavor assertion proves it did. + - name: Build and test MCP server (arm64 + x86_64 universal) + timeout-minutes: 25 + run: | + cd notepatra-mcp + cargo test --release + cargo test --release --features remote + rustup target add x86_64-apple-darwin + cargo build --release + cargo build --release --target x86_64-apple-darwin + # Time-capped flavor assertion — see the linux x64 job for why this + # cannot be a plain command substitution. + ./target/release/notepatra-mcp serve flavor.txt 2>&1 & PID=$! + for _ in $(seq 1 20); do kill -0 $PID 2>/dev/null || break; sleep 0.5; done + kill -9 $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + grep -q "built without remote support" flavor.txt || { echo "::error::shipped sidecar unexpectedly has remote support compiled in"; cat flavor.txt; exit 1; } + lipo -create -output target/notepatra-mcp-universal target/release/notepatra-mcp target/x86_64-apple-darwin/release/notepatra-mcp + lipo -archs target/notepatra-mcp-universal | grep -q x86_64 || { echo "::error::universal sidecar missing x86_64 slice"; exit 1; } + lipo -archs target/notepatra-mcp-universal | grep -q arm64 || { echo "::error::universal sidecar missing arm64 slice"; exit 1; } + # Prove the x86_64 slice actually RUNS, not just that it links — + # /dev/null; then arch -x86_64 ./target/x86_64-apple-darwin/release/notepatra-mcp /dev/null && echo "x86_64 slice executed under Rosetta"; else echo "::warning::Rosetta unavailable — x86_64 slice not smoke-executed"; fi - name: Upload MCP server artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 @@ -883,6 +959,12 @@ jobs: name: notepatra-mcp-macos-arm64 path: notepatra-mcp/target/release/notepatra-mcp + - name: Upload MCP server universal artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: notepatra-mcp-macos-universal + path: notepatra-mcp/target/notepatra-mcp-universal + build-windows: # PINNED (v0.1.113) for determinism: both windows-latest AND windows-2025 # now carry VS2026 (VS 18, MSVC 14.5x) as of mid-2026 — there is no hosted @@ -894,6 +976,13 @@ jobs: # runner image from moving under us again. Durable path later: KDE-patched # Qt 5.15 or the Qt6 migration (see project_qt6_migration_assessment). runs-on: windows-2025 + # HANG CONTAINMENT (v0.1.120). Without a job ceiling a wedge anywhere — + # QScintilla nmake, CMake, NSIS/WiX, an exe probe blocked on a modal + # Windows error box, or the regression suite — runs to GitHub's + # 360-minute default and reports NOTHING. This is the outermost net; + # the per-step ceilings below name the culprit, and the in-test + # watchdogs name the individual test. + timeout-minutes: 180 permissions: contents: read issues: write @@ -1082,6 +1171,65 @@ jobs: shell: cmd run: cd rust-core && cargo build --release + # MCP sidecar (Windows, x86_64-pc-windows-msvc — the runner default). + # Named-pipe transport shipped in v0.1.119 (src/transport/socket.rs + # cfg(windows)), so a prebuilt Windows binary is honest now. Canonical + # unit tests run in the linux x64 job; we ALSO run tests here because + # this is the only job that compiles the cfg(windows) pipe code — + # tests/socket_bridge.rs is #![cfg(unix)] and compiles to empty, the + # protocol + mock-transport suites are OS-neutral. + # Ordering law (see the linux x64 job): the remote-feature test run + # rebuilds the bin WITH remote compiled in, so the plain build comes LAST. + # `--test-threads=1` (v0.1.120): tests/pipe_bridge.rs drives REAL named + # pipes, and under the default parallel harness a hang cannot be pinned on + # a test — absence of an "ok" line only means "not finished yet", which is + # exactly how a previous incident burned two ~80-minute cycles. Serial + # order makes the stdout log alone name the culprit. These suites are + # ~3 s total, so serializing costs nothing. + - name: Build and test MCP server + timeout-minutes: 25 + shell: cmd + run: cd notepatra-mcp && del /q "%TEMP%\np-*-tracker.log" 2>nul & cargo build --release && cargo test --release -- --test-threads=1 && cargo test --release --features remote -- --test-threads=1 && cargo build --release + + # Every transport suite (named pipe, unix socket, loopback gateway) arms a + # watchdog that ABORTS on a hang, and abort DISCARDS libtest's captured + # output — so the culprit's name is written to a file instead. Globbed, + # not hard-coded to one suite: the previous version only knew about + # np-pipe-bridge-tracker.log, so a hang in the remote-gateway suite would + # have printed "no tracker file" and named nothing. + # `always()` because the only run that matters is the failing one. + - name: Transport test trackers (localize a hang) + if: always() + shell: cmd + run: | + if exist "%TEMP%\np-*-tracker.log" ( + for %%F in ("%TEMP%\np-*-tracker.log") do ( + echo === %%~nxF === + type "%%F" + ) + echo === an ENTER with no matching LEAVE is the test that hung === + ) else ( + echo no tracker file — no transport suite reached its first test + ) + + # Separate step because the assertion needs pwsh string matching, and cmd + # has no clean equivalent. `serve` on a std-only build exits 2 after + # printing this, so we match on the text rather than the exit code. + - name: Assert shipped MCP server is std-only + shell: pwsh + working-directory: notepatra-mcp + run: | + # Start-Process + WaitForExit(10s) rather than `$out = & ... | Out-String` + # for the same reason as the Unix jobs: a remote-ENABLED build's + # `serve` binds a port and never returns, and the pipeline would block + # forever instead of failing. Kill at 10s, then judge by the output. + $p = Start-Process -FilePath .\target\release\notepatra-mcp.exe -ArgumentList 'serve' ` + -RedirectStandardOutput flavor.txt -RedirectStandardError flavor.err ` + -PassThru -NoNewWindow + if (-not $p.WaitForExit(10000)) { $p.Kill(); $p.WaitForExit() } + $out = ((Get-Content flavor.txt -Raw -ErrorAction SilentlyContinue) + (Get-Content flavor.err -Raw -ErrorAction SilentlyContinue)) + if ($out -notmatch 'built without remote support') { Write-Host '::error::shipped sidecar unexpectedly has remote support compiled in'; Write-Host $out; exit 1 } + - name: Build C++ with CMake (MSVC) shell: pwsh run: | @@ -1324,6 +1472,8 @@ jobs: } - name: Diagnose notepatra.exe imports + Qt plugin loading + # Launches notepatra.exe: a Qt plugin-load stall or a modal error box would otherwise hang here forever. + timeout-minutes: 5 shell: pwsh run: | # Write all diagnostic output to BOTH stdout AND diag.log so the @@ -1626,6 +1776,8 @@ jobs: Write-Host "✓ All critical files present in Full bundle" - name: Smoke-test the binary (--version output check) + # This step EXPECTS a DLL_PROCESS_DETACH crash, which can raise a modal WER/missing-DLL box that blocks cmd /c forever. + timeout-minutes: 5 shell: pwsh run: | # Run notepatra.exe --version via cmd.exe with file redirection. @@ -1680,6 +1832,15 @@ jobs: } - name: Build and run regression suite + # ctest's blanket TIMEOUT 600 is per-TEST, not per-step: a systemic wedge across ~48 tests still runs for hours without this. + # 150, not 45: this step legitimately takes ~64 min on Windows (the + # MSVC + Qt test-target build dominates; ctest itself is ~3 min). A + # ceiling set near the median KILLS HEALTHY RUNS — a 45 did exactly + # that once, failing a job whose tests had all passed. These ceilings + # exist to turn an hours-long wedge into minutes, so set them at >=2x + # the observed maximum, never near the average. Runner performance + # varies run to run, and the suite only grows. + timeout-minutes: 150 shell: pwsh run: | # Reconfigure the existing build tree with -DBUILD_TESTING=ON so the @@ -1714,8 +1875,48 @@ jobs: # Add Qt + bundled QScintilla DLLs to PATH so CTest can load them. $env:PATH = "$qtRoot\bin;$PWD\..\notepatra-win;$env:PATH" - ctest --output-on-failure -C Release - if ($LASTEXITCODE -ne 0) { + # Crash-localizer for test_mcp_bridge: ctest discards a crashed test's + # captured output on Windows, so the test appends each section to this + # file (survives the access violation); we print it below on failure. + $env:NP_MCP_PROGRESS = "$env:RUNNER_TEMP\mcp_progress.log" + if (Test-Path $env:NP_MCP_PROGRESS) { Remove-Item $env:NP_MCP_PROGRESS } + $ctestLog = "$env:RUNNER_TEMP\ctest.log" + ctest --output-on-failure -C Release 2>&1 | Tee-Object -FilePath $ctestLog + $ctestExit = $LASTEXITCODE + + $enters = 0; $leaves = 0; $sectionFailures = 0 + if (Test-Path $env:NP_MCP_PROGRESS) { + Write-Host "=== test_mcp_bridge section progress (ENTER without LEAVE = crashing section) ===" + Get-Content $env:NP_MCP_PROGRESS | ForEach-Object { + Write-Host $_ + if ($_ -like 'ENTER *') { $enters++ } + if ($_ -like 'LEAVE *') { $leaves++ } + if ($_ -like 'FAILED *') { $sectionFailures++ } + } + Write-Host "=== sections entered=$enters passed=$leaves failed=$sectionFailures ===" + } + + # QtTest's own report, written by `-o ,txt` from the test's + # main(). ctest printed NOTHING for this test even with + # --output-on-failure — no PASS lines, no failing QCOMPARE, no line + # number — so three failing sections cost a full cycle each just to + # identify. This file is independent of ctest's capture and carries + # actual-vs-expected plus file:line for every failure. + $qtestLog = "$env:NP_MCP_PROGRESS.qtest.txt" + if (Test-Path $qtestLog) { + Write-Host "=== test_mcp_bridge QtTest report (failures with actual/expected + line) ===" + Select-String -Path $qtestLog -Pattern '^(FAIL!|XFAIL|Totals:)' -Context 0,4 | + ForEach-Object { Write-Host $_.Line; $_.Context.PostContext | ForEach-Object { Write-Host " $_" } } + } + + # NO tolerance clause here, deliberately. One previously existed, for a + # "teardown crash" that turned out not to exist: the marker it relied + # on wrote LEAVE even for a FAILED section, so three genuinely failing + # tests read as "all sections passed, crashes at exit". With the marker + # recording QTest::currentTestFailed(), the tracker reports + # entered=76 passed=73 failed=3 and qExec returns normally — there is + # no crash to forgive. Any failure here is a real failure; fail loudly. + if ($ctestExit -ne 0) { Write-Host "::error::Windows regression suite FAILED" exit 1 } @@ -2125,15 +2326,35 @@ jobs: path: notepatra-*-full.msi if-no-files-found: ignore + - name: Upload MCP server artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: notepatra-mcp-windows-x64 + path: notepatra-mcp/target/release/notepatra-mcp.exe + # ─── Failure diagnostics — must be LAST so it sees failures from any prior step ─── - - name: Upload build logs on failure - if: failure() + # `always()`, NOT `failure()`: a job CANCEL or a job TIMEOUT does not fire + # `failure()`, so the one run whose logs matter most — the wedged one — + # used to upload nothing. ctest.log and mcp_progress.log are included + # because they name WHICH test was running; previously they were printed + # only from inside the regression step, so if that step was the thing that + # hung, the operator learned nothing at all. + - name: Upload build logs + if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-build-logs path: | cmake-configure.log cmake-build.log + ${{ runner.temp }}/ctest.log + ${{ runner.temp }}/mcp_progress.log + # Rust's env::temp_dir() is %TEMP% (C:\Users\runneradmin\AppData\ + # Local\Temp), which on Windows runners is NOT runner.temp + # (D:\a\_temp) — globbing runner.temp here would silently upload + # nothing. The printed "Transport test trackers" step already uses + # %TEMP% correctly; this makes the artifact agree with it. + ${{ env.TEMP }}/np-*-tracker.log if-no-files-found: ignore - name: Post Windows failure diagnostics to issue #1 @@ -2233,6 +2454,8 @@ jobs: # Was previously ubuntu-latest, which would silently roll forward and # could shift cosign / SLSA action runtime expectations under our feet. runs-on: ubuntu-24.04 + # Fail fast instead of burning the 360-minute default on a wedge. + timeout-minutes: 30 if: startsWith(github.ref, 'refs/tags/v') permissions: contents: write # publish the release @@ -2250,8 +2473,8 @@ jobs: tar czf notepatra-linux-x64.tar.gz -C notepatra-linux-x64 notepatra tar czf notepatra-linux-arm64.tar.gz -C notepatra-linux-arm64 notepatra # v0.1.118 — MCP sidecar tarballs (Linux x64/arm64 + macOS arm64; - # no Windows binary — named-pipe transport not shipped yet). Hard - # requirement: if a job uploaded the dir, the binary must be in it. + # Windows ships as a .zip below). Hard requirement: if a job + # uploaded the dir, the binary must be in it. for P in linux-x64 linux-arm64 macos-arm64; do if [ -d "notepatra-mcp-$P" ]; then test -f "notepatra-mcp-$P/notepatra-mcp" || { echo "::error::notepatra-mcp-$P artifact dir missing the binary"; exit 1; } @@ -2261,6 +2484,12 @@ jobs: echo "::error::expected MCP artifact dir notepatra-mcp-$P is missing"; exit 1 fi done + # MCP sidecar Windows zip (zip, not tar — Windows convention). + # NOTE the name is notepatra-mcp-windows-x64.zip — distinct from the + # editor's notepatra-windows-x64.zip; every downstream list references + # it by EXACT filename (prefix-collision lesson, 01d8f08). + test -f notepatra-mcp-windows-x64/notepatra-mcp.exe || { echo "::error::notepatra-mcp-windows-x64 artifact dir missing notepatra-mcp.exe"; exit 1; } + (cd notepatra-mcp-windows-x64 && zip ../notepatra-mcp-windows-x64.zip notepatra-mcp.exe) # v0.1.64 — Linux "full" flavor tarballs (bundles QtWebEngine for # inline Vega-Lite chart rendering). LINUX ONLY for the foreseeable # future — Charts Pack is downloaded on-demand on macOS/Windows @@ -2371,6 +2600,24 @@ jobs: with: path: src + - name: Build .mcpb bundle (Claude Desktop one-click) + run: | + # .mcpb = zip of manifest.json + per-platform sidecar binaries. + # Tag-gated for free: this whole release job runs only on + # startsWith(github.ref, 'refs/tags/v'). + TAG="${GITHUB_REF##*/}" + python3 src/notepatra-mcp/mcpb/build-mcpb.py \ + --manifest src/notepatra-mcp/mcpb/manifest.json \ + --cargo-toml src/notepatra-mcp/Cargo.toml \ + --expect-version "${TAG#v}" \ + --require-all \ + --binary linux-x64=notepatra-mcp-linux-x64/notepatra-mcp \ + --binary linux-arm64=notepatra-mcp-linux-arm64/notepatra-mcp \ + --binary darwin=notepatra-mcp-macos-universal/notepatra-mcp-universal \ + --binary win32-x64=notepatra-mcp-windows-x64/notepatra-mcp.exe \ + --out notepatra-mcp.mcpb + unzip -l notepatra-mcp.mcpb + - name: Generate SHA-256 checksums run: | # Generate a single SHA256SUMS file in the standard format that @@ -2380,6 +2627,7 @@ jobs: notepatra-linux-x64-full.tar.gz notepatra-linux-arm64-full.tar.gz \ notepatra-mcp-linux-x64.tar.gz notepatra-mcp-linux-arm64.tar.gz \ notepatra-mcp-macos-arm64.tar.gz \ + notepatra-mcp-windows-x64.zip notepatra-mcp.mcpb \ notepatra-macos-arm64.dmg notepatra-macos-arm64-full.dmg \ notepatra-windows-x64.zip notepatra-windows-x64-full.zip notepatra-setup-*.exe notepatra-*.msi notepatra-full-*.msi \ notepatra_*_amd64.deb notepatra_*_arm64.deb \ @@ -2438,6 +2686,7 @@ jobs: notepatra-linux-x64-full.tar.gz notepatra-linux-arm64-full.tar.gz \ notepatra-mcp-linux-x64.tar.gz notepatra-mcp-linux-arm64.tar.gz \ notepatra-mcp-macos-arm64.tar.gz \ + notepatra-mcp-windows-x64.zip notepatra-mcp.mcpb \ notepatra-macos-arm64.dmg notepatra-macos-arm64-full.dmg \ notepatra-windows-x64.zip notepatra-windows-x64-full.zip notepatra-setup-*.exe notepatra-*.msi notepatra-full-*.msi \ notepatra_*_amd64.deb notepatra_*_arm64.deb \ @@ -2464,6 +2713,8 @@ jobs: notepatra-mcp-linux-x64.tar.gz notepatra-mcp-linux-arm64.tar.gz notepatra-mcp-macos-arm64.tar.gz + notepatra-mcp-windows-x64.zip + notepatra-mcp.mcpb notepatra-macos-arm64.dmg notepatra-macos-arm64-full.dmg notepatra-windows-x64.zip @@ -2490,6 +2741,12 @@ jobs: notepatra-mcp-*.tar.gz notepatra-mcp-*.tar.gz.sig notepatra-mcp-*.tar.gz.pem + notepatra-mcp-windows-x64.zip + notepatra-mcp-windows-x64.zip.sig + notepatra-mcp-windows-x64.zip.pem + notepatra-mcp.mcpb + notepatra-mcp.mcpb.sig + notepatra-mcp.mcpb.pem notepatra-macos-arm64.dmg notepatra-macos-arm64-full.dmg notepatra-windows-x64.zip diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7b7cf4d..87027f7 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -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 @@ -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 diff --git a/.gitignore b/.gitignore index e9faf74..994caa2 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd0cdd..a9e3705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `/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.** diff --git a/CMakeLists.txt b/CMakeLists.txt index c36778b..6277cca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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") diff --git a/README.md b/README.md index e002c53..9e385e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ C++ + Rust · ~12 MB bare native executable · Zero Electron · 238 file types · 82 language lexers · Local AI formatters · MCP server

- New in v0.1.119: notepatra-mcp — connect Claude Desktop, Claude Code, OpenAI Codex, and any MCP client to the running editor. 35 tools (Git, read-only SQL, Noter notes); every write human-approved in-window; nothing leaves your machine. + New in v0.1.120: notepatra-mcp now ships a prebuilt signed Windows sidecar and a one-click Claude Desktop bundle — connect Claude Desktop, Claude Code, OpenAI Codex, and any MCP client to the running editor. 48 tools (Git, read-only SQL, saved-connection queries, charts, Noter notes); every write human-approved in-window; nothing leaves your machine.

Website · @@ -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.4 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.119 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–42.7 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.4 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.120 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–42.7 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. @@ -239,13 +239,13 @@ A first-class diagramming surface that **renders in the default binary on every From v0.1.118, Notepatra ships **`notepatra-mcp`** — a stdio JSON-RPC 2.0 [Model Context Protocol](https://modelcontextprotocol.io) server that connects external AI assistants (Claude Desktop, Claude Code, OpenAI Codex, the OpenAI Agents SDK, and any spec-compliant MCP client) to the running editor over a local socket. Nothing leaves your machine: stdio to the client, local socket to the editor, no network connections. -**35 tools in three tiers** (as of v0.1.119): +**48 tools in three tiers**: | Tier | Tools | Gate | |---|---|---| -| **Read** (18) | tabs, selection, status, recent files, in-tab + project search, Noter notes, reminders, read-only Git (status / diff / log / show / branch), `.npd` validation, read-only SQL (`run_sql`) | None — observation only | -| **Act** (9) | open file, new tab, go to line, set language, compare tabs, format JSON/SQL/HTML, open note | None — visible, non-destructive | -| **Write** (8) | insert text, replace selection, find-and-replace, save, create note, append note, set reminder, export diagram | **Approve/Deny card inside the editor** — 120 s auto-deny, FIFO one card at a time, no headless bypass | +| **Read** (24) | tabs, selection, status, recent files, in-tab + project search, Noter notes, reminders, read-only Git (status / diff / log / show / branch), `.npd` validation, read-only SQL (`run_sql`), language list (`list_languages`), capability probe (`get_capabilities`), `.npd` source read (`get_diagram_source`), saved connections (`list_connections` / `run_query` / `list_tables`) | None — observation only | +| **Act** (13) | open file, new tab, go to line, set language, compare tabs, format JSON/SQL/HTML, open note, create diagram, open Noter, open Data Analyst (`open_data_analyst`), render chart (`render_chart`) | None — visible, non-destructive | +| **Write** (11) | insert text, replace selection, find-and-replace, save, create note, append note, set reminder, export diagram, set diagram source, export query results (`export_query_results`), export chart (`export_chart`) | **Approve/Deny card inside the editor** — 120 s auto-deny, FIFO one card at a time, no headless bypass | `find_in_tab` and `search_project` also take an optional `regex` flag. `run_sql` is **SELECT-only** (rejected by the SQL classifier otherwise) and, on the Full/DuckDB edition, runs in an engine sandbox — the target file is materialized into an in-memory table, then DuckDB's external filesystem access is disabled (`enable_external_access=false`) before the untrusted query runs, so it cannot read host files. Since v0.1.119 the sidecar also supports Windows over a named pipe. @@ -263,7 +263,7 @@ command = "notepatra-mcp" args = ["--socket"] ``` -Full tool reference, Claude Desktop / Agents SDK snippets, security model, and honest limitations (editor must be running; Windows named-pipe transport lands in a later release; cloud-only connector surfaces can't reach a desktop editor): [docs/mcp.html](docs/mcp.html) / [notepatra.org/mcp.html](https://notepatra.org/mcp.html). +Full tool reference, Claude Desktop / Agents SDK snippets, security model, and honest limitations (editor must be running; prebuilt Windows sidecar zip + one-click .mcpb bundle ship from the next release — until then Windows uses cargo install notepatra-mcp; cloud-only connector surfaces can't reach a desktop editor): [docs/mcp.html](docs/mcp.html) / [notepatra.org/mcp.html](https://notepatra.org/mcp.html). --- @@ -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.4 MB Linux x64, similar on macOS / Windows). Latest v0.1.119 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **42.7 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.4 MB Linux x64, similar on macOS / Windows). Latest v0.1.120 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **42.7 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.119](https://github.com/singhpratech/notepatra/releases/latest) +### Or download manually — [Latest release: v0.1.120](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.119.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.120.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.119_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.119_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.119-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.119-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.118-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.118-x86_64.AppImage && sudo cp Notepatra-0.1.118-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.120_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.120_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.120-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.120-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.120-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.120-x86_64.AppImage && sudo cp Notepatra-0.1.120-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.119.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.119.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.119_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.119_amd64.deb` | -| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.119_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.119_arm64.deb` | +| 🪟 **Windows** | [`notepatra-local-ai-0.1.120.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.120.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.120_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.120_amd64.deb` | +| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.120_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.120_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.119_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.119` for the lite build and `Notepatra v0.1.119` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.119` / `Notepatra Local AI v0.1.119` 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.120_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.120` for the lite build and `Notepatra v0.1.120` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.120` / `Notepatra Local AI v0.1.120` for the cloud-free (local-ai) builds; the same name shows in the window title bar and the About dialog. ### Verify your download @@ -585,13 +585,13 @@ cl /LD myplugin.cpp /Fe:myplugin.dll | **Kate / Gedit** | ~30 MB | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | | **Notepatra** | **4.1 / 27.5 / 42.7 MB** | ✓ C++/Rust | ✓ Ollama | ✓ regex + AI | ✓ Rust mmap | ✓ | ✓ | ✓ | ✓ GPL-3 | -> *Notepatra download sizes are Linux x64 tar.gz / macOS DMG / Windows MSI from v0.1.119. Linux is just the binary (Qt is system-installed). macOS and Windows include bundled Qt. The bare `notepatra` executable inside is ~12 MB on every platform (Linux 12.4 MB, similar on macOS / Windows). Compressed download is much smaller because tar.gz / DMG / MSI all compress the binary plus shared libraries.* +> *Notepatra download sizes are Linux x64 tar.gz / macOS DMG / Windows MSI from v0.1.120. Linux is just the binary (Qt is system-installed). macOS and Windows include bundled Qt. The bare `notepatra` executable inside is ~12 MB on every platform (Linux 12.4 MB, similar on macOS / Windows). Compressed download is much smaller because tar.gz / DMG / MSI all compress the binary plus shared libraries.* --- ## Tests -Focused automated regression tests are wired through CMake + CTest and run in CI — **70 test suites** on the Full build (67 on Lite, which omits the two DuckDB suites and the WebEngine-gated Noter-export suite); all green, each with many assertions. A representative sample: +Focused automated regression tests are wired through CMake + CTest and run in CI — **71 test suites** on the Full build (67 on Lite, which omits the two DuckDB suites and the WebEngine-gated Noter-export suite); all green, each with many assertions. A representative sample: - `test_lexers` — verifies every shipped QScintilla lexer produces real styling - `test_palette` — verifies the canonical 9-hue palette colors and bold/italic styles @@ -622,6 +622,7 @@ Notepatra follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic | Version | Date | Highlights | |---|---|---| +| [**v0.1.120**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.120) | 2026-07-20 | **MCP full control — 48 tools, a remote gateway, and the sidecar finally works on Windows and macOS.** `notepatra-mcp` grows from 35 to 48 tools (Read 24 / Act 13 / Write 11), making every sub-app AI-drivable: **Diagram** (`create_diagram`, `get_diagram_source`, `set_diagram_source`), **Data-analyst** (`list_connections`, `run_query`, `list_tables`, `open_data_analyst`, `export_query_results` — reaches your saved PostgreSQL / MySQL / SQL Server / SQLite / DuckDB connections, SELECT-only and row-capped), **Charts** (`render_chart`, `export_chart`), **Noter** (`open_noter`), and **discovery** (`list_languages`, `get_capabilities`). **Two defects that made the sidecar unusable outside Linux are fixed:** the Windows named-pipe transport deadlocked on every verb — the pipe was opened without `FILE_FLAG_OVERLAPPED` and a parked reader thread blocked every write forever, so no tool call ever completed (only the first request *after* the greeting hung, which is why it looked healthy) — and on macOS the sidecar could not locate a running editor at all, because it guessed `$TMPDIR` while Qt binds under `NSTemporaryDirectory()`; the editor now publishes its real bound endpoint. New opt-in **loopback-only remote gateway** (`serve` / `pair` / `connect`, 8-digit HMAC pairing, fail-closed scopes) — the default build stays **crypto-free**, asserted in every CI job, and a remote write still raises the same local Approve card. Windows gains a prebuilt **cosign-signed sidecar zip** and a one-click **`.mcpb`** Claude Desktop bundle. `.mcpb` also fixed to stop handing Intel Macs an arm64 binary and ARM64 Linux an x64 one. Approval gate unchanged: every write needs an in-editor click, 120 s auto-deny, no headless bypass. Full offscreen ctest 71/71, sidecar 83 tests (117 with `--features remote`). | | [**v0.1.119**](https://github.com/singhpratech/notepatra/releases/tag/v0.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**. New read tools: `list_reminders`, read-only Git (`git_status` / `git_diff` / `git_log` / `git_show` / `git_branch`), `validate_npd`, and **`run_sql`** (SELECT-only). New act tool: `open_note`. New **human-approved** write verbs: `create_note`, `append_note`, `set_reminder`, `export_diagram`. `find_in_tab` / `search_project` gained an optional `regex` flag. **Windows named-pipe transport is now supported** (Linux/macOS/Windows all connect via `--socket`; prebuilt Windows binaries not yet in the signed bundle — build from source / `cargo install`). **`run_sql` security:** SELECT-only via the SQL classifier and, on the Full/DuckDB edition, an engine sandbox — the file is materialized in-memory, then `enable_external_access=false` is set before the untrusted query runs, so it cannot read host files; an adversarial security pass hardened it before ship. Bare Lite binary unchanged at 12.4 MB (the new tools live in the sidecar). Full offscreen ctest 71/71, Lite 68/68, 60 sidecar cargo tests, live end-to-end across all 35 tools. | | [**v0.1.118**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.118) | 2026-07-17 | **MCP support — your editor, readable by your AI.** New standalone **`notepatra-mcp`** sidecar: a spec-compliant stdio JSON-RPC 2.0 [Model Context Protocol](https://modelcontextprotocol.io) server exposing **22 tools in three tiers** — read (10), act (8), and write (4) — plus open tabs / Noter notes as MCP resources and 3 ready-made prompts. Works with **Claude Desktop, Claude Code, OpenAI Codex CLI, the OpenAI Agents SDK**, and any spec-compliant stdio MCP client. **Every write is human-gated:** an Approve/Deny card inside the editor window, 120 s auto-deny, FIFO one-at-a-time, no headless bypass — the gate lives in the editor process, so no MCP client can write without a human click. Local socket + stdio only, no network connections; Linux/macOS first (Windows named-pipe transport in a future release). Prebuilt `notepatra-mcp` binaries ship as cosign-signed release artifacts; new docs page [notepatra.org/mcp.html](https://notepatra.org/mcp.html). Bare-binary size claim corrected 12.4 → 12.4 MB. Full ctest 68/68 + 47 sidecar cargo tests + live E2E 17/17. | | [**v0.1.117**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.117) | 2026-07-17 | **The honesty release — a 32-agent adversarial audit of every component, every confirmed defect fixed, ~4,100 LOC of dead code removed. No new deps, same bare binary (slightly smaller).** **Find & Replace tells the truth:** Find in Files finally shows its results (they were written to a never-shown widget), whole-word is honored by Count / Replace All / Find in Files, line numbers are right in non-ASCII files, and dead controls are hidden until they work. **Noter export unlocked:** notes export as PDF / Markdown from the right-click menu (the exporter was fully built and tested — it just had no UI entry point); resizable pop-out with visible pin state; Dark/Monokai-correct dialogs. **UTF-32 files with emoji no longer corrupt on save** (byte-level round-trip regression-tested). Terminal gained a real Stop button; tab colors survive reorder; image attach is no longer gated by a hardcoded model list. **~150 Rust-core unit tests promoted into CI + release gates**; git-panel test resurrected; full ctest **70/70**. New for AI assistants: [llms.txt](https://notepatra.org/llms.txt) + crawler-friendly robots.txt + CITATION.cff. | diff --git a/docs/docs.html b/docs/docs.html index 2ddc03e..03eca6b 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -299,7 +299,7 @@

Notepatra Notepatra - v0.1.119 docs + v0.1.120 docs