diff --git a/.agents/skills/create-a-plan/SKILL.md b/.agents/skills/create-a-plan/SKILL.md new file mode 100644 index 0000000000..0890aeee03 --- /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 index 61a3587ad6..e21e87713e 100644 --- a/.agents/skills/create-pr/SKILL.md +++ b/.agents/skills/create-pr/SKILL.md @@ -1,115 +1,89 @@ --- name: create-pr -description: Create a pull request from the current branch. Use when changes are ready for review, when asked to submit work, or when explicitly asked to create a 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. -# Create Pull Request +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. -## Prerequisites +$`git rev-parse --abbrev-ref @{upstream} 2>/dev/null && echo "Upstream branch exists." || echo "There is no upstream branch yet."` -Before creating a PR, ensure: -1. You are NOT on the `main` branch -2. All changes are committed -3. Tests pass locally (run `pnpm lint:check` at minimum) +**Existing PR:** $`gh pr view --json number,title,url --jq '"#\(.number): \(.title) - \(.url)"' 2>/dev/null || echo "None"` -## Steps +The user requested a PR. -### 1. Verify Branch State +Follow these exact steps: -```bash -# Check current branch -git branch --show-current +## Phase 1: Review the code -# Ensure not on main -if [ "$(git branch --show-current)" = "main" ]; then - echo "ERROR: Cannot create PR from main. Create a feature branch first." - exit 1 -fi +1. Review test coverage +2. Check for silent failures +3. Verify code comments are accurate +4. Review any new types +5. General code review -# Check for uncommitted changes -git status --porcelain -``` +## Phase 2: Create/Update PR -If there are uncommitted changes, commit them first using conventional commit format. +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. -### 2. Check Remote Status +The PR description should summarize ALL commits in the PR, not just the latest changes. -```bash -# Fetch latest from origin -git fetch origin +## Phase 3: Monitor CI and Address Issues -# Check if branch exists on remote -git ls-remote --heads origin $(git branch --show-current) +Note: Keep commands CI-safe and avoid interactive `gh` prompts. Ensure `GH_TOKEN` or `GITHUB_TOKEN` is set in CI. -# If branch doesn't exist on remote, push it -git push -u origin $(git branch --show-current) -``` +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 -### 3. Generate PR Summary +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 -Analyze the diff between the current branch and main: +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 -```bash -# Get the diff summary -git log origin/main..HEAD --oneline +## Phase 4: Merge and Cleanup -# Get detailed diff for analysis -git diff origin/main...HEAD --stat -``` +14. Once CI passes and the PR is approved, ask the user if they want to merge the PR. -Based on the changes, generate: -- **Title**: Concise description following conventional commit style (e.g., "feat: add session timeout handling") -- **Summary**: 2-3 bullet points explaining what changed and why +15. If the user confirms, merge the PR: + - Use `gh pr merge --squash --delete-branch` to squash-merge and delete the remote branch -### 4. Create the Pull Request +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` -```bash -gh pr create \ - --title "your-title-here" \ - --body "$(cat <<'EOF' -## Summary -- First change description -- Second change description +## Completion -## Testing -- [ ] Ran `pnpm lint:check` -- [ ] Ran `pnpm test` (if applicable) -- [ ] Tested manually (if UI changes) -EOF -)" -``` +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) -### 5. Link Issues (if applicable) - -If the PR addresses a GitHub issue, include in the body: -- `Closes #123` - Automatically closes the issue when PR merges -- `Fixes #123` - Same as above -- `Relates to #123` - Links without auto-closing - -### 6. Post-Creation - -After creating the PR: -1. Return the PR URL to the user -2. Note that CI will run automatically -3. Mention that Claude will review the PR shortly (via `.github/workflows/claude.yml`) - -## Common Issues - -### Branch behind main -```bash -git fetch origin main -git rebase origin/main -git push --force-with-lease -``` - -### Need to update existing PR -Push new commits to the same branch - the PR updates automatically. - -## Example Output - -``` -Created PR #42: feat: add WebAuthn timeout handling -URL: https://github.com/cartridge-gg/controller/pull/42 - -CI checks will run automatically. Claude will review shortly. -``` +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 0000000000..12fec49222 --- /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 0000000000..bfbb4ac31e --- /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 0000000000..0731d1222d --- /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 0000000000..2b7a412b8f --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.claude/skills/agent-browser b/.claude/skills/agent-browser deleted file mode 120000 index e298b7be3c..0000000000 --- a/.claude/skills/agent-browser +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/agent-browser \ No newline at end of file diff --git a/.claude/skills/clean-build b/.claude/skills/clean-build deleted file mode 120000 index 9acb51f9e6..0000000000 --- a/.claude/skills/clean-build +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/clean-build \ No newline at end of file diff --git a/.claude/skills/code-review b/.claude/skills/code-review deleted file mode 120000 index 2d88efdf94..0000000000 --- a/.claude/skills/code-review +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/code-review \ No newline at end of file diff --git a/.claude/skills/codegen b/.claude/skills/codegen deleted file mode 120000 index e303260777..0000000000 --- a/.claude/skills/codegen +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/codegen \ No newline at end of file diff --git a/.claude/skills/create-pr b/.claude/skills/create-pr deleted file mode 120000 index d5b42b7251..0000000000 --- a/.claude/skills/create-pr +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/create-pr \ No newline at end of file diff --git a/.claude/skills/dispatch-release b/.claude/skills/dispatch-release deleted file mode 120000 index c732c75297..0000000000 --- a/.claude/skills/dispatch-release +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/dispatch-release \ No newline at end of file diff --git a/.claude/skills/find-skills b/.claude/skills/find-skills deleted file mode 120000 index 0b17e357d7..0000000000 --- a/.claude/skills/find-skills +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/find-skills \ No newline at end of file diff --git a/.claude/skills/package-filter b/.claude/skills/package-filter deleted file mode 120000 index 01d52eb5dc..0000000000 --- a/.claude/skills/package-filter +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/package-filter \ No newline at end of file diff --git a/.claude/skills/pre-commit-check b/.claude/skills/pre-commit-check deleted file mode 120000 index ec3c587a30..0000000000 --- a/.claude/skills/pre-commit-check +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/pre-commit-check \ No newline at end of file diff --git a/.claude/skills/release-prep b/.claude/skills/release-prep deleted file mode 120000 index 696b8a37d6..0000000000 --- a/.claude/skills/release-prep +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/release-prep \ No newline at end of file diff --git a/.claude/skills/run-tests b/.claude/skills/run-tests deleted file mode 120000 index f56d6a181c..0000000000 --- a/.claude/skills/run-tests +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/run-tests \ No newline at end of file diff --git a/.claude/skills/test b/.claude/skills/test deleted file mode 120000 index 20211d1708..0000000000 --- a/.claude/skills/test +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/test \ No newline at end of file diff --git a/.claude/skills/update-pr b/.claude/skills/update-pr deleted file mode 120000 index 4ee7f19c0c..0000000000 --- a/.claude/skills/update-pr +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/update-pr \ No newline at end of file diff --git a/.claude/skills/update-storybook-snapshots b/.claude/skills/update-storybook-snapshots deleted file mode 120000 index e4424bf9c9..0000000000 --- a/.claude/skills/update-storybook-snapshots +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/update-storybook-snapshots \ No newline at end of file diff --git a/.claude/skills/validate-before-merge b/.claude/skills/validate-before-merge deleted file mode 120000 index 4b44d3b746..0000000000 --- a/.claude/skills/validate-before-merge +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/validate-before-merge \ No newline at end of file diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 0000000000..2b7a412b8f --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.cursor/skills/agent-browser b/.cursor/skills/agent-browser deleted file mode 120000 index e298b7be3c..0000000000 --- a/.cursor/skills/agent-browser +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/agent-browser \ No newline at end of file diff --git a/.cursor/skills/clean-build b/.cursor/skills/clean-build deleted file mode 120000 index 9acb51f9e6..0000000000 --- a/.cursor/skills/clean-build +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/clean-build \ No newline at end of file diff --git a/.cursor/skills/code-review b/.cursor/skills/code-review deleted file mode 120000 index 2d88efdf94..0000000000 --- a/.cursor/skills/code-review +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/code-review \ No newline at end of file diff --git a/.cursor/skills/codegen b/.cursor/skills/codegen deleted file mode 120000 index e303260777..0000000000 --- a/.cursor/skills/codegen +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/codegen \ No newline at end of file diff --git a/.cursor/skills/create-pr b/.cursor/skills/create-pr deleted file mode 120000 index d5b42b7251..0000000000 --- a/.cursor/skills/create-pr +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/create-pr \ No newline at end of file diff --git a/.cursor/skills/dispatch-release b/.cursor/skills/dispatch-release deleted file mode 120000 index c732c75297..0000000000 --- a/.cursor/skills/dispatch-release +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/dispatch-release \ No newline at end of file diff --git a/.cursor/skills/find-skills b/.cursor/skills/find-skills deleted file mode 120000 index 0b17e357d7..0000000000 --- a/.cursor/skills/find-skills +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/find-skills \ No newline at end of file diff --git a/.cursor/skills/package-filter b/.cursor/skills/package-filter deleted file mode 120000 index 01d52eb5dc..0000000000 --- a/.cursor/skills/package-filter +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/package-filter \ No newline at end of file diff --git a/.cursor/skills/pre-commit-check b/.cursor/skills/pre-commit-check deleted file mode 120000 index ec3c587a30..0000000000 --- a/.cursor/skills/pre-commit-check +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/pre-commit-check \ No newline at end of file diff --git a/.cursor/skills/release-prep b/.cursor/skills/release-prep deleted file mode 120000 index 696b8a37d6..0000000000 --- a/.cursor/skills/release-prep +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/release-prep \ No newline at end of file diff --git a/.cursor/skills/run-tests b/.cursor/skills/run-tests deleted file mode 120000 index f56d6a181c..0000000000 --- a/.cursor/skills/run-tests +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/run-tests \ No newline at end of file diff --git a/.cursor/skills/test b/.cursor/skills/test deleted file mode 120000 index 20211d1708..0000000000 --- a/.cursor/skills/test +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/test \ No newline at end of file diff --git a/.cursor/skills/update-pr b/.cursor/skills/update-pr deleted file mode 120000 index 4ee7f19c0c..0000000000 --- a/.cursor/skills/update-pr +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/update-pr \ No newline at end of file diff --git a/.cursor/skills/update-storybook-snapshots b/.cursor/skills/update-storybook-snapshots deleted file mode 120000 index e4424bf9c9..0000000000 --- a/.cursor/skills/update-storybook-snapshots +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/update-storybook-snapshots \ No newline at end of file diff --git a/.cursor/skills/validate-before-merge b/.cursor/skills/validate-before-merge deleted file mode 120000 index 4b44d3b746..0000000000 --- a/.cursor/skills/validate-before-merge +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/validate-before-merge \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000000..9960484682 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMRT) + +if [[ -z "$STAGED_FILES" ]]; then + exit 0 +fi + +# Node (.) +NODE_FILES="$STAGED_FILES" +if echo "$NODE_FILES" | grep -qE '\.(ts|tsx|js|jsx|json|css|scss|yml|yaml)$|^package\.json$|^pnpm-lock\.yaml$|^bun\.lockb?$|^biome\.json$|^tsconfig\..*\.json$'; then + echo 'Running Node checks (.)...' + pnpm run lint:check + pnpm run test:ci + pnpm run build + + if ! git diff --quiet; then + echo 'Pre-commit modified files (likely formatting). Stage the changes and retry.' + git diff --name-only + exit 1 + fi +fi + +echo 'Pre-commit checks complete.' diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd4dc..5593735239 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -2,43 +2,29 @@ name: Claude Code Review on: pull_request: - types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" + types: [opened, synchronize, reopened, ready_for_review] -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' +permissions: + contents: read + pull-requests: write + issues: read + id-token: write +jobs: + claude-code-review: runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write steps: - - name: Checkout repository + - name: Checkout uses: actions/checkout@v4 with: - fetch-depth: 1 + fetch-depth: 0 - - name: Run Claude Code Review - id: claude-review + - name: Claude Review uses: anthropics/claude-code-action@v1 with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - + 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/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 index ea4a6bd4eb..eaa8097b66 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,4 @@ -pnpm run format --ui stream -pnpm run lint:check --ui stream +#!/usr/bin/env sh +set -e + +bash .githooks/pre-commit diff --git a/AGENT.md b/AGENT.md new file mode 120000 index 0000000000..47dc3e3d86 --- /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 0000000000..6bec5b1933 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,178 @@ +# Repository Guidelines + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Core Development + +- `pnpm dev` - Starts all development servers in parallel: + - localhost:3001 (Keychain) + - localhost:3002 (Next.js example) + - localhost:5174 (Svelte example) +- `pnpm build` - Builds all packages and dependencies +- `pnpm storybook` - Launches Storybook for component development + +### Testing + +- `pnpm test` - Runs keychain test suite (Vitest) +- `pnpm test:ci` - Runs tests in CI mode +- `pnpm test:storybook` - Runs Storybook visual regression tests +- `pnpm test:storybook:update` - Updates Storybook snapshots (requires Docker) +- `pnpm e2e` - Runs end-to-end tests with Playwright (currently disabled in CI) +- `pnpm e2e:ui` - Runs E2E tests with UI + +### Code Quality + +- `pnpm lint` - Runs linting and format checking +- `pnpm format` - Runs formatting and lint fixes + +### Package Management + +- `pnpm i` - Install dependencies +- `pnpm clean` - Clean all build artifacts and prune store +- `corepack enable pnpm` - Enable pnpm via corepack + +## Architecture Overview + +### Project Structure + +This is a **monorepo** using **pnpm workspaces** and **Turbo** for build orchestration. The project implements a gaming-specific smart contract wallet ecosystem for StarkNet. + +### Core Packages (`/packages/`) + +**Primary Applications:** + +- **`controller/`** - Main SDK implementing StarkNet account interfaces. Implements account abstractions and communicates with embedded keychain iframe for secure operations. +- **`keychain/`** - Sandboxed React app () responsible for sensitive operations like transaction signing, user authentication, and key management. +- **`connector/`** - Lightweight connector interface for easy integration with StarkNet React applications. + +**Supporting Packages:** + +- **`eslint/`** - Shared ESLint configuration +- **`tsconfig/`** - Shared TypeScript configuration +- **`torii-config/`** - Token metadata configuration + +### Integration Examples (`/examples/`) + +- **`next/`** - Next.js integration example with full transaction signing +- **`svelte/`** - Svelte integration example +- **`node/`** - Node.js server-side usage example + +### Technology Stack + +- **Frontend**: React 18, TypeScript 5.7, Next.js 15.3, Svelte, TailwindCSS 3.4, Vite 6.0 +- **Blockchain**: StarkNet 8.5.4, starknet.js, @starknet-react/core 5.0.1 +- **Testing**: Vitest 2.1.8, Jest 29, Playwright, Storybook 8.5 +- **Build**: Turbo, tsup, pnpm workspaces (v10) with catalog for deps +- **Authentication**: WebAuthn/Passkeys, Session Tokens, Auth0, Turnkey + +### Security Architecture + +The project uses an **iframe-based security model** where: + +- Sensitive operations (key management, signing) occur in the sandboxed keychain iframe +- Controller SDK communicates with keychain via secure postMessage +- Session tokens enable seamless gaming UX while maintaining security +- WebAuthn/Passkeys provide passwordless authentication + +### Development Workflow + +1. **Monorepo Dependencies**: `pnpm dev` automatically handles workspace dependencies +2. **Visual Testing**: Storybook provides component isolation and visual regression testing +3. **Account Import**: For local development, export accounts from production keychain using `window.cartridge.exportAccount()` and import with `window.cartridge.importAccount()` + +### Key Integration Points + +- **Multi-wallet Support**: Integrates with MetaMask, Solana wallets, etc. +- **Session Management**: Gaming-optimized session tokens for reduced signing friction +- **GraphQL Data Layer**: Account information and state management +- **Stripe Integration**: Fiat payment processing +- **Cross-platform**: Web, mobile WebView support + +### File Architecture Notes + +- **Storybook snapshots** in `__image_snapshots__/` for visual regression testing +- **Example apps** demonstrate various integration patterns + +## Claude Code Workflow Guidelines + +### Code Quality Requirements + +- **Always run linting/formatting** before committing: `pnpm lint` and `pnpm format` +- **TypeScript compliance** - All TypeScript errors must be resolved +- **Test coverage** - Run relevant tests after making changes: `pnpm test` for unit tests, `pnpm test:storybook` for visual regression + +### Common Development Tasks + +**Working with Components:** + +- After modifying components, run `pnpm storybook` to verify visually +- Update Storybook snapshots with `pnpm test:storybook:update` if needed + +**Adding New Features:** + +- Check existing patterns in `packages/controller/src/` for SDK features +- For UI changes, update keychain (`packages/keychain/`) as needed +- Test integration with examples in `examples/next/` or `examples/svelte/` + +**Debugging Integration Issues:** + +- Use browser dev tools with local keychain at +- Account import/export via `window.cartridge.exportAccount()` and `window.cartridge.importAccount()` +- Check iframe communication between controller and keychain + +**Monorepo Navigation:** + +- Use `pnpm --filter ` to run commands in specific packages +- Common filters: `@cartridge/controller`, `@cartridge/keychain`, `@cartridge/connector` +- Dependencies are automatically linked via workspace protocol + +### Testing Strategy + +- **Unit tests** in individual packages (Vitest for keychain, Jest for others) +- **Visual regression** via Storybook snapshots (Docker-based in CI) +- **E2E testing** with Playwright for full user flows (Note: currently disabled in CI) +- **Integration testing** via example applications +- **Pre-commit hooks** enforce formatting and linting (Husky) + +### Key Files to Check When Making Changes + +- `packages/controller/src/index.ts` - Main SDK exports +- `packages/keychain/src/components/` - UI components for wallet operations +- `packages/keychain/src/utils/api/codegen.yaml` - GraphQL codegen configuration +- `turbo.json` - Build dependencies and caching configuration +- `pnpm-workspace.yaml` - Package workspace configuration +- `.husky/pre-commit` - Pre-commit hooks for code quality + +### Build Process Notes + +- **WASM Dependencies**: controller-wasm and session-wasm built via `build:deps` task +- **Dual Builds**: Controller package has separate browser (Vite) and Node.js (tsup) builds +- **GraphQL Codegen**: Automatically runs during keychain build +- **Turbo Caching**: Aggressive caching for faster builds, use `pnpm clean` if issues arise + +## Agent Tooling + +- **Pre-commit hooks:** run `bin/setup-githooks` (configures `core.hooksPath` for this repo). + +- **Source of truth:** `.agents/`. +- **Symlinks:** `CLAUDE.md` is a symlink to this file (`AGENTS.md`). Editor/agent configs should symlink skills from `.agents/skills`. +- **Skills install/update:** + +```bash +npm_config_cache=/tmp/npm-cache npx -y skills add https://github.com/cartridge-gg/agents --skill create-pr create-a-plan --agent claude-code cursor -y +``` + +- **Configs:** + - `.agents/skills/` (canonical) + - `.claude/skills` -> `../.agents/skills` + - `.cursor/skills` -> `../.agents/skills` + +## Code Review Invariants + +- No secrets in code or logs. +- Keep diffs small and focused; avoid drive-by refactors. +- Add/adjust tests for behavior changes; keep CI green. +- Prefer check-only commands in CI (`format:check`, `lint:check`) and keep local hooks aligned. +- For Starknet/Cairo/Rust/crypto code: treat input validation, authZ, serialization, and signature/origin checks as **blocking** review items. diff --git a/CLAUDE.legacy.md b/CLAUDE.legacy.md new file mode 100644 index 0000000000..07500c1114 --- /dev/null +++ b/CLAUDE.legacy.md @@ -0,0 +1,153 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Core Development + +- `pnpm dev` - Starts all development servers in parallel: + - localhost:3001 (Keychain) + - localhost:3002 (Next.js example) + - localhost:5174 (Svelte example) +- `pnpm build` - Builds all packages and dependencies +- `pnpm storybook` - Launches Storybook for component development + +### Testing + +- `pnpm test` - Runs keychain test suite (Vitest) +- `pnpm test:ci` - Runs tests in CI mode +- `pnpm test:storybook` - Runs Storybook visual regression tests +- `pnpm test:storybook:update` - Updates Storybook snapshots (requires Docker) +- `pnpm e2e` - Runs end-to-end tests with Playwright (currently disabled in CI) +- `pnpm e2e:ui` - Runs E2E tests with UI + +### Code Quality + +- `pnpm lint` - Runs linting and format checking +- `pnpm format` - Runs formatting and lint fixes + +### Package Management + +- `pnpm i` - Install dependencies +- `pnpm clean` - Clean all build artifacts and prune store +- `corepack enable pnpm` - Enable pnpm via corepack + +## Architecture Overview + +### Project Structure + +This is a **monorepo** using **pnpm workspaces** and **Turbo** for build orchestration. The project implements a gaming-specific smart contract wallet ecosystem for StarkNet. + +### Core Packages (`/packages/`) + +**Primary Applications:** + +- **`controller/`** - Main SDK implementing StarkNet account interfaces. Implements account abstractions and communicates with embedded keychain iframe for secure operations. +- **`keychain/`** - Sandboxed React app () responsible for sensitive operations like transaction signing, user authentication, and key management. +- **`connector/`** - Lightweight connector interface for easy integration with StarkNet React applications. + +**Supporting Packages:** + +- **`eslint/`** - Shared ESLint configuration +- **`tsconfig/`** - Shared TypeScript configuration +- **`torii-config/`** - Token metadata configuration + +### Integration Examples (`/examples/`) + +- **`next/`** - Next.js integration example with full transaction signing +- **`svelte/`** - Svelte integration example +- **`node/`** - Node.js server-side usage example + +### Technology Stack + +- **Frontend**: React 18, TypeScript 5.7, Next.js 15.3, Svelte, TailwindCSS 3.4, Vite 6.0 +- **Blockchain**: StarkNet 8.5.4, starknet.js, @starknet-react/core 5.0.1 +- **Testing**: Vitest 2.1.8, Jest 29, Playwright, Storybook 8.5 +- **Build**: Turbo, tsup, pnpm workspaces (v10) with catalog for deps +- **Authentication**: WebAuthn/Passkeys, Session Tokens, Auth0, Turnkey + +### Security Architecture + +The project uses an **iframe-based security model** where: + +- Sensitive operations (key management, signing) occur in the sandboxed keychain iframe +- Controller SDK communicates with keychain via secure postMessage +- Session tokens enable seamless gaming UX while maintaining security +- WebAuthn/Passkeys provide passwordless authentication + +### Development Workflow + +1. **Monorepo Dependencies**: `pnpm dev` automatically handles workspace dependencies +2. **Visual Testing**: Storybook provides component isolation and visual regression testing +3. **Account Import**: For local development, export accounts from production keychain using `window.cartridge.exportAccount()` and import with `window.cartridge.importAccount()` + +### Key Integration Points + +- **Multi-wallet Support**: Integrates with MetaMask, Solana wallets, etc. +- **Session Management**: Gaming-optimized session tokens for reduced signing friction +- **GraphQL Data Layer**: Account information and state management +- **Stripe Integration**: Fiat payment processing +- **Cross-platform**: Web, mobile WebView support + +### File Architecture Notes + +- **Storybook snapshots** in `__image_snapshots__/` for visual regression testing +- **Example apps** demonstrate various integration patterns + +## Claude Code Workflow Guidelines + +### Code Quality Requirements + +- **Always run linting/formatting** before committing: `pnpm lint` and `pnpm format` +- **TypeScript compliance** - All TypeScript errors must be resolved +- **Test coverage** - Run relevant tests after making changes: `pnpm test` for unit tests, `pnpm test:storybook` for visual regression + +### Common Development Tasks + +**Working with Components:** + +- After modifying components, run `pnpm storybook` to verify visually +- Update Storybook snapshots with `pnpm test:storybook:update` if needed + +**Adding New Features:** + +- Check existing patterns in `packages/controller/src/` for SDK features +- For UI changes, update keychain (`packages/keychain/`) as needed +- Test integration with examples in `examples/next/` or `examples/svelte/` + +**Debugging Integration Issues:** + +- Use browser dev tools with local keychain at +- Account import/export via `window.cartridge.exportAccount()` and `window.cartridge.importAccount()` +- Check iframe communication between controller and keychain + +**Monorepo Navigation:** + +- Use `pnpm --filter ` to run commands in specific packages +- Common filters: `@cartridge/controller`, `@cartridge/keychain`, `@cartridge/connector` +- Dependencies are automatically linked via workspace protocol + +### Testing Strategy + +- **Unit tests** in individual packages (Vitest for keychain, Jest for others) +- **Visual regression** via Storybook snapshots (Docker-based in CI) +- **E2E testing** with Playwright for full user flows (Note: currently disabled in CI) +- **Integration testing** via example applications +- **Pre-commit hooks** enforce formatting and linting (Husky) + +### Key Files to Check When Making Changes + +- `packages/controller/src/index.ts` - Main SDK exports +- `packages/keychain/src/components/` - UI components for wallet operations +- `packages/keychain/src/utils/api/codegen.yaml` - GraphQL codegen configuration +- `turbo.json` - Build dependencies and caching configuration +- `pnpm-workspace.yaml` - Package workspace configuration +- `.husky/pre-commit` - Pre-commit hooks for code quality + +### Build Process Notes + +- **WASM Dependencies**: controller-wasm and session-wasm built via `build:deps` task +- **Dual Builds**: Controller package has separate browser (Vite) and Node.js (tsup) builds +- **GraphQL Codegen**: Automatically runs during keychain build +- **Turbo Caching**: Aggressive caching for faster builds, use `pnpm clean` if issues arise diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 07500c1114..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,153 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Development Commands - -### Core Development - -- `pnpm dev` - Starts all development servers in parallel: - - localhost:3001 (Keychain) - - localhost:3002 (Next.js example) - - localhost:5174 (Svelte example) -- `pnpm build` - Builds all packages and dependencies -- `pnpm storybook` - Launches Storybook for component development - -### Testing - -- `pnpm test` - Runs keychain test suite (Vitest) -- `pnpm test:ci` - Runs tests in CI mode -- `pnpm test:storybook` - Runs Storybook visual regression tests -- `pnpm test:storybook:update` - Updates Storybook snapshots (requires Docker) -- `pnpm e2e` - Runs end-to-end tests with Playwright (currently disabled in CI) -- `pnpm e2e:ui` - Runs E2E tests with UI - -### Code Quality - -- `pnpm lint` - Runs linting and format checking -- `pnpm format` - Runs formatting and lint fixes - -### Package Management - -- `pnpm i` - Install dependencies -- `pnpm clean` - Clean all build artifacts and prune store -- `corepack enable pnpm` - Enable pnpm via corepack - -## Architecture Overview - -### Project Structure - -This is a **monorepo** using **pnpm workspaces** and **Turbo** for build orchestration. The project implements a gaming-specific smart contract wallet ecosystem for StarkNet. - -### Core Packages (`/packages/`) - -**Primary Applications:** - -- **`controller/`** - Main SDK implementing StarkNet account interfaces. Implements account abstractions and communicates with embedded keychain iframe for secure operations. -- **`keychain/`** - Sandboxed React app () responsible for sensitive operations like transaction signing, user authentication, and key management. -- **`connector/`** - Lightweight connector interface for easy integration with StarkNet React applications. - -**Supporting Packages:** - -- **`eslint/`** - Shared ESLint configuration -- **`tsconfig/`** - Shared TypeScript configuration -- **`torii-config/`** - Token metadata configuration - -### Integration Examples (`/examples/`) - -- **`next/`** - Next.js integration example with full transaction signing -- **`svelte/`** - Svelte integration example -- **`node/`** - Node.js server-side usage example - -### Technology Stack - -- **Frontend**: React 18, TypeScript 5.7, Next.js 15.3, Svelte, TailwindCSS 3.4, Vite 6.0 -- **Blockchain**: StarkNet 8.5.4, starknet.js, @starknet-react/core 5.0.1 -- **Testing**: Vitest 2.1.8, Jest 29, Playwright, Storybook 8.5 -- **Build**: Turbo, tsup, pnpm workspaces (v10) with catalog for deps -- **Authentication**: WebAuthn/Passkeys, Session Tokens, Auth0, Turnkey - -### Security Architecture - -The project uses an **iframe-based security model** where: - -- Sensitive operations (key management, signing) occur in the sandboxed keychain iframe -- Controller SDK communicates with keychain via secure postMessage -- Session tokens enable seamless gaming UX while maintaining security -- WebAuthn/Passkeys provide passwordless authentication - -### Development Workflow - -1. **Monorepo Dependencies**: `pnpm dev` automatically handles workspace dependencies -2. **Visual Testing**: Storybook provides component isolation and visual regression testing -3. **Account Import**: For local development, export accounts from production keychain using `window.cartridge.exportAccount()` and import with `window.cartridge.importAccount()` - -### Key Integration Points - -- **Multi-wallet Support**: Integrates with MetaMask, Solana wallets, etc. -- **Session Management**: Gaming-optimized session tokens for reduced signing friction -- **GraphQL Data Layer**: Account information and state management -- **Stripe Integration**: Fiat payment processing -- **Cross-platform**: Web, mobile WebView support - -### File Architecture Notes - -- **Storybook snapshots** in `__image_snapshots__/` for visual regression testing -- **Example apps** demonstrate various integration patterns - -## Claude Code Workflow Guidelines - -### Code Quality Requirements - -- **Always run linting/formatting** before committing: `pnpm lint` and `pnpm format` -- **TypeScript compliance** - All TypeScript errors must be resolved -- **Test coverage** - Run relevant tests after making changes: `pnpm test` for unit tests, `pnpm test:storybook` for visual regression - -### Common Development Tasks - -**Working with Components:** - -- After modifying components, run `pnpm storybook` to verify visually -- Update Storybook snapshots with `pnpm test:storybook:update` if needed - -**Adding New Features:** - -- Check existing patterns in `packages/controller/src/` for SDK features -- For UI changes, update keychain (`packages/keychain/`) as needed -- Test integration with examples in `examples/next/` or `examples/svelte/` - -**Debugging Integration Issues:** - -- Use browser dev tools with local keychain at -- Account import/export via `window.cartridge.exportAccount()` and `window.cartridge.importAccount()` -- Check iframe communication between controller and keychain - -**Monorepo Navigation:** - -- Use `pnpm --filter ` to run commands in specific packages -- Common filters: `@cartridge/controller`, `@cartridge/keychain`, `@cartridge/connector` -- Dependencies are automatically linked via workspace protocol - -### Testing Strategy - -- **Unit tests** in individual packages (Vitest for keychain, Jest for others) -- **Visual regression** via Storybook snapshots (Docker-based in CI) -- **E2E testing** with Playwright for full user flows (Note: currently disabled in CI) -- **Integration testing** via example applications -- **Pre-commit hooks** enforce formatting and linting (Husky) - -### Key Files to Check When Making Changes - -- `packages/controller/src/index.ts` - Main SDK exports -- `packages/keychain/src/components/` - UI components for wallet operations -- `packages/keychain/src/utils/api/codegen.yaml` - GraphQL codegen configuration -- `turbo.json` - Build dependencies and caching configuration -- `pnpm-workspace.yaml` - Package workspace configuration -- `.husky/pre-commit` - Pre-commit hooks for code quality - -### Build Process Notes - -- **WASM Dependencies**: controller-wasm and session-wasm built via `build:deps` task -- **Dual Builds**: Controller package has separate browser (Vite) and Node.js (tsup) builds -- **GraphQL Codegen**: Automatically runs during keychain build -- **Turbo Caching**: Aggressive caching for faster builds, use `pnpm clean` if issues arise diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/bin/setup-githooks b/bin/setup-githooks new file mode 100755 index 0000000000..e92bcd4799 --- /dev/null +++ b/bin/setup-githooks @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(git rev-parse --show-toplevel) +cd "$ROOT" + +if [[ -d .husky ]]; then + git config core.hooksPath .husky + echo "Configured git hooksPath to .husky" +else + git config core.hooksPath .githooks + echo "Configured git hooksPath to .githooks" +fi