From 20009dadc34fbcf54b58d80d2a99e60cae9dfd2a Mon Sep 17 00:00:00 2001 From: DK09876 Date: Thu, 18 Jun 2026 13:45:09 -0700 Subject: [PATCH] feat(github-copilot): add GitHub Copilot (VS Code) integration via MCP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds hindsight-copilot: long-term memory for GitHub Copilot in VS Code, using Copilot agent mode's native MCP support (HTTP servers) — no bridge. `hindsight-copilot init`: - merges a Hindsight HTTP MCP server into .vscode/mcp.json (servers.hindsight), JSON-safe (prints a snippet if the file is JSONC), and - writes a recall/retain rule into .github/copilot-instructions.md, which Copilot applies to every chat in the workspace. Resolves the ask in #1588. Mirrors the Zed/OpenHands MCP-config pattern. - hindsight_copilot package: config, mcp_config (.vscode/mcp.json writer), instructions (copilot-instructions.md rule), cli (init/status/uninstall) - 25 deterministic tests (mcp.json merge incl. preserving servers/inputs + JSONC fallback, instructions rule block) + gated requires_real_llm MCP handshake E2E - CI job, release registration (VALID_INTEGRATIONS + changelog generator), docs page, registry entry, icon (octicons), README row Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 43 ++++ .../hindsight_dev/generate_changelog.py | 1 + .../docs-integrations/github-copilot.md | 51 +++++ hindsight-docs/src/data/integrations.json | 10 + .../static/img/icons/github-copilot.svg | 1 + hindsight-integrations/README.md | 1 + .../github-copilot/README.md | 76 +++++++ .../hindsight_copilot/__init__.py | 12 ++ .../github-copilot/hindsight_copilot/cli.py | 172 ++++++++++++++++ .../hindsight_copilot/config.py | 76 +++++++ .../hindsight_copilot/instructions.py | 87 ++++++++ .../hindsight_copilot/mcp_config.py | 128 ++++++++++++ .../github-copilot/hindsight_copilot/py.typed | 0 .../github-copilot/pyproject.toml | 47 +++++ .../github-copilot/tests/__init__.py | 0 .../github-copilot/tests/test_cli.py | 68 +++++++ .../github-copilot/tests/test_config.py | 34 ++++ .../github-copilot/tests/test_e2e.py | 60 ++++++ .../github-copilot/tests/test_instructions.py | 47 +++++ .../github-copilot/tests/test_mcp_config.py | 97 +++++++++ hindsight-integrations/github-copilot/uv.lock | 185 ++++++++++++++++++ scripts/release-integration.sh | 2 +- 22 files changed, 1197 insertions(+), 1 deletion(-) create mode 100644 hindsight-docs/docs-integrations/github-copilot.md create mode 100644 hindsight-docs/static/img/icons/github-copilot.svg create mode 100644 hindsight-integrations/github-copilot/README.md create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/__init__.py create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/cli.py create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/config.py create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/instructions.py create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/mcp_config.py create mode 100644 hindsight-integrations/github-copilot/hindsight_copilot/py.typed create mode 100644 hindsight-integrations/github-copilot/pyproject.toml create mode 100644 hindsight-integrations/github-copilot/tests/__init__.py create mode 100644 hindsight-integrations/github-copilot/tests/test_cli.py create mode 100644 hindsight-integrations/github-copilot/tests/test_config.py create mode 100644 hindsight-integrations/github-copilot/tests/test_e2e.py create mode 100644 hindsight-integrations/github-copilot/tests/test_instructions.py create mode 100644 hindsight-integrations/github-copilot/tests/test_mcp_config.py create mode 100644 hindsight-integrations/github-copilot/uv.lock diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8caadf058..fbfb2f4fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,7 @@ jobs: integrations-claude-code: ${{ steps.filter.outputs.integrations-claude-code }} integrations-cline: ${{ steps.filter.outputs.integrations-cline }} integrations-codex: ${{ steps.filter.outputs.integrations-codex }} + integrations-github-copilot: ${{ steps.filter.outputs.integrations-github-copilot }} integrations-continue: ${{ steps.filter.outputs.integrations-continue }} integrations-cursor-cli: ${{ steps.filter.outputs.integrations-cursor-cli }} integrations-crewai: ${{ steps.filter.outputs.integrations-crewai }} @@ -142,6 +143,8 @@ jobs: - 'hindsight-integrations/cline/**' integrations-codex: - 'hindsight-integrations/codex/**' + integrations-github-copilot: + - 'hindsight-integrations/github-copilot/**' integrations-continue: - 'hindsight-integrations/continue/**' integrations-cursor-cli: @@ -588,6 +591,45 @@ jobs: working-directory: ./hindsight-integrations/cline run: uv run pytest tests -v + test-github-copilot-integration: + needs: [detect-changes] + if: >- + (github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.integrations-github-copilot == 'true' || + needs.detect-changes.outputs.ci == 'true') + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha || '' }} + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + prune-cache: false + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version-file: ".python-version" + + - name: Build github-copilot integration + working-directory: ./hindsight-integrations/github-copilot + run: uv build + + - name: Install dependencies + working-directory: ./hindsight-integrations/github-copilot + run: uv sync --frozen + + - name: Run tests + working-directory: ./hindsight-integrations/github-copilot + # PR CI runs only the deterministic bucket; the real-LLM E2E bucket + # (requires_real_llm) needs a live Hindsight server and runs separately. + run: uv run pytest tests -v -m "not requires_real_llm" + test-codex-integration: needs: [detect-changes] if: >- @@ -4746,6 +4788,7 @@ jobs: - test-claude-code-integration - test-cursor-integration - test-cline-integration + - test-github-copilot-integration - test-codex-integration - test-cursor-cli-integration - build-ai-sdk-integration diff --git a/hindsight-dev/hindsight_dev/generate_changelog.py b/hindsight-dev/hindsight_dev/generate_changelog.py index 2cbc336d0..99f4bdee3 100644 --- a/hindsight-dev/hindsight_dev/generate_changelog.py +++ b/hindsight-dev/hindsight_dev/generate_changelog.py @@ -53,6 +53,7 @@ class IntegrationMeta: "claude-agent-sdk": IntegrationMeta("hindsight-claude-agent-sdk", "Claude Agent SDK"), "llamaindex": IntegrationMeta("hindsight-llamaindex", "LlamaIndex"), "codex": IntegrationMeta("hindsight-codex", "Codex"), + "github-copilot": IntegrationMeta("hindsight-copilot", "GitHub Copilot"), "cline": IntegrationMeta("hindsight-cline", "Cline"), "cursor-cli": IntegrationMeta("hindsight-cursor-cli", "Cursor CLI"), "cursor": IntegrationMeta("hindsight-cursor", "Cursor"), diff --git a/hindsight-docs/docs-integrations/github-copilot.md b/hindsight-docs/docs-integrations/github-copilot.md new file mode 100644 index 000000000..5d33ee801 --- /dev/null +++ b/hindsight-docs/docs-integrations/github-copilot.md @@ -0,0 +1,51 @@ +--- +sidebar_position: 39 +title: "GitHub Copilot Persistent Memory with Hindsight | Integration" +description: "Add long-term memory to GitHub Copilot in VS Code with Hindsight via MCP. One command wires up the Hindsight MCP server plus a recall/retain rule." +--- + +# GitHub Copilot + +Long-term memory for [GitHub Copilot](https://github.com/features/copilot) in VS Code, powered by [Hindsight](https://vectorize.io/hindsight). One command connects Copilot's agent mode to the Hindsight MCP server and adds a recall/retain rule — so Copilot recalls relevant memory at the start of a task and retains durable facts as it works. + +## How It Works + +VS Code Copilot supports two things this integration uses: + +- **MCP servers** via `.vscode/mcp.json` (agent mode), including **HTTP servers** with headers — so the Hindsight MCP endpoint connects directly: + + ```json + { + "servers": { + "hindsight": { + "type": "http", + "url": "https://api.hindsight.vectorize.io/mcp/my-project/", + "headers": { "Authorization": "Bearer hsk_..." } + } + } + } + ``` + +- **`.github/copilot-instructions.md`**, which Copilot applies to every chat in the workspace — that's where the recall/retain rule lives. + +## Setup + +```bash +pip install hindsight-copilot +cd your-project +hindsight-copilot init --api-token YOUR_HINDSIGHT_API_KEY --bank-id my-project +``` + +`init` merges the `servers` entry into `./.vscode/mcp.json` and writes the rule into `./.github/copilot-instructions.md`. Reload VS Code, open Copilot Chat in **agent mode**, and start the `hindsight` MCP server from the chat's tools menu. + +Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or a self-hosted server with `--api-url http://localhost:8888` (no token needed for an open local server). If `mcp.json` has comments, `init` prints the snippet to paste instead — or run `hindsight-copilot init --print-only` anytime. + +## Commands + +| Command | Description | +| --- | --- | +| `hindsight-copilot init` | Add the MCP server + recall/retain rule | +| `hindsight-copilot status` | Show whether the server + rule are configured | +| `hindsight-copilot uninstall` | Remove the server + rule | + +See the [package README](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/github-copilot) for full configuration options. diff --git a/hindsight-docs/src/data/integrations.json b/hindsight-docs/src/data/integrations.json index 162c7d10a..f9ea5eebd 100644 --- a/hindsight-docs/src/data/integrations.json +++ b/hindsight-docs/src/data/integrations.json @@ -160,6 +160,16 @@ "link": "/sdks/integrations/codex", "icon": "/img/icons/codex.svg" }, + { + "id": "github-copilot", + "name": "GitHub Copilot", + "description": "Long-term memory for GitHub Copilot in VS Code. One command wires the Hindsight MCP server into .vscode/mcp.json plus a recall/retain rule in .github/copilot-instructions.md.", + "type": "official", + "by": "hindsight", + "category": "tool", + "link": "/sdks/integrations/github-copilot", + "icon": "/img/icons/github-copilot.svg" + }, { "id": "continue", "name": "Continue", diff --git a/hindsight-docs/static/img/icons/github-copilot.svg b/hindsight-docs/static/img/icons/github-copilot.svg new file mode 100644 index 000000000..201ecf408 --- /dev/null +++ b/hindsight-docs/static/img/icons/github-copilot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hindsight-integrations/README.md b/hindsight-integrations/README.md index 46118c6c1..c66416cba 100644 --- a/hindsight-integrations/README.md +++ b/hindsight-integrations/README.md @@ -13,6 +13,7 @@ Each integration lives in its own subdirectory with its own README, configuratio | [**Codex CLI**](./codex) | Python hook scripts for OpenAI's Codex CLI. Auto-recall on `UserPromptSubmit`, auto-retain on `Stop`. | `curl -fsSL https://hindsight.vectorize.io/get-codex \| bash` | | [**Cursor CLI**](./cursor-cli) | Python hook scripts for Cursor CLI. Auto-recall on `beforeSubmitPrompt`, auto-retain on `stop`, final flush on `sessionEnd`. | `./scripts/install.sh` | | [**Continue.dev**](./continue) | HTTP context provider for precise `@hindsight` recall in chat, plus optional MCP-server + rules for automatic recall/retain in agent mode. | `pip install hindsight-continue` | +| [**GitHub Copilot**](./github-copilot) | MCP server config (`.vscode/mcp.json`) + a recall/retain rule for VS Code Copilot's agent mode. | `pip install hindsight-copilot` | | [**Roo Code**](./roo-code) | Persistent memory for Roo Code VS Code extension. | See README | | [**Hermes (OpenAI Agents SDK)**](./hermes) | Memory layer for OpenAI Agents SDK. | See README | | [**Grok Build**](./grok-build) | Hooks for Grok Build (xAI). | See README | diff --git a/hindsight-integrations/github-copilot/README.md b/hindsight-integrations/github-copilot/README.md new file mode 100644 index 000000000..54a72510a --- /dev/null +++ b/hindsight-integrations/github-copilot/README.md @@ -0,0 +1,76 @@ +# hindsight-copilot + +Long-term memory for **GitHub Copilot** in VS Code, powered by [Hindsight](https://github.com/vectorize-io/hindsight). + +`hindsight-copilot init` wires the Hindsight **MCP server** into VS Code's +`.vscode/mcp.json` and adds a recall/retain rule to `.github/copilot-instructions.md`. +Copilot's agent mode then has `recall` / `retain` / `reflect` tools and — guided +by the rule — recalls relevant memory at the start of a task and retains durable +facts as it works. + +## How it works + +VS Code Copilot supports two things this integration uses: + +- **MCP servers** in `.vscode/mcp.json` (agent mode), including **HTTP servers** + with headers — so the Hindsight MCP endpoint connects directly: + + ```json + { + "servers": { + "hindsight": { + "type": "http", + "url": "https://api.hindsight.vectorize.io/mcp/my-project/", + "headers": { "Authorization": "Bearer hsk_..." } + } + } + } + ``` + +- **`.github/copilot-instructions.md`**, which Copilot applies to every chat in + the workspace — that's where the recall/retain rule lives. + +## Install + +```bash +pip install hindsight-copilot +cd your-project +hindsight-copilot init --api-token YOUR_HINDSIGHT_API_KEY --bank-id my-project +``` + +`init` merges the `servers` entry into `./.vscode/mcp.json` and writes the rule +into `./.github/copilot-instructions.md`. Reload VS Code, open Copilot Chat in +**agent mode**, and start the `hindsight` MCP server from the chat's tools menu. + +Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or a self-hosted +server with `--api-url http://localhost:8888` (no token needed for an open local +server). If `mcp.json` has comments, `init` prints the snippet to paste instead +of touching the file — or run `hindsight-copilot init --print-only` anytime. + +## Commands + +| Command | Description | +| --- | --- | +| `hindsight-copilot init` | Add the MCP server + recall/retain rule | +| `hindsight-copilot status` | Show whether the server + rule are configured | +| `hindsight-copilot uninstall` | Remove the server + rule | + +## Configuration + +| Setting | Env var | Default | +| --- | --- | --- | +| API URL | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | +| API token | `HINDSIGHT_API_TOKEN` | _(none; required for Cloud)_ | +| Bank id | `HINDSIGHT_COPILOT_BANK_ID` | `copilot` | + +## Development + +```bash +uv sync +uv run pytest tests -v -m 'not requires_real_llm' # deterministic suite +uv run pytest tests -v -m requires_real_llm # gated MCP-endpoint check +``` + +## License + +MIT diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/__init__.py b/hindsight-integrations/github-copilot/hindsight_copilot/__init__.py new file mode 100644 index 000000000..a0d307ac7 --- /dev/null +++ b/hindsight-integrations/github-copilot/hindsight_copilot/__init__.py @@ -0,0 +1,12 @@ +"""Hindsight memory integration for GitHub Copilot (VS Code). + +Wires the Hindsight MCP server into VS Code's ``.vscode/mcp.json`` and writes a +recall/retain rule into ``.github/copilot-instructions.md``, so Copilot's agent +mode has ``recall``/``retain``/``reflect`` tools and uses them automatically. + +CLI:: + + hindsight-copilot init --api-token hsk_... --bank-id my-project +""" + +__version__ = "0.1.0" diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/cli.py b/hindsight-integrations/github-copilot/hindsight_copilot/cli.py new file mode 100644 index 000000000..397035044 --- /dev/null +++ b/hindsight-integrations/github-copilot/hindsight_copilot/cli.py @@ -0,0 +1,172 @@ +"""CLI for the Hindsight GitHub Copilot integration. + +``hindsight-copilot init`` wires the Hindsight MCP server into VS Code's +``.vscode/mcp.json`` and writes a recall/retain rule into +``.github/copilot-instructions.md``. Copilot's agent mode then exposes +``recall``/``retain``/``reflect`` and (via the rule) uses them automatically. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +from . import __version__ +from .config import USER_CONFIG_FILE, CopilotConfig, load_config +from .instructions import RULE_TEXT, clear_rule, default_instructions_path, write_rule +from .instructions import is_installed as rule_installed +from .mcp_config import ( + McpResult, + apply_to_mcp, + build_http_server, + default_mcp_path, + remove_from_mcp, + render_snippet, +) +from .mcp_config import is_installed as server_installed + + +@dataclass +class InstallOutcome: + mcp: McpResult + instructions_path: Path + + +def build_install(config: CopilotConfig, mcp_path: Path, instructions_path: Path) -> InstallOutcome: + """Apply the MCP server entry and the recall/retain rule (the testable core).""" + server = build_http_server(config.hindsight_api_url, config.hindsight_api_token, config.bank_id) + mcp = apply_to_mcp(mcp_path, server) + write_rule(instructions_path) + return InstallOutcome(mcp=mcp, instructions_path=instructions_path) + + +def _resolve_config(args: argparse.Namespace) -> CopilotConfig: + cfg = load_config(config_file=_user_config_path(args)) + if args.api_url: + cfg.hindsight_api_url = args.api_url + if args.api_token: + cfg.hindsight_api_token = args.api_token + if args.bank_id: + cfg.bank_id = args.bank_id + return cfg + + +def _user_config_path(args: argparse.Namespace) -> Path: + return Path(args.user_config_path) if args.user_config_path else USER_CONFIG_FILE + + +def _mcp_path(args: argparse.Namespace) -> Path: + return Path(args.mcp_path) if args.mcp_path else default_mcp_path() + + +def _instructions_path(args: argparse.Namespace) -> Path: + return Path(args.instructions_path) if args.instructions_path else default_instructions_path() + + +def _scaffold_user_config(cfg: CopilotConfig, path: Path) -> None: + if path.is_file(): + return + data = {"hindsightApiUrl": cfg.hindsight_api_url, "bankId": cfg.bank_id} + if cfg.hindsight_api_token: + data["hindsightApiToken"] = cfg.hindsight_api_token + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + + +def cmd_init(args: argparse.Namespace) -> None: + cfg = _resolve_config(args) + mcp_path = _mcp_path(args) + instructions_path = _instructions_path(args) + server = build_http_server(cfg.hindsight_api_url, cfg.hindsight_api_token, cfg.bank_id) + + if args.print_only: + print("Add this to your .vscode/mcp.json:\n") + print(render_snippet(server)) + print("\nAnd add this rule to .github/copilot-instructions.md:\n") + print(RULE_TEXT) + return + + print("Setting up Hindsight for GitHub Copilot ...") + _scaffold_user_config(cfg, _user_config_path(args)) + outcome = build_install(cfg, mcp_path, instructions_path) + + if outcome.mcp.action == "manual": + print(f" Your {outcome.mcp.path} has comments, so I won't rewrite it.") + print(" Add this `servers` entry yourself:\n") + print(render_snippet(server)) + else: + verb = {"created": "Created", "merged": "Updated", "unchanged": "Already configured in"}[outcome.mcp.action] + print(f" {verb} {outcome.mcp.path} (MCP server: hindsight -> bank '{cfg.bank_id}')") + print(f" Wrote recall/retain rule to {outcome.instructions_path}") + print("\nDone. Reload VS Code, open Copilot Chat in agent mode, and the") + print("hindsight MCP tools (recall/retain/reflect) are available + used automatically.") + + +def cmd_status(args: argparse.Namespace) -> None: + mcp_path = _mcp_path(args) + instructions_path = _instructions_path(args) + print(f"MCP server in {mcp_path}: {'installed' if server_installed(mcp_path) else 'not installed'}") + print( + f"Recall/retain rule in {instructions_path}: {'installed' if rule_installed(instructions_path) else 'not installed'}" + ) + + +def cmd_uninstall(args: argparse.Namespace) -> None: + mcp_path = _mcp_path(args) + instructions_path = _instructions_path(args) + result = remove_from_mcp(mcp_path) + if result.action == "manual": + print(f" {mcp_path} has comments — remove the `hindsight` server entry yourself.") + elif result.action == "removed": + print(f" Removed the hindsight MCP server from {mcp_path}") + else: + print(f" No hindsight MCP server found in {mcp_path}") + clear_rule(instructions_path) + print(f" Removed the recall/retain rule from {instructions_path}") + + +def _add_overrides(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--mcp-path", default=None, help=".vscode/mcp.json path (default: ./.vscode/mcp.json)") + parser.add_argument( + "--instructions-path", default=None, help="copilot-instructions.md path (default: ./.github/...)" + ) + parser.add_argument("--user-config-path", default=None, help=argparse.SUPPRESS) + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser( + prog="hindsight-copilot", description="Hindsight memory for GitHub Copilot (VS Code, via MCP)" + ) + parser.add_argument("--version", action="version", version=f"hindsight-copilot {__version__}") + sub = parser.add_subparsers(dest="command") + + init_p = sub.add_parser("init", help="Configure Copilot's MCP server + recall/retain rule") + init_p.add_argument("--api-url", default=None, help="Hindsight API URL (default: cloud)") + init_p.add_argument("--api-token", default=None, help="Hindsight API token (for Cloud)") + init_p.add_argument("--bank-id", default=None, help="Memory bank for the MCP server (default: copilot)") + init_p.add_argument("--print-only", action="store_true", help="Print the config to add manually; write nothing") + _add_overrides(init_p) + init_p.set_defaults(func=cmd_init) + + status_p = sub.add_parser("status", help="Show whether the MCP server + rule are configured") + _add_overrides(status_p) + status_p.set_defaults(func=cmd_status) + + uninst_p = sub.add_parser("uninstall", help="Remove the MCP server + rule") + _add_overrides(uninst_p) + uninst_p.set_defaults(func=cmd_uninstall) + + args = parser.parse_args(argv) + if not hasattr(args, "func"): + parser.print_help() + return 1 + args.func(args) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/config.py b/hindsight-integrations/github-copilot/hindsight_copilot/config.py new file mode 100644 index 000000000..9473fe99f --- /dev/null +++ b/hindsight-integrations/github-copilot/hindsight_copilot/config.py @@ -0,0 +1,76 @@ +"""Configuration for the Hindsight GitHub Copilot integration. + +Settings layer (later wins): built-in defaults -> ``~/.hindsight/copilot.json`` +-> environment variables. Resolved into a typed :class:`CopilotConfig`. + +The integration is configuration-only: it wires the Hindsight MCP server into +VS Code's ``.vscode/mcp.json`` and writes a recall/retain rule into +``.github/copilot-instructions.md`` (which Copilot applies to every chat in the +workspace). Memory operations run through the MCP server at runtime. +""" + +from __future__ import annotations + +import json +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +DEFAULT_HINDSIGHT_API_URL = "https://api.hindsight.vectorize.io" +DEFAULT_BANK_ID = "copilot" + +USER_CONFIG_FILE = Path.home() / ".hindsight" / "copilot.json" + + +@dataclass +class CopilotConfig: + """Resolved configuration for the Copilot MCP setup.""" + + hindsight_api_url: str = DEFAULT_HINDSIGHT_API_URL + hindsight_api_token: Optional[str] = None + # The memory bank the MCP server is scoped to (the last path segment of the + # MCP endpoint URL). + bank_id: str = DEFAULT_BANK_ID + + +_FILE_KEYS = { + "hindsightApiUrl": "hindsight_api_url", + "hindsightApiToken": "hindsight_api_token", + "bankId": "bank_id", +} + +_ENV_KEYS = { + "HINDSIGHT_API_URL": "hindsight_api_url", + "HINDSIGHT_API_TOKEN": "hindsight_api_token", + "HINDSIGHT_COPILOT_BANK_ID": "bank_id", +} + + +def load_config(config_file: Optional[Path] = None, env: Optional[dict] = None) -> CopilotConfig: + """Load and resolve configuration from file then environment.""" + cfg = CopilotConfig() + env = os.environ if env is None else env + + path = config_file if config_file is not None else USER_CONFIG_FILE + if path.is_file(): + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + data = {} + for key, attr in _FILE_KEYS.items(): + value = data.get(key) + if value: + setattr(cfg, attr, str(value)) + + for key, attr in _ENV_KEYS.items(): + value = env.get(key) + if value: + setattr(cfg, attr, str(value)) + + if not cfg.hindsight_api_url: + cfg.hindsight_api_url = DEFAULT_HINDSIGHT_API_URL + if not cfg.bank_id: + cfg.bank_id = DEFAULT_BANK_ID + + return cfg diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/instructions.py b/hindsight-integrations/github-copilot/hindsight_copilot/instructions.py new file mode 100644 index 000000000..d1cb2cc2d --- /dev/null +++ b/hindsight-integrations/github-copilot/hindsight_copilot/instructions.py @@ -0,0 +1,87 @@ +"""Write Hindsight's recall/retain rule into ``.github/copilot-instructions.md``. + +VS Code Copilot automatically applies ``.github/copilot-instructions.md`` to +every chat request in the workspace, so a rule there tells Copilot to use the +Hindsight MCP tools — recall relevant memory at the start of a task, and retain +durable facts. + +The rule lives inside a fenced ```` ... ```` +block so we can update or remove it without disturbing the user's own content. +""" + +from __future__ import annotations + +from pathlib import Path + +BEGIN_MARKER = "" +END_MARKER = "" + +RULE_TEXT = ( + "You have persistent long-term memory through the Hindsight MCP server " + "(`recall`, `retain`, and `reflect` tools).\n\n" + "- At the start of each task, call `recall` with the user's request to load " + "relevant decisions, preferences, and project context before you answer. " + "Use what's relevant and ignore the rest.\n" + "- When you learn a durable fact — an architectural decision, a user " + "preference, a convention, or anything worth remembering across sessions — " + "call `retain` to store it.\n" + "- Do not mention these memory operations unless the user asks about them." +) + + +def default_instructions_path() -> Path: + """The workspace ``.github/copilot-instructions.md`` (always-on in Copilot).""" + return Path.cwd() / ".github" / "copilot-instructions.md" + + +def _strip_block(text: str) -> str: + start = text.find(BEGIN_MARKER) + if start == -1: + return text + end = text.find(END_MARKER, start) + if end == -1: + return text[:start].rstrip() + "\n" + end += len(END_MARKER) + before = text[:start].rstrip() + after = text[end:].lstrip() + if before and after: + return f"{before}\n\n{after}" + return (before or after).rstrip() + ("\n" if (before or after) else "") + + +def render_block(rule_text: str = RULE_TEXT) -> str: + return f"{BEGIN_MARKER}\n{rule_text.strip()}\n{END_MARKER}" + + +def write_rule(path: Path, rule_text: str = RULE_TEXT) -> Path: + """Write/replace Hindsight's rule block in the instructions file at ``path``. + + Preserves any user-authored content; only rewrites our fenced block, placing + it at the top so the memory rule leads the instructions. + """ + existing = path.read_text(encoding="utf-8") if path.is_file() else "" + base = _strip_block(existing).rstrip() + block = render_block(rule_text) + new_text = f"{block}\n\n{base}\n" if base else f"{block}\n" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(new_text, encoding="utf-8") + return path + + +def clear_rule(path: Path) -> Path: + """Remove Hindsight's rule block from the instructions file; delete if empty.""" + if not path.is_file(): + return path + existing = path.read_text(encoding="utf-8") + if BEGIN_MARKER not in existing: + return path + stripped = _strip_block(existing).strip() + if not stripped: + path.unlink() + return path + path.write_text(stripped + "\n", encoding="utf-8") + return path + + +def is_installed(path: Path) -> bool: + return path.is_file() and BEGIN_MARKER in path.read_text(encoding="utf-8") diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/mcp_config.py b/hindsight-integrations/github-copilot/hindsight_copilot/mcp_config.py new file mode 100644 index 000000000..534272af3 --- /dev/null +++ b/hindsight-integrations/github-copilot/hindsight_copilot/mcp_config.py @@ -0,0 +1,128 @@ +"""Wire Hindsight into VS Code's MCP config (``.vscode/mcp.json``). + +VS Code's Copilot agent mode reads MCP servers from ``.vscode/mcp.json`` under +the ``servers`` key, and supports HTTP servers directly — so the Hindsight MCP +endpoint connects with no bridge:: + + { + "servers": { + "hindsight": { + "type": "http", + "url": "https://api.hindsight.vectorize.io/mcp//", + "headers": { "Authorization": "Bearer hsk_..." } + } + } + } + +``.vscode/mcp.json`` may contain comments (JSONC), which the stdlib JSON parser +can't round-trip. So we only edit in place when the file parses as strict JSON; +otherwise we return the exact snippet to paste, never risking the user's file. +""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Optional + +SERVER_NAME = "hindsight" + + +def default_mcp_path() -> Path: + """The workspace ``.vscode/mcp.json`` (project-shared MCP config).""" + return Path.cwd() / ".vscode" / "mcp.json" + + +def mcp_endpoint_url(api_url: str, bank_id: str) -> str: + """The Hindsight MCP endpoint for a bank (bank is the last path segment).""" + return f"{api_url.rstrip('/')}/mcp/{bank_id}/" + + +def build_http_server(api_url: str, api_token: Optional[str], bank_id: str) -> dict[str, Any]: + """Build the ``servers.hindsight`` entry for ``.vscode/mcp.json``. + + An HTTP MCP server pointing at the Hindsight endpoint, with a Bearer auth + header when a token is set (omitted for an open self-hosted server). + """ + server: dict[str, Any] = {"type": "http", "url": mcp_endpoint_url(api_url, bank_id)} + if api_token: + server["headers"] = {"Authorization": f"Bearer {api_token}"} + return server + + +def render_snippet(server: dict[str, Any]) -> str: + """Render the snippet a user can paste into ``.vscode/mcp.json``.""" + return json.dumps({"servers": {SERVER_NAME: server}}, indent=2) + + +@dataclass +class McpResult: + """Outcome of editing ``.vscode/mcp.json``. + + ``action`` is one of ``created``, ``merged``, ``unchanged``, ``removed``, or + ``manual`` (file is JSONC we won't rewrite — ``snippet`` holds what to paste). + """ + + action: str + path: Path + snippet: Optional[str] = None + + +def _load_strict(path: Path) -> Optional[dict[str, Any]]: + if not path.is_file(): + return None + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return None + return data if isinstance(data, dict) else None + + +def apply_to_mcp(path: Path, server: dict[str, Any]) -> McpResult: + """Add/update ``servers.hindsight`` in ``.vscode/mcp.json`` at ``path``.""" + if not path.is_file(): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps({"servers": {SERVER_NAME: server}}, indent=2) + "\n", encoding="utf-8") + return McpResult("created", path) + + data = _load_strict(path) + if data is None: + return McpResult("manual", path, snippet=render_snippet(server)) + + servers = data.get("servers") + if not isinstance(servers, dict): + servers = {} + if servers.get(SERVER_NAME) == server: + return McpResult("unchanged", path) + servers[SERVER_NAME] = server + data["servers"] = servers + path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + return McpResult("merged", path) + + +def remove_from_mcp(path: Path) -> McpResult: + """Remove ``servers.hindsight`` from ``.vscode/mcp.json`` at ``path``.""" + data = _load_strict(path) + if data is None: + return McpResult("manual" if path.is_file() else "unchanged", path) + + servers = data.get("servers") + if not isinstance(servers, dict) or SERVER_NAME not in servers: + return McpResult("unchanged", path) + del servers[SERVER_NAME] + if servers: + data["servers"] = servers + else: + data.pop("servers", None) + path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + return McpResult("removed", path) + + +def is_installed(path: Path) -> bool: + """Whether our server is present in ``.vscode/mcp.json`` at ``path``.""" + data = _load_strict(path) + if data is None: + return False + servers = data.get("servers") + return isinstance(servers, dict) and SERVER_NAME in servers diff --git a/hindsight-integrations/github-copilot/hindsight_copilot/py.typed b/hindsight-integrations/github-copilot/hindsight_copilot/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/hindsight-integrations/github-copilot/pyproject.toml b/hindsight-integrations/github-copilot/pyproject.toml new file mode 100644 index 000000000..4526c45e2 --- /dev/null +++ b/hindsight-integrations/github-copilot/pyproject.toml @@ -0,0 +1,47 @@ +[project] +name = "hindsight-copilot" +version = "0.1.0" +description = "GitHub Copilot (VS Code) integration for Hindsight - persistent long-term memory via MCP" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +authors = [{ name = "Vectorize", email = "support@vectorize.io" }] +keywords = ["ai", "memory", "github-copilot", "copilot", "vscode", "agents", "hindsight", "mcp"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] +dependencies = [] + +[project.scripts] +hindsight-copilot = "hindsight_copilot.cli:main" + +[project.urls] +Homepage = "https://github.com/vectorize-io/hindsight" +Documentation = "https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/github-copilot" +Repository = "https://github.com/vectorize-io/hindsight" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["hindsight_copilot"] + +[tool.ruff] +line-length = 120 + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "requires_real_llm: end-to-end test that needs live external services (a running Hindsight server and/or real LLM provider keys). Excluded from the deterministic PR-CI bucket via -m 'not requires_real_llm'; run on its own via -m requires_real_llm.", +] + +[dependency-groups] +dev = ["pytest>=9.0.2", "ruff>=0.8.0"] diff --git a/hindsight-integrations/github-copilot/tests/__init__.py b/hindsight-integrations/github-copilot/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/hindsight-integrations/github-copilot/tests/test_cli.py b/hindsight-integrations/github-copilot/tests/test_cli.py new file mode 100644 index 000000000..d70acb431 --- /dev/null +++ b/hindsight-integrations/github-copilot/tests/test_cli.py @@ -0,0 +1,68 @@ +"""Tests for the CLI (init/status/uninstall).""" + +import json + +from hindsight_copilot.cli import build_install, main +from hindsight_copilot.config import CopilotConfig +from hindsight_copilot.mcp_config import SERVER_NAME, is_installed as server_installed + + +class TestBuildInstall: + def test_writes_mcp_and_rule(self, tmp_path): + mcp = tmp_path / "mcp.json" + instr = tmp_path / "copilot-instructions.md" + cfg = CopilotConfig( + hindsight_api_url="https://api.hindsight.vectorize.io", hindsight_api_token="k", bank_id="proj" + ) + outcome = build_install(cfg, mcp, instr) + assert outcome.mcp.action == "created" + server = json.loads(mcp.read_text())["servers"][SERVER_NAME] + assert server["url"] == "https://api.hindsight.vectorize.io/mcp/proj/" + assert server["headers"]["Authorization"] == "Bearer k" + assert "HINDSIGHT:BEGIN" in instr.read_text() + + +class TestMain: + def _common(self, tmp_path): + return [ + "--mcp-path", + str(tmp_path / "mcp.json"), + "--instructions-path", + str(tmp_path / "copilot-instructions.md"), + "--user-config-path", + str(tmp_path / "user.json"), + ] + + def test_init_status_uninstall(self, tmp_path, capsys): + common = self._common(tmp_path) + assert main(["init", "--api-url", "http://localhost:8888", "--bank-id", "b", *common]) == 0 + assert server_installed(tmp_path / "mcp.json") + main(["status", *common]) + assert "installed" in capsys.readouterr().out + main(["uninstall", *common]) + assert not server_installed(tmp_path / "mcp.json") + assert not (tmp_path / "copilot-instructions.md").exists() + + def test_print_only_writes_nothing(self, tmp_path, capsys): + mcp = tmp_path / "mcp.json" + instr = tmp_path / "copilot-instructions.md" + rc = main( + [ + "init", + "--print-only", + "--api-url", + "http://localhost:8888", + "--mcp-path", + str(mcp), + "--instructions-path", + str(instr), + "--user-config-path", + str(tmp_path / "user.json"), + ] + ) + assert rc == 0 + assert not mcp.exists() and not instr.exists() + assert "servers" in capsys.readouterr().out + + def test_no_command_returns_1(self): + assert main([]) == 1 diff --git a/hindsight-integrations/github-copilot/tests/test_config.py b/hindsight-integrations/github-copilot/tests/test_config.py new file mode 100644 index 000000000..3d71d0fcd --- /dev/null +++ b/hindsight-integrations/github-copilot/tests/test_config.py @@ -0,0 +1,34 @@ +"""Tests for config loading.""" + +import json + +from hindsight_copilot.config import DEFAULT_BANK_ID, DEFAULT_HINDSIGHT_API_URL, load_config + + +def test_defaults(tmp_path): + cfg = load_config(config_file=tmp_path / "missing.json", env={}) + assert cfg.hindsight_api_url == DEFAULT_HINDSIGHT_API_URL + assert cfg.hindsight_api_token is None + assert cfg.bank_id == DEFAULT_BANK_ID + + +def test_file_values(tmp_path): + p = tmp_path / "copilot.json" + p.write_text(json.dumps({"hindsightApiToken": "t", "bankId": "proj"})) + cfg = load_config(config_file=p, env={}) + assert cfg.hindsight_api_token == "t" + assert cfg.bank_id == "proj" + + +def test_env_overrides_file(tmp_path): + p = tmp_path / "copilot.json" + p.write_text(json.dumps({"bankId": "from-file"})) + cfg = load_config(config_file=p, env={"HINDSIGHT_COPILOT_BANK_ID": "from-env", "HINDSIGHT_API_TOKEN": "k"}) + assert cfg.bank_id == "from-env" + assert cfg.hindsight_api_token == "k" + + +def test_malformed_file_falls_back(tmp_path): + p = tmp_path / "copilot.json" + p.write_text("{ broken") + assert load_config(config_file=p, env={}).bank_id == DEFAULT_BANK_ID diff --git a/hindsight-integrations/github-copilot/tests/test_e2e.py b/hindsight-integrations/github-copilot/tests/test_e2e.py new file mode 100644 index 000000000..5909c6c48 --- /dev/null +++ b/hindsight-integrations/github-copilot/tests/test_e2e.py @@ -0,0 +1,60 @@ +"""Gated MCP-endpoint E2E (requires_real_llm).""" + +from __future__ import annotations + +import json +import os +import urllib.request + +import pytest + +from hindsight_copilot.mcp_config import mcp_endpoint_url + +HINDSIGHT_API_URL = os.getenv("HINDSIGHT_API_URL", "http://localhost:8888") +HINDSIGHT_API_TOKEN = os.getenv("HINDSIGHT_API_TOKEN") + + +def _reachable() -> bool: + try: + with urllib.request.urlopen(f"{HINDSIGHT_API_URL}/health", timeout=3) as r: + return r.status == 200 + except Exception: + return False + + +pytestmark = [ + pytest.mark.requires_real_llm, + pytest.mark.skipif(not _reachable(), reason=f"Hindsight not reachable at {HINDSIGHT_API_URL}"), +] + + +def _rpc(url, payload, session=None): + req = urllib.request.Request(url, data=json.dumps(payload).encode(), method="POST") + req.add_header("Content-Type", "application/json") + req.add_header("Accept", "application/json, text/event-stream") + if session: + req.add_header("Mcp-Session-Id", session) + if HINDSIGHT_API_TOKEN: + req.add_header("Authorization", f"Bearer {HINDSIGHT_API_TOKEN}") + return urllib.request.urlopen(req, timeout=15) + + +def test_mcp_endpoint_lists_memory_tools(): + url = mcp_endpoint_url(HINDSIGHT_API_URL, "copilot-e2e") + init = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "copilot-e2e", "version": "0"}, + }, + } + resp = _rpc(url, init) + session = resp.headers.get("Mcp-Session-Id") + resp.read() + _rpc(url, {"jsonrpc": "2.0", "method": "notifications/initialized"}, session=session).read() + resp = _rpc(url, {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}, session=session) + text = resp.read().decode("utf-8", "replace") + assert "recall" in text and "retain" in text, f"tools/list missing memory tools: {text[:300]}" diff --git a/hindsight-integrations/github-copilot/tests/test_instructions.py b/hindsight-integrations/github-copilot/tests/test_instructions.py new file mode 100644 index 000000000..310c0f402 --- /dev/null +++ b/hindsight-integrations/github-copilot/tests/test_instructions.py @@ -0,0 +1,47 @@ +"""Tests for the copilot-instructions.md rule writer.""" + +from hindsight_copilot.instructions import BEGIN_MARKER, RULE_TEXT, clear_rule, is_installed, write_rule + + +def test_write_creates_with_block(tmp_path): + path = tmp_path / "copilot-instructions.md" + write_rule(path) + text = path.read_text() + assert BEGIN_MARKER in text and "recall" in text and "retain" in text + assert is_installed(path) + + +def test_write_preserves_user_content_block_leads(tmp_path): + path = tmp_path / "copilot-instructions.md" + path.write_text("# Project\n\nUse TypeScript.\n") + write_rule(path) + text = path.read_text() + assert "Use TypeScript." in text + assert text.index(BEGIN_MARKER) < text.index("Use TypeScript.") + + +def test_write_replaces_existing_block(tmp_path): + path = tmp_path / "copilot-instructions.md" + write_rule(path) + write_rule(path) + assert path.read_text().count(BEGIN_MARKER) == 1 + + +def test_clear_keeps_user_content(tmp_path): + path = tmp_path / "copilot-instructions.md" + path.write_text("Keep me.\n") + write_rule(path) + clear_rule(path) + assert "Keep me." in path.read_text() and BEGIN_MARKER not in path.read_text() + + +def test_clear_deletes_if_only_block(tmp_path): + path = tmp_path / "copilot-instructions.md" + write_rule(path) + clear_rule(path) + assert not path.exists() + + +def test_rule_mentions_all_tools(): + for tool in ("recall", "retain", "reflect"): + assert tool in RULE_TEXT diff --git a/hindsight-integrations/github-copilot/tests/test_mcp_config.py b/hindsight-integrations/github-copilot/tests/test_mcp_config.py new file mode 100644 index 000000000..d8f1197a2 --- /dev/null +++ b/hindsight-integrations/github-copilot/tests/test_mcp_config.py @@ -0,0 +1,97 @@ +"""Tests for the .vscode/mcp.json servers writer.""" + +import json + +from hindsight_copilot.mcp_config import ( + SERVER_NAME, + apply_to_mcp, + build_http_server, + is_installed, + mcp_endpoint_url, + remove_from_mcp, + render_snippet, +) + + +class TestBuildServer: + def test_endpoint_url_embeds_bank(self): + assert mcp_endpoint_url("https://api.hindsight.vectorize.io", "proj") == ( + "https://api.hindsight.vectorize.io/mcp/proj/" + ) + assert mcp_endpoint_url("http://localhost:8888/", "b") == "http://localhost:8888/mcp/b/" + + def test_cloud_server_http_with_auth_header(self): + s = build_http_server("https://api.hindsight.vectorize.io", "hsk_abc", "proj") + assert s["type"] == "http" + assert s["url"] == "https://api.hindsight.vectorize.io/mcp/proj/" + assert s["headers"] == {"Authorization": "Bearer hsk_abc"} + + def test_open_server_omits_headers(self): + s = build_http_server("http://localhost:8888", None, "proj") + assert s == {"type": "http", "url": "http://localhost:8888/mcp/proj/"} + assert "headers" not in s + + +class TestApply: + def test_creates_file(self, tmp_path): + path = tmp_path / "mcp.json" + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + result = apply_to_mcp(path, s) + assert result.action == "created" + assert json.loads(path.read_text())["servers"][SERVER_NAME] == s + + def test_merges_preserves_other_servers_and_inputs(self, tmp_path): + path = tmp_path / "mcp.json" + path.write_text(json.dumps({"inputs": [{"id": "tok"}], "servers": {"other": {"type": "stdio"}}})) + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + result = apply_to_mcp(path, s) + assert result.action == "merged" + data = json.loads(path.read_text()) + assert data["inputs"] == [{"id": "tok"}] # untouched + assert data["servers"]["other"] == {"type": "stdio"} # untouched + assert data["servers"][SERVER_NAME] == s + + def test_unchanged_when_identical(self, tmp_path): + path = tmp_path / "mcp.json" + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + apply_to_mcp(path, s) + assert apply_to_mcp(path, s).action == "unchanged" + + def test_jsonc_returns_manual(self, tmp_path): + path = tmp_path / "mcp.json" + original = '{\n // comment\n "servers": {}\n}\n' + path.write_text(original) + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + result = apply_to_mcp(path, s) + assert result.action == "manual" + assert result.snippet and SERVER_NAME in result.snippet + assert path.read_text() == original # untouched + + +class TestRemoveAndStatus: + def test_remove_only_our_entry(self, tmp_path): + path = tmp_path / "mcp.json" + path.write_text(json.dumps({"servers": {"other": {"type": "stdio"}, SERVER_NAME: {"type": "http"}}})) + result = remove_from_mcp(path) + assert result.action == "removed" + servers = json.loads(path.read_text())["servers"] + assert SERVER_NAME not in servers and "other" in servers + + def test_remove_drops_empty_servers(self, tmp_path): + path = tmp_path / "mcp.json" + path.write_text(json.dumps({"inputs": [], "servers": {SERVER_NAME: {"type": "http"}}})) + remove_from_mcp(path) + data = json.loads(path.read_text()) + assert "servers" not in data + assert "inputs" in data + + def test_is_installed(self, tmp_path): + path = tmp_path / "mcp.json" + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + assert is_installed(path) is False + apply_to_mcp(path, s) + assert is_installed(path) is True + + def test_render_snippet_valid_json(self): + s = build_http_server("https://api.hindsight.vectorize.io", "k", "b") + assert json.loads(render_snippet(s))["servers"][SERVER_NAME] == s diff --git a/hindsight-integrations/github-copilot/uv.lock b/hindsight-integrations/github-copilot/uv.lock new file mode 100644 index 000000000..221e00889 --- /dev/null +++ b/hindsight-integrations/github-copilot/uv.lock @@ -0,0 +1,185 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "hindsight-copilot" +version = "0.1.0" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.2" }, + { name = "ruff", specifier = ">=0.8.0" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" }, + { url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" }, + { url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" }, + { url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" }, + { url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" }, + { url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/scripts/release-integration.sh b/scripts/release-integration.sh index d6de647f9..c9c16600e 100755 --- a/scripts/release-integration.sh +++ b/scripts/release-integration.sh @@ -13,7 +13,7 @@ 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=("ag2" "agent-framework" "agentcore" "agno" "ai-sdk" "autogen" "chat" "claude-agent-sdk" "claude-code" "cline" "cloudflare-oauth-proxy" "codex" "composio" "continue" "crewai" "cursor" "cursor-cli" "dify" "flowise" "gemini-spark" "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" "zed") +VALID_INTEGRATIONS=("ag2" "agent-framework" "agentcore" "agno" "ai-sdk" "autogen" "chat" "claude-agent-sdk" "claude-code" "cline" "cloudflare-oauth-proxy" "codex" "composio" "continue" "crewai" "cursor" "cursor-cli" "dify" "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" "zed") usage() { print_error "Usage: $0 "