name: CI
on:
push:
branches: [main, develop, "release/**"]
tags: ["v*"]
pull_request:
branches: [main, develop, "release/**"]
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: Check (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings -D clippy::all -D clippy::pedantic
- name: Run tests
env:
AI_MEMORY_NO_CONFIG: "1"
run: cargo test
- name: Security audit
if: matrix.os == 'ubuntu-latest'
run: |
cargo install cargo-audit --locked
cargo audit
- name: Build release
run: cargo build --release
release:
name: Release (${{ matrix.target }})
needs: check
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
artifact: ai-memory
nfpm_arch: amd64
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
artifact: ai-memory
nfpm_arch: arm64
- target: x86_64-apple-darwin
os: macos-latest
artifact: ai-memory
- target: aarch64-apple-darwin
os: macos-latest
artifact: ai-memory
- target: x86_64-pc-windows-msvc
os: windows-latest
artifact: ai-memory.exe
steps:
- uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
- name: Package binary (unix)
if: ${{ !contains(matrix.target, 'windows') }}
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/${{ matrix.artifact }} dist/
cd dist
tar czf ai-memory-${{ matrix.target }}.tar.gz ${{ matrix.artifact }}
- name: Package binary (windows)
if: contains(matrix.target, 'windows')
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist
Copy-Item "target/${{ matrix.target }}/release/${{ matrix.artifact }}" -Destination dist/
Compress-Archive -Path "dist/${{ matrix.artifact }}" -DestinationPath "dist/ai-memory-${{ matrix.target }}.zip"
- name: Build deb and rpm packages
if: matrix.nfpm_arch
run: |
# Install nfpm (map aarch64 -> arm64 for nfpm release naming)
NFPM_ARCH=$(uname -m | sed 's/aarch64/arm64/')
curl -sfL https://github.com/goreleaser/nfpm/releases/download/v2.41.1/nfpm_2.41.1_$(uname -s)_${NFPM_ARCH}.tar.gz | tar xz -C /usr/local/bin nfpm
VERSION="${GITHUB_REF_NAME#v}"
# Build .deb
ARCH=${{ matrix.nfpm_arch }} VERSION=$VERSION nfpm package -p deb -f nfpm.yaml -t dist/
# Build .rpm
ARCH=${{ matrix.nfpm_arch }} VERSION=$VERSION nfpm package -p rpm -f nfpm.yaml -t dist/
ls -la dist/*.deb dist/*.rpm
- name: Upload release artifact
uses: actions/upload-artifact@v5
with:
name: ai-memory-${{ matrix.target }}
path: dist/ai-memory*
- name: Create GitHub Release
if: github.event_name == 'push'
uses: softprops/action-gh-release@v2
with:
files: dist/ai-memory*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
crates-io:
name: Publish to crates.io
needs: check
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --allow-dirty || echo "::warning::crates.io publish skipped (version may already exist)"
homebrew:
name: Update Homebrew formula
needs: release
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Download release assets and compute SHA256
id: sha
run: |
VERSION=${{ steps.version.outputs.version }}
BASE="https://github.com/alphaonedev/ai-memory-mcp/releases/download/v${VERSION}"
for TARGET in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-apple-darwin aarch64-apple-darwin; do
curl -sL -o "ai-memory-${TARGET}.tar.gz" "${BASE}/ai-memory-${TARGET}.tar.gz"
SHA=$(sha256sum "ai-memory-${TARGET}.tar.gz" | cut -d' ' -f1)
# Convert target to env-safe name
SAFE=$(echo "$TARGET" | tr '-' '_')
echo "sha_${SAFE}=${SHA}" >> "$GITHUB_OUTPUT"
done
- name: Checkout Homebrew tap
uses: actions/checkout@v5
with:
repository: alphaonedev/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-tap
- name: Update formula
run: |
VERSION=${{ steps.version.outputs.version }}
cat > homebrew-tap/Formula/ai-memory.rb << 'FORMULA_EOF'
class AiMemory < Formula
desc "AI-agnostic persistent memory system — MCP server, HTTP API, and CLI"
homepage "https://alphaonedev.github.io/ai-memory-mcp/"
version "VERSION_PLACEHOLDER"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/alphaonedev/ai-memory-mcp/releases/download/v#{version}/ai-memory-aarch64-apple-darwin.tar.gz"
sha256 "SHA_AARCH64_APPLE_DARWIN"
else
url "https://github.com/alphaonedev/ai-memory-mcp/releases/download/v#{version}/ai-memory-x86_64-apple-darwin.tar.gz"
sha256 "SHA_X86_64_APPLE_DARWIN"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/alphaonedev/ai-memory-mcp/releases/download/v#{version}/ai-memory-aarch64-unknown-linux-gnu.tar.gz"
sha256 "SHA_AARCH64_UNKNOWN_LINUX_GNU"
else
url "https://github.com/alphaonedev/ai-memory-mcp/releases/download/v#{version}/ai-memory-x86_64-unknown-linux-gnu.tar.gz"
sha256 "SHA_X86_64_UNKNOWN_LINUX_GNU"
end
end
def install
bin.install "ai-memory"
end
def caveats
<<~EOS
To use with an MCP-compatible AI (Claude Code, Codex CLI, Gemini CLI, OpenClaw, etc.),
add to your AI platform's MCP config:
{
"mcpServers": {
"memory": {
"command": "ai-memory",
"args": ["--db", "~/.local/share/ai-memory/memories.db", "mcp"]
}
}
}
Documentation: https://alphaonedev.github.io/ai-memory-mcp/
EOS
end
test do
system "#{bin}/ai-memory", "stats", "--json", "--db", testpath/"test.db"
end
end
FORMULA_EOF
# Replace placeholders with actual values
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/" homebrew-tap/Formula/ai-memory.rb
sed -i "s/SHA_X86_64_UNKNOWN_LINUX_GNU/${{ steps.sha.outputs.sha_x86_64_unknown_linux_gnu }}/" homebrew-tap/Formula/ai-memory.rb
sed -i "s/SHA_AARCH64_UNKNOWN_LINUX_GNU/${{ steps.sha.outputs.sha_aarch64_unknown_linux_gnu }}/" homebrew-tap/Formula/ai-memory.rb
sed -i "s/SHA_X86_64_APPLE_DARWIN/${{ steps.sha.outputs.sha_x86_64_apple_darwin }}/" homebrew-tap/Formula/ai-memory.rb
sed -i "s/SHA_AARCH64_APPLE_DARWIN/${{ steps.sha.outputs.sha_aarch64_apple_darwin }}/" homebrew-tap/Formula/ai-memory.rb
- name: Push updated formula
run: |
cd homebrew-tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/ai-memory.rb
git commit -m "Update ai-memory to ${{ steps.version.outputs.version }}"
git push
docker:
name: Docker (GHCR)
needs: check
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/ai-memory:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/ai-memory:latest
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.version=${{ steps.version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
ppa:
name: Ubuntu PPA
needs: check
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Install packaging tools
run: |
sudo apt-get update
sudo apt-get install -y devscripts debhelper dput gnupg pkg-config libssl-dev build-essential
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
gpgconf --reload gpg-agent
- name: Update changelog version
run: |
VERSION=${{ steps.version.outputs.version }}
SERIES=noble
sed -i "1s/(.*)/(${VERSION}-1) ${SERIES}; urgency=medium/" debian/changelog
- name: Create orig tarball
run: |
VERSION=${{ steps.version.outputs.version }}
cd ..
tar czf "ai-memory_${VERSION}.orig.tar.gz" \
--exclude='.git' \
--exclude='target' \
--exclude='backup' \
ai-memory-mcp
ls -la "ai-memory_${VERSION}.orig.tar.gz"
- name: Build source package
env:
DEBUILD_DPKG_BUILDPACKAGE_OPTS: "-d"
run: |
debuild -S -sa -k4BAA63E9957BD865FA881D1E64A81A0A19B5E324 --no-sign -d
- name: Sign source package
run: |
cd ..
debsign -k4BAA63E9957BD865FA881D1E64A81A0A19B5E324 ai-memory_${{ steps.version.outputs.version }}-1_source.changes
- name: Upload to PPA
run: |
cd ..
dput ppa:jbridger2021/ppa ai-memory_${{ steps.version.outputs.version }}-1_source.changes
copr:
name: Fedora COPR
needs: release
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Install copr-cli
run: |
sudo apt-get update
sudo apt-get install -y pipx
pipx ensurepath
pipx install copr-cli
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Configure copr-cli
run: |
mkdir -p ~/.config
echo "${{ secrets.COPR_CONFIG }}" > ~/.config/copr
- name: Install RPM build tools
run: sudo apt-get update && sudo apt-get install -y rpm
- name: Build source RPM
run: |
VERSION=${{ steps.version.outputs.version }}
mkdir -p ~/rpmbuild/{SOURCES,SPECS,SRPMS}
# Download both architecture tarballs (filename uses the full SemVer tag)
curl -sL -o ~/rpmbuild/SOURCES/ai-memory-x86_64-unknown-linux-gnu.tar.gz \
"https://github.com/alphaonedev/ai-memory-mcp/releases/download/v${VERSION}/ai-memory-x86_64-unknown-linux-gnu.tar.gz"
curl -sL -o ~/rpmbuild/SOURCES/ai-memory-aarch64-unknown-linux-gnu.tar.gz \
"https://github.com/alphaonedev/ai-memory-mcp/releases/download/v${VERSION}/ai-memory-aarch64-unknown-linux-gnu.tar.gz"
# RPM Version: field forbids '-'. For SemVer pre-release tags
# (e.g. 0.6.0-alpha.1), split into Version: <core> + Release: 0.<n>.<pre>%{?dist}
# per Fedora packaging guidelines for pre-release versions.
if [[ "$VERSION" == *-* ]]; then
RPM_VERSION="${VERSION%%-*}"
PRE="${VERSION#*-}"
# Replace dots in the pre-release identifier so rpm sees a clean token
# (e.g. "alpha.1" -> "0.1.alpha1")
PRE_TAG="${PRE//./}"
RPM_RELEASE="0.1.${PRE_TAG}%{?dist}"
else
RPM_VERSION="$VERSION"
RPM_RELEASE="1%{?dist}"
fi
echo "RPM Version: $RPM_VERSION"
echo "RPM Release: $RPM_RELEASE"
# Rewrite spec with the computed Version:/Release: pair
sed -e "s/^Version:.*/Version: ${RPM_VERSION}/" \
-e "s/^Release:.*/Release: ${RPM_RELEASE}/" \
ai-memory.spec > ~/rpmbuild/SPECS/ai-memory.spec
# Build source RPM
rpmbuild -bs ~/rpmbuild/SPECS/ai-memory.spec \
--define "_sourcedir $HOME/rpmbuild/SOURCES" \
--define "_srcrpmdir $HOME/rpmbuild/SRPMS"
ls -la ~/rpmbuild/SRPMS/
- name: Upload to COPR
run: |
copr-cli build alpha-one-ai/ai-memory ~/rpmbuild/SRPMS/ai-memory-*.src.rpm