Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 33 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04
artifact: graphiq-aarch64-unknown-linux-gnu
- target: x86_64-pc-windows-msvc
os: windows-2022
artifact: graphiq-x86_64-pc-windows-msvc

runs-on: ${{ matrix.os }}

Expand Down Expand Up @@ -61,23 +64,44 @@ jobs:
- name: Build release
run: cargo build --release --target ${{ matrix.target }} --features gpu

- name: Package
- name: Package (Unix)
if: runner.os != 'Windows'
run: |
set -euo pipefail
mkdir -p dist
for bin in graphiq graphiq-mcp graphiq-bench; do
if [ -f "target/${{ matrix.target }}/release/$bin" ]; then
cp "target/${{ matrix.target }}/release/$bin" dist/
fi
test -f "target/${{ matrix.target }}/release/$bin"
cp "target/${{ matrix.target }}/release/$bin" dist/
done
cd dist && tar czf "${{ matrix.artifact }}.tar.gz" -- *
shasum -a 256 "${{ matrix.artifact }}.tar.gz" > "${{ matrix.artifact }}.sha256"

- name: Package (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$dist = Join-Path $PWD 'dist'
New-Item -ItemType Directory -Path $dist -Force | Out-Null
$binaries = @('graphiq.exe', 'graphiq-mcp.exe', 'graphiq-bench.exe')
foreach ($binary in $binaries) {
$source = Join-Path $PWD "target\${{ matrix.target }}\release\$binary"
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) {
throw "missing release binary: $source"
}
Copy-Item -LiteralPath $source -Destination (Join-Path $dist $binary)
}
$archiveName = '${{ matrix.artifact }}.zip'
$archive = Join-Path $dist $archiveName
Compress-Archive -Path (Join-Path $dist 'graphiq.exe'), (Join-Path $dist 'graphiq-mcp.exe'), (Join-Path $dist 'graphiq-bench.exe') -DestinationPath $archive -Force
$hash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $archiveName" | Set-Content -LiteralPath (Join-Path $dist '${{ matrix.artifact }}.sha256')

- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: |
dist/${{ matrix.artifact }}.tar.gz
dist/${{ matrix.artifact }}.sha256
path: dist/*
if-no-files-found: error

release:
needs: [build]
Expand All @@ -94,4 +118,6 @@ jobs:
with:
files: |
artifacts/**/*.tar.gz
artifacts/**/*.zip
artifacts/**/*.sha256
generate_release_notes: true
174 changes: 174 additions & 0 deletions .github/workflows/windows-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: Windows smoke

# Validates the native Windows build, Windows path handling, setup/update
# integration points, and the MCP stdio protocol. GPU availability is not a
# property of the hosted runner, so the deterministic smoke path forces the
# documented CPU fallback while the release build still includes the gpu feature.

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
smoke:
runs-on: windows-2022
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Build release (gpu feature)
run: cargo build --workspace --release --features gpu

- name: Run workspace tests
run: cargo test --workspace --features gpu

- name: Run Windows CLI and setup smoke
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$root = (Get-Location).Path
$graphiq = Join-Path $root 'target\release\graphiq.exe'
$mcp = Join-Path $root 'target\release\graphiq-mcp.exe'
$env:GRAPHIQ_DISABLE_GPU = '1'

$version = & $graphiq --version
if ($LASTEXITCODE -ne 0 -or $version -notmatch '^graphiq ') {
throw "graphiq --version failed: $version"
}

$fixture = Join-Path $env:RUNNER_TEMP 'graphiq Windows fixture with spaces'
Remove-Item -LiteralPath $fixture -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path (Join-Path $fixture 'src') -Force | Out-Null
@'
pub struct RateLimiter { max: u32 }
impl RateLimiter {
pub fn check_rate(&self) -> bool { true }
pub fn reset(&self) {}
}
pub fn handle_request(rl: &RateLimiter) -> bool { rl.check_rate() }
'@ | Set-Content -LiteralPath (Join-Path $fixture 'src\lib.rs') -Encoding utf8

