Skip to content
Open
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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "hindsight",
"version": "0.7.5",
"version": "0.7.6",
"description": "Official Hindsight integrations for Claude Code",
"owner": {
"name": "vectorize-io"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hindsight-memory",
"description": "Automatic long-term memory for Claude Code via Hindsight. Recalls relevant memories before each prompt, retains conversation transcripts, and provides knowledge page tools.",
"version": "0.7.5",
"version": "0.7.6",
"author": {"name": "Hindsight Team", "url": "https://vectorize.io/hindsight"},
"license": "MIT",
"keywords": ["memory", "hindsight", "recall", "retain"]
Expand Down
66 changes: 63 additions & 3 deletions scripts/release-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
VALID_INTEGRATIONS=("ag2" "agent-framework" "agentcore" "agno" "aider" "ai-sdk" "autogen" "chat" "claude-agent-sdk" "claude-code" "cline" "cloudflare-oauth-proxy" "codex" "composio" "continue" "crewai" "cursor" "cursor-cli" "devin-desktop" "dify" "eve" "flowise" "gemini-spark" "github-copilot" "google-adk" "haystack" "langgraph" "litellm" "llamaindex" "n8n" "nemoclaw" "obsidian" "omo" "openai-agents" "openclaw" "opencode" "openhands" "paperclip" "pipecat" "pydantic-ai" "roo-code" "smolagents" "strands" "superagent" "vapi" "zcode" "zed")

usage() {
print_error "Usage: $0 <integration> <version>"
print_error "Usage: $0 [--check] <integration> [version]"
echo ""
echo " integration One of: ${VALID_INTEGRATIONS[*]}"
echo " version Semantic version (e.g. 0.2.0) or bump keyword: patch, minor, major"
echo " --check Verify the integration has no changes since its latest release tag"
echo ""
echo "Examples:"
echo " $0 litellm 0.2.0"
Expand All @@ -28,12 +29,21 @@ usage() {
exit 1
}

if [ -z "$1" ] || [ -z "$2" ]; then
CHECK_ONLY=false
if [ "${1:-}" = "--check" ]; then
CHECK_ONLY=true
shift
fi

if [ -z "${1:-}" ] || { [ "$CHECK_ONLY" = "false" ] && [ -z "${2:-}" ]; }; then
usage
fi
if [ "$CHECK_ONLY" = "true" ] && [ "$#" -ne 1 ]; then
usage
fi

INTEGRATION=$1
VERSION_ARG=$2
VERSION_ARG=${2:-}

# Validate integration name
VALID=false
Expand Down Expand Up @@ -65,6 +75,56 @@ get_current_version() {
fi
}

check_integration_release() {
local dir="hindsight-integrations/$INTEGRATION"
local current_version last_tag tag_version unreleased_count

if [ ! -d "$dir" ]; then
print_error "Integration directory not found: $dir"
exit 1
fi

current_version=$(get_current_version)
if [ -z "$current_version" ]; then
print_error "Could not read current version for '$INTEGRATION'"
exit 1
fi

if [ "$INTEGRATION" = "claude-code" ]; then
local marketplace_version
marketplace_version=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["version"])' .claude-plugin/marketplace.json)
if [ "$current_version" != "$marketplace_version" ]; then
print_error "Claude Code plugin and marketplace versions must match ($current_version != $marketplace_version)"
exit 1
fi
fi

last_tag=$(git tag --list "integrations/$INTEGRATION/v*" --sort=-v:refname | head -1)
if [ -z "$last_tag" ]; then
print_error "$INTEGRATION has no integration release tag"
exit 1
fi

unreleased_count=$(git rev-list --count "$last_tag"..HEAD -- "$dir")
if [ "$unreleased_count" -ne 0 ]; then
print_error "$INTEGRATION has $unreleased_count unreleased changes since $last_tag; run scripts/release-integration.sh $INTEGRATION <version> before the core release"
exit 1
fi

tag_version=${last_tag#integrations/$INTEGRATION/v}
if [ "$current_version" != "$tag_version" ]; then
print_error "$INTEGRATION manifest version $current_version does not match latest release tag $last_tag"
exit 1
fi

print_info "$INTEGRATION is released at v$current_version"
}

if [ "$CHECK_ONLY" = "true" ]; then
check_integration_release
exit 0
fi

# Bump a semver component: bump_version <current> <part>
bump_version() {
local current=$1 part=$2
Expand Down
19 changes: 12 additions & 7 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ if [[ -n $(git status -s) ]]; then
exit 1
fi

# Integrations keep independent versions, but a core release must not ship
# unreleased Claude Code changes behind a stale marketplace manifest.
print_info "Checking Claude Code integration release status..."
./scripts/release-integration.sh --check claude-code

# Check if tag already exists
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
print_error "Tag v$VERSION already exists"
Expand Down Expand Up @@ -199,13 +204,13 @@ else
print_warn "update-docs-version.sh not found, skipping docs update"
fi

# Regenerate OpenAPI spec and clients with new version
print_info "Regenerating OpenAPI spec and client SDKs..."
if ./scripts/generate-openapi.sh && ./scripts/generate-clients.sh; then
print_info "✓ OpenAPI spec and clients regenerated"
# Regenerate OpenAPI spec, clients, and the docs skill copy with new version
print_info "Regenerating OpenAPI spec, client SDKs, and docs skill..."
if ./scripts/generate-openapi.sh && ./scripts/generate-clients.sh && ./scripts/generate-docs-skill.sh; then
print_info "✓ OpenAPI spec, clients, and docs skill regenerated"
else
print_error "Failed to regenerate clients"
print_warn "You may need to fix this manually before committing"
print_error "Failed to regenerate OpenAPI, clients, or docs skill"
print_warn "Fix the generation failure before committing"
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
Expand All @@ -227,7 +232,7 @@ PATCH_VERSION=$(echo "$VERSION" | sed -E 's/^[0-9]+\.[0-9]+\.([0-9]+)$/\1/')
COMMIT_MSG="Release v$VERSION

- Update version to $VERSION in all components
- Regenerate OpenAPI spec and client SDKs
- Regenerate OpenAPI spec, client SDKs, and docs skill
- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-embed
- Python client: hindsight-clients/python
- TypeScript client: hindsight-clients/typescript
Expand Down
121 changes: 121 additions & 0 deletions scripts/tests/test-release-integration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/bin/bash
set -euo pipefail

ROOT=$(cd "$(dirname "$0")/../.." && pwd)
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT

fail() {
echo "FAIL: $1" >&2
exit 1
}

write_manifest() {
local path=$1 version=$2
mkdir -p "$(dirname "$path")"
printf '{\n "version": "%s"\n}\n' "$version" > "$path"
}

# Exercise the real integration release script in an isolated repository. The
# check path must not create tags, push, or require changelog credentials.
INTEGRATION_REPO="$TMP_DIR/integration"
mkdir -p "$INTEGRATION_REPO/scripts" "$INTEGRATION_REPO/hindsight-integrations/claude-code"
cp "$ROOT/scripts/release-integration.sh" "$INTEGRATION_REPO/scripts/release-integration.sh"
write_manifest "$INTEGRATION_REPO/hindsight-integrations/claude-code/.claude-plugin/plugin.json" "0.7.5"
write_manifest "$INTEGRATION_REPO/.claude-plugin/marketplace.json" "0.7.5"
printf 'initial\n' > "$INTEGRATION_REPO/hindsight-integrations/claude-code/integration.txt"

git -C "$INTEGRATION_REPO" init -q -b main
git -C "$INTEGRATION_REPO" config user.name "Release Test"
git -C "$INTEGRATION_REPO" config user.email "release-test@example.com"
git -C "$INTEGRATION_REPO" add .
git -C "$INTEGRATION_REPO" commit -q -m "initial integration"
git -C "$INTEGRATION_REPO" tag "integrations/claude-code/v0.7.5"
printf 'changed\n' >> "$INTEGRATION_REPO/hindsight-integrations/claude-code/integration.txt"
git -C "$INTEGRATION_REPO" add .
git -C "$INTEGRATION_REPO" commit -q -m "change integration"

set +e
STALE_OUTPUT=$(cd "$INTEGRATION_REPO" && ./scripts/release-integration.sh --check claude-code 2>&1)
STALE_STATUS=$?
set -e
[ "$STALE_STATUS" -ne 0 ] || fail "stale Claude Code manifest passed the release check"
case "$STALE_OUTPUT" in
*"unreleased changes"*) ;;
*) fail "stale release check did not explain the unreleased changes: $STALE_OUTPUT" ;;
esac

write_manifest "$INTEGRATION_REPO/hindsight-integrations/claude-code/.claude-plugin/plugin.json" "0.7.6"
write_manifest "$INTEGRATION_REPO/.claude-plugin/marketplace.json" "0.7.6"
git -C "$INTEGRATION_REPO" add .
git -C "$INTEGRATION_REPO" commit -q -m "release claude-code 0.7.6"
git -C "$INTEGRATION_REPO" tag "integrations/claude-code/v0.7.6"
(cd "$INTEGRATION_REPO" && ./scripts/release-integration.sh --check claude-code)

# Nested plugin entries may carry their own versions. The guard must read the
# top-level marketplace version regardless of JSON key order.
cat > "$INTEGRATION_REPO/.claude-plugin/marketplace.json" <<'JSON'
{
"plugins": [{"name": "hindsight-memory", "version": "9.9.9"}],
"version": "0.7.6"
}
JSON
(cd "$INTEGRATION_REPO" && ./scripts/release-integration.sh --check claude-code)

write_manifest "$INTEGRATION_REPO/.claude-plugin/marketplace.json" "0.8.4"
set +e
MISMATCH_OUTPUT=$(cd "$INTEGRATION_REPO" && ./scripts/release-integration.sh --check claude-code 2>&1)
MISMATCH_STATUS=$?
set -e
[ "$MISMATCH_STATUS" -ne 0 ] || fail "mismatched Claude Code manifests passed the release check"
case "$MISMATCH_OUTPUT" in
*"must match"*) ;;
*) fail "manifest mismatch did not produce an actionable error: $MISMATCH_OUTPUT" ;;
esac

