diff --git a/.agents/skills/create-a-plan/SKILL.md b/.agents/skills/create-a-plan/SKILL.md new file mode 100644 index 0000000..0890aee --- /dev/null +++ b/.agents/skills/create-a-plan/SKILL.md @@ -0,0 +1,231 @@ +--- +name: create-a-plan +description: Conduct a focused technical planning interview to produce an implementable, parallelizable plan or spec with clear dependencies, risks, and open questions. +--- + +# Create a Plan Skill + +This skill runs a structured technical interview to turn a rough idea or an existing spec into a detailed, implementable plan. The output is organized for parallel execution: foundations first, then independent workstreams, then merge and integration. + +## Invocation + +The user will provide one of: +- A path to a spec or plan file (for example: `SPEC.md`, `PLAN.md`, `RFC.md`) +- A rough description of what they want to build +- A feature request or problem statement + +Output is always written to `PLAN.md` in the repo root. + +## Process + +### Phase 0: Preflight + +1. If a file path is provided, read it first and note goals, non-goals, constraints, and gaps. +2. Confirm you will produce `PLAN.md` as the output in the repo root. If `PLAN.md` already exists, update it rather than creating a new file. + +### Phase 1: Discovery + +Summarize what is known, then identify missing details. Focus on: +- Goals and non-goals +- Constraints (time, budget, platform, dependencies) +- Success metrics and acceptance criteria + +### Phase 2: Deep Interview + +Use the `AskUserQuestion` (Claude) and/or `request_user_input` (Codex) tools in rounds. Ask 1-3 questions per round. Each round should go deeper and avoid repeating what is already known. + +CRITICAL RULES: +1. Never ask obvious questions. If the codebase or spec already answers it, do not ask it again. +2. Ask about edge cases and failure modes. +3. Probe for hidden complexity (state transitions, migrations, concurrency). +4. Challenge assumptions when they create risk or ambiguity. +5. Identify parallelization boundaries and serial dependencies. +6. If the user is unsure, propose a default and ask for confirmation. + +Question categories to cover as relevant: +- Technical architecture and data flow +- Data model and state management +- API contracts and versioning +- Caching and invalidation +- Background jobs, retries, and idempotency +- Error handling and recovery +- Observability and debugging +- Performance, scale, and SLAs +- Security, privacy, and compliance +- Integrations and external dependencies +- UX flows, accessibility, and responsiveness +- Rollout, migration, and rollback +- Testing strategy and validation + +### Phase 3: Dependency Analysis + +Identify: +1. Serial dependencies that must complete first +2. Parallel workstreams that can run independently +3. Merge points where work reconvenes + +### Phase 4: Plan Generation + +Write the final plan to `PLAN.md`. Ensure the plan includes concrete verification steps the agent can run end to end. If the user only wants a plan in chat, provide it inline and mention that it would be written to `PLAN.md`. + +## Output Format + +The generated plan MUST follow this structure: + +```markdown +# [Feature Name] Implementation Plan + +## Overview +[2-3 sentence summary of what this implements and why] + +## Goals +- [Explicit goal 1] +- [Explicit goal 2] + +## Non-Goals +- [What this explicitly does NOT do] + +## Assumptions and Constraints +- [Known constraints or assumptions] + +## Requirements + +### Functional +- [Requirement] + +### Non-Functional +- [Performance, reliability, security, compliance] + +## Technical Design + +### Data Model +[Schema changes, new entities, relationships] + +### API Design +[New endpoints, request/response shapes, versioning] + +### Architecture +[System diagram in text or mermaid, component interactions] + +### UX Flow (if applicable) +[Key screens, loading states, error recovery] + +--- + +## Implementation Plan + +### Serial Dependencies (Must Complete First) + +These tasks create foundations that other work depends on. Complete in order. + +#### Phase 0: [Foundation Name] +**Prerequisite for:** All subsequent phases + +| Task | Description | Output | +|------|-------------|--------| +| 0.1 | [Task description] | [Concrete deliverable] | +| 0.2 | [Task description] | [Concrete deliverable] | + +--- + +### Parallel Workstreams + +These workstreams can be executed independently after Phase 0. + +#### Workstream A: [Name] +**Dependencies:** Phase 0 +**Can parallelize with:** Workstreams B, C + +| Task | Description | Output | +|------|-------------|--------| +| A.1 | [Task description] | [Concrete deliverable] | +| A.2 | [Task description] | [Concrete deliverable] | + +#### Workstream B: [Name] +**Dependencies:** Phase 0 +**Can parallelize with:** Workstreams A, C + +| Task | Description | Output | +|------|-------------|--------| +| B.1 | [Task description] | [Concrete deliverable] | + +--- + +### Merge Phase + +After parallel workstreams complete, these tasks integrate the work. + +#### Phase N: Integration +**Dependencies:** Workstreams A, B, C + +| Task | Description | Output | +|------|-------------|--------| +| N.1 | [Integration task] | [Concrete deliverable] | + +--- + +## Testing and Validation + +- [Unit, integration, end-to-end coverage] +- [Manual test plan if needed] + +## Rollout and Migration + +- [Feature flags, staged rollout, migration steps] +- [Rollback plan] + +## Verification Checklist + +- [Exact commands or manual steps the agent can run to verify correctness] +- [Expected outputs or success criteria] + +## Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +|------|------------|--------|------------| +| [Risk description] | Low/Med/High | Low/Med/High | [Strategy] | + +## Open Questions + +- [ ] [Question that still needs resolution] + +## Decision Log + +| Decision | Rationale | Alternatives Considered | +|----------|-----------|------------------------| +| [Decision made] | [Why] | [What else was considered] | +``` + +## Interview Flow Example + +Round 1: High-Level Architecture +- "The spec mentions a sync engine. Is this push-based (webhooks), pull-based (polling), or event-driven (queue)?" +- "What is the expected data volume and throughput?" + +Round 2: Edge Cases +- "If a batch fails mid-run, do we retry the whole batch or resume from a checkpoint?" +- "What happens when source data is deleted but still referenced downstream?" + +Round 3: Parallelization +- "Can we process different categories independently, or are there cross-category dependencies?" +- "Is there a natural partition key that allows sharding?" + +Round 4: Operational +- "What is the acceptable latency for sync or processing?" +- "How will operators debug failures and what visibility do they need?" + +## Key Behaviors + +1. Persist until the plan is implementable and verifiable by the agent, but avoid user fatigue by batching questions. +2. Challenge vague answers when they affect design decisions. +3. Identify hidden work and operational overhead. +4. Think about the merge and integration steps early. +5. Summarize understanding and confirm before writing the final plan. + +## Completing the Interview + +After sufficient rounds of questions: +1. Summarize your understanding back to the user +2. Confirm the parallelization strategy +3. Write the complete plan to the target file +4. Ask if any sections need refinement diff --git a/.agents/skills/create-pr/SKILL.md b/.agents/skills/create-pr/SKILL.md new file mode 100644 index 0000000..e21e877 --- /dev/null +++ b/.agents/skills/create-pr/SKILL.md @@ -0,0 +1,89 @@ +--- +name: create-pr +description: Create or update a PR from current branch to main, watch CI, and address feedback +--- +The user likes the state of the code. + +There are $`git status --porcelain | wc -l | tr -d ' '` uncommitted changes. +The current branch is $`git branch --show-current`. +The target branch is origin/main. + +$`git rev-parse --abbrev-ref @{upstream} 2>/dev/null && echo "Upstream branch exists." || echo "There is no upstream branch yet."` + +**Existing PR:** $`gh pr view --json number,title,url --jq '"#\(.number): \(.title) - \(.url)"' 2>/dev/null || echo "None"` + +The user requested a PR. + +Follow these exact steps: + +## Phase 1: Review the code + +1. Review test coverage +2. Check for silent failures +3. Verify code comments are accurate +4. Review any new types +5. General code review + +## Phase 2: Create/Update PR + +6. Run `git diff` to review uncommitted changes +7. Commit them. Follow any instructions the user gave you about writing commit messages. +8. Push to origin. +9. Use `git diff origin/main...` to review the full PR diff +10. Check if a PR already exists for this branch: + - **If PR exists**: + - Draft/update the description in a temp file (e.g. `/tmp/pr-body.txt`). + - Update the PR body using the non-deprecated script: + - `./.agents/skills/create-pr/scripts/pr-body-update.sh --file /tmp/pr-body.txt` + - Re-fetch the body with `gh pr view --json body --jq .body` to confirm it changed. + - **If no PR exists**: Use `gh pr create --base main` to create a new PR. Keep the title under 80 characters and the description under five sentences. + +The PR description should summarize ALL commits in the PR, not just the latest changes. + +## Phase 3: Monitor CI and Address Issues + +Note: Keep commands CI-safe and avoid interactive `gh` prompts. Ensure `GH_TOKEN` or `GITHUB_TOKEN` is set in CI. + +11. Watch CI status and feedback using the polling script (instead of running `gh` in a loop): + - Run `./.agents/skills/create-pr/scripts/poll-pr.sh --triage-on-change --exit-when-green` (polls every 30s for 10 mins). + - If checks fail, use `gh pr checks` or `gh run list` to find the failing run id, then: + - Fetch the failed check logs using `gh run view --log-failed` + - Analyze the failure and fix the issue + - Commit and push the fix + - Continue polling until all checks pass + +12. Check for merge conflicts: + - Run `git fetch origin main && git merge origin/main` + - If conflicts exist, resolve them sensibly + - Commit the merge resolution and push + +13. Use the polling script output to notice new reviews and comments (avoid direct polling via `gh`): + - If you need a full snapshot, run `./.agents/skills/create-pr/scripts/triage-pr.sh` once. + - If you need full context after the script reports a new item, fetch details once with `gh pr view --comments` or `gh api ...`. + - **Address feedback**: + - For bot reviews, read the review body and any inline comments carefully + - Address comments that are clearly actionable (bug fixes, typos, simple improvements) + - Skip comments that require design decisions or user input + - For addressed feedback, commit fixes with a message referencing the review/comment + +## Phase 4: Merge and Cleanup + +14. Once CI passes and the PR is approved, ask the user if they want to merge the PR. + +15. If the user confirms, merge the PR: + - Use `gh pr merge --squash --delete-branch` to squash-merge and delete the remote branch + +16. After successful merge, check if we're in a git worktree: + - Run: `[ "$(git rev-parse --git-common-dir)" != "$(git rev-parse --git-dir)" ]` + - **If in a worktree**: Use the ask user question tool (`request_user_input`) to ask if they want to clean up the worktree. If yes, run `wt remove --yes --force` to remove the worktree and local branch, then switch back to the main worktree. + - **If not in a worktree**: Just switch back to main with `git checkout main && git pull` + +## Completion + +Report the final PR status to the user, including: +- PR URL +- CI status (passed/merged) +- Any unresolved review comments that need user attention +- Cleanup status (worktree removed or branch switched) + +If any step fails in a way you cannot resolve, ask the user for help. diff --git a/.agents/skills/create-pr/scripts/poll-pr.sh b/.agents/skills/create-pr/scripts/poll-pr.sh new file mode 100755 index 0000000..12fec49 --- /dev/null +++ b/.agents/skills/create-pr/scripts/poll-pr.sh @@ -0,0 +1,225 @@ +#!/usr/bin/env bash +set -euo pipefail + +interval="${POLL_INTERVAL:-30}" +minutes="${POLL_MINUTES:-10}" +poll_once="${POLL_ONCE:-0}" +pr="" +repo="" +exit_when_green=0 +triage_on_change=0 + +usage() { + cat <<'USAGE' +Usage: poll-pr.sh [--pr ] [--repo ] [--interval ] [--minutes ] [--exit-when-green] [--triage-on-change] + +Polls PR checks, review comments, and conversation comments every 30s for 10 minutes by default. +Environment overrides: POLL_INTERVAL, POLL_MINUTES, POLL_ONCE=1. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --pr) + pr="$2" + shift 2 + ;; + --repo) + repo="$2" + shift 2 + ;; + --interval) + interval="$2" + shift 2 + ;; + --minutes) + minutes="$2" + shift 2 + ;; + --exit-when-green) + exit_when_green=1 + shift 1 + ;; + --triage-on-change) + triage_on_change=1 + shift 1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown arg: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$pr" ]]; then + pr="$(gh pr view --json number --jq .number 2>/dev/null || true)" +fi + +if [[ -z "$pr" ]]; then + echo "Could not determine PR number. Use --pr ." >&2 + exit 1 +fi + +if [[ -z "$repo" ]]; then + repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)" +fi + +if [[ -z "$repo" ]]; then + echo "Could not determine repo. Use --repo owner/name." >&2 + exit 1 +fi + +if ! [[ "$interval" =~ ^[0-9]+$ && "$minutes" =~ ^[0-9]+$ ]]; then + echo "interval and minutes must be integers." >&2 + exit 1 +fi + +iterations=$(( (minutes * 60) / interval )) +if (( iterations < 1 )); then + iterations=1 +fi +if [[ "$poll_once" == "1" ]]; then + iterations=1 +fi + +echo "Polling PR #$pr in $repo every ${interval}s for ${minutes}m (${iterations} iterations)." + +last_issue_comment_id="" +last_review_comment_id="" +last_review_id="" +last_failed_signature="" + +if ! gh auth status >/dev/null 2>&1; then + if [[ -z "${GITHUB_TOKEN:-}" && -z "${GH_TOKEN:-}" ]]; then + echo "Warning: gh auth not configured (set GH_TOKEN or GITHUB_TOKEN)." + fi +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +print_new() { + local kind="$1" + local time="$2" + local user="$3" + local url="$4" + local body="$5" + + echo "New $kind by @$user at $time" + if [[ -n "$body" ]]; then + echo " $body" + fi + if [[ -n "$url" ]]; then + echo " $url" + fi +} + +for i in $(seq 1 "$iterations"); do + now=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + echo "[$now] Poll $i/$iterations" + changed=0 + + checks_counts=$(gh pr checks "$pr" --repo "$repo" --json status,conclusion --jq ' + [length, + (map(select(.status != "COMPLETED")) | length), + (map(select(.conclusion != null and .conclusion != "SUCCESS")) | length), + (map(select(.conclusion == "SUCCESS")) | length) + ] | @tsv + ' 2>/dev/null || true) + if [[ -n "$checks_counts" ]]; then + IFS=$'\t' read -r total pending failed success <<< "$checks_counts" + echo "Checks: total=$total pending=$pending failed=$failed success=$success" + else + echo "Checks: unavailable" + fi + + failed_checks=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion,detailsUrl --jq ' + [.[] | select(.conclusion != null and .conclusion != "SUCCESS") | + "\(.name) (\(.conclusion))\t\(.detailsUrl // \"\")" + ] | .[] + ' 2>/dev/null || true) + failed_signature=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion --jq ' + [.[] | select(.conclusion != null and .conclusion != "SUCCESS") | + "\(.name):\(.conclusion)" + ] | sort | join("|") + ' 2>/dev/null || true) + + if [[ -n "$failed_checks" ]]; then + echo "Failed checks:" + while IFS=$'\t' read -r name url; do + if [[ -n "$name" ]]; then + if [[ -n "$url" ]]; then + echo " - $name $url" + else + echo " - $name" + fi + fi + done <<< "$failed_checks" + fi + if [[ -n "$failed_signature" && "$failed_signature" != "$last_failed_signature" ]]; then + last_failed_signature="$failed_signature" + changed=1 + fi + + issue_line=$(gh api "repos/$repo/issues/$pr/comments?per_page=100" --jq ' + if length == 0 then "" else + (max_by(.created_at)) | "\(.id)\t\(.created_at)\t\(.user.login)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])" + end + ' 2>/dev/null || true) + if [[ -n "$issue_line" ]]; then + IFS=$'\t' read -r issue_id issue_time issue_user issue_url issue_body <<< "$issue_line" + if [[ "$issue_id" != "$last_issue_comment_id" ]]; then + last_issue_comment_id="$issue_id" + print_new "conversation comment" "$issue_time" "$issue_user" "$issue_url" "$issue_body" + changed=1 + fi + fi + + review_comment_line=$(gh api "repos/$repo/pulls/$pr/comments?per_page=100" --jq ' + if length == 0 then "" else + (max_by(.created_at)) | "\(.id)\t\(.created_at)\t\(.user.login)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])" + end + ' 2>/dev/null || true) + if [[ -n "$review_comment_line" ]]; then + IFS=$'\t' read -r rc_id rc_time rc_user rc_url rc_body <<< "$review_comment_line" + if [[ "$rc_id" != "$last_review_comment_id" ]]; then + last_review_comment_id="$rc_id" + print_new "inline review comment" "$rc_time" "$rc_user" "$rc_url" "$rc_body" + changed=1 + fi + fi + + review_line=$(gh api "repos/$repo/pulls/$pr/reviews?per_page=100" --jq ' + [ .[] | select(.submitted_at != null) ] | + if length == 0 then "" else + (max_by(.submitted_at)) | "\(.id)\t\(.submitted_at)\t\(.user.login)\t\(.html_url)\t\(.state)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])" + end + ' 2>/dev/null || true) + if [[ -n "$review_line" ]]; then + IFS=$'\t' read -r r_id r_time r_user r_url r_state r_body <<< "$review_line" + if [[ "$r_id" != "$last_review_id" ]]; then + last_review_id="$r_id" + print_new "review ($r_state)" "$r_time" "$r_user" "$r_url" "$r_body" + changed=1 + fi + fi + + if [[ "$triage_on_change" == "1" && "$changed" == "1" ]]; then + "$script_dir/triage-pr.sh" --pr "$pr" --repo "$repo" || true + fi + + if [[ "$exit_when_green" == "1" && -n "${pending:-}" ]]; then + if (( pending == 0 && failed == 0 && total > 0 )); then + echo "Checks green; exiting early." + break + fi + fi + + if (( i < iterations )); then + sleep "$interval" + fi +done diff --git a/.agents/skills/create-pr/scripts/pr-body-update.sh b/.agents/skills/create-pr/scripts/pr-body-update.sh new file mode 100755 index 0000000..bfbb4ac --- /dev/null +++ b/.agents/skills/create-pr/scripts/pr-body-update.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +body_file="" +pr="" +repo="" + +usage() { + cat <<'USAGE' +Usage: pr-body-update.sh --file [--pr ] [--repo ] + +Updates a PR body using the GraphQL updatePullRequest mutation and verifies the result. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --file) + body_file="$2" + shift 2 + ;; + --pr) + pr="$2" + shift 2 + ;; + --repo) + repo="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown arg: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$body_file" ]]; then + echo "--file is required." >&2 + exit 1 +fi + +if [[ ! -f "$body_file" ]]; then + echo "Body file not found: $body_file" >&2 + exit 1 +fi + +if [[ ! -s "$body_file" ]]; then + echo "Body file is empty: $body_file" >&2 + exit 1 +fi + +if [[ -z "$pr" ]]; then + pr="$(gh pr view --json number --jq .number 2>/dev/null || true)" +fi + +if [[ -z "$pr" ]]; then + echo "Could not determine PR number. Use --pr ." >&2 + exit 1 +fi + +if [[ -z "$repo" ]]; then + repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)" +fi + +if [[ -z "$repo" ]]; then + echo "Could not determine repo. Use --repo owner/name." >&2 + exit 1 +fi + +pr_id="$(gh pr view "$pr" --repo "$repo" --json id --jq .id 2>/dev/null || true)" +if [[ -z "$pr_id" ]]; then + echo "Could not determine PR id for #$pr in $repo." >&2 + exit 1 +fi + +gh api graphql \ + -f query='mutation($id:ID!,$body:String!){updatePullRequest(input:{pullRequestId:$id, body:$body}){pullRequest{id}}}' \ + -f id="$pr_id" \ + -f body="$(cat "$body_file")" \ + >/dev/null + +updated_body="$(gh pr view "$pr" --repo "$repo" --json body --jq .body 2>/dev/null || true)" +if [[ -z "$updated_body" ]]; then + echo "Failed to fetch updated PR body for #$pr in $repo." >&2 + exit 1 +fi + +if [[ "$updated_body" != "$(cat "$body_file")" ]]; then + echo "PR body mismatch after update." >&2 + exit 1 +fi + +echo "Updated PR #$pr body in $repo." diff --git a/.agents/skills/create-pr/scripts/triage-pr.sh b/.agents/skills/create-pr/scripts/triage-pr.sh new file mode 100755 index 0000000..0731d12 --- /dev/null +++ b/.agents/skills/create-pr/scripts/triage-pr.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +pr="" +repo="" + +usage() { + cat <<'USAGE' +Usage: triage-pr.sh [--pr ] [--repo ] + +Prints a single-shot summary of CI status, latest review, and latest comments. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --pr) + pr="$2" + shift 2 + ;; + --repo) + repo="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown arg: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$pr" ]]; then + pr="$(gh pr view --json number --jq .number 2>/dev/null || true)" +fi + +if [[ -z "$pr" ]]; then + echo "Could not determine PR number. Use --pr ." >&2 + exit 1 +fi + +if [[ -z "$repo" ]]; then + repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)" +fi + +if [[ -z "$repo" ]]; then + echo "Could not determine repo. Use --repo owner/name." >&2 + exit 1 +fi + +checks_counts=$(gh pr checks "$pr" --repo "$repo" --json status,conclusion --jq ' + [length, + (map(select(.status != "COMPLETED")) | length), + (map(select(.conclusion != null and .conclusion != "SUCCESS")) | length), + (map(select(.conclusion == "SUCCESS")) | length) + ] | @tsv +' 2>/dev/null || true) + +if [[ -n "$checks_counts" ]]; then + IFS=$'\t' read -r total pending failed success <<< "$checks_counts" + echo "CI: total=$total pending=$pending failed=$failed success=$success" +else + echo "CI: unavailable" +fi + +failed_checks=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion,detailsUrl --jq ' + [.[] | select(.conclusion != null and .conclusion != "SUCCESS") | + "\(.name)\t\(.conclusion)\t\(.detailsUrl // \"\")" + ] | .[] +' 2>/dev/null || true) +if [[ -n "$failed_checks" ]]; then + while IFS=$'\t' read -r name conclusion url; do + if [[ -n "$name" ]]; then + echo "FAIL: $name $conclusion $url" + fi + done <<< "$failed_checks" +fi + +review_line=$(gh api "repos/$repo/pulls/$pr/reviews?per_page=100" --jq ' + [ .[] | select(.submitted_at != null) ] | + if length == 0 then "" else + (max_by(.submitted_at)) | "\(.state)\t\(.user.login)\t\(.submitted_at)\t\(.html_url)" + end +' 2>/dev/null || true) +if [[ -n "$review_line" ]]; then + IFS=$'\t' read -r r_state r_user r_time r_url <<< "$review_line" + echo "REVIEW: $r_state $r_user $r_time $r_url" +fi + +issue_line=$(gh api "repos/$repo/issues/$pr/comments?per_page=100" --jq ' + if length == 0 then "" else + (max_by(.created_at)) | "\(.user.login)\t\(.created_at)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])" + end +' 2>/dev/null || true) +if [[ -n "$issue_line" ]]; then + IFS=$'\t' read -r c_user c_time c_url c_body <<< "$issue_line" + echo "COMMENT: conversation $c_user $c_time $c_url $c_body" +fi + +review_comment_line=$(gh api "repos/$repo/pulls/$pr/comments?per_page=100" --jq ' + if length == 0 then "" else + (max_by(.created_at)) | "\(.user.login)\t\(.created_at)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])" + end +' 2>/dev/null || true) +if [[ -n "$review_comment_line" ]]; then + IFS=$'\t' read -r rc_user rc_time rc_url rc_body <<< "$review_comment_line" + echo "COMMENT: inline $rc_user $rc_time $rc_url $rc_body" +fi diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000..2b7a412 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 0000000..2b7a412 --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..594b376 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,30 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + issues: read + id-token: write + +jobs: + claude-code-review: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Claude Review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: "/review" + # CLAUDE.md is a symlink to AGENTS.md in this repo; keep repo rules there. + claude_args: | + --max-turns 4 diff --git a/AGENT.md b/AGENT.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/AGENT.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e828a05 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ + +[Agent Skills Index]|root: ./agents|IMPORTANT: Prefer retrieval-led reasoning over pre-training for any tasks covered by skills.|skills|create-a-plan:{create-a-plan.md},create-pr:{create-pr.md} + +# Agent Instructions + +This repository uses `AGENTS.md` as the canonical agent prompt. + diff --git a/CLAUDE.legacy.md b/CLAUDE.legacy.md new file mode 100644 index 0000000..2f85dd4 --- /dev/null +++ b/CLAUDE.legacy.md @@ -0,0 +1,391 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with the Cartridge Controller Presets repository. + +## Repository Purpose + +This is the **@cartridge/presets** package - a TypeScript library containing preset configurations for Cartridge Controller themes and session scopes. It provides ready-to-use configurations for gaming applications in the Cartridge ecosystem. + +## Architecture Overview + +### Core Components + +- **configs/**: Game-specific configuration directories containing theme assets and config.json files +- **src/**: TypeScript source code for the package API and configuration loading +- **scripts/**: Build automation and validation scripts +- **built-configs/**: Generated output directory (auto-generated) + +### Package Structure + +``` +├── configs/ # Game preset configurations +│ ├── {game-name}/ +│ │ ├── config.json # Game configuration +│ │ ├── icon.{svg|png}# Game icon +│ │ └── cover.{png|jpg}# Game cover image +├── src/ +│ ├── index.ts # Main package exports +│ ├── config-loader.ts # Configuration loading utilities +│ └── generated/ # Auto-generated content +├── scripts/ # Build and automation scripts +└── built-configs/ # Generated configurations (build output) +``` + +## Essential Commands + +### Development + +```bash +# Install dependencies +pnpm install + +# Build the package (full build including configs and AASA) +pnpm build + +# Build only configuration files +pnpm build:configs + +# Build Apple App Site Association files +pnpm build:aasa + +# Format code +pnpm format + +# Check formatting +pnpm format:check +``` + +### Validation and Maintenance + +```bash +# Validate all configurations +pnpm validate:configs + +# Fetch latest ERC20 metadata +pnpm fetch:erc20 + +# Optimize images +pnpm image:optimize +``` + +### Publishing + +```bash +# Build for publishing (without type declarations for faster builds) +pnpm build:js + +# The package is published to NPM as @cartridge/presets +``` + +## Configuration System + +### Game Configuration Format + +Each game in `configs/{game-name}/config.json` follows this structure: + +```json +{ + "origin": "https://game-domain.com", + "theme": { + "name": "Game Name", + "icon": "icon.svg", + "cover": "cover.png", + "colors": { + "primary": "#hex-color", + "primaryForeground": "#hex-color" + } + }, + "chains": { + "SN_MAIN": { + "policies": { + "contracts": { + "0x123...": { + "name": "Game Contract", + "methods": [ + { + "entrypoint": "method_name", + "description": "Method description" + } + ] + } + } + } + } + } +} +``` + +### Theme Assets + +- **Icons**: SVG preferred for scalability, PNG supported +- **Covers**: PNG/JPG format, optimized for multiple resolutions +- **Images**: Automatically optimized during build process + +### Session Policies + +Session policies define what smart contract methods and message signing permissions are available: + +- **Contract Methods**: Specific entrypoints that can be called +- **Message Signing**: TypedData policies for off-chain signatures +- **Chain-specific**: Different policies per StarkNet chain + +## Build Process + +### Automated Generation + +The build process (`pnpm build:configs`) performs: + +1. **Configuration Processing**: Reads all `config.json` files +2. **Asset URL Generation**: Creates CDN URLs for theme assets +3. **Image Optimization**: Generates multiple resolutions and formats +4. **Validation**: Ensures all configurations are valid +5. **Output Generation**: Creates `built-configs/` with processed configurations + +### CDN Integration + +Assets are automatically configured for CDN delivery: +- Base URL: `https://static.cartridge.gg/presets/{game-name}/{asset}` +- Optimized versions generated for different sizes and formats + +## Development Workflows + +### Adding a New Game Preset + +1. **Create Configuration Directory**: + ```bash + mkdir configs/new-game-name + ``` + +2. **Add Required Assets**: + ```bash + # Add to configs/new-game-name/ + # - config.json (required) + # - icon.svg or icon.png (required) + # - cover.png or cover.jpg (optional) + ``` + +3. **Configure Game Settings**: + - Edit `config.json` with origin, theme, and policies + - Follow existing game configurations as examples + +4. **Validate and Build**: + ```bash + pnpm validate:configs + pnpm build:configs + ``` + +### Updating ERC20 Metadata + +```bash +# Fetch latest token metadata from external sources +pnpm fetch:erc20 + +# This updates src/generated/erc20-metadata.ts +pnpm format # Format the generated file +``` + +### Asset Optimization + +```bash +# Optimize all images in configs/ directories +pnpm image:optimize + +# This creates multiple sizes and formats for better performance +``` + +## Integration Points + +### StarkNet Integration + +- **Chain IDs**: Supports SN_MAIN, SN_SEPOLIA +- **Contract Addresses**: StarkNet contract address format (0x...) +- **Session Policies**: Define contract method permissions + +### Cartridge Controller SDK + +- **Theme System**: Provides visual styling for controller UI +- **Session Management**: Defines what actions games can perform +- **Asset Loading**: Optimized asset delivery for controller interface + +### Gaming Applications + +- **Origin Validation**: Ensures presets only work with authorized domains +- **Permission Scoping**: Limits what actions each game can request +- **Branding**: Custom themes for game-specific controller appearance + +## Code Patterns + +### TypeScript Types + +```typescript +import type { ControllerConfig, ControllerTheme, SessionPolicies } from '@cartridge/presets'; + +// Load a specific game configuration +const gameConfig: ControllerConfig = getConfig('game-name'); + +// Access theme data +const theme: ControllerTheme = gameConfig.theme; + +// Work with session policies +const policies: SessionPolicies = gameConfig.chains?.SN_MAIN?.policies; +``` + +### Configuration Loading + +```typescript +// Use the config loader utility +import { loadConfig } from '@cartridge/presets'; + +const config = loadConfig('eternum'); // Loads configs/eternum/config.json +``` + +### ERC20 Metadata Access + +```typescript +import { erc20Metadata } from '@cartridge/presets'; + +// Access token metadata +const tokenInfo = erc20Metadata.find(token => + token.l2_token_address === '0x123...' +); +``` + +## Testing and Validation + +### Configuration Validation + +The `validate:configs` script checks: +- **JSON Schema**: All config.json files follow correct structure +- **Asset References**: Referenced assets exist in the directory +- **Contract Addresses**: Valid StarkNet address format +- **Required Fields**: All mandatory configuration fields present + +### Asset Validation + +- **Image Formats**: Supported formats (SVG, PNG, JPG) +- **File Sizes**: Reasonable limits for web delivery +- **Optimization**: Proper image optimization applied + +## Security Considerations + +### Origin Validation + +- **Domain Restrictions**: Games can only be used from configured origins +- **Wildcard Usage**: Use `"*"` for development only, never production + +### Session Policy Security + +- **Least Privilege**: Only grant minimum required permissions +- **Method Scoping**: Limit contract methods to game-necessary functions +- **Review Process**: All new policies should be security reviewed + +### Asset Security + +- **Content Validation**: Images are validated during optimization +- **CDN Security**: Assets served through secure CDN with integrity checks + +## Troubleshooting + +### Build Issues + +```bash +# Clear build cache +rm -rf built-configs/ +rm -rf dist/ + +# Reinstall dependencies +rm -rf node_modules/ +pnpm install + +# Rebuild everything +pnpm build +``` + +### Validation Errors + +```bash +# Check specific game configuration +pnpm validate:configs + +# Common issues: +# - Missing required assets (icon.svg/png) +# - Invalid JSON syntax in config.json +# - Incorrect StarkNet address format +# - Missing required configuration fields +``` + +### Image Optimization Issues + +```bash +# Check Sharp installation (image processing library) +pnpm list sharp + +# Reinstall Sharp if needed +pnpm add sharp --save-dev + +# Run optimization manually +pnpm image:optimize +``` + +## Deployment + +### NPM Publishing + +The package is automatically published to NPM as `@cartridge/presets` when: +- Version is bumped in package.json +- Changes are merged to main branch +- Build succeeds and all tests pass + +### CDN Asset Deployment + +Theme assets are deployed to `https://static.cartridge.gg/presets/` via: +- Automated sync from built-configs/ +- CDN cache invalidation on updates +- Multi-resolution asset serving + +## Best Practices + +### Configuration Design + +- **Meaningful Names**: Use descriptive game names for configuration directories +- **Consistent Theming**: Follow established color and design patterns +- **Minimal Permissions**: Grant only necessary session policy permissions + +### Asset Management + +- **SVG Icons**: Prefer SVG for scalable icons when possible +- **Image Quality**: Use high-quality source images for covers +- **File Sizes**: Keep assets reasonably sized for web delivery + +### Development Workflow + +- **Validate Early**: Run validation before committing changes +- **Test Locally**: Build and test configurations locally +- **Review Process**: Have configuration changes reviewed for security + +## Package Exports + +```typescript +// Main exports from @cartridge/presets +export { + // Configuration loading utilities + loadConfig, + getConfig, + + // Type definitions + ControllerConfig, + ControllerTheme, + SessionPolicies, + + // ERC20 metadata + erc20Metadata, + + // Default theme + defaultTheme +} from '@cartridge/presets'; + +// Generated ERC20 metadata +export { metadata } from '@cartridge/presets/generated/erc20-metadata'; +``` + +This package is essential infrastructure for the Cartridge gaming ecosystem, providing standardized configuration and theming for game integrations with the Cartridge Controller wallet and session management system. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 2f85dd4..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,391 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with the Cartridge Controller Presets repository. - -## Repository Purpose - -This is the **@cartridge/presets** package - a TypeScript library containing preset configurations for Cartridge Controller themes and session scopes. It provides ready-to-use configurations for gaming applications in the Cartridge ecosystem. - -## Architecture Overview - -### Core Components - -- **configs/**: Game-specific configuration directories containing theme assets and config.json files -- **src/**: TypeScript source code for the package API and configuration loading -- **scripts/**: Build automation and validation scripts -- **built-configs/**: Generated output directory (auto-generated) - -### Package Structure - -``` -├── configs/ # Game preset configurations -│ ├── {game-name}/ -│ │ ├── config.json # Game configuration -│ │ ├── icon.{svg|png}# Game icon -│ │ └── cover.{png|jpg}# Game cover image -├── src/ -│ ├── index.ts # Main package exports -│ ├── config-loader.ts # Configuration loading utilities -│ └── generated/ # Auto-generated content -├── scripts/ # Build and automation scripts -└── built-configs/ # Generated configurations (build output) -``` - -## Essential Commands - -### Development - -```bash -# Install dependencies -pnpm install - -# Build the package (full build including configs and AASA) -pnpm build - -# Build only configuration files -pnpm build:configs - -# Build Apple App Site Association files -pnpm build:aasa - -# Format code -pnpm format - -# Check formatting -pnpm format:check -``` - -### Validation and Maintenance - -```bash -# Validate all configurations -pnpm validate:configs - -# Fetch latest ERC20 metadata -pnpm fetch:erc20 - -# Optimize images -pnpm image:optimize -``` - -### Publishing - -```bash -# Build for publishing (without type declarations for faster builds) -pnpm build:js - -# The package is published to NPM as @cartridge/presets -``` - -## Configuration System - -### Game Configuration Format - -Each game in `configs/{game-name}/config.json` follows this structure: - -```json -{ - "origin": "https://game-domain.com", - "theme": { - "name": "Game Name", - "icon": "icon.svg", - "cover": "cover.png", - "colors": { - "primary": "#hex-color", - "primaryForeground": "#hex-color" - } - }, - "chains": { - "SN_MAIN": { - "policies": { - "contracts": { - "0x123...": { - "name": "Game Contract", - "methods": [ - { - "entrypoint": "method_name", - "description": "Method description" - } - ] - } - } - } - } - } -} -``` - -### Theme Assets - -- **Icons**: SVG preferred for scalability, PNG supported -- **Covers**: PNG/JPG format, optimized for multiple resolutions -- **Images**: Automatically optimized during build process - -### Session Policies - -Session policies define what smart contract methods and message signing permissions are available: - -- **Contract Methods**: Specific entrypoints that can be called -- **Message Signing**: TypedData policies for off-chain signatures -- **Chain-specific**: Different policies per StarkNet chain - -## Build Process - -### Automated Generation - -The build process (`pnpm build:configs`) performs: - -1. **Configuration Processing**: Reads all `config.json` files -2. **Asset URL Generation**: Creates CDN URLs for theme assets -3. **Image Optimization**: Generates multiple resolutions and formats -4. **Validation**: Ensures all configurations are valid -5. **Output Generation**: Creates `built-configs/` with processed configurations - -### CDN Integration - -Assets are automatically configured for CDN delivery: -- Base URL: `https://static.cartridge.gg/presets/{game-name}/{asset}` -- Optimized versions generated for different sizes and formats - -## Development Workflows - -### Adding a New Game Preset - -1. **Create Configuration Directory**: - ```bash - mkdir configs/new-game-name - ``` - -2. **Add Required Assets**: - ```bash - # Add to configs/new-game-name/ - # - config.json (required) - # - icon.svg or icon.png (required) - # - cover.png or cover.jpg (optional) - ``` - -3. **Configure Game Settings**: - - Edit `config.json` with origin, theme, and policies - - Follow existing game configurations as examples - -4. **Validate and Build**: - ```bash - pnpm validate:configs - pnpm build:configs - ``` - -### Updating ERC20 Metadata - -```bash -# Fetch latest token metadata from external sources -pnpm fetch:erc20 - -# This updates src/generated/erc20-metadata.ts -pnpm format # Format the generated file -``` - -### Asset Optimization - -```bash -# Optimize all images in configs/ directories -pnpm image:optimize - -# This creates multiple sizes and formats for better performance -``` - -## Integration Points - -### StarkNet Integration - -- **Chain IDs**: Supports SN_MAIN, SN_SEPOLIA -- **Contract Addresses**: StarkNet contract address format (0x...) -- **Session Policies**: Define contract method permissions - -### Cartridge Controller SDK - -- **Theme System**: Provides visual styling for controller UI -- **Session Management**: Defines what actions games can perform -- **Asset Loading**: Optimized asset delivery for controller interface - -### Gaming Applications - -- **Origin Validation**: Ensures presets only work with authorized domains -- **Permission Scoping**: Limits what actions each game can request -- **Branding**: Custom themes for game-specific controller appearance - -## Code Patterns - -### TypeScript Types - -```typescript -import type { ControllerConfig, ControllerTheme, SessionPolicies } from '@cartridge/presets'; - -// Load a specific game configuration -const gameConfig: ControllerConfig = getConfig('game-name'); - -// Access theme data -const theme: ControllerTheme = gameConfig.theme; - -// Work with session policies -const policies: SessionPolicies = gameConfig.chains?.SN_MAIN?.policies; -``` - -### Configuration Loading - -```typescript -// Use the config loader utility -import { loadConfig } from '@cartridge/presets'; - -const config = loadConfig('eternum'); // Loads configs/eternum/config.json -``` - -### ERC20 Metadata Access - -```typescript -import { erc20Metadata } from '@cartridge/presets'; - -// Access token metadata -const tokenInfo = erc20Metadata.find(token => - token.l2_token_address === '0x123...' -); -``` - -## Testing and Validation - -### Configuration Validation - -The `validate:configs` script checks: -- **JSON Schema**: All config.json files follow correct structure -- **Asset References**: Referenced assets exist in the directory -- **Contract Addresses**: Valid StarkNet address format -- **Required Fields**: All mandatory configuration fields present - -### Asset Validation - -- **Image Formats**: Supported formats (SVG, PNG, JPG) -- **File Sizes**: Reasonable limits for web delivery -- **Optimization**: Proper image optimization applied - -## Security Considerations - -### Origin Validation - -- **Domain Restrictions**: Games can only be used from configured origins -- **Wildcard Usage**: Use `"*"` for development only, never production - -### Session Policy Security - -- **Least Privilege**: Only grant minimum required permissions -- **Method Scoping**: Limit contract methods to game-necessary functions -- **Review Process**: All new policies should be security reviewed - -### Asset Security - -- **Content Validation**: Images are validated during optimization -- **CDN Security**: Assets served through secure CDN with integrity checks - -## Troubleshooting - -### Build Issues - -```bash -# Clear build cache -rm -rf built-configs/ -rm -rf dist/ - -# Reinstall dependencies -rm -rf node_modules/ -pnpm install - -# Rebuild everything -pnpm build -``` - -### Validation Errors - -```bash -# Check specific game configuration -pnpm validate:configs - -# Common issues: -# - Missing required assets (icon.svg/png) -# - Invalid JSON syntax in config.json -# - Incorrect StarkNet address format -# - Missing required configuration fields -``` - -### Image Optimization Issues - -```bash -# Check Sharp installation (image processing library) -pnpm list sharp - -# Reinstall Sharp if needed -pnpm add sharp --save-dev - -# Run optimization manually -pnpm image:optimize -``` - -## Deployment - -### NPM Publishing - -The package is automatically published to NPM as `@cartridge/presets` when: -- Version is bumped in package.json -- Changes are merged to main branch -- Build succeeds and all tests pass - -### CDN Asset Deployment - -Theme assets are deployed to `https://static.cartridge.gg/presets/` via: -- Automated sync from built-configs/ -- CDN cache invalidation on updates -- Multi-resolution asset serving - -## Best Practices - -### Configuration Design - -- **Meaningful Names**: Use descriptive game names for configuration directories -- **Consistent Theming**: Follow established color and design patterns -- **Minimal Permissions**: Grant only necessary session policy permissions - -### Asset Management - -- **SVG Icons**: Prefer SVG for scalable icons when possible -- **Image Quality**: Use high-quality source images for covers -- **File Sizes**: Keep assets reasonably sized for web delivery - -### Development Workflow - -- **Validate Early**: Run validation before committing changes -- **Test Locally**: Build and test configurations locally -- **Review Process**: Have configuration changes reviewed for security - -## Package Exports - -```typescript -// Main exports from @cartridge/presets -export { - // Configuration loading utilities - loadConfig, - getConfig, - - // Type definitions - ControllerConfig, - ControllerTheme, - SessionPolicies, - - // ERC20 metadata - erc20Metadata, - - // Default theme - defaultTheme -} from '@cartridge/presets'; - -// Generated ERC20 metadata -export { metadata } from '@cartridge/presets/generated/erc20-metadata'; -``` - -This package is essential infrastructure for the Cartridge gaming ecosystem, providing standardized configuration and theming for game integrations with the Cartridge Controller wallet and session management system. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file