& git -C $fixture init --quiet
& git -C $fixture config user.email ci@example.invalid
& git -C $fixture config user.name 'GraphIQ CI'
& git -C $fixture add --all
& git -C $fixture commit --quiet -m init
if ($LASTEXITCODE -ne 0) { throw 'failed to create the Windows fixture repository' }

$db = Join-Path $fixture '.graphiq\graphiq.db'
& $graphiq index $fixture --db $db
if ($LASTEXITCODE -ne 0) { throw 'graphiq index failed on Windows' }

$search = (& $graphiq search 'rate limit' --db $db --top 3 | Out-String)
if ($LASTEXITCODE -ne 0 -or $search -notmatch 'RateLimiter') {
throw "Windows search did not find RateLimiter: $search"
}

$status = (& $graphiq status --db $db | Out-String)
if ($LASTEXITCODE -ne 0 -or $status -notmatch 'GPU: unavailable \(disabled by GRAPHIQ_DISABLE_GPU\)') {
throw "Windows status did not report the CPU fallback: $status"
}

# This exercises sibling graphiq-mcp.exe discovery, Windows command
# lookup, and the no-Unix-package-manager setup path.
& $graphiq setup --project $fixture --skip-index --persistent --harness claude-code
if ($LASTEXITCODE -ne 0) { throw 'graphiq setup failed on Windows' }
$configPath = Join-Path $fixture '.claude\.mcp.json'
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
if ($null -eq $config.mcpServers.graphiq) { throw 'setup did not write the Claude Code MCP entry' }
$configuredProject = [string]$config.mcpServers.graphiq.args[0]
# Rust canonicalize may use the Windows extended-path prefix; both
# spellings identify the same project and are accepted by the MCP.
if ($configuredProject.StartsWith('\\?\')) {
$configuredProject = $configuredProject.Substring(4)
}
if ($configuredProject -ne $fixture) { throw "setup wrote the wrong Windows project path: $configuredProject" }

$sync = (& $graphiq sync --project $fixture --harness claude-code | Out-String)
if ($LASTEXITCODE -ne 0 -or $sync -notmatch 'connected:') {
throw "graphiq sync did not verify the Windows MCP entry: $sync"
}

function Read-McpJsonLine([System.Diagnostics.Process]$Process) {
$read = $Process.StandardOutput.ReadLineAsync()
if (-not $read.Wait(10000)) { throw 'timed out waiting for MCP response' }
if ($null -eq $read.Result) { throw 'MCP exited before returning a response' }
return ($read.Result | ConvertFrom-Json)
}

$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $mcp
$quotedFixture = '"' + $fixture.Replace('"', '\"') + '"'
$quotedDb = '"' + $db.Replace('"', '\"') + '"'
$psi.Arguments = "$quotedFixture --db $quotedDb"
$psi.WorkingDirectory = $root
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$psi.RedirectStandardInput = $true
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$mcpProcess = New-Object System.Diagnostics.Process
$mcpProcess.StartInfo = $psi
[void]$mcpProcess.Start()
try {
$mcpProcess.StandardInput.WriteLine('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}')
$mcpProcess.StandardInput.WriteLine('{"jsonrpc":"2.0","method":"notifications/initialized"}')
$mcpProcess.StandardInput.WriteLine('{"jsonrpc":"2.0","id":2,"method":"ping"}')
$mcpProcess.StandardInput.WriteLine('{"jsonrpc":"2.0","id":3,"method":"shutdown"}')

$initialize = Read-McpJsonLine $mcpProcess
$ping = Read-McpJsonLine $mcpProcess
$shutdown = Read-McpJsonLine $mcpProcess
if ($initialize.id -ne 1 -or $initialize.result.serverInfo.name -ne 'graphiq') { throw 'invalid MCP initialize response on Windows' }
if ($ping.id -ne 2 -or $null -eq $ping.result) { throw 'invalid MCP ping response on Windows' }
if ($shutdown.id -ne 3) { throw 'invalid MCP shutdown response on Windows' }
} finally {
$mcpProcess.StandardInput.Close()
if (-not $mcpProcess.WaitForExit(10000)) {
$mcpProcess.Kill()
throw 'MCP process did not exit after stdin closed'
}
$mcpProcess.Dispose()
}

- name: Test PowerShell installer with the local release archive
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$root = (Get-Location).Path
$stage = Join-Path $env:RUNNER_TEMP 'graphiq Windows package'
$installDir = Join-Path $env:RUNNER_TEMP 'graphiq Windows install'
Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $installDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $stage -Force | Out-Null
foreach ($binary in @('graphiq.exe', 'graphiq-mcp.exe', 'graphiq-bench.exe')) {
Copy-Item -LiteralPath (Join-Path $root "target\release\$binary") -Destination (Join-Path $stage $binary)
}
$archiveName = 'graphiq-x86_64-pc-windows-msvc.zip'
$archive = Join-Path $stage $archiveName
Compress-Archive -Path (Join-Path $stage 'graphiq.exe'), (Join-Path $stage 'graphiq-mcp.exe'), (Join-Path $stage 'graphiq-bench.exe') -DestinationPath $archive -Force
$hash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $archiveName" | Set-Content -LiteralPath ([IO.Path]::ChangeExtension($archive, 'sha256'))

& (Join-Path $root 'install.ps1') -ArchivePath $archive -InstallDir $installDir -NoPathUpdate -Version 'ci'
if (-not (Test-Path -LiteralPath (Join-Path $installDir 'graphiq.exe'))) {
throw 'PowerShell installer did not install graphiq.exe'
}
$installedVersion = & (Join-Path $installDir 'graphiq.exe') --version
if ($LASTEXITCODE -ne 0 -or $installedVersion -notmatch '^graphiq ') {
throw "installed Windows binary failed: $installedVersion"
}

& (Join-Path $root 'install.ps1') -Uninstall -InstallDir $installDir -NoPathUpdate
if (Test-Path -LiteralPath (Join-Path $installDir 'graphiq.exe')) {
throw 'PowerShell installer did not uninstall graphiq.exe'
}
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
<a href="https://github.com/aaf2tbz/graphiq/actions/workflows/linux-smoke.yml"><img src="https://img.shields.io/github/actions/workflow/status/aaf2tbz/graphiq/linux-smoke.yml?branch=main&label=linux&style=for-the-badge" alt="Linux smoke"></a>
<a href="https://github.com/aaf2tbz/graphiq/actions/workflows/macos-smoke.yml"><img src="https://img.shields.io/github/actions/workflow/status/aaf2tbz/graphiq/macos-smoke.yml?branch=main&label=macOS&style=for-the-badge" alt="macOS smoke"></a>
<a href="https://github.com/aaf2tbz/graphiq/actions/workflows/windows-smoke.yml"><img src="https://img.shields.io/github/actions/workflow/status/aaf2tbz/graphiq/windows-smoke.yml?branch=main&label=Windows&style=for-the-badge" alt="Windows smoke"></a>
</p>


Expand All @@ -35,6 +36,18 @@ Or install a prebuilt binary on macOS or Linux:
curl -fsSL https://raw.githubusercontent.com/aaf2tbz/graphiq/main/install.sh | bash
```

On Windows, run the PowerShell installer:

```powershell
$installer = Join-Path $env:TEMP "graphiq-install.ps1"
Invoke-WebRequest https://raw.githubusercontent.com/aaf2tbz/graphiq/main/install.ps1 -OutFile $installer
powershell -ExecutionPolicy Bypass -File $installer
Remove-Item $installer
```

The Windows installer places `graphiq.exe` and `graphiq-mcp.exe` in
`%LOCALAPPDATA%\GraphIQ\bin` and adds that directory to the user PATH.

### Index and search

```bash
Expand Down
2 changes: 1 addition & 1 deletion crates/graphiq-bench/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphiq-bench"
version = "4.4.0"
version = "4.5.0"
edition = "2021"

[[bin]]
Expand Down
2 changes: 1 addition & 1 deletion crates/graphiq-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphiq-cli"
version = "4.4.0"
version = "4.5.0"
edition = "2021"

[[bin]]
Expand Down
Loading
Loading