set +e
EXTRA_ARG_OUTPUT=$(cd "$INTEGRATION_REPO" && ./scripts/release-integration.sh --check claude-code 0.9.9 2>&1)
EXTRA_ARG_STATUS=$?
set -e
[ "$EXTRA_ARG_STATUS" -ne 0 ] || fail "--check silently accepted an unexpected version argument"
case "$EXTRA_ARG_OUTPUT" in
*"Usage:"*) ;;
*) fail "extra --check argument did not show usage: $EXTRA_ARG_OUTPUT" ;;
esac

# The core release path must invoke the independent integration guard before it
# mutates files or reaches any tag/push/changelog work.
CORE_REPO="$TMP_DIR/core"
mkdir -p "$CORE_REPO/scripts"
cp "$ROOT/scripts/release.sh" "$CORE_REPO/scripts/release.sh"
printf '#!/bin/bash\nprintf "%%s\\n" "$*" > "$CHECK_LOG"\nexit 42\n' > "$CORE_REPO/scripts/release-integration.sh"
chmod +x "$CORE_REPO/scripts/release-integration.sh"
git -C "$CORE_REPO" init -q -b main
git -C "$CORE_REPO" config user.name "Release Test"
git -C "$CORE_REPO" config user.email "release-test@example.com"
printf 'fixture\n' > "$CORE_REPO/README.md"
git -C "$CORE_REPO" add .
git -C "$CORE_REPO" commit -q -m "core fixture"

