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
14 changes: 3 additions & 11 deletions .github/workflows/agent-memory-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,9 @@ jobs:
working-directory: agent-memory-client
run: uv sync --extra dev

- name: Lint with Ruff
working-directory: agent-memory-client
run: uv run ruff check agent_memory_client

- name: Check formatting with Ruff formatter
working-directory: agent-memory-client
run: uv run ruff format --check agent_memory_client

- name: Type check with mypy
working-directory: agent-memory-client
run: uv run mypy agent_memory_client
- name: Run pre-commit
run: |
uv run pre-commit run --all-files

- name: Run tests
working-directory: agent-memory-client
Expand Down
88 changes: 88 additions & 0 deletions .github/workflows/agent-memory-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Agent Memory Server CI

on:
push:
branches: [main]
tags:
- 'server/v*.*.*'
pull_request:
branches: [main]

jobs:
build:
name: Build package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build

- name: Build package
run: python -m build

- name: Upload dist artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/*

publish-testpypi:
name: Publish to TestPyPI
needs: build
if: startsWith(github.ref, 'refs/tags/server/') && contains(github.ref, '-test')
runs-on: ubuntu-latest
environment: testpypi
permissions:
id-token: write
contents: read
steps:
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist

- name: Publish package to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: dist/

publish-pypi:
name: Publish to PyPI
needs: build
if: startsWith(github.ref, 'refs/tags/server/') && !contains(github.ref, '-test')
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
contents: read
steps:
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/

# Tag Format Guide:
# - For TestPyPI (testing): server/v1.0.0-test
# - For PyPI (production): server/v1.0.0
#
# Use the script: python scripts/tag_and_push_server.py --test (for TestPyPI)
# python scripts/tag_and_push_server.py (for PyPI)
#
# This workflow uses PyPI Trusted Publishing (OIDC). Ensure the project is configured
# on PyPI to trust this GitHub repository before releasing.
3 changes: 3 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: Python Tests
on:
push:
branches: [ main ]
tags:
- 'server/v*.*.*'
- 'client/v*.*.*'
pull_request:
branches: [ main ]

Expand Down
2 changes: 1 addition & 1 deletion agent_memory_server/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Redis Agent Memory Server - A memory system for conversational AI."""

__version__ = "0.12.3"
__version__ = "0.12.4"
174 changes: 174 additions & 0 deletions scripts/tag_and_push_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
Script to tag and push agent-memory-server releases.

This script:
1. Reads the current version from agent_memory_server/__init__.py
2. Creates a git tag in the format: server/v{version}
3. Pushes the tag to origin

Usage:
python scripts/tag_and_push_server.py [--dry-run] [--force] [--test]
"""

import argparse
import re
import subprocess
import sys
from pathlib import Path


def get_server_version() -> str:
"""Read the version from agent_memory_server/__init__.py"""
init_file = Path("agent_memory_server/__init__.py")

if not init_file.exists():
raise FileNotFoundError(f"Could not find {init_file}")

content = init_file.read_text()

# Look for __version__ = "x.y.z"
version_match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)

if not version_match:
raise ValueError(f"Could not find __version__ in {init_file}")

return version_match.group(1)


def run_command(cmd: list[str], dry_run: bool = False) -> subprocess.CompletedProcess:
"""Run a command, optionally in dry-run mode."""
print(f"Running: {' '.join(cmd)}")

if dry_run:
print(" (dry-run mode - command not executed)")
return subprocess.CompletedProcess(cmd, 0, "", "")

try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
if result.stdout:
print(f" Output: {result.stdout.strip()}")
return result
except subprocess.CalledProcessError as e:
print(f" Error: {e.stderr.strip()}")
raise


def check_git_status():
"""Check if git working directory is clean."""
try:
result = subprocess.run(
["git", "status", "--porcelain"], capture_output=True, text=True, check=True
)
if result.stdout.strip():
print("Warning: Git working directory is not clean:")
print(result.stdout)
response = input("Continue anyway? (y/N): ")
if response.lower() != "y":
sys.exit(1)
except subprocess.CalledProcessError:
print("Error: Could not check git status")
sys.exit(1)


def tag_exists(tag_name: str) -> bool:
"""Check if a tag already exists."""
try:
subprocess.run(
["git", "rev-parse", f"refs/tags/{tag_name}"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
return True
except subprocess.CalledProcessError:
return False


def main():
parser = argparse.ArgumentParser(
description="Tag and push agent-memory-server release"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be done without actually doing it",
)
parser.add_argument(
"--force",
action="store_true",
help="Force tag creation even if tag already exists",
)
parser.add_argument(
"--test",
action="store_true",
help="Add '-test' suffix to tag for TestPyPI deployment",
)

args = parser.parse_args()

# Change to project root directory
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import os statement should be moved to the top of the file with other imports (around line 14-18). Importing modules inside functions or conditional blocks is generally discouraged as it can make code harder to understand and debug. Move it to the module-level imports section.

Copilot uses AI. Check for mistakes.
script_dir = Path(__file__).parent
project_root = script_dir.parent

try:
original_cwd = Path.cwd()
if project_root.resolve() != original_cwd.resolve():
print(f"Changing to project root: {project_root}")
import os

os.chdir(project_root)
except Exception as e:
print(f"Warning: Could not change to project root: {e}")

try:
# Get the current version
version = get_server_version()
tag_suffix = "-test" if args.test else ""
tag_name = f"server/v{version}{tag_suffix}"

print(f"Current server version: {version}")
print(f"Tag to create: {tag_name}")
print(f"Deployment target: {'TestPyPI' if args.test else 'PyPI (Production)'}")

if not args.dry_run:
# Check git status
check_git_status()

# Check if tag already exists
if tag_exists(tag_name):
if args.force:
print(f"Tag {tag_name} already exists, but --force specified")
run_command(["git", "tag", "-d", tag_name], args.dry_run)
else:
print(
f"Error: Tag {tag_name} already exists. Use --force to overwrite."
)
sys.exit(1)

# Create the tag
run_command(["git", "tag", tag_name], args.dry_run)

# Push the tag
push_cmd = ["git", "push", "origin", tag_name]
if args.force:
push_cmd.insert(2, "--force")

run_command(push_cmd, args.dry_run)

print(f"\n✅ Successfully tagged and pushed {tag_name}")

if not args.dry_run:
print("\nThis should trigger the GitHub Actions workflow for:")
if args.test:
print(" - TestPyPI publication (testing)")
else:
print(" - PyPI publication (production)")

except Exception as e:
print(f"Error: {e}")
sys.exit(1)


if __name__ == "__main__":
main()