From 22865453af087fe8113ccc207ebefd9393cfc787 Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Thu, 5 Feb 2026 10:36:47 -0500 Subject: [PATCH 1/5] feat(scripts): Add release helper script --- scripts/strands_release_helper.py | 571 ++++++++++++++++++++++++++++++ 1 file changed, 571 insertions(+) create mode 100755 scripts/strands_release_helper.py diff --git a/scripts/strands_release_helper.py b/scripts/strands_release_helper.py new file mode 100755 index 0000000..ca3dc08 --- /dev/null +++ b/scripts/strands_release_helper.py @@ -0,0 +1,571 @@ +#!/usr/bin/env python3 +""" +Strands Agents Release Helper + +Automates the release preparation process for all Strands Agents repositories. + +WHAT IT DOES: + 1. Clones all 8 repos (or pulls if already cloned) + 2. Gets latest tag and commits since that tag + 3. Auto-determines version bump (MAJOR/MINOR/PATCH) from commit messages + 4. Runs integration tests for each repo + 5. Generates MCM parameters and release report + +USAGE: + python3 strands_release_helper.py # Full run with tests + python3 strands_release_helper.py --skip-tests # Just changelogs, no tests + python3 strands_release_helper.py --skip-tests --parallel # Fast parallel mode + python3 strands_release_helper.py --repo sdk-python # Single repo only + python3 strands_release_helper.py --work-dir /tmp/release # Custom work dir + +OUTPUT: + {work_dir}/ + ├── release_report.md # Human-readable summary + ├── mcm_params.txt # Copy-paste ready MCM parameters + ├── logs/ # Full test output logs + │ ├── sdk-python_tests.log + │ ├── sdk-typescript_tests.log + │ └── ... + └── {repo}/ # Cloned repositories + +REPOS COVERED: + - sdk-python (hatch run test-integ) + - sdk-typescript (npm run test:integ) + - tools (hatch run test-integ) + - agent-sop (hatch test) + - agent-builder (hatch test) + - evals (hatch run test-integ) + - mcp-server (hatch test) + +VERSION BUMP LOGIC: + For 1.x+ versions (standard semver): + - MAJOR: Commits with "BREAKING" in message + - MINOR: Commits starting with "feat:" + - PATCH: All other commits (fix, perf, refactor, etc.) + + For 0.x versions (pre-1.0 semver): + - MINOR: Commits with "BREAKING" in message + - PATCH: All other commits (including features) + - This allows controlling when to go 1.0 while still using semver + + - NONE: No commits since last tag + +NOTES: + - Tests run sequentially by default (parallel can cause port conflicts) + - --parallel only works with --skip-tests + - Test output streams live to console and saves to logs/ +""" + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field +from datetime import datetime +from pathlib import Path +from typing import Optional + +# Repository configurations +REPOS = { + "sdk-python": { + "url": "https://github.com/strands-agents/sdk-python", + "test_cmd": "hatch run test-integ", + "mcm_params": { + "version": ["strands-sdk-python-version", "strands-agent-sdk-python-version"], + "changelog": ["strands-agent-sdk-python-changelog"], + }, + }, + "sdk-typescript": { + "url": "https://github.com/strands-agents/sdk-typescript", + "test_cmd": "npm install && npm run test:integ", + "mcm_params": { + "version": ["strands-sdk-typescript-version", "strands-agent-sdk-typescript-version"], + "changelog": ["strands-agent-sdk-typescript-changelog"], + }, + }, + "tools": { + "url": "https://github.com/strands-agents/tools", + "test_cmd": "hatch run test-integ", + "mcm_params": { + "version": ["strands-tools-version", "strands-agent-tools-version"], + "changelog": ["strands-agent-tool-changelog"], + }, + }, + + "agent-sop": { + "url": "https://github.com/strands-agents/agent-sop", + "test_cmd": "cd python && hatch test", + "mcm_params": { + "version": ["strands-agent-sop-version"], + "changelog": ["strands-agent-sop-changelog"], + }, + }, + "agent-builder": { + "url": "https://github.com/strands-agents/agent-builder", + "test_cmd": "hatch test", + "mcm_params": { + "version": ["strands-agent-builder-version"], + "changelog": ["strands-agent-builder-changelog"], + }, + }, + "evals": { + "url": "https://github.com/strands-agents/evals", + "test_cmd": "hatch run test-integ", + "mcm_params": { + "version": ["strands-evals-version", "strands-agent-evals-version"], + "changelog": ["strands-agent-sdk-evals-changelog"], + }, + }, + "mcp-server": { + "url": "https://github.com/strands-agents/mcp-server", + "test_cmd": "hatch test", + "mcm_params": { + "version": ["strands-mcp-server-version", "strands-agent-mcp-server-version"], + "changelog": ["strands-agent-mcp-server-changelog"], + }, + }, +} + + +@dataclass +class Commit: + sha: str + message: str + author: str + date: str + + @property + def type(self) -> str: + """Extract commit type (feat, fix, etc.)""" + match = re.match(r"^(\w+)(?:\([^)]+\))?:", self.message) + return match.group(1).lower() if match else "other" + + @property + def first_line(self) -> str: + return self.message.split("\n")[0] + + +@dataclass +class RepoResult: + name: str + current_version: str = "" + new_version: str = "" + bump_type: str = "" + commits: list[Commit] = field(default_factory=list) + changelog: str = "" + test_passed: Optional[bool] = None + test_output: str = "" + error: str = "" + + +def run_cmd(cmd: str, cwd: Optional[Path] = None, timeout: int = 1800, stream: bool = False) -> tuple[int, str, str]: + """Run a shell command and return (returncode, stdout, stderr)""" + try: + if stream: + # Stream output in real-time + process = subprocess.Popen( + cmd, + shell=True, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + ) + output_lines = [] + for line in process.stdout: + print(f" {line}", end="") + output_lines.append(line) + process.wait(timeout=timeout) + return process.returncode, "".join(output_lines), "" + else: + result = subprocess.run( + cmd, + shell=True, + cwd=cwd, + capture_output=True, + text=True, + timeout=timeout, + ) + return result.returncode, result.stdout, result.stderr + except subprocess.TimeoutExpired: + return -1, "", f"Command timed out after {timeout}s" + except Exception as e: + return -1, "", str(e) + + +def clone_repo(name: str, url: str, work_dir: Path) -> bool: + """Clone a repository (shallow clone for speed)""" + repo_path = work_dir / name + if repo_path.exists(): + print(f" {name}: Already exists, pulling latest...") + code, _, err = run_cmd("git fetch --tags && git pull", cwd=repo_path) + return code == 0 + + print(f" {name}: Cloning...") + code, _, err = run_cmd(f"git clone --depth 100 --no-single-branch {url} {name}", cwd=work_dir) + if code != 0: + print(f" {name}: Clone failed - {err}") + return False + + # Fetch all tags + run_cmd("git fetch --tags", cwd=repo_path) + return True + + +def get_latest_tag(repo_path: Path) -> str: + """Get the latest semver tag""" + code, stdout, _ = run_cmd( + "git tag -l 'v*' --sort=-version:refname | head -1", + cwd=repo_path, + ) + if code == 0 and stdout.strip(): + return stdout.strip() + + # Fallback: try without 'v' prefix + code, stdout, _ = run_cmd( + "git tag -l '[0-9]*' --sort=-version:refname | head -1", + cwd=repo_path, + ) + return stdout.strip() if code == 0 else "" + + +def get_commits_since_tag(repo_path: Path, tag: str) -> list[Commit]: + """Get all commits since a tag""" + if not tag: + # No tag, get last 50 commits + cmd = 'git log -50 --pretty=format:"%H|%s|%an|%ai"' + else: + cmd = f'git log {tag}..HEAD --pretty=format:"%H|%s|%an|%ai"' + + code, stdout, _ = run_cmd(cmd, cwd=repo_path) + if code != 0 or not stdout.strip(): + return [] + + commits = [] + for line in stdout.strip().split("\n"): + if "|" in line: + parts = line.split("|", 3) + if len(parts) >= 4: + commits.append(Commit(sha=parts[0], message=parts[1], author=parts[2], date=parts[3])) + return commits + + +def determine_version_bump(commits: list[Commit], current_version: str) -> tuple[str, str]: + """Determine version bump type and new version based on commits. + + For 0.x versions, semantic versioning shifts down one level: + - Breaking changes → MINOR (not MAJOR) + - Features → PATCH (not MINOR) + - Fixes → PATCH + + This allows controlling when to go 1.0 while still using semver. + """ + version = current_version.lstrip("v") + + if not commits: + return "NONE", current_version + + # Parse current version first to check if pre-1.0 + match = re.match(r"(\d+)\.(\d+)\.(\d+)", version) + if not match: + return "PATCH", current_version + + major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3)) + is_pre_1_0 = major == 0 + + # Check commit types + has_breaking = any("BREAKING" in c.message.upper() or c.message.startswith("!") for c in commits) + has_feat = any(c.type == "feat" for c in commits) + + # Determine bump type and calculate new version + if is_pre_1_0: + # Pre-1.0: shift everything down one level + if has_breaking: + bump_type = "MINOR" + new_version = f"{major}.{minor + 1}.0" + else: + # Both features and fixes are PATCH in 0.x + bump_type = "PATCH" + new_version = f"{major}.{minor}.{patch + 1}" + else: + # Post-1.0: standard semver + if has_breaking: + bump_type = "MAJOR" + new_version = f"{major + 1}.0.0" + elif has_feat: + bump_type = "MINOR" + new_version = f"{major}.{minor + 1}.0" + else: + bump_type = "PATCH" + new_version = f"{major}.{minor}.{patch + 1}" + + return bump_type, f"v{new_version}" + + +def generate_changelog(commits: list[Commit], new_version: str) -> str: + """Generate a formatted changelog from commits""" + # Ensure version displays with 'v' prefix + display_version = new_version if new_version.startswith("v") else f"v{new_version}" + + if not commits: + return f"## {display_version} - No changes since last release" + + # Group commits by type + groups = {"feat": [], "fix": [], "other": []} + for c in commits: + if c.type == "feat": + groups["feat"].append(c) + elif c.type in ("fix", "perf"): + groups["fix"].append(c) + else: + groups["other"].append(c) + + lines = [f"## {display_version} Changelog ({len(commits)} commits)"] + + if groups["feat"]: + lines.append("\n### Features") + for c in groups["feat"]: + lines.append(f"- {c.first_line}") + + if groups["fix"]: + lines.append("\n### Fixes") + for c in groups["fix"]: + lines.append(f"- {c.first_line}") + + if groups["other"]: + lines.append("\n### Other") + for c in groups["other"]: + lines.append(f"- {c.first_line}") + + return "\n".join(lines) + + +def run_tests(name: str, repo_path: Path, test_cmd: str, log_dir: Path) -> tuple[bool, str]: + """Run integration tests for a repo""" + if not test_cmd: + return True, "No tests configured" + + print(f" {name}: Running tests...") + print(f" Command: {test_cmd}") + print(f" Log: {log_dir / f'{name}_tests.log'}") + print() + + code, stdout, stderr = run_cmd(test_cmd, cwd=repo_path, timeout=1800, stream=True) + + output = stdout + "\n" + stderr + + # Save full log + log_dir.mkdir(parents=True, exist_ok=True) + log_file = log_dir / f"{name}_tests.log" + log_file.write_text(output) + + passed = code == 0 + + # Extract summary from output + summary_lines = [] + for line in output.split("\n")[-50:]: + if any(x in line.lower() for x in ["passed", "failed", "error", "test"]): + summary_lines.append(line) + + return passed, "\n".join(summary_lines[-20:]) if summary_lines else output[-2000:] + + +def process_repo(name: str, config: dict, work_dir: Path, skip_tests: bool, log_dir: Path) -> RepoResult: + """Process a single repository""" + result = RepoResult(name=name) + repo_path = work_dir / name + + try: + # Clone + if not clone_repo(name, config["url"], work_dir): + result.error = "Failed to clone" + return result + + # Get version info + result.current_version = get_latest_tag(repo_path) + result.commits = get_commits_since_tag(repo_path, result.current_version) + result.bump_type, result.new_version = determine_version_bump(result.commits, result.current_version) + result.changelog = generate_changelog(result.commits, result.new_version) + + # Run tests + if not skip_tests and config["test_cmd"]: + result.test_passed, result.test_output = run_tests(name, repo_path, config["test_cmd"], log_dir) + elif not config["test_cmd"]: + result.test_passed = True + result.test_output = "No tests configured" + + except Exception as e: + result.error = str(e) + + return result + + +def generate_report(results: list[RepoResult], work_dir: Path) -> str: + """Generate the final release report""" + lines = [ + "# Strands Agents Release Report", + f"\nGenerated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + f"Working Directory: {work_dir}", + "\n---\n", + ] + + # Summary table + lines.append("## Summary\n") + lines.append("| Repository | Current | New | Bump | Commits | Tests |") + lines.append("|------------|---------|-----|------|---------|-------|") + + all_tests_pass = True + for r in results: + test_status = "✅" if r.test_passed else ("⏭️ SKIP" if r.test_passed is None else "❌") + if r.test_passed is False: + all_tests_pass = False + lines.append( + f"| {r.name} | {r.current_version or 'N/A'} | {r.new_version} | {r.bump_type} | {len(r.commits)} | {test_status} |" + ) + + # Test status + lines.append(f"\n## Test Status\n") + lines.append(f"**{{{{are-test-passing}}}} = {'YES' if all_tests_pass else 'NO'}**\n") + + # Changelogs + lines.append("\n---\n") + lines.append("## Changelogs\n") + for r in results: + lines.append(f"### {r.name}\n") + lines.append(f"**{r.current_version or 'N/A'}** → **{r.new_version}**\n") + lines.append(r.changelog) + lines.append("\n") + + # Test details (failures only) + failures = [r for r in results if r.test_passed is False] + if failures: + lines.append("\n---\n") + lines.append("## Test Failures\n") + for r in failures: + lines.append(f"### {r.name}\n") + lines.append("```") + lines.append(r.test_output[:3000]) + lines.append("```\n") + + return "\n".join(lines) + + +def generate_mcm_params(results: list[RepoResult]) -> str: + """Generate MCM parameters file""" + lines = ["# MCM Parameters", f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] + + all_tests_pass = all(r.test_passed is not False for r in results) + lines.append(f"{{{{are-test-passing}}}} = {'YES' if all_tests_pass else 'NO'}") + lines.append("") + + for r in results: + config = REPOS.get(r.name, {}) + mcm = config.get("mcm_params", {}) + + # Version params + for param in mcm.get("version", []): + lines.append(f"{{{{{param}}}}} = {r.new_version}") + + # Changelog params + for param in mcm.get("changelog", []): + lines.append(f"{{{{{param}}}}} = ") + lines.append(r.changelog) + lines.append("") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser(description="Strands Agents Release Helper") + parser.add_argument("--skip-tests", action="store_true", help="Skip running integration tests") + parser.add_argument("--repo", type=str, help="Process only a specific repo") + parser.add_argument("--work-dir", type=str, default="./release_work", help="Working directory for clones") + parser.add_argument("--parallel", action="store_true", help="Run repos in parallel (tests may conflict)") + args = parser.parse_args() + + work_dir = Path(args.work_dir).resolve() + work_dir.mkdir(parents=True, exist_ok=True) + log_dir = work_dir / "logs" + log_dir.mkdir(parents=True, exist_ok=True) + + print(f"Strands Agents Release Helper") + print(f"Working directory: {work_dir}") + print(f"Test logs: {log_dir}") + print(f"Skip tests: {args.skip_tests}") + print() + + # Filter repos if specified + repos_to_process = {args.repo: REPOS[args.repo]} if args.repo else REPOS + + results = [] + + if args.parallel and args.skip_tests: + # Parallel processing (only safe without tests) + print("Processing repos in parallel...") + with ThreadPoolExecutor(max_workers=4) as executor: + futures = { + executor.submit(process_repo, name, config, work_dir, args.skip_tests, log_dir): name + for name, config in repos_to_process.items() + } + for future in as_completed(futures): + result = future.result() + results.append(result) + print(f" ✓ {result.name}: {result.current_version} → {result.new_version} ({len(result.commits)} commits)") + else: + # Sequential processing + print("Processing repos sequentially...") + for name, config in repos_to_process.items(): + print(f"\n{'='*60}") + print(f"[{name}]") + print(f"{'='*60}") + result = process_repo(name, config, work_dir, args.skip_tests, log_dir) + results.append(result) + if result.error: + print(f" ✗ Error: {result.error}") + else: + test_str = "" + if result.test_passed is True: + test_str = " | Tests: ✅" + elif result.test_passed is False: + test_str = " | Tests: ❌" + print(f"\n ✓ {result.current_version} → {result.new_version} ({len(result.commits)} commits){test_str}") + + # Sort results by repo order + repo_order = list(REPOS.keys()) + results.sort(key=lambda r: repo_order.index(r.name) if r.name in repo_order else 999) + + # Generate outputs + print("\n" + "=" * 60) + print("Generating reports...") + + report = generate_report(results, work_dir) + report_path = work_dir / "release_report.md" + report_path.write_text(report) + print(f" Report: {report_path}") + + mcm_params = generate_mcm_params(results) + params_path = work_dir / "mcm_params.txt" + params_path.write_text(mcm_params) + print(f" MCM Params: {params_path}") + + # Print summary + print("\n" + "=" * 60) + print("SUMMARY") + print("=" * 60) + all_pass = all(r.test_passed is not False for r in results) + print(f"Tests passing: {'YES ✅' if all_pass else 'NO ❌'}") + print() + for r in results: + status = "✅" if r.test_passed else ("⏭️" if r.test_passed is None else "❌") + print(f" {r.name}: {r.current_version} → {r.new_version} ({r.bump_type}) {status}") + + print(f"\nReports saved to: {work_dir}") + + +if __name__ == "__main__": + main() From 7c8826ed318761664cc6903e165344afd5103f65 Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Thu, 5 Feb 2026 10:58:16 -0500 Subject: [PATCH 2/5] fix: rename params --- scripts/strands_release_helper.py | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/scripts/strands_release_helper.py b/scripts/strands_release_helper.py index ca3dc08..fb278dc 100755 --- a/scripts/strands_release_helper.py +++ b/scripts/strands_release_helper.py @@ -9,7 +9,7 @@ 2. Gets latest tag and commits since that tag 3. Auto-determines version bump (MAJOR/MINOR/PATCH) from commit messages 4. Runs integration tests for each repo - 5. Generates MCM parameters and release report + 5. Generates release parameters and release report USAGE: python3 strands_release_helper.py # Full run with tests @@ -21,7 +21,7 @@ OUTPUT: {work_dir}/ ├── release_report.md # Human-readable summary - ├── mcm_params.txt # Copy-paste ready MCM parameters + ├── release_params.txt # Copy-paste ready release parameters ├── logs/ # Full test output logs │ ├── sdk-python_tests.log │ ├── sdk-typescript_tests.log @@ -74,7 +74,7 @@ "sdk-python": { "url": "https://github.com/strands-agents/sdk-python", "test_cmd": "hatch run test-integ", - "mcm_params": { + "release_params": { "version": ["strands-sdk-python-version", "strands-agent-sdk-python-version"], "changelog": ["strands-agent-sdk-python-changelog"], }, @@ -82,7 +82,7 @@ "sdk-typescript": { "url": "https://github.com/strands-agents/sdk-typescript", "test_cmd": "npm install && npm run test:integ", - "mcm_params": { + "release_params": { "version": ["strands-sdk-typescript-version", "strands-agent-sdk-typescript-version"], "changelog": ["strands-agent-sdk-typescript-changelog"], }, @@ -90,16 +90,15 @@ "tools": { "url": "https://github.com/strands-agents/tools", "test_cmd": "hatch run test-integ", - "mcm_params": { + "release_params": { "version": ["strands-tools-version", "strands-agent-tools-version"], "changelog": ["strands-agent-tool-changelog"], }, }, - "agent-sop": { "url": "https://github.com/strands-agents/agent-sop", "test_cmd": "cd python && hatch test", - "mcm_params": { + "release_params": { "version": ["strands-agent-sop-version"], "changelog": ["strands-agent-sop-changelog"], }, @@ -107,7 +106,7 @@ "agent-builder": { "url": "https://github.com/strands-agents/agent-builder", "test_cmd": "hatch test", - "mcm_params": { + "release_params": { "version": ["strands-agent-builder-version"], "changelog": ["strands-agent-builder-changelog"], }, @@ -115,7 +114,7 @@ "evals": { "url": "https://github.com/strands-agents/evals", "test_cmd": "hatch run test-integ", - "mcm_params": { + "release_params": { "version": ["strands-evals-version", "strands-agent-evals-version"], "changelog": ["strands-agent-sdk-evals-changelog"], }, @@ -123,7 +122,7 @@ "mcp-server": { "url": "https://github.com/strands-agents/mcp-server", "test_cmd": "hatch test", - "mcm_params": { + "release_params": { "version": ["strands-mcp-server-version", "strands-agent-mcp-server-version"], "changelog": ["strands-agent-mcp-server-changelog"], }, @@ -455,24 +454,24 @@ def generate_report(results: list[RepoResult], work_dir: Path) -> str: return "\n".join(lines) -def generate_mcm_params(results: list[RepoResult]) -> str: - """Generate MCM parameters file""" - lines = ["# MCM Parameters", f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] +def generate_release_params(results: list[RepoResult]) -> str: + """Generate release parameters file""" + lines = ["# Release Parameters", f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] all_tests_pass = all(r.test_passed is not False for r in results) - lines.append(f"{{{{are-test-passing}}}} = {'YES' if all_tests_pass else 'NO'}") + lines.append(f"{{{{are-tests-passing}}}} = {'YES' if all_tests_pass else 'NO'}") lines.append("") for r in results: config = REPOS.get(r.name, {}) - mcm = config.get("mcm_params", {}) + params = config.get("release_params", {}) # Version params - for param in mcm.get("version", []): + for param in params.get("version", []): lines.append(f"{{{{{param}}}}} = {r.new_version}") # Changelog params - for param in mcm.get("changelog", []): + for param in params.get("changelog", []): lines.append(f"{{{{{param}}}}} = ") lines.append(r.changelog) lines.append("") @@ -548,10 +547,10 @@ def main(): report_path.write_text(report) print(f" Report: {report_path}") - mcm_params = generate_mcm_params(results) - params_path = work_dir / "mcm_params.txt" - params_path.write_text(mcm_params) - print(f" MCM Params: {params_path}") + release_params = generate_release_params(results) + params_path = work_dir / "release_params.txt" + params_path.write_text(release_params) + print(f" Release Params: {params_path}") # Print summary print("\n" + "=" * 60) From 005db303c68cd6e900bacdeea2be2738c58b3800 Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Thu, 5 Feb 2026 13:09:11 -0500 Subject: [PATCH 3/5] feat: add multi-repo filter support --- scripts/strands_release_helper.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/strands_release_helper.py b/scripts/strands_release_helper.py index fb278dc..1617df6 100755 --- a/scripts/strands_release_helper.py +++ b/scripts/strands_release_helper.py @@ -5,7 +5,7 @@ Automates the release preparation process for all Strands Agents repositories. WHAT IT DOES: - 1. Clones all 8 repos (or pulls if already cloned) + 1. Clones all repos (or pulls if already cloned) 2. Gets latest tag and commits since that tag 3. Auto-determines version bump (MAJOR/MINOR/PATCH) from commit messages 4. Runs integration tests for each repo @@ -15,7 +15,7 @@ python3 strands_release_helper.py # Full run with tests python3 strands_release_helper.py --skip-tests # Just changelogs, no tests python3 strands_release_helper.py --skip-tests --parallel # Fast parallel mode - python3 strands_release_helper.py --repo sdk-python # Single repo only + python3 strands_release_helper.py --repos sdk-python,tools # Specific repos python3 strands_release_helper.py --work-dir /tmp/release # Custom work dir OUTPUT: @@ -482,7 +482,7 @@ def generate_release_params(results: list[RepoResult]) -> str: def main(): parser = argparse.ArgumentParser(description="Strands Agents Release Helper") parser.add_argument("--skip-tests", action="store_true", help="Skip running integration tests") - parser.add_argument("--repo", type=str, help="Process only a specific repo") + parser.add_argument("--repos", type=str, help="Comma-separated list of repos to process (e.g., sdk-python,tools)") parser.add_argument("--work-dir", type=str, default="./release_work", help="Working directory for clones") parser.add_argument("--parallel", action="store_true", help="Run repos in parallel (tests may conflict)") args = parser.parse_args() @@ -499,7 +499,13 @@ def main(): print() # Filter repos if specified - repos_to_process = {args.repo: REPOS[args.repo]} if args.repo else REPOS + repos_to_process = REPOS + if args.repos: + repo_names = [r.strip() for r in args.repos.split(",")] + repos_to_process = {name: REPOS[name] for name in repo_names if name in REPOS} + invalid = [name for name in repo_names if name not in REPOS] + if invalid: + print(f"Warning: Unknown repos ignored: {invalid}") results = [] From 33fe8854e0f8f549c58fb608b1ea31b78aa8c5ee Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Wed, 11 Feb 2026 17:47:29 -0500 Subject: [PATCH 4/5] feat: add doc writer sop --- strands-command/README.md | 18 + .../agent-sops/task-doc-writer.sop.md | 527 ++++++++++++++++++ .../scripts/javascript/process-input.cjs | 5 +- 3 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 strands-command/agent-sops/task-doc-writer.sop.md diff --git a/strands-command/README.md b/strands-command/README.md index 75cc4b3..a610cb8 100644 --- a/strands-command/README.md +++ b/strands-command/README.md @@ -393,6 +393,24 @@ Creates high-quality release notes highlighting major features and bug fixes. **Trigger**: - `/strands release-notes` on an Issue +### Documentation Writer (`task-doc-writer.sop.md`) + +Produces documentation updates based on tasks described in GitHub issues. + +**Workflow**: Understand Task → Research → Plan (with user approval) → Develop Examples → Write Docs → Commit → Pull Request + +**Capabilities:** +- Handles any documentation task: new feature docs, fixes, restructuring, examples, tutorials +- Clones source SDK repositories when needed to understand features +- Studies existing docs structure to match style and depth +- Writes documentation following the Strands style guide +- Tests all code examples before inclusion +- Creates pull requests with conventional commit messages +- Iterates on review feedback + +**Trigger**: +- `/strands docs` on an Issue + ## Security diff --git a/strands-command/agent-sops/task-doc-writer.sop.md b/strands-command/agent-sops/task-doc-writer.sop.md new file mode 100644 index 0000000..4b48597 --- /dev/null +++ b/strands-command/agent-sops/task-doc-writer.sop.md @@ -0,0 +1,527 @@ +# Documentation Writer SOP + +## Role + +You are a Documentation Writer for the Strands Agents SDK documentation site. Your goal is to produce high-quality documentation based on the task described in a GitHub issue. The issue may ask you to document a new feature (often referencing a source PR), fix existing documentation, restructure content, add examples, improve clarity, or any other documentation task. You analyze the issue, research the relevant source code and existing docs, write documentation following the Strands style guide, and create a pull request with your changes. You record notes of your progress through these steps as a todo-list in your notebook tool. + +**Your output is commits, not text.** You must make actual file changes in the docs repository, commit them to a feature branch, and create a pull request. The commits are the deliverable. + +## Steps + +### 1. Setup Task Environment + +Initialize the task environment and understand the documentation repository structure. + +**Constraints:** +- You MUST create a progress notebook to track your work using markdown checklists +- You MUST check for environment setup instructions in: + - `AGENTS.md` + - `CONTRIBUTING.md` + - `README.md` +- You MUST check the `GITHUB_WRITE` environment variable value to determine if you have github write permission + - If the value is `true`, then you can run git write commands like `add_comment` or run `git push` + - If the value is not `true`, you are running in a read-restricted sandbox. Any write commands you do run will be deferred to run outside the sandbox + - Any staged or unstaged changes will be pushed after you finish executing to the feature branch +- You MUST make note of the issue number +- You MUST check the current branch using `git branch --show-current` +- You MUST create a new feature branch if currently on main branch: + - Use `git checkout -b docs/issue--` + - Push the branch if `GITHUB_WRITE` is `true` + - If the push operation is deferred, continue with the workflow and note the deferred status + +### 2. Understand the Task + +Read the issue and research everything needed to complete the documentation task. + +#### 2.1 Extract Task Context + +**Constraints:** +- You MUST read the issue description thoroughly +- You MUST read all existing comments to understand full context +- You MUST capture issue metadata (title, labels, status) +- You MUST investigate any links provided in the issue (PRs, other issues, external references) +- You MUST determine the type of documentation task: + - **New documentation for a feature/PR**: The issue references a source PR or feature that needs documenting + - **Fix or improve existing docs**: The issue describes problems with current documentation (errors, unclear content, missing info) + - **Restructure or reorganize**: The issue asks for content to be moved, split, or reorganized + - **Add examples or tutorials**: The issue requests new code examples, tutorials, or how-to guides + - **General documentation task**: Any other documentation work described in the issue +- You MUST record the task type and key details in your notebook + +#### 2.2 Research Source Code (if applicable) + +If the task involves documenting a feature or requires understanding SDK source code: + +**Constraints:** +- You MUST clone the relevant source repository if needed: + ``` + git clone https://github.com//.git /tmp/ + ``` + Common repositories: `strands-agents/sdk-python`, `strands-agents/sdk-typescript`, `strands-agents/tools` +- If the issue references a PR: + - You MUST examine the PR diff, description, and review comments + - When using `git diff`, add `| head -n 99999` to ensure it's not interactive + - You MUST cross-reference PR descriptions with review comments — descriptions may be stale after code review + - You MUST treat the merged code as the source of truth when descriptions conflict with review feedback +- You MUST understand how the relevant feature works by reading: + - The source code + - Related files, imports, and dependencies + - Existing tests to understand expected behavior + - Any linked issues for additional context +- You MUST record your findings in your notebook +- You MAY skip this step if the task doesn't require source code analysis (e.g., fixing typos, restructuring existing content) + +#### 2.3 Research Existing Documentation + +**Constraints:** +- You MUST explore the existing docs repository structure to understand what already exists +- You MUST identify any existing pages related to the task +- You MUST determine the scope of changes needed: + - New pages to create + - Existing pages to update + - Pages that need cross-reference updates +- You MUST record your findings in your notebook + +### 3. Plan Documentation + +Plan what to write before writing it. + +#### 3.1 Study Similar Existing Documentation + +**Constraints:** +- You MUST read 2-3 similar existing pages in the docs repository before writing anything + - If creating a new concept page, read other concept pages (e.g., `docs/user-guide/concepts/agents/`, `docs/user-guide/concepts/tools/`) + - If creating a new tool page, read existing tool documentation + - If updating existing content, read the page and its surrounding pages + - If adding examples, read pages that already have good examples +- You MUST pay attention to: + - How much depth existing pages provide + - The typical structure and flow + - How they explain the "why" and problem context + - What kind of examples they use +- Your documentation MUST feel like it belongs with its siblings — match the depth and style + +#### 3.2 Identify Document Type + +**Constraints:** +- You MUST identify the document type: + - Concept page — Explains what something is + - How-to/Procedure page — Guides through a task + - Reference page — Documents options, parameters, features + - Getting started page — Initial setup guide + - Tutorial page — Learning-focused, hands-on content + - Troubleshooting page — Problem resolution +- You MUST record the document type in your notebook + +#### 3.3 Plan Content and Integration + +**Constraints:** +- You MUST determine which existing pages need cross-references +- You MUST identify where the new content should link from +- You MUST outline the sections you'll write +- You MUST identify code examples needed +- You MUST note any diagrams that would help +- You MUST record your plan in your notebook +- You MUST use the `handoff_to_user` tool to present your documentation plan and get approval before proceeding to writing + - Include: task summary, document type(s), proposed location(s), outline of sections, list of existing pages to update + +### 4. Develop Code Examples + +Before writing documentation, develop and test all code examples. + +**Constraints:** +- You MUST write minimal, focused examples + - Start with the simplest possible example + - Add complexity progressively if needed + - Use clear, descriptive variable names +- You MUST test every code example by executing it + - Execute Python examples to verify they work + - Execute TypeScript examples to verify they work (if applicable) + - Fix any errors before including in documentation +- You MUST validate examples against the PR — ensure they use the new feature correctly and reflect the actual API +- You MUST record which examples passed/failed in your notebook + +### 5. Write Documentation + +Write documentation following the Strands style guide (see Style Guide Reference section below). + +**Constraints:** +- You MUST prioritize teaching over formatting — a page that genuinely teaches the concept is better than a perfectly formatted page that leaves readers confused +- Before writing, you MUST ask yourself: + - Would a developer who reads this *understand* the feature, or just know it exists? + - Have I explained *why* this feature exists and what problem it solves? + - Have I given a concrete, relatable example they can connect to? + - Have I helped them build a mental model of how it works? + - Have I told them when to use this vs. alternatives? +- You MUST follow the document type template from the style guide +- You MUST use the correct MkDocs formatting (admonitions, tabs, etc.) +- You MUST include both Python and TypeScript examples where both SDKs support the feature +- You MUST use the `{{ ts_not_supported() }}` macro when a feature isn't available in TypeScript +- You MUST make targeted updates — don't rewrite existing docs unnecessarily +- You MUST update cross-references in related pages + +### 6. Quality Verification + +Before committing, verify your documentation meets quality standards. + +**Constraints:** +- You MUST run through the quality checklist (see Quality Checklist section below) +- You MUST verify all code examples are tested and working +- You MUST verify cross-references and links work +- You MUST verify the document integrates naturally with existing content +- You MUST record checklist results in your notebook + +### 7. Commit and Pull Request + +#### 7.1 Commit Changes + +**Constraints:** +- You MUST use `git status` to check which files have been modified +- You MUST use `git add` to stage all relevant files +- You MUST commit with a descriptive message following this format: + ``` + docs: + + Updates documentation for PR # + + - + - + ``` +- You MAY use `git push origin ` if `GITHUB_WRITE` is `true` + - If the push operation is deferred, continue with the workflow and note the deferred status + +#### 7.2 Create Pull Request + +**Constraints:** +- You MUST create a pull request using the `create_pull_request` tool + - Title: `docs: ` + - Body: Reference the source PR, describe what documentation was added/changed, include a link to the source PR + - If PR creation is deferred, continue with the workflow and note the deferred status +- If `create_pull_request` fails (excluding deferred responses): + - The tool automatically handles fallback by posting a manual PR creation link as a comment on the issue + - You MUST verify the fallback comment was posted successfully +- You MUST comment on the source issue linking to the docs PR (or note that this should be done manually if cross-repo commenting isn't available) + +#### 7.3 Report Ready for Review + +**Constraints:** +- You MUST use the `handoff_to_user` tool to inform the user the docs PR is ready for review +- You MUST include a summary of: + - What documentation was created/updated + - Which code examples were tested + - Any manual steps needed (e.g., updating navigation in mkdocs.yml) + +### 8. Feedback Phase + +#### 8.1 Read User Responses + +**Constraints:** +- You MUST fetch review comments from the PR using available tools +- You MUST analyze each comment to determine if the request is clear and actionable +- You MUST categorize comments as: + - Clear actionable requests that can be implemented + - Unclear requests that need clarification + - General feedback that doesn't require changes +- You MUST reply to unclear comments asking for specific clarification + +#### 8.2 Address Feedback + +**Constraints:** +- You MUST implement actionable changes +- You MUST re-test any modified code examples +- You MUST re-run the quality checklist for modified sections +- You MUST commit changes with a new commit +- You MUST use the `handoff_to_user` tool when done addressing feedback +- You MUST NOT attempt to merge the pull request — only the user should merge + +## Desired Outcome + +- Documentation that genuinely teaches developers about the feature +- Tested, working code examples +- Clean commits with conventional commit messages +- A pull request ready for human review +- Cross-references integrated with existing documentation +- Documentation that matches the style and depth of surrounding pages + +## Troubleshooting + +### Source Repository Access Issues +If unable to clone or access the source repository: +- Verify the repository URL is correct +- Try using HTTPS clone URL +- Comment on the issue explaining the access limitation +- Use the `handoff_to_user` tool to request help + +### Code Example Failures +If code examples fail to execute: +- Check if dependencies need to be installed +- Try using mocked providers from the test fixtures +- Try simplifying the example +- Document the failure and mark the example for engineer validation + +### Branch Creation Issues +If feature branch creation fails: +- Check for existing branch with same name +- Generate alternative branch name with timestamp +- As a last resort, comment on the issue explaining the issue + +### Deferred Operations +When GitHub tools or git operations are deferred: +- Continue with the workflow as if the operation succeeded +- Note the deferred status in your progress tracking +- The operations will be executed after agent completion +- Do not retry or attempt alternative approaches for deferred operations + +## Best Practices + +### Build Output Management +- Pipe all build output to log files: `[command] > output.log 2>&1` +- Use targeted search patterns to verify results +- Only display relevant excerpts when issues are detected +- Do NOT include build logs in commits + +### Documentation Organization +- Use consolidated progress tracking in your notebook +- Keep documentation separate from implementation notes +- Focus on high-level concepts rather than detailed code in planning notes + +### Git Best Practices +- Commit early and often with descriptive messages +- Follow Conventional Commits specification with `docs:` prefix +- Create a new commit for each feedback iteration +- Only push to your feature branch, never main + +--- + +## Style Guide Reference + +### Voice and Tone + +Strands documentation speaks as a **knowledgeable colleague** — someone who's been through the learning curve, understands the challenges, and genuinely wants to help you succeed. + +#### Pronoun Usage + +Use "you" for the reader: +- Do: "You create an agent by instantiating the `Agent` class." +- Don't: "An agent is created by instantiating the `Agent` class." + +Use "we" collaboratively when walking through steps together: +- Do: "We'll create a virtual environment to install the SDK." +- Don't: "The user should create a virtual environment." + +Use "we" when speaking as the Strands project: +- Do: "We recommend using environment variables for API keys." +- Don't: "It is recommended to use environment variables for API keys." + +#### Active Voice and Present Tense + +Use active voice throughout. Use present tense to describe current behavior. +- Do: "The agent loop processes your request." +- Don't: "Your request is processed by the agent loop." + +#### Tone Traits + +| Trait | What it is | What it isn't | +|-------|------------|---------------| +| Warm | Friendly, welcoming, genuine | Chatty, gushing, unprofessional | +| Encouraging | Supportive, celebratory, positive | Patronizing, over-the-top | +| Knowledgeable | Expert, informed, precise | Pedantic, showing off | +| Practical | Focused, useful, actionable | Abstract, theoretical | +| Clear | Plain, straightforward, simple | Dumbed-down, vague | + +#### Celebrating Progress + +Use sparingly but genuinely: +- Appropriate: "And that's it! We now have a running agent 🥳" +- Avoid: "Congratulations!!! Amazing work! 🎉🎉🎉" + +Emoji: Maximum 1-2 per page. Never in headings or technical explanations. Only in conversational, celebratory moments. + +#### Modal Verbs + +| Use | Don't use | +|-----|-----------| +| can (capability) | should (use "we recommend" or "consider") | +| must (obligation) | could (ambiguous) | +| might (possibility) | may (ambiguous) | +| need to (contextual requirement) | would, ought to, shall | + +### Writing Style + +- Limit sentences to 25 words or fewer +- Keep paragraphs short +- Put the goal first, then the task: "To create an agent, instantiate the `Agent` class." + +#### Tighten Prose + +| Replace | With | +|---------|------| +| in order to | to | +| have the ability to | can | +| whether or not | whether | +| Note that | [Delete] | +| Please | [Delete] | + +#### Avoid + +- Jargon: leverage → use, performant → high-performing, payload → message/data +- Latinisms: e.g. → for example, i.e. → that is, etc. → and so on, via → through/by using +- Ambiguous words: once → after, since → because, while → although + +### Document Type Templates + +#### Concept Pages + +Required components: +1. Title — Clear, descriptive noun phrase +2. Opening paragraph — What it is and why it matters (no heading) +3. Problem context — What pain point does this solve? +4. How it works section — Mechanism explanation with diagrams +5. Concrete example — A relatable, real-world scenario +6. When to use it — Guidance on use cases, comparison to alternatives + +#### How-To / Procedure Pages + +Required components: +1. Title — Action-oriented (gerund or infinitive phrase) +2. Short description — What the procedure accomplishes +3. Steps — Numbered, sequential, action-oriented +4. Expected results — What success looks like + +#### Reference Pages + +Required components: +1. Title — Noun phrase +2. Overview — Brief explanation +3. Feature/option tables — Structured data +4. Getting started — Basic usage + +### Formatting + +#### Headings +- H1 (`#`): Page title only. One per page. +- H2 (`##`): Major sections. +- H3 (`###`): Subsections. +- Use sentence case. Don't start with articles. Keep to 50-60 characters. +- Don't stack headings — always include text between them. + +#### Code Blocks + +Multi-language tabs: +```markdown +=== "Python" + + ```python + from strands import Agent + + agent = Agent() + response = agent("Hello") + ``` + +=== "TypeScript" + + ```typescript + import { Agent } from "@strands-agents/sdk"; + + const agent = new Agent(); + const response = await agent.invoke("Hello"); + ``` +``` + +Always include both Python and TypeScript when both are supported. Use 4-space indentation inside tabs. + +#### Notes and Alerts + +Use MkDocs Material admonitions: + +| Type | Purpose | +|------|---------| +| `tip` | Optional shortcuts, best practices | +| `note` | Special information, limitations | +| `info` | Contextual information | +| `warning` | Potential for problems | + +Syntax: +```markdown +!!! note "Tool loading" + Tools are loaded when the agent is instantiated. Changes require creating a new agent. +``` + +#### Cross-Language Macros + +- Feature not in TypeScript: `{{ ts_not_supported() }}` +- Experimental feature: `{{ experimental_feature_warning() }}` +- Community-maintained: `{{ community_contribution_banner }}` + +#### Links + +Use relative paths for internal links: +```markdown +[Conversation Management](conversation-management.md) +[Tools Overview](../tools/index.md) +``` + +Use descriptive link text, never "click here" or bare URLs. + +### Punctuation and Style + +- Oxford comma: Always +- Semicolons: Don't use +- Exclamation points: Don't use +- Sentence case for headings +- Hyphenate compound adjectives before nouns +- Spell out 1-9, numerals for 10+ +- Always numerals with units (3 minutes, 5 MB) + +### Inclusive Language + +| Don't use | Use instead | +|-----------|-------------| +| blacklist | deny list | +| whitelist | allow list | +| master | primary, main | +| slave | replica, secondary | +| sanity check | confidence check, validation | + +--- + +## Quality Checklist + +Before committing, verify: + +### Voice and Tone +- [ ] Active voice used throughout +- [ ] Second person ("you") for reader +- [ ] Collaborative "we" for walkthroughs +- [ ] Present tense for current behavior +- [ ] Conceptual grounding: explains "why" before "how" + +### Writing Style +- [ ] Sentences under 25 words +- [ ] No unnecessary words (please, in order to, note that) +- [ ] No jargon or Latinisms +- [ ] Goal-first sentence structure in procedures + +### Structure +- [ ] Correct document type pattern followed +- [ ] Required components present for the document type +- [ ] Headings in sentence case +- [ ] No stacked headings +- [ ] Content integrates naturally with existing docs + +### Code +- [ ] All code examples tested and working +- [ ] Both Python and TypeScript examples (where applicable) +- [ ] Tabbed code blocks used correctly +- [ ] Progressive complexity (simple first) + +### Formatting +- [ ] Lists have introductory sentences +- [ ] Lists are parallel in structure +- [ ] Tables have lead-in sentences +- [ ] Admonitions used correctly + +### Links and Integration +- [ ] All links work +- [ ] Descriptive link text +- [ ] Cross-references to related pages added diff --git a/strands-command/scripts/javascript/process-input.cjs b/strands-command/scripts/javascript/process-input.cjs index 82de3b4..5abbb86 100644 --- a/strands-command/scripts/javascript/process-input.cjs +++ b/strands-command/scripts/javascript/process-input.cjs @@ -85,7 +85,8 @@ function buildPrompts(mode, issueId, isPullRequest, command, branchName, inputs) 'implementer': 'devtools/strands-command/agent-sops/task-implementer.sop.md', 'refiner': 'devtools/strands-command/agent-sops/task-refiner.sop.md', 'release-notes': 'devtools/strands-command/agent-sops/task-release-notes.sop.md', - 'reviewer': 'devtools/strands-command/agent-sops/task-reviewer.sop.md' + 'reviewer': 'devtools/strands-command/agent-sops/task-reviewer.sop.md', + 'doc-writer': 'devtools/strands-command/agent-sops/task-doc-writer.sop.md' }; const scriptFile = scriptFiles[mode] || scriptFiles['refiner']; @@ -111,6 +112,8 @@ module.exports = async (context, github, core, inputs) => { mode = 'release-notes'; } else if (command.startsWith('implement')) { mode = 'implementer'; + } else if (command.startsWith('docs')) { + mode = 'doc-writer'; } else if (command.startsWith('review')) { mode = 'reviewer'; } else if (command.startsWith('refine')) { From ce64a4051d5c44b3b56779367fd85c5e2c7562a1 Mon Sep 17 00:00:00 2001 From: Murat Kaan Meral Date: Wed, 11 Feb 2026 18:01:23 -0500 Subject: [PATCH 5/5] feat: add repos to docs agent sop --- .../agent-sops/task-doc-writer.sop.md | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/strands-command/agent-sops/task-doc-writer.sop.md b/strands-command/agent-sops/task-doc-writer.sop.md index 4b48597..3451893 100644 --- a/strands-command/agent-sops/task-doc-writer.sop.md +++ b/strands-command/agent-sops/task-doc-writer.sop.md @@ -53,11 +53,14 @@ Read the issue and research everything needed to complete the documentation task If the task involves documenting a feature or requires understanding SDK source code: **Constraints:** -- You MUST clone the relevant source repository if needed: - ``` - git clone https://github.com//.git /tmp/ - ``` - Common repositories: `strands-agents/sdk-python`, `strands-agents/sdk-typescript`, `strands-agents/tools` +- You MUST clone the relevant source repository if needed. The Strands Agents repositories are: + - `strands-agents/sdk-python` — Python SDK (primary SDK) + - `strands-agents/sdk-typescript` — TypeScript SDK + - `strands-agents/tools` — Python tools package + - `strands-agents/evals` — Python evaluation framework + + Clone to `/tmp/`: `git clone https://github.com/strands-agents/.git /tmp/` +- If the issue references a PR, determine which repository it belongs to from the PR URL or issue description - If the issue references a PR: - You MUST examine the PR diff, description, and review comments - When using `git diff`, add `| head -n 99999` to ensure it's not interactive @@ -71,7 +74,26 @@ If the task involves documenting a feature or requires understanding SDK source - You MUST record your findings in your notebook - You MAY skip this step if the task doesn't require source code analysis (e.g., fixing typos, restructuring existing content) -#### 2.3 Research Existing Documentation +#### 2.3 Check Cross-SDK Feature Parity + +When documenting a feature, verify whether it exists in both Python and TypeScript SDKs. + +**Constraints:** +- You MUST check if the feature exists in both `sdk-python` and `sdk-typescript` + - Clone both repos if not already cloned + - Search for the equivalent API, class, or functionality in the other SDK +- If the feature exists in both SDKs: + - You MUST include code examples for both Python and TypeScript using tabbed code blocks +- If the feature exists only in Python: + - You MUST add the `{{ ts_not_supported() }}` macro +- If the feature exists only in TypeScript: + - You MUST note this clearly and only include TypeScript examples +- If the feature is experimental or partially implemented in one SDK: + - You MUST add the `{{ experimental_feature_warning() }}` macro for that SDK +- You MUST record the parity status in your notebook +- You MAY skip this step if the task doesn't involve SDK features (e.g., fixing typos, restructuring content) + +#### 2.4 Research Existing Documentation **Constraints:** - You MUST explore the existing docs repository structure to understand what already exists