CHECK_LOG="$TMP_DIR/check-args"
set +e
CORE_OUTPUT=$(cd "$CORE_REPO" && CHECK_LOG="$CHECK_LOG" ./scripts/release.sh 9.9.9 </dev/null 2>&1)
CORE_STATUS=$?
set -e
[ "$CORE_STATUS" -eq 42 ] || fail "core release bypassed the integration guard (status $CORE_STATUS): $CORE_OUTPUT"
[ "$(cat "$CHECK_LOG")" = "--check claude-code" ] || fail "core release called the guard with unexpected arguments"
[ ! -e "$CORE_REPO/.git/refs/tags/v9.9.9" ] || fail "core release created a tag after the guard failed"

# Core releases must refresh the generated docs skill after regenerating the
# OpenAPI spec and clients so committed copies cannot drift between releases.
grep -q 'generate-openapi.sh.*generate-clients.sh.*generate-docs-skill.sh' "$ROOT/scripts/release.sh" || \
fail "core release does not regenerate the docs skill after OpenAPI and clients"

PLUGIN_VERSION=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["version"])' \
"$ROOT/hindsight-integrations/claude-code/.claude-plugin/plugin.json")
MARKETPLACE_VERSION=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["version"])' \
"$ROOT/.claude-plugin/marketplace.json")
[ "$PLUGIN_VERSION" = "0.7.6" ] || fail "plugin manifest version is $PLUGIN_VERSION, expected 0.7.6"
[ "$MARKETPLACE_VERSION" = "0.7.6" ] || fail "marketplace version is $MARKETPLACE_VERSION, expected 0.7.6"

echo "PASS: Claude Code release guard regression tests"
2 changes: 1 addition & 1 deletion skills/hindsight-docs/references/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "0.8.4"
"version": "0.8.5"
},
"paths": {
"/health": {
Expand Down