diff --git a/.claude/skills/api-integration/SKILL.md b/.claude/skills/api-integration/SKILL.md index f08ea2d6c..6f7165ad6 100644 --- a/.claude/skills/api-integration/SKILL.md +++ b/.claude/skills/api-integration/SKILL.md @@ -333,9 +333,16 @@ Append tool reference block after the FRED block (before `## ENRICHMENT PROTOCOL - tool_name_1, tool_name_2, tool_name_3 ``` +**Also**: append a per-domain block to the `MCP_FALLBACK_INSTRUCTIONS` template literal at `_promptConstants.js:721`. This teaches subagents what tool prefix maps to the new domain when MCP routing falls back. Format: + +``` +**{domain}** — `mcp__{domain}__*` (e.g., `mcp__{domain}__search_items`) +``` + - [ ] Tool count matches actual number of schemas - [ ] Domain name matches DOMAIN_GROUPS key - [ ] Tool names match schema names exactly +- [ ] `MCP_FALLBACK_INSTRUCTIONS` block present at `_promptConstants.js:721` ### 3.6 Client Registry — `src/server/clientRegistry.js` @@ -368,6 +375,29 @@ Three updates: - [ ] All `expectedCount` sites updated - [ ] All subagent assertion arrays updated +### 3.8 Feature Flag Registration (when API is gated) + +When the new API requires runtime gating (e.g., FMP_ENABLED, EMBEDDING_PERSISTENCE pattern), register the flag in two places: + +1. **`super-legal-mcp-refactored/flags.env`** — append the flag near related flags (e.g., near `FMP_ENABLED`): + ```bash + {SERVICE}_ENABLED=false + ``` +2. **`super-legal-mcp-refactored/src/config/featureFlags.js`** — export from the canonical block. Pattern (matching FMP): + ```javascript + {SERVICE}_ENABLED: process.env.{SERVICE}_ENABLED === 'true', + ``` + +When gated: +- `domainMcpServers.js` `DOMAIN_GROUPS` — wrap the domain key in a conditional spread: `...(featureFlags.{SERVICE}_ENABLED ? { '{domain}': {Name}Tools } : {})` +- `toolDefinitions.js` `allTools` — same conditional spread pattern (`...(featureFlags.{SERVICE}_ENABLED ? {Name}Tools : [])`) +- `clientRegistry.js` slot — conditional construction: `...(featureFlags.{SERVICE}_ENABLED ? { {slotName}: new {Name}HybridClient(...) } : {})` + +- [ ] Flag declared in `flags.env` +- [ ] Flag exported in `featureFlags.js` +- [ ] All conditional spreads wired in `domainMcpServers.js`, `toolDefinitions.js`, `clientRegistry.js` +- [ ] Default value is `false` (opt-in) unless flag is universal + --- ## Phase 4: Testing (MUST PASS before merge) diff --git a/.claude/skills/client-offboarding/SKILL.md b/.claude/skills/client-offboarding/SKILL.md index c7abe3594..6a30cd1d7 100644 --- a/.claude/skills/client-offboarding/SKILL.md +++ b/.claude/skills/client-offboarding/SKILL.md @@ -94,13 +94,15 @@ All exported via `psql COPY TO STDOUT` + gzip to `gs://super-legal-worm-{client_ ### Phase 4: Final Report -**Step 15**: Generate offboarding report +**Step 15**: Generate offboarding report (markdown emitted to stdout + saved to `~/.aperture/offboarding-{client_id}-{date}.md`) - Client ID, offboarding date, operator - Resources deleted (with timestamps) - Archives created (with GCS paths + checksums) - Remaining resources (WORM bucket — retained for compliance) - Final cost estimate (last 30 days billing for this client's resources) +**Future enhancement (deferred)**: PDF rendering via pandoc (already used elsewhere in repo for `aperture-demo-preview.pdf`). Markdown output is the canonical artifact today; pandoc wiring is a 1-command future addition (`pandoc offboarding-{client_id}.md -o offboarding-{client_id}.pdf --pdf-engine=xelatex`). Operator can run manually post-offboarding. + ## Resource Naming Convention (matches provisioner) | Resource | Pattern | Action | diff --git a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D10-hooks.py b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D10-hooks.py index 3c6a2fbd1..9c4d56db7 100755 --- a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D10-hooks.py +++ b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D10-hooks.py @@ -107,6 +107,61 @@ def main(): "remediation": "", }) + # D10.4 — new SSE event_types that drive transcript_events / frontend + # rendering must appear in (a) transcriptDBBridge.js allowlist (or + # equivalent persistence path) AND (b) frontend handleStreamEvent switch. + import os as _os + repo_root_p = _os.environ.get("REPO_ROOT") or "" + if not repo_root_p: + cur_p = _os.path.abspath(_os.getcwd()) + for _ in range(10): + if _os.path.isdir(_os.path.join(cur_p, "super-legal-mcp-refactored")): + repo_root_p = cur_p + break + cur_p = _os.path.dirname(cur_p) + transcript_paths = [ + _os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "transcriptDBBridge.js"), + _os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "transcriptPersistence.js"), + ] + frontend_path = _os.path.join(repo_root_p, "super-legal-mcp-refactored", "test", "react-frontend", "app.js") + transcript_text = "" + for p in transcript_paths: + if _os.path.isfile(p): + try: + transcript_text += "\n" + open(p, errors="replace").read() + except OSError: + pass + frontend_text = "" + if _os.path.isfile(frontend_path): + try: + frontend_text = open(frontend_path, errors="replace").read() + except OSError: + frontend_text = "" + + for event_type in s.get("event_types") or []: + # Skip the synthetic types (already handled above) + if event_type in SYNTHETIC_TYPES: + continue + in_transcript = transcript_text and (f"'{event_type}'" in transcript_text or f'"{event_type}"' in transcript_text) + in_frontend = frontend_text and (f"case '{event_type}'" in frontend_text or f'case "{event_type}"' in frontend_text) + if not transcript_text and not frontend_text: + # Neither file accessible; skip silently (covered by D10.1 above) + continue + if not in_transcript: + findings.append({ + "dimension": "D10", "status": "WARNING", + "check": f"D10.4 event_type '{event_type}' transcript persistence", + "message": f"event_type '{event_type}' not found as a string literal in transcriptDBBridge.js / transcriptPersistence.js — may be silently dropped from transcript_events", + "remediation": f"Add '{event_type}' to the transcript-event allowlist in transcriptDBBridge.js (or equivalent persistence file).", + }) + if not in_frontend: + findings.append({ + "dimension": "D10", "status": "WARNING", + "check": f"D10.4 event_type '{event_type}' frontend handler", + "message": f"event_type '{event_type}' not handled in test/react-frontend/app.js handleStreamEvent switch — frontend will ignore it", + "remediation": f"Add `case '{event_type}': ...` branch in handleStreamEvent (app.js).", + }) + emit_findings("D10", findings) diff --git a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D5-embeddings.py b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D5-embeddings.py index 4534bed9b..4a3e63774 100755 --- a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D5-embeddings.py +++ b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D5-embeddings.py @@ -83,6 +83,48 @@ def main(): "remediation": "Confirm the producing path triggers embeddingService.chunkByHeaders() with EMBEDDING_PERSISTENCE=true.", }) + # D5.4 — new tables that look like report-derived content should have an + # embedding write path wired in hookDBBridge.js (chunkByHeaders call). + # Without this check, an embedding-friendly table can ship with no producer. + import os, re as _re + repo_root_p = os.environ.get("REPO_ROOT", "") + if not repo_root_p: + # Best-effort: walk up from CWD + cur_p = os.path.abspath(os.getcwd()) + for _ in range(10): + if os.path.isdir(os.path.join(cur_p, "super-legal-mcp-refactored")): + repo_root_p = cur_p + break + cur_p = os.path.dirname(cur_p) + bridge_path = os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "hookDBBridge.js") + bridge_text = "" + if os.path.isfile(bridge_path): + try: + bridge_text = open(bridge_path, errors="replace").read() + except OSError: + bridge_text = "" + for table in s.get("tables") or []: + # Heuristic: tables suffixed _embeddings / _chunks always need a + # producer; tables ending in _reports / _content / _memos are + # candidates for the report_embeddings flow + report_like = _re.search(r"(_embeddings|_chunks|_reports|_content|_memos|_artifacts)$", table) + if not report_like: + continue + if bridge_text and table in bridge_text and "chunkByHeaders" in bridge_text: + findings.append({ + "dimension": "D5", "status": "WARNING", + "check": f"D5.4 embedding write-path for '{table}'", + "message": f"table '{table}' looks embeddable; hookDBBridge.js mentions both — verify chunkByHeaders() runs against rows from this table", + "remediation": "Manually trace the call site in hookDBBridge.js. Confirm INSERT to {table} triggers an embeddingService.chunkByHeaders() call.", + }) + else: + findings.append({ + "dimension": "D5", "status": "FAILED", + "check": f"D5.4 embedding write-path for '{table}'", + "message": f"new table '{table}' looks embeddable (suffix matches /_(embeddings|chunks|reports|content|memos|artifacts)$/) but no chunkByHeaders() call references it in hookDBBridge.js", + "remediation": f"Add an embedding write path in src/utils/hookDBBridge.js: when INSERT to {table} happens, call embeddingService.chunkByHeaders(content, {{table:'{table}'}}). Gated behind EMBEDDING_PERSISTENCE=true.", + }) + emit_findings("D5", findings) diff --git a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D6-provenance.py b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D6-provenance.py index 79750d0f2..a16bfb434 100755 --- a/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D6-provenance.py +++ b/.claude/skills/feature-compliance-scaffold/scripts/dimensions/D6-provenance.py @@ -70,6 +70,50 @@ def main(): "remediation": "Run post-deploy-verify Tier 2 t3-bridge-metadata-git-sha.sql + t3-code-execution-models.sql after deploy. If git_sha='unknown', deploy.sh missed --build-arg COMMIT_SHA=$(git rev-parse HEAD).", }) + # D6.5 — KG-relevant subagents must populate kg_provenance.source_hash + # from upstream source_writes (Wave 2 provenance bridge architecture). + import os as _os, re as _re + repo_root_p = _os.environ.get("REPO_ROOT") or "" + if not repo_root_p: + cur_p = _os.path.abspath(_os.getcwd()) + for _ in range(10): + if _os.path.isdir(_os.path.join(cur_p, "super-legal-mcp-refactored")): + repo_root_p = cur_p + break + cur_p = _os.path.dirname(cur_p) + kg_paths = [ + _os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "knowledgeGraphExtractor.js"), + _os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "kgService.js"), + _os.path.join(repo_root_p, "super-legal-mcp-refactored", "src", "utils", "kgWrite.js"), + ] + kg_text = "" + for p in kg_paths: + if _os.path.isfile(p): + try: + kg_text += "\n" + open(p, errors="replace").read() + except OSError: + pass + for agent in s.get("agent_types") or []: + if not kg_text: + break + # If the agent is referenced by KG-write paths, source_hash must be populated + agent_in_kg = agent in kg_text or _re.search(rf"agent[_\s]?type\s*[:=]\s*['\"]?{_re.escape(agent)}", kg_text) + if agent_in_kg: + if "source_hash" in kg_text and "kg_provenance" in kg_text: + findings.append({ + "dimension": "D6", "status": "PASSED", + "check": f"D6.5 KG provenance for agent '{agent}'", + "message": f"agent '{agent}' appears in KG-write path; kg_provenance.source_hash populated", + "remediation": "", + }) + else: + findings.append({ + "dimension": "D6", "status": "WARNING", + "check": f"D6.5 KG provenance for agent '{agent}'", + "message": f"agent '{agent}' is KG-relevant but kg_provenance.source_hash population is not visible in knowledgeGraphExtractor.js / kgService.js / kgWrite.js", + "remediation": "Confirm KG INSERT path writes source_hash from upstream source_writes (Wave 2 provenance bridge — KG operates on reports, not raw sources).", + }) + emit_findings("D6", findings) diff --git a/.claude/skills/sdk-upgrade/SKILL.md b/.claude/skills/sdk-upgrade/SKILL.md new file mode 100644 index 000000000..0c34644d9 --- /dev/null +++ b/.claude/skills/sdk-upgrade/SKILL.md @@ -0,0 +1,92 @@ +--- +name: sdk-upgrade +description: Automate Anthropic SDK version bumps for Super Legal MCP. Fetches CHANGELOG diff, audits all call sites for breaking changes, validates beta-token compatibility, runs the SDK regression test suite, and emits a one-page operator report. Replaces the ½-day manual loop Edwin has done 5+ times since 0.2.47. Use when bumping `@anthropic-ai/claude-agent-sdk` or `@anthropic-ai/sdk` versions. Triggers — sdk upgrade, anthropic sdk bump, agent sdk update, sdk version bump, /sdk-upgrade. Supports flags — --to , --check (dry-run), --regression-test (re-run 14 SDK tests). +--- + +# SDK Upgrade — Anthropic SDK Version Bump + +## Workflow + +```bash +/sdk-upgrade --check # dry-run audit only — no package.json changes +/sdk-upgrade --to # full upgrade flow (fetch CHANGELOG → audit → bump → test) +/sdk-upgrade --regression-test # re-run 14 SDK test files against current version +``` + +## What it does + +1. **Fetch CHANGELOG** for the version delta via `gh api repos/anthropics/claude-agent-sdk-typescript/releases` and `npm view @anthropic-ai/claude-agent-sdk versions`. +2. **Audit call sites** — greps the 5 known SDK consumer files for usage of API symbols that changed in the delta (e.g., `agentQuery`, `agentProgressSummaries`, `settingSources`, `output_config`, `maxThinkingTokens`). +3. **Validate beta tokens** — checks 5 active beta headers (`context-1m-2025-08-07`, `interleaved-thinking-2025-05-14`, `effort-2025-11-24`, `files-api-2025-04-14`, `code_execution_20250825`) against the new version's release notes for deprecation/graduation status. +4. **Bump versions** in `super-legal-mcp-refactored/package.json` (both `@anthropic-ai/claude-agent-sdk` and `@anthropic-ai/sdk` peer dep). +5. **Run regression test suite** — 14 SDK tests under `test/sdk/` (full list in `references/call-sites.md`). +6. **Emit operator report** — markdown summary with breaking-change matrix, beta-token diff, test pass/fail, and next-step remediation. + +## Pre-flight + +Required: `python3`, `gh`, `npm`, `node`. The skill reads code locally (no `gcloud` needed). + +## Current state (baseline) + +Verified 2026-05-07: +- `@anthropic-ai/claude-agent-sdk`: `0.2.119` (exact pin) +- `@anthropic-ai/sdk`: `^0.86.1` +- `zod`: `^4.3.6` + +## Known regressions to check (per upgrade) + +GitHub issues (Number531/Legal-API): +- **#25** — `maxThinkingTokens` breaks all hooks/streaming on Agent SDK path (still open; commented out in current code) +- **#14** — `defer_loading` for Agent SDK (blocked on upstream) +- **#210** — Agent/Task tool broken in `query()` mode (resolved 0.2.70+) +- **#40** — `task_progress` upstream shipping status (still pending as of 0.2.119) +- **#66** — MCP Protocol SubagentStart race (resolved 0.2.97) +- **#79** — SDK 0.2.119 upgrade notes + +## Output format + +``` +## SDK Upgrade Report +From: 0.2.119 → To: 0.2.121 +Timestamp: 2026-05-07T... + +### CHANGELOG delta (2 versions) +- 0.2.120: ... +- 0.2.121: ... + +### Call site audit +✓ agentStreamHandler.js:280-320 — agentQuery shape unchanged +✓ p0Orchestrator.js:106-135 — settingSources accepted +⚠ codeExecutionBridge.js:325 — files-api-2025-04-14 now graduated; consider dropping beta header + +### Beta tokens +✓ context-1m-2025-08-07 — still required +⚠ interleaved-thinking-2025-05-14 — DEPRECATED on 4.6 per CHANGELOG +✓ effort-2025-11-24 — functional +✓ files-api-2025-04-14 — graduated; safe to drop in next major +✓ code_execution_20250825 — server-tool unchanged + +### Regression tests (14 files) +✓ agent-stream-handler.test.js +✓ p0-orchestrator.test.js +✓ ... (12 more) + +### Recommended next steps +- [ ] Drop interleaved-thinking-2025-05-14 from agentQuery betas if no 4.5 fallback needed +- [ ] Open follow-up issue if files-api graduation breaks existing chart pipeline +``` + +Exit codes: `0` = clean, `1` = warnings (review before merge), `2` = call-site break or test failure. + +## Sequencing + +Run **before** `/deploy`. After upgrade, `/post-deploy-verify` Tier 2 covers runtime regression (FMP tools, code-execution models, Cloud Trace). + +## Read-only guarantee + +The skill never: +- Mutates code other than `package.json` version pin (gated behind `--to`, NOT `--check`) +- Runs the live container or hits production +- Auto-rolls back + +It reports what changed; operator decides remediation. diff --git a/.claude/skills/sdk-upgrade/references/call-sites.md b/.claude/skills/sdk-upgrade/references/call-sites.md new file mode 100644 index 000000000..d1c4742bf --- /dev/null +++ b/.claude/skills/sdk-upgrade/references/call-sites.md @@ -0,0 +1,46 @@ +# SDK Call Sites — Truth Map + +7 source files audited per upgrade. Verified 2026-05-07 against `0.2.119`. + +## Agent SDK consumers (3) + +| File | Lines | Notes | +|------|-------|-------| +| `src/server/agentStreamHandler.js` | 280-320 | Main `agentQuery` invocation; betas at 296-298; `settingSources: []` at 315 | +| `src/server/p0Orchestrator.js` | 100-135 | `for await … agentQuery({...})` at 106; `agentProgressSummaries: true` at 113 | +| `src/server/claude-sdk-server.js` | 284-310 | Constraints comment block; nearby orchestrator config | + +## Anthropic SDK (Messages API) consumers (2) + +| File | Lines | Notes | +|------|-------|-------| +| `src/tools/codeExecutionBridge.js` | 325, 352, 484 | Standard `client.messages.create` path; `code_execution_20250825` server-tool | +| `src/utils/skillsRequestBuilder.js` | 41, 52, 55 | Beta header builder for skills | + +## Hook lifecycle (2) + +| File | Lines | Notes | +|------|-------|-------| +| `src/utils/sdkHooks.js` | exports | `sdkHooksConfig` lifecycle handlers | +| `src/utils/hookDBBridge.js` | 1739-1750 | `HOOKS_TO_BRIDGE` array — must verify on upgrade | + +## Test files (14) + +Re-run via `test-harness.sh` after every upgrade: + +``` +test/sdk/agent-stream-handler.test.js +test/sdk/p0-orchestrator.test.js +test/sdk/subagents.test.js +test/sdk/subagents-e2e.test.js +test/sdk/code-execution-bridge.test.js +test/sdk/hookDBBridge.test.js +test/sdk/streaming-events.test.js +test/sdk/thinking-preservation.test.js +test/sdk/structured-outputs.test.js +test/sdk/domain-mcp-servers.test.js +test/sdk/skills-headers.test.js +test/sdk/prompt-caching.test.js +test/sdk/stream-context.test.js +test/sdk/server-refactor-regression.test.js +``` diff --git a/.claude/skills/sdk-upgrade/scripts/audit-call-sites.py b/.claude/skills/sdk-upgrade/scripts/audit-call-sites.py new file mode 100755 index 000000000..a18c064ad --- /dev/null +++ b/.claude/skills/sdk-upgrade/scripts/audit-call-sites.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Audit Anthropic SDK call sites + beta-token usage in Super Legal MCP. + +Reports: + - File:line of every agentQuery / agentProgressSummaries / settingSources call + - Beta token references (5 known) + - Unexpected SDK API symbols (potential breaking-change indicators) + +Usage: audit-call-sites.py --repo-root [--betas-only] +""" + +import argparse +import re +import sys +from pathlib import Path + + +SDK_FILES = [ + "src/server/agentStreamHandler.js", + "src/server/p0Orchestrator.js", + "src/server/claude-sdk-server.js", + "src/tools/codeExecutionBridge.js", + "src/utils/skillsRequestBuilder.js", + "src/utils/sdkHooks.js", + "src/utils/hookDBBridge.js", +] + +API_SYMBOLS = [ + "agentQuery", "agentProgressSummaries", "settingSources", + "output_config", "output_format", "maxThinkingTokens", + "HOOKS_TO_BRIDGE", +] + +BETA_TOKENS = [ + "context-1m-2025-08-07", + "interleaved-thinking-2025-05-14", + "effort-2025-11-24", + "files-api-2025-04-14", + "code_execution_20250825", +] + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--repo-root", required=True) + p.add_argument("--betas-only", action="store_true") + args = p.parse_args() + + base = Path(args.repo_root) / "super-legal-mcp-refactored" + + if not args.betas_only: + for rel in SDK_FILES: + f = base / rel + if not f.exists(): + continue + text = f.read_text(errors="replace") + for sym in API_SYMBOLS: + for m in re.finditer(rf"\b{re.escape(sym)}\b", text): + line_no = text.count("\n", 0, m.start()) + 1 + print(f" ✓ {rel}:{line_no} — {sym}") + + if args.betas_only or not args.betas_only: + # Beta tokens scan + if args.betas_only: + print("") + seen = {b: [] for b in BETA_TOKENS} + for rel in SDK_FILES: + f = base / rel + if not f.exists(): + continue + text = f.read_text(errors="replace") + for tok in BETA_TOKENS: + for m in re.finditer(re.escape(tok), text): + line_no = text.count("\n", 0, m.start()) + 1 + seen[tok].append(f"{rel}:{line_no}") + for tok, refs in seen.items(): + if refs: + print(f" ✓ {tok} ({len(refs)} ref{'s' if len(refs) != 1 else ''})") + for r in refs[:3]: + print(f" {r}") + else: + print(f" — {tok} (not found — may be deprecated)") + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/sdk-upgrade/scripts/fetch-changelog.sh b/.claude/skills/sdk-upgrade/scripts/fetch-changelog.sh new file mode 100755 index 000000000..d1256fcec --- /dev/null +++ b/.claude/skills/sdk-upgrade/scripts/fetch-changelog.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# fetch-changelog.sh +# Fetches release notes from anthropics/claude-agent-sdk-typescript between two versions. + +set -uo pipefail + +CURRENT="${1:-}" +TARGET="${2:-latest}" + +[ -z "$CURRENT" ] && { echo " (no current version)"; exit 0; } + +if [ "$TARGET" = "latest" ]; then + TARGET=$(npm view @anthropic-ai/claude-agent-sdk version 2>/dev/null || echo "unknown") +fi + +echo " Fetching releases from anthropics/claude-agent-sdk-typescript..." +gh api "repos/anthropics/claude-agent-sdk-typescript/releases?per_page=30" 2>/dev/null \ + | python3 -c " +import json, sys, re +try: + rels = json.load(sys.stdin) +except Exception: + print(' (gh api unavailable; install gh + auth, or use npm view)') + sys.exit(0) +current = '$CURRENT'.lstrip('^~=') +target = '$TARGET'.lstrip('^~=') +def vkey(v): + parts = re.findall(r'\d+', v) + return tuple(int(p) for p in parts) if parts else (0,) +in_range = [] +for r in rels: + tag = (r.get('tag_name') or '').lstrip('v') + if not tag: continue + if vkey(current) < vkey(tag) <= vkey(target): + in_range.append((tag, r.get('name') or '', r.get('body') or '')) +in_range.sort(key=lambda x: vkey(x[0])) +if not in_range: + print(f' No releases between {current} and {target}') +else: + for tag, name, body in in_range: + body_first = body.split('\n')[0] if body else '' + print(f' - {tag}: {body_first[:200]}') +" diff --git a/.claude/skills/sdk-upgrade/scripts/test-harness.sh b/.claude/skills/sdk-upgrade/scripts/test-harness.sh new file mode 100755 index 000000000..17e539935 --- /dev/null +++ b/.claude/skills/sdk-upgrade/scripts/test-harness.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Re-run the 14 SDK-relevant test files to catch regressions post-upgrade. +# Usage: test-harness.sh +set -uo pipefail + +REPO_ROOT="${1:-}" +[ -n "$REPO_ROOT" ] || { echo "ERROR: pass repo root as arg 1" >&2; exit 2; } + +cd "$REPO_ROOT/super-legal-mcp-refactored" || exit 2 + +# 14 SDK-relevant test files (per Phase B2 plan; trim if some don't exist yet) +TESTS=( + test/sdk/agent-stream-handler.test.js + test/sdk/p0-orchestrator.test.js + test/sdk/subagents.test.js + test/sdk/subagents-e2e.test.js + test/sdk/code-execution-bridge.test.js + test/sdk/hookDBBridge.test.js + test/sdk/streaming-events.test.js + test/sdk/thinking-preservation.test.js + test/sdk/structured-outputs.test.js + test/sdk/domain-mcp-servers.test.js + test/sdk/skills-headers.test.js + test/sdk/prompt-caching.test.js + test/sdk/stream-context.test.js + test/sdk/server-refactor-regression.test.js +) + +PASS=0 +FAIL=0 +SKIP=0 + +for t in "${TESTS[@]}"; do + if [ ! -f "$t" ]; then + echo " — SKIP $t (file not found)" + SKIP=$((SKIP + 1)) + continue + fi + echo " Running $t..." + if npx vitest run "$t" --reporter=basic 2>&1 | tail -3; then + PASS=$((PASS + 1)) + else + FAIL=$((FAIL + 1)) + fi +done + +echo "" +echo " Tests: $PASS passed, $FAIL failed, $SKIP skipped" +exit $FAIL diff --git a/.claude/skills/sdk-upgrade/scripts/upgrade.sh b/.claude/skills/sdk-upgrade/scripts/upgrade.sh new file mode 100755 index 000000000..eb1c5c394 --- /dev/null +++ b/.claude/skills/sdk-upgrade/scripts/upgrade.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# sdk-upgrade entry point. +# +# Usage: +# upgrade.sh --check # dry-run audit (no package.json changes) +# upgrade.sh --to # full upgrade flow +# upgrade.sh --regression-test # re-run 14 SDK tests against current version + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ── Resolve repo root ───────────────────────────────────────────────────────── +cur="$SCRIPT_DIR" +while [ "$cur" != "/" ]; do + if [ -d "$cur/super-legal-mcp-refactored" ]; then + REPO_ROOT="$cur" + break + fi + cur="$(dirname "$cur")" +done +[ -n "${REPO_ROOT:-}" ] || { echo "ERROR: cannot locate repo root" >&2; exit 2; } + +# ── Defaults ────────────────────────────────────────────────────────────────── +MODE="check" +TARGET_VERSION="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --check) MODE="check"; shift ;; + --to) MODE="upgrade"; TARGET_VERSION="$2"; shift 2 ;; + --regression-test) MODE="test"; shift ;; + -h|--help) + grep -E '^#( |$)' "$0" | sed 's/^# \{0,1\}//' | head -10 + exit 0 ;; + *) + echo "Unknown flag: $1" >&2 + exit 2 ;; + esac +done + +# ── Pre-flight ──────────────────────────────────────────────────────────────── +for cmd in python3 git gh npm node; do + command -v "$cmd" >/dev/null || { echo "ERROR: $cmd not found" >&2; exit 2; } +done + +cd "$REPO_ROOT" + +# ── Read current pins ───────────────────────────────────────────────────────── +PKG_JSON="$REPO_ROOT/super-legal-mcp-refactored/package.json" +[ -f "$PKG_JSON" ] || { echo "ERROR: package.json not found at $PKG_JSON" >&2; exit 2; } + +CURRENT_AGENT_SDK=$(python3 -c "import json; d=json.load(open('$PKG_JSON')); print(d.get('dependencies',{}).get('@anthropic-ai/claude-agent-sdk','none'))") +CURRENT_BASE_SDK=$(python3 -c "import json; d=json.load(open('$PKG_JSON')); print(d.get('dependencies',{}).get('@anthropic-ai/sdk','none'))") + +echo "## SDK Upgrade Report" +echo "Mode: $MODE" +echo "Current: claude-agent-sdk=$CURRENT_AGENT_SDK | sdk=$CURRENT_BASE_SDK" +echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" +echo "" + +# ── Mode dispatch ───────────────────────────────────────────────────────────── +case "$MODE" in + check) + [ -z "$TARGET_VERSION" ] && TARGET_VERSION="latest" + echo "### CHANGELOG fetch (target: $TARGET_VERSION)" + bash "$SCRIPT_DIR/fetch-changelog.sh" "$CURRENT_AGENT_SDK" "$TARGET_VERSION" || true + echo "" + echo "### Call site audit" + python3 "$SCRIPT_DIR/audit-call-sites.py" --repo-root "$REPO_ROOT" + echo "" + echo "### Beta tokens" + python3 "$SCRIPT_DIR/audit-call-sites.py" --repo-root "$REPO_ROOT" --betas-only + ;; + upgrade) + [ -z "$TARGET_VERSION" ] && { echo "ERROR: --to required" >&2; exit 2; } + echo "### CHANGELOG fetch ($CURRENT_AGENT_SDK → $TARGET_VERSION)" + bash "$SCRIPT_DIR/fetch-changelog.sh" "$CURRENT_AGENT_SDK" "$TARGET_VERSION" || true + echo "" + echo "### Call site audit" + python3 "$SCRIPT_DIR/audit-call-sites.py" --repo-root "$REPO_ROOT" + echo "" + echo "### Bumping package.json" + python3 -c " +import json +p = '$PKG_JSON' +d = json.load(open(p)) +d['dependencies']['@anthropic-ai/claude-agent-sdk'] = '$TARGET_VERSION' +json.dump(d, open(p, 'w'), indent=2) +print(' bumped to $TARGET_VERSION') +" + echo "" + echo "### npm install" + (cd "$REPO_ROOT/super-legal-mcp-refactored" && npm install 2>&1 | tail -5) + echo "" + echo "### Regression tests" + bash "$SCRIPT_DIR/test-harness.sh" "$REPO_ROOT" + ;; + test) + bash "$SCRIPT_DIR/test-harness.sh" "$REPO_ROOT" + ;; +esac + +echo "" +echo "### Done"