From 4bf12b78e4c2f0845352972139a0dd197652ed42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 13 Mar 2026 14:38:12 +0100 Subject: [PATCH] feat: independent versioning for integrations - Add per-integration changelog pages at /changelog/integrations/ - Move main changelog to changelog/index.md (URL unchanged) - Add --integration flag to generate-changelog for LLM-based per-integration changelog generation - Add scripts/release-integration.sh for cutting integration releases - Add .github/workflows/release-integration.yml to publish on integrations/** tags - Remove integrations from main release.sh and release.yml cycle --- .github/workflows/release-integration.yml | 99 +++++++ .github/workflows/release.yml | 208 +-------------- .../hindsight_dev/generate_changelog.py | 251 +++++++++++++++--- .../{changelog.md => changelog/index.md} | 11 + .../pages/changelog/integrations/ai-sdk.md | 11 + .../src/pages/changelog/integrations/chat.md | 11 + .../pages/changelog/integrations/crewai.md | 11 + .../pages/changelog/integrations/litellm.md | 11 + .../pages/changelog/integrations/openclaw.md | 11 + .../changelog/integrations/pydantic-ai.md | 11 + scripts/release-integration.sh | 148 +++++++++++ scripts/release.sh | 39 +-- 12 files changed, 549 insertions(+), 273 deletions(-) create mode 100644 .github/workflows/release-integration.yml rename hindsight-docs/src/pages/{changelog.md => changelog/index.md} (98%) create mode 100644 hindsight-docs/src/pages/changelog/integrations/ai-sdk.md create mode 100644 hindsight-docs/src/pages/changelog/integrations/chat.md create mode 100644 hindsight-docs/src/pages/changelog/integrations/crewai.md create mode 100644 hindsight-docs/src/pages/changelog/integrations/litellm.md create mode 100644 hindsight-docs/src/pages/changelog/integrations/openclaw.md create mode 100644 hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md create mode 100755 scripts/release-integration.sh diff --git a/.github/workflows/release-integration.yml b/.github/workflows/release-integration.yml new file mode 100644 index 000000000..b5d18b9bb --- /dev/null +++ b/.github/workflows/release-integration.yml @@ -0,0 +1,99 @@ +name: Release Integration + +on: + push: + tags: + - 'integrations/**' + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write # for PyPI trusted publishing + + steps: + - uses: actions/checkout@v6 + + - name: Extract integration info + id: info + run: | + # refs/tags/integrations/litellm/v0.1.0 → integration=litellm, version=0.1.0 + TAG="${GITHUB_REF#refs/tags/}" + INTEGRATION=$(echo "$TAG" | cut -d'/' -f2) + VERSION=$(echo "$TAG" | cut -d'/' -f3 | sed 's/^v//') + echo "integration=$INTEGRATION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Integration: $INTEGRATION, Version: $VERSION" + + - name: Detect integration type + id: type + run: | + if [ -f "hindsight-integrations/${{ steps.info.outputs.integration }}/pyproject.toml" ]; then + echo "type=python" >> $GITHUB_OUTPUT + else + echo "type=typescript" >> $GITHUB_OUTPUT + fi + + # ── Python integrations (litellm, pydantic-ai, crewai) ────────────────── + + - name: Install uv + if: steps.type.outputs.type == 'python' + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Set up Python + if: steps.type.outputs.type == 'python' + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Build Python package + if: steps.type.outputs.type == 'python' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: uv build --out-dir dist + + - name: Publish Python package to PyPI + if: steps.type.outputs.type == 'python' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ./hindsight-integrations/${{ steps.info.outputs.integration }}/dist + skip-existing: true + + # ── TypeScript integrations (ai-sdk, chat, openclaw) ──────────────────── + + - name: Set up Node.js + if: steps.type.outputs.type == 'typescript' + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: npm ci + + - name: Build TypeScript package + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: npm run build + + - name: Publish TypeScript package to npm + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: | + set +e + OUTPUT=$(npm publish --access public 2>&1) + EXIT_CODE=$? + echo "$OUTPUT" + if [ $EXIT_CODE -ne 0 ]; then + if echo "$OUTPUT" | grep -q "cannot publish over"; then + echo "Package version already published, skipping..." + exit 0 + fi + exit $EXIT_CODE + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 708dca091..df8f01d1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,22 +46,10 @@ jobs: working-directory: ./hindsight-all-slim run: uv build --out-dir dist - - name: Build hindsight-litellm - working-directory: ./hindsight-integrations/litellm - run: uv build --out-dir dist - - name: Build hindsight-embed working-directory: ./hindsight-embed run: uv build --out-dir dist - - name: Build hindsight-crewai - working-directory: ./hindsight-integrations/crewai - run: uv build --out-dir dist - - - name: Build hindsight-pydantic-ai - working-directory: ./hindsight-integrations/pydantic-ai - run: uv build --out-dir dist - # Publish in order (client and api-slim first, then api/all wrappers which depend on them) - name: Publish hindsight-client to PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -93,30 +81,12 @@ jobs: packages-dir: ./hindsight-all-slim/dist skip-existing: true - - name: Publish hindsight-litellm to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/litellm/dist - skip-existing: true - - name: Publish hindsight-embed to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: ./hindsight-embed/dist skip-existing: true - - name: Publish hindsight-crewai to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/crewai/dist - skip-existing: true - - - name: Publish hindsight-pydantic-ai to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/pydantic-ai/dist - skip-existing: true - # Upload artifacts for GitHub release - name: Upload artifacts uses: actions/upload-artifact@v7 @@ -128,10 +98,7 @@ jobs: hindsight-api/dist/* hindsight-all/dist/* hindsight-all-slim/dist/* - hindsight-integrations/litellm/dist/* hindsight-embed/dist/* - hindsight-integrations/crewai/dist/* - hindsight-integrations/pydantic-ai/dist/* retention-days: 1 release-typescript-client: @@ -183,153 +150,6 @@ jobs: path: hindsight-clients/typescript/*.tgz retention-days: 1 - release-openclaw-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/openclaw - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/openclaw - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/openclaw - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/openclaw - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: openclaw-integration - path: hindsight-integrations/openclaw/*.tgz - retention-days: 1 - - release-ai-sdk-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/ai-sdk - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/ai-sdk - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/ai-sdk - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/ai-sdk - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: ai-sdk-integration - path: hindsight-integrations/ai-sdk/*.tgz - retention-days: 1 - - release-chat-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/chat - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/chat - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/chat - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/chat - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: chat-integration - path: hindsight-integrations/chat/*.tgz - retention-days: 1 - release-control-plane: runs-on: ubuntu-latest environment: npm @@ -587,7 +407,7 @@ jobs: create-github-release: runs-on: ubuntu-latest - needs: [release-python-packages, release-typescript-client, release-openclaw-integration, release-ai-sdk-integration, release-chat-integration, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart] + needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart] permissions: contents: write @@ -610,24 +430,6 @@ jobs: name: typescript-client path: ./artifacts/typescript-client - - name: Download OpenClaw Integration - uses: actions/download-artifact@v4 - with: - name: openclaw-integration - path: ./artifacts/openclaw-integration - - - name: Download AI SDK Integration - uses: actions/download-artifact@v4 - with: - name: ai-sdk-integration - path: ./artifacts/ai-sdk-integration - - - name: Download Chat Integration - uses: actions/download-artifact@v4 - with: - name: chat-integration - path: ./artifacts/chat-integration - - name: Download Control Plane uses: actions/download-artifact@v4 with: @@ -667,17 +469,9 @@ jobs: cp artifacts/python-packages/hindsight-api/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-all/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-all-slim/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/pydantic-ai/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-embed/dist/* release-assets/ || true # TypeScript client cp artifacts/typescript-client/*.tgz release-assets/ || true - # OpenClaw Integration - cp artifacts/openclaw-integration/*.tgz release-assets/ || true - # AI SDK Integration - cp artifacts/ai-sdk-integration/*.tgz release-assets/ || true - # Chat Integration - cp artifacts/chat-integration/*.tgz release-assets/ || true # Control Plane cp artifacts/control-plane/*.tgz release-assets/ || true # Rust CLI binaries diff --git a/hindsight-dev/hindsight_dev/generate_changelog.py b/hindsight-dev/hindsight_dev/generate_changelog.py index 760c23810..97b58acf2 100644 --- a/hindsight-dev/hindsight_dev/generate_changelog.py +++ b/hindsight-dev/hindsight_dev/generate_changelog.py @@ -25,7 +25,10 @@ GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases" GITHUB_COMMIT_URL = f"https://github.com/{GITHUB_REPO}/commit" REPO_PATH = Path(__file__).parent.parent.parent -CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog.md" +CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "index.md" +INTEGRATION_CHANGELOG_DIR = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "integrations" + +VALID_INTEGRATIONS = ["litellm", "pydantic-ai", "crewai", "ai-sdk", "chat", "openclaw"] class ChangelogEntry(BaseModel): @@ -82,6 +85,31 @@ def get_git_tags() -> list[str]: return valid_tags +def get_integration_tags(integration: str) -> list[str]: + """Get all tags for a specific integration, sorted by semver (newest first).""" + prefix = f"integrations/{integration}/v" + result = subprocess.run( + ["git", "tag", "-l", f"{prefix}*"], + cwd=REPO_PATH, + capture_output=True, + text=True, + check=True, + ) + tags = [t.strip() for t in result.stdout.strip().split("\n") if t.strip()] + + valid_tags = [] + for tag in tags: + version_part = tag.removeprefix(prefix) + try: + parse_semver(version_part) + valid_tags.append(tag) + except ValueError: + continue + + valid_tags.sort(key=lambda t: parse_semver(t.removeprefix(prefix)), reverse=True) + return valid_tags + + def find_previous_version(new_version: str, existing_tags: list[str]) -> str | None: """Find the previous version based on semver rules.""" new_major, new_minor, new_patch = parse_semver(new_version) @@ -105,13 +133,41 @@ def find_previous_version(new_version: str, existing_tags: list[str]) -> str | N return candidates[0][0] -def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]: +def find_previous_integration_tag(new_version: str, existing_tags: list[str], integration: str) -> str | None: + """Find the previous integration tag based on semver rules.""" + prefix = f"integrations/{integration}/v" + new_major, new_minor, new_patch = parse_semver(new_version) + + candidates = [] + for tag in existing_tags: + version_part = tag.removeprefix(prefix) + try: + major, minor, patch = parse_semver(version_part) + except ValueError: + continue + + if (major, minor, patch) >= (new_major, new_minor, new_patch): + continue + + candidates.append((tag, (major, minor, patch))) + + if not candidates: + return None + + candidates.sort(key=lambda x: x[1], reverse=True) + return candidates[0][0] + + +def get_commits(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> list[Commit]: """Get commits between two refs as structured data.""" if from_ref: cmd = ["git", "log", "--format=%h|%s", "--no-merges", f"{from_ref}..{to_ref}"] else: cmd = ["git", "log", "--format=%h|%s", "--no-merges", to_ref] + if path_filter: + cmd += ["--", path_filter] + result = subprocess.run( cmd, cwd=REPO_PATH, @@ -131,13 +187,16 @@ def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]: return commits -def get_detailed_diff(from_ref: str | None, to_ref: str) -> str: +def get_detailed_diff(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> str: """Get file change stats between two refs.""" if from_ref: cmd = ["git", "diff", "--stat", f"{from_ref}..{to_ref}"] else: cmd = ["git", "diff", "--stat", f"{to_ref}^..{to_ref}"] + if path_filter: + cmd += ["--", path_filter] + result = subprocess.run( cmd, cwd=REPO_PATH, @@ -153,11 +212,14 @@ def analyze_commits_with_llm( version: str, commits: list[Commit], file_diff: str, + integration: str | None = None, ) -> list[ChangelogEntry]: """Use LLM to analyze commits and return structured changelog entries.""" commits_json = json.dumps([{"commit_id": c.hash, "message": c.message} for c in commits], indent=2) - prompt = f"""Analyze the following git commits for release {version} of Hindsight (an AI memory system). + subject = f"the {integration} integration for Hindsight" if integration else f"release {version} of Hindsight" + + prompt = f"""Analyze the following git commits for {subject} (an AI memory system). For each meaningful change, create a changelog entry with: - category: one of "feature", "improvement", "bugfix", "breaking", "other" @@ -192,9 +254,10 @@ def build_changelog_markdown( version: str, tag: str, entries: list[ChangelogEntry], + integration: str | None = None, ) -> str: """Build markdown changelog from structured entries.""" - release_url = f"{GITHUB_RELEASES_URL}/tag/{tag}" + tag_url = f"https://github.com/{GITHUB_REPO}/releases/tag/{tag}" if not integration else f"https://github.com/{GITHUB_REPO}/tree/{tag}" # Group entries by category categories = { @@ -213,7 +276,7 @@ def build_changelog_markdown( categories["other"][1].append(entry) # Build markdown - lines = [f"## [{version}]({release_url})", ""] + lines = [f"## [{version}]({tag_url})", ""] has_entries = False for cat_key in ["breaking", "feature", "improvement", "bugfix", "other"]: @@ -234,22 +297,12 @@ def build_changelog_markdown( return "\n".join(lines) -def read_existing_changelog() -> tuple[str, str]: +def read_existing_changelog(path: Path, default_header: str) -> tuple[str, str]: """Read existing changelog and split into header and content.""" - if not CHANGELOG_PATH.exists(): - header = """--- -hide_table_of_contents: true ---- + if not path.exists(): + return default_header, "" -# Changelog - -This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted. - -For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). -""" - return header, "" - - content = CHANGELOG_PATH.read_text() + content = path.read_text() match = re.search(r"^## ", content, re.MULTILINE) if match: @@ -262,11 +315,11 @@ def read_existing_changelog() -> tuple[str, str]: return header, releases -def write_changelog(header: str, new_entry: str, existing_releases: str) -> None: +def write_changelog(path: Path, header: str, new_entry: str, existing_releases: str) -> None: """Write changelog with new entry prepended.""" content = header + new_entry + "\n" + existing_releases - CHANGELOG_PATH.parent.mkdir(parents=True, exist_ok=True) - CHANGELOG_PATH.write_text(content.rstrip() + "\n") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content.rstrip() + "\n") def generate_changelog_entry( @@ -329,22 +382,148 @@ def generate_changelog_entry( new_entry = build_changelog_markdown(display_version, tag, entries) - header, existing_releases = read_existing_changelog() + default_header = """--- +hide_table_of_contents: true +--- + +# Changelog + +This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted. + +For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). + +""" + header, existing_releases = read_existing_changelog(CHANGELOG_PATH, default_header) if f"## [{display_version}]" in existing_releases: console.print(f"[red]Error: Version {display_version} already exists in changelog[/red]") sys.exit(1) - write_changelog(header, new_entry, existing_releases) + write_changelog(CHANGELOG_PATH, header, new_entry, existing_releases) console.print(f"\n[green]Changelog updated: {CHANGELOG_PATH}[/green]") console.print(f"\n[bold]New entry:[/bold]\n{new_entry}") +def generate_integration_changelog_entry( + integration: str, + version: str, + llm_model: str = "gpt-5.2", +) -> None: + """Generate changelog entry for a specific integration version.""" + if integration not in VALID_INTEGRATIONS: + console.print(f"[red]Error: Unknown integration '{integration}'. Valid: {', '.join(VALID_INTEGRATIONS)}[/red]") + sys.exit(1) + + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key: + console.print("[red]Error: OPENAI_API_KEY environment variable not set[/red]") + sys.exit(1) + + client = OpenAI(api_key=api_key) + + display_version = version.lstrip("v") + path_filter = f"hindsight-integrations/{integration}/" + changelog_path = INTEGRATION_CHANGELOG_DIR / f"{integration}.md" + + console.print(f"[blue]Fetching integration tags for {integration}...[/blue]") + existing_tags = get_integration_tags(integration) + + previous_tag = find_previous_integration_tag(display_version, existing_tags, integration) + + if previous_tag: + console.print(f"[green]Found previous tag: {previous_tag}[/green]") + else: + console.print("[yellow]No previous tag found, will include all commits touching this integration[/yellow]") + + console.print(f"[blue]Getting commits for {path_filter}...[/blue]") + commits = get_commits(previous_tag, "HEAD", path_filter=path_filter) + file_diff = get_detailed_diff(previous_tag, "HEAD", path_filter=path_filter) + + if not commits: + console.print("[yellow]Warning: No commits found touching this integration path[/yellow]") + entries = [] + else: + console.print(f"[blue]Found {len(commits)} commits[/blue]") + + console.print("\n[bold]Commits:[/bold]") + for c in commits: + console.print(f" {c.hash} {c.message}") + + console.print("\n[bold]Files changed:[/bold]") + console.print(file_diff[:4000] if len(file_diff) > 4000 else file_diff) + console.print("") + + console.print(f"[blue]Analyzing commits with LLM ({llm_model})...[/blue]") + entries = analyze_commits_with_llm(client, llm_model, display_version, commits, file_diff, integration=integration) + + console.print(f"\n[bold]LLM identified {len(entries)} changelog entries:[/bold]") + for entry in entries: + console.print(f" [{entry.category}] {entry.summary} ({entry.commit_id})") + + integration_tag = f"integrations/{integration}/v{display_version}" + new_entry = build_changelog_markdown(display_version, integration_tag, entries, integration=integration) + + package_name = _get_package_name(integration) + default_header = f"""--- +hide_table_of_contents: true +--- + +# {_integration_display_name(integration)} Integration Changelog + +Changelog for [`{package_name}`]({_package_url(integration, package_name)}). + +For the source code, see [`hindsight-integrations/{integration}`](https://github.com/{GITHUB_REPO}/tree/main/hindsight-integrations/{integration}). + +← [Back to main changelog](/changelog) + +""" + header, existing_releases = read_existing_changelog(changelog_path, default_header) + + if f"## [{display_version}]" in existing_releases: + console.print(f"[red]Error: Version {display_version} already exists in integration changelog[/red]") + sys.exit(1) + + write_changelog(changelog_path, header, new_entry, existing_releases) + + console.print(f"\n[green]Integration changelog updated: {changelog_path}[/green]") + console.print(f"\n[bold]New entry:[/bold]\n{new_entry}") + + +def _get_package_name(integration: str) -> str: + packages = { + "litellm": "hindsight-litellm", + "pydantic-ai": "hindsight-pydantic-ai", + "crewai": "hindsight-crewai", + "ai-sdk": "@vectorize-io/hindsight-ai-sdk", + "chat": "@vectorize-io/hindsight-chat", + "openclaw": "@vectorize-io/hindsight-openclaw", + } + return packages[integration] + + +def _package_url(integration: str, package_name: str) -> str: + if package_name.startswith("@"): + return f"https://www.npmjs.com/package/{package_name}" + return f"https://pypi.org/project/{package_name}/" + + +def _integration_display_name(integration: str) -> str: + names = { + "litellm": "LiteLLM", + "pydantic-ai": "Pydantic AI", + "crewai": "CrewAI", + "ai-sdk": "AI SDK", + "chat": "Chat SDK", + "openclaw": "OpenClaw", + } + return names.get(integration, integration) + + def main(): parser = argparse.ArgumentParser( description="Generate changelog entry for a release", - usage="generate-changelog VERSION [--model MODEL]", + usage="generate-changelog VERSION [--model MODEL] [--integration NAME]", ) parser.add_argument( "version", @@ -355,13 +534,25 @@ def main(): default="gpt-5.2", help="OpenAI model to use (default: gpt-5.2)", ) + parser.add_argument( + "--integration", + default=None, + help=f"Generate changelog for a specific integration. Valid: {', '.join(VALID_INTEGRATIONS)}", + ) args = parser.parse_args() - generate_changelog_entry( - version=args.version, - llm_model=args.model, - ) + if args.integration: + generate_integration_changelog_entry( + integration=args.integration, + version=args.version, + llm_model=args.model, + ) + else: + generate_changelog_entry( + version=args.version, + llm_model=args.model, + ) if __name__ == "__main__": diff --git a/hindsight-docs/src/pages/changelog.md b/hindsight-docs/src/pages/changelog/index.md similarity index 98% rename from hindsight-docs/src/pages/changelog.md rename to hindsight-docs/src/pages/changelog/index.md index 3c4f8e54f..d0f00fdd6 100644 --- a/hindsight-docs/src/pages/changelog.md +++ b/hindsight-docs/src/pages/changelog/index.md @@ -8,6 +8,17 @@ This changelog highlights user-facing changes only. Internal maintenance, CI/CD, For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). +## Integration Changelogs + +| Integration | Package | Description | +|---|---|---| +| [LiteLLM](./integrations/litellm) | `hindsight-litellm` | Universal LLM memory via LiteLLM (100+ providers) | +| [Pydantic AI](./integrations/pydantic-ai) | `hindsight-pydantic-ai` | Persistent memory tools for Pydantic AI agents | +| [CrewAI](./integrations/crewai) | `hindsight-crewai` | Persistent memory for CrewAI agents | +| [AI SDK](./integrations/ai-sdk) | `@vectorize-io/hindsight-ai-sdk` | Memory integration for Vercel AI SDK | +| [Chat SDK](./integrations/chat) | `@vectorize-io/hindsight-chat` | Memory integration for Vercel Chat SDK | +| [OpenClaw](./integrations/openclaw) | `@vectorize-io/hindsight-openclaw` | Hindsight memory plugin for OpenClaw | + ## [0.4.17](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.17) **Features** diff --git a/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md b/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md new file mode 100644 index 000000000..2587c9605 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# AI SDK Integration Changelog + +Changelog for [`@vectorize-io/hindsight-ai-sdk`](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) — memory integration for Vercel AI SDK. + +For the source code, see [`hindsight-integrations/ai-sdk`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/ai-sdk). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/chat.md b/hindsight-docs/src/pages/changelog/integrations/chat.md new file mode 100644 index 000000000..6311de485 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/chat.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# Chat SDK Integration Changelog + +Changelog for [`@vectorize-io/hindsight-chat`](https://www.npmjs.com/package/@vectorize-io/hindsight-chat) — memory integration for Vercel Chat SDK. + +For the source code, see [`hindsight-integrations/chat`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/chat). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/crewai.md b/hindsight-docs/src/pages/changelog/integrations/crewai.md new file mode 100644 index 000000000..1bf3fba40 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/crewai.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# CrewAI Integration Changelog + +Changelog for [`hindsight-crewai`](https://pypi.org/project/hindsight-crewai/) — persistent memory for CrewAI agents. + +For the source code, see [`hindsight-integrations/crewai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/crewai). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/litellm.md b/hindsight-docs/src/pages/changelog/integrations/litellm.md new file mode 100644 index 000000000..f0090c3bd --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/litellm.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# LiteLLM Integration Changelog + +Changelog for [`hindsight-litellm`](https://pypi.org/project/hindsight-litellm/) — universal LLM memory integration via LiteLLM. + +For the source code, see [`hindsight-integrations/litellm`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/litellm). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/openclaw.md b/hindsight-docs/src/pages/changelog/integrations/openclaw.md new file mode 100644 index 000000000..1208d2c14 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/openclaw.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# OpenClaw Integration Changelog + +Changelog for [`@vectorize-io/hindsight-openclaw`](https://www.npmjs.com/package/@vectorize-io/hindsight-openclaw) — Hindsight memory plugin for OpenClaw. + +For the source code, see [`hindsight-integrations/openclaw`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/openclaw). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md b/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md new file mode 100644 index 000000000..6a779c8bc --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# Pydantic AI Integration Changelog + +Changelog for [`hindsight-pydantic-ai`](https://pypi.org/project/hindsight-pydantic-ai/) — persistent memory tools for Pydantic AI agents. + +For the source code, see [`hindsight-integrations/pydantic-ai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/pydantic-ai). + +← [Back to main changelog](/changelog) diff --git a/scripts/release-integration.sh b/scripts/release-integration.sh new file mode 100755 index 000000000..c8351ea59 --- /dev/null +++ b/scripts/release-integration.sh @@ -0,0 +1,148 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")/.." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +print_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +VALID_INTEGRATIONS=("litellm" "pydantic-ai" "crewai" "ai-sdk" "chat" "openclaw") + +usage() { + print_error "Usage: $0 " + echo "" + echo " integration One of: ${VALID_INTEGRATIONS[*]}" + echo " version Semantic version (e.g. 0.2.0)" + echo "" + echo "Examples:" + echo " $0 litellm 0.2.0" + echo " $0 pydantic-ai 1.0.0" + exit 1 +} + +if [ -z "$1" ] || [ -z "$2" ]; then + usage +fi + +INTEGRATION=$1 +VERSION=$2 + +# Validate integration name +VALID=false +for v in "${VALID_INTEGRATIONS[@]}"; do + if [ "$v" = "$INTEGRATION" ]; then + VALID=true + break + fi +done +if [ "$VALID" = "false" ]; then + print_error "Unknown integration '$INTEGRATION'" + print_info "Valid integrations: ${VALID_INTEGRATIONS[*]}" + exit 1 +fi + +# Validate version format +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + print_error "Invalid version format. Please use semantic versioning (e.g., 0.2.0)" + exit 1 +fi + +TAG="integrations/$INTEGRATION/v$VERSION" + +print_info "Releasing $INTEGRATION v$VERSION (tag: $TAG)" + +# Check if we're on main branch +CURRENT_BRANCH=$(git branch --show-current) +if [ "$CURRENT_BRANCH" != "main" ]; then + print_warn "You are not on the main branch (current: $CURRENT_BRANCH)" + read -p "Do you want to continue? (y/n) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_error "Release cancelled" + exit 1 + fi +fi + +# Check if working directory is clean +if [[ -n $(git status -s) ]]; then + print_error "Working directory is not clean. Please commit or stash your changes." + git status -s + exit 1 +fi + +# Check if tag already exists +if git rev-parse "$TAG" >/dev/null 2>&1; then + print_error "Tag $TAG already exists" + exit 1 +fi + +# Load .env for OPENAI_API_KEY if needed +if [ -z "$OPENAI_API_KEY" ] && [ -f ".env" ]; then + print_info "Loading environment from .env" + set -a + source ".env" + set +a +fi + +if [ -z "$OPENAI_API_KEY" ]; then + print_error "OPENAI_API_KEY is not set and no .env file found. Required for changelog generation." + exit 1 +fi + +# Determine integration type and update version +INTEGRATION_DIR="hindsight-integrations/$INTEGRATION" + +if [ ! -d "$INTEGRATION_DIR" ]; then + print_error "Integration directory not found: $INTEGRATION_DIR" + exit 1 +fi + +if [ -f "$INTEGRATION_DIR/pyproject.toml" ]; then + print_info "Updating version in $INTEGRATION_DIR/pyproject.toml" + sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$INTEGRATION_DIR/pyproject.toml" + rm "$INTEGRATION_DIR/pyproject.toml.bak" +elif [ -f "$INTEGRATION_DIR/package.json" ]; then + print_info "Updating version in $INTEGRATION_DIR/package.json" + sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$INTEGRATION_DIR/package.json" + rm "$INTEGRATION_DIR/package.json.bak" +else + print_error "No pyproject.toml or package.json found in $INTEGRATION_DIR" + exit 1 +fi + +# Generate changelog entry using LLM +print_info "Generating changelog entry..." +if cd hindsight-dev && uv run generate-changelog "$VERSION" --integration "$INTEGRATION"; then + cd .. + print_info "Changelog generated" +else + cd .. + print_error "Changelog generation failed" + git checkout . + exit 1 +fi + +# Commit version bump + changelog together +print_info "Committing changes..." +git add "hindsight-integrations/$INTEGRATION/" "hindsight-docs/src/pages/changelog/integrations/$INTEGRATION.md" +git commit --no-verify -m "release($INTEGRATION): v$VERSION" + +# Create annotated tag +print_info "Creating tag $TAG..." +git tag -a "$TAG" -m "Release $INTEGRATION v$VERSION" + +# Push commit and tag +print_info "Pushing to remote..." +git push origin "$CURRENT_BRANCH" +git push origin "$TAG" + +print_info "✅ Released $INTEGRATION v$VERSION" +print_info "Tag: $TAG" +print_info "GitHub Actions will publish to the package registry." diff --git a/scripts/release.sh b/scripts/release.sh index 77190302d..c602fb5ae 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -64,8 +64,8 @@ fi print_info "Updating version in all components..." -# Update Python packages -PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-integrations/litellm" "hindsight-integrations/crewai" "hindsight-integrations/pydantic-ai" "hindsight-embed") +# Update Python packages (integrations are versioned independently via scripts/release-integration.sh) +PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-embed") for package in "${PYTHON_PACKAGES[@]}"; do PYPROJECT_FILE="$package/pyproject.toml" if [ -f "$PYPROJECT_FILE" ]; then @@ -144,36 +144,6 @@ else print_warn "File $TYPESCRIPT_CLIENT_PKG not found, skipping" fi -# Update OpenClaw integration -OPENCLAW_PKG="hindsight-integrations/openclaw/package.json" -if [ -f "$OPENCLAW_PKG" ]; then - print_info "Updating $OPENCLAW_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$OPENCLAW_PKG" - rm "${OPENCLAW_PKG}.bak" -else - print_warn "File $OPENCLAW_PKG not found, skipping" -fi - -# Update AI SDK integration -AI_SDK_PKG="hindsight-integrations/ai-sdk/package.json" -if [ -f "$AI_SDK_PKG" ]; then - print_info "Updating $AI_SDK_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$AI_SDK_PKG" - rm "${AI_SDK_PKG}.bak" -else - print_warn "File $AI_SDK_PKG not found, skipping" -fi - -# Update Chat SDK integration -CHAT_SDK_PKG="hindsight-integrations/chat/package.json" -if [ -f "$CHAT_SDK_PKG" ]; then - print_info "Updating $CHAT_SDK_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$CHAT_SDK_PKG" - rm "${CHAT_SDK_PKG}.bak" -else - print_warn "File $CHAT_SDK_PKG not found, skipping" -fi - # Update documentation version (creates new version or syncs to existing) print_info "Updating documentation for version $VERSION..." if [ -f "scripts/update-docs-version.sh" ]; then @@ -216,14 +186,11 @@ COMMIT_MSG="Release v$VERSION - Update version to $VERSION in all components - Regenerate OpenAPI spec and client SDKs -- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm, hindsight-crewai, hindsight-pydantic-ai, hindsight-embed +- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-embed - Python client: hindsight-clients/python - TypeScript client: hindsight-clients/typescript - Rust CLI: hindsight-cli - Control Plane: hindsight-control-plane -- OpenClaw integration: hindsight-integrations/openclaw -- AI SDK integration: hindsight-integrations/ai-sdk -- Chat SDK integration: hindsight-integrations/chat - Helm chart" # Add docs update note