Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"image":"mcr.microsoft.com/devcontainers/universal:2"}
10 changes: 10 additions & 0 deletions .github/PR_NOTES/PR-134-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PR #134 update
================

Moved `SUBAGENT.md` from `.vscode` to `.github/skills/summarize-repo-structure/SUBAGENT.md` and removed the `.vscode` copy.

Test results (local): 162 passed, 70 warnings.

Summarizer: output saved to `/tmp/repo_summary_local.json`.

This file was added automatically by the assistant to surface the update in the pull request.
29 changes: 29 additions & 0 deletions .github/scripts/merge_pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail

# Usage: ./merge_pr.sh <pr-number> [repo] [mode]
# mode: squash (default) or merge
PR_NUMBER=${1:-134}
REPO=${2:-coinbase/coinbase-advanced-py}
MODE=${3:-squash}

if ! command -v gh >/dev/null 2>&1; then
echo "gh CLI is required. Install and authenticate first (gh auth login)."
exit 2
fi

echo "Checking gh auth..."
if ! gh auth status >/dev/null 2>&1; then
echo "Not authenticated with GitHub CLI. Run: gh auth login" >&2
exit 3
fi

echo "Merging PR #${PR_NUMBER} on ${REPO} (mode=${MODE})"
# Use the global --repo flag before the subcommand to avoid positional-arg parsing
if [ "${MODE}" = "squash" ]; then
gh --repo "${REPO}" pr merge "${PR_NUMBER}" --squash --delete-branch -m "Merge repo summarizer skill: CI, docs, and subagent metadata"
else
gh --repo "${REPO}" pr merge "${PR_NUMBER}" --merge -m "Merge repo summarizer skill: CI, docs, and subagent metadata"
fi

echo "Done."
21 changes: 21 additions & 0 deletions .github/skills/summarize-repo-structure/SUBAGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: summarize-repo-structure-subagent
description: Run the repository summarizer to produce a concise JSON summary of the workspace layout.
entrypoint: tools/skills/summarize_repo.py
runner: .vscode/skills/run_summarize_repo.sh
outputs:
- repo_summary.json
invocation:
manual: bash .vscode/skills/run_summarize_repo.sh
ci_workflow: .github/workflows/summarize_repo.yml
author: automated by GitHub Copilot assistant
---

This file registers a local "subagent" descriptor for the `summarize-repo-structure` skill.
Tools or automation that look for subagent manifests can use this file to discover the
entrypoint, runner script, and outputs.

Notes
-----
- The runner script is executable and calls the Python entrypoint. It prints pretty JSON when run.
- The GitHub Actions workflow already produces `repo_summary.json` and uploads it as an artifact.
33 changes: 33 additions & 0 deletions .github/workflows/summarize_repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Repo Summarizer

on:
push:
branches:
- master
workflow_dispatch: {}

jobs:
summarize:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Install minimal deps
run: |
python -m pip install --upgrade pip

- name: Run summarizer
run: |
python3 ./.vscode/skills/../tools/skills/summarize_repo.py --workspace-root . --depth 2 --pretty > repo_summary.json

- name: Upload summary
uses: actions/upload-artifact@v4
with:
name: repo-summary
path: repo_summary.json
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[![PyPI version](https://badge.fury.io/py/coinbase-advanced-py.svg)](https://badge.fury.io/py/coinbase-advanced-py)
[![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/license/apache-2-0/)
[![Code Style](https://img.shields.io/badge/code_style-black-black)](https://black.readthedocs.io/en/stable/)
[![Repo Summarizer](https://github.com/coinbase/coinbase-advanced-py/actions/workflows/summarize_repo.yml/badge.svg?branch=master)](https://github.com/coinbase/coinbase-advanced-py/actions/workflows/summarize_repo.yml)

Welcome to the official Coinbase Advanced API Python SDK. This python project was created to allow coders to easily plug into the [Coinbase Advanced API](https://docs.cdp.coinbase.com/advanced-trade/docs/welcome).
This SDK also supports easy connection to the [Coinbase Advanced Trade WebSocket API](https://docs.cdp.coinbase.com/advanced-trade/docs/ws-overview).
Expand Down
148 changes: 148 additions & 0 deletions tools/skills/summarize_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Summarize repository structure as JSON.

Usage: python tools/skills/summarize_repo.py --workspace-root . --depth 2
"""
from __future__ import annotations

import argparse
import ast
import json
import os
import re
import sys

try:
import tomllib as toml
except Exception:
toml = None


def read_pyproject(path: str) -> dict | None:
if not toml:
return None
try:
with open(path, "rb") as f:
return toml.load(f)
except Exception:
return None


def read_setup_name(path: str) -> str | None:
try:
text = open(path, "r", encoding="utf-8").read()
except Exception:
return None
# Try quick regex for name='pkg'
m = re.search(r"name\s*=\s*['\"]([^'\"]+)['\"]", text)
if m:
return m.group(1)
# Try to AST-parse and find setup(...) call with keyword 'name'
try:
tree = ast.parse(text)
for node in ast.walk(tree):
if isinstance(node, ast.Call) and getattr(node.func, 'id', '') == 'setup':
for kw in node.keywords:
if kw.arg == 'name' and isinstance(kw.value, ast.Constant):
return kw.value.value
except Exception:
pass
return None


def list_packages(root: str, depth: int) -> list:
out = []
for entry in sorted(os.listdir(root)):
p = os.path.join(root, entry)
if os.path.isdir(p) and os.path.exists(os.path.join(p, "__init__.py")):
pkg = {"name": entry, "subpackages": []}
if depth > 1:
try:
for sub in sorted(os.listdir(p)):
sp = os.path.join(p, sub)
if os.path.isdir(sp) and os.path.exists(os.path.join(sp, "__init__.py")):
pkg["subpackages"].append(sub)
except Exception:
pass
out.append(pkg)
return out


def top_level_modules(root: str) -> list:
mods = []
for f in sorted(os.listdir(root)):
if f.endswith('.py') and f != '__init__.py':
mods.append(f)
return mods


def collect_tests(root: str) -> list:
tests_dir = os.path.join(root, 'tests')
if not os.path.isdir(tests_dir):
tests_dir = os.path.join(root, 'test')
if not os.path.isdir(tests_dir):
return []
files = []
for dirpath, dirnames, filenames in os.walk(tests_dir):
for fn in filenames:
if fn.endswith('.py'):
rel = os.path.relpath(os.path.join(dirpath, fn), root)
files.append(rel)
return sorted(files)


def notable_files(root: str) -> list:
candidates = ['README.md', 'README.rst', 'LICENSE', 'LICENSE.md', 'setup.py', 'pyproject.toml']
return [c for c in candidates if os.path.exists(os.path.join(root, c))]


def summarize(root: str, depth: int) -> dict:
root = os.path.abspath(root)
pkg_name = None
pyproject = os.path.join(root, 'pyproject.toml')
if os.path.exists(pyproject):
data = read_pyproject(pyproject)
if data:
# Try common locations for project name
name = data.get('project', {}).get('name') if isinstance(data.get('project'), dict) else None
if not name:
name = data.get('tool', {}).get('poetry', {}).get('name') if isinstance(data.get('tool'), dict) else None
pkg_name = name
setup_py = os.path.join(root, 'setup.py')
if not pkg_name and os.path.exists(setup_py):
pkg_name = read_setup_name(setup_py)

packages = list_packages(root, depth)
modules = top_level_modules(root)
tests = collect_tests(root)
notes = notable_files(root)

summary = f"Repository at {os.path.basename(root)}: {len(packages)} package(s), {len(modules)} top-level module(s), {len(tests)} test file(s)."

return {
'summary': summary,
'package_name': pkg_name,
'packages': packages,
'top_level_modules': modules,
'tests': tests[:20],
'notable_files': notes,
}


def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description='Summarize repository structure')
ap.add_argument('--workspace-root', '-w', default='.', help='Repository root')
ap.add_argument('--depth', '-d', type=int, default=2, help='Directory depth for package discovery')
ap.add_argument('--pretty', action='store_true', help='Pretty-print JSON')
args = ap.parse_args(argv)

out = summarize(args.workspace_root, args.depth)
if args.pretty:
print(json.dumps(out, indent=2, ensure_ascii=False))
else:
print(json.dumps(out, ensure_ascii=False))
return 0


if __name__ == '__main__':
raise SystemExit(main())