{"owner":"wshobson","repo":"agents","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","GEMINI.md","plugins/ui-design/skills/mobile-ios-design/references/swiftui-components.md"],"skills":{"AGENTS.md":"# claude-agents — multi-harness agentic plugin marketplace\n\nProduction-ready agentic-workflow building blocks: **94 plugins** (90 local + 4 external), **203 agents**, **175 skills**, **109 commands**. Native source-of-truth for Claude Code; also consumed by OpenAI Codex CLI, Cursor, OpenCode, and Gemini CLI from a single Markdown source.\n\nThis file is the canonical context file. Codex / Cursor / OpenCode read it directly. Claude Code reads it via `CLAUDE.md`, a symlink to this file. Gemini CLI reads it via `gemini-extension.json` (`contextFileName`) / `.gemini/settings.json`.\n\n> **Read this file like a table of contents.** Detail lives in `docs/`. Authoring conventions live in `docs/authoring.md`. Per-harness setup and capability deltas live in [`docs/harnesses.md`](docs/harnesses.md). Gemini-specific setup is in `GEMINI.md` (also auto-loaded by Gemini CLI). This file should never grow beyond ~150 lines (per OpenAI's [harness-engineering](https://openai.com/index/harness-engineering/) practice).\n\n## Map\n\n- **[ARCHITECTURE.md](ARCHITECTURE.md)** — top-level architectural overview (adapter framework, source-of-truth invariant, capability matrix summary)\n- **[docs/architecture.md](docs/architecture.md)** — detailed design principles\n- **[docs/plugins.md](docs/plugins.md)** — full plugin catalog (94 plugins by category)\n- **[docs/agents.md](docs/agents.md)** — agent reference (203 agents, model tiers)\n- **[docs/agent-skills.md](docs/agent-skills.md)** — skill reference (progressive disclosure model)\n- **[docs/usage.md](docs/usage.md)** — commands, workflows, examples\n- **[docs/authoring.md](docs/authoring.md)** — portable-content style guide (read before adding plugins)\n- **[docs/harnesses.md](docs/harnesses.md)** — per-harness capability matrix\n- **[docs/plugin-eval.md](docs/plugin-eval.md)** — three-layer quality evaluation framework\n- **[docs/round-trip-results.md](docs/round-trip-results.md)** — real-CLI verification recipes\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** — how to contribute\n\n## Working in this repo\n\n- Python tooling: **uv** (package manager), **ruff** (lint/format), **ty** (type check). Do not use pip / mypy / black.\n- Plugins live under `plugins/<name>/` with auto-discovery — see `docs/authoring.md` for frontmatter shapes.\n- Plugin names: lowercase, hyphen-separated. Never use `__` (it's the adapter namespace separator).\n- Never commit secrets. Never run destructive git (force-push, `reset --hard`, branch -D) without explicit ask.\n\n## Quality gates (run these before pushing)\n\n```bash\nmake validate STRICT=1     # structural validation across all harness outputs\nmake garden                # drift detection (dead links, stale artifacts, oversize skills)\nmake test                  # full pytest suite (plugin-eval + tools/tests/)\nmake smoke-test            # real-CLI subprocess tests against generated artifacts\n```\n\nCI (`.github/workflows/validate.yml`) runs all four on every PR plus installs OpenCode + Gemini CLI for live verification.\n\n## Regenerating per-harness artifacts\n\n```bash\nmake generate HARNESS=codex      # .codex/skills, .codex/agents, .codex/plugins/<p>/, .agents/plugins/marketplace.json\nmake generate HARNESS=cursor     # .cursor-plugin/{marketplace,plugin}.json, .cursor/rules/\nmake generate HARNESS=opencode   # .opencode/{skills,agents,commands,plugins}/, opencode.json\nmake generate HARNESS=gemini     # skills/, agents/, commands/ at extension root\nmake generate-all                # all four\n```\n\nGenerated artifacts are **committed** so each harness installs natively from a clone / GitHub URL (native-install commands in [`docs/harnesses.md`](docs/harnesses.md)). Run `make generate-all` before committing source changes — CI fails on drift. Source-of-truth lives only under `plugins/`; never hand-edit generated files.\n\n## Skills (cross-harness)\n\n175 skills under `plugins/*/skills/<n>/SKILL.md` — discoverable by every harness:\n\n- **Claude Code**: auto-discovery via Anthropic's SKILL.md spec\n- **Codex CLI**: mirrored to `.codex/skills/<plugin>__<skill>/` (8 KB body cap; detail in `references/details.md`)\n- **OpenCode**: mirrored to `.opencode/skills/<plugin>-<skill>/` using hyphenated names for global install\n- **Cursor**: reads `.claude/skills/` directly (no re-emit)\n- **Gemini CLI**: native skills at `skills/<plugin>__<skill>/SKILL.md`\n\nTop-level `skills/` is Gemini output; do not use it for OpenCode installs.\n\n## Subagents (cross-harness)\n\n203 subagents under `plugins/*/agents/<name>.md`. Per-harness transpilation:\n\n- **Codex**: `.codex/agents/<plugin>__<agent>.toml` (drop `tools:`, map model alias to the GPT-5.x family, infer `sandbox_mode`)\n- **OpenCode**: `.opencode/agents/<plugin>__<agent>.md` with `mode: subagent` + `permission:` block (locked agents — those with source `tools: []` — get deny-everything except base `skill`/`task`)\n- **Gemini**: `agents/<plugin>__<agent>.md` (April 2026 subagent spec)\n- **Cursor**: reads `.claude/agents/` directly\n\n## Why this file is short\n\nPer OpenAI's harness-engineering practice: this file is a **map**, not an encyclopedia. Procedural detail lives in skills (loaded on demand by agents). Reference material lives in `docs/` (loaded when an agent navigates). A single bloated AGENTS.md crowds out the task, rots quickly, and is hard to verify mechanically. Keep it lean; push detail elsewhere.\n","GEMINI.md":"# Gemini CLI — setup guide\n\n> The canonical context file is [`AGENTS.md`](AGENTS.md) at the repo root. Gemini CLI reads it via `.gemini/settings.json` (`context.fileName`). This guide covers Gemini-specific setup only.\n\n## Install\n\n```bash\ngemini extensions install https://github.com/wshobson/agents\ncd ~/.gemini/extensions/claude-code-workflows\nmake generate HARNESS=gemini\n# restart Gemini CLI\n```\n\n## What you get\n\n- **156 skills** at `skills/<plugin>__<skill>/SKILL.md` — described in `AGENTS.md`. Describe a task to activate.\n- **192 subagents** at `agents/<plugin>__<agent>.md` — invoke with `@<agent>`.\n- **102 slash commands** at `/<plugin>:<command>` — use `/help` to list.\n\n## Companion Memory Extension\n\nPensyve is maintained upstream as a separate Gemini CLI extension with MCP-backed\nmemory and context injection:\n\n```bash\ngemini extensions install https://github.com/major7apps/pensyve\n```\n\n## Gemini-specific differences\n\n| Capability | Claude Code | Gemini CLI |\n|---|---|---|\n| Plugin installation | `/plugin install` | `gemini extensions install <url>` |\n| Context file | reads `CLAUDE.md` (a symlink to `AGENTS.md`) | reads via `.gemini/settings.json` redirect to AGENTS.md |\n| Per-agent tool allowlist | `tools:` (always) | `tools:` (honored — remapped to Gemini-native names) |\n| Skill / agent discovery | native | native (skills/, agents/ at extension root) |\n| Model assignment | per-agent | session-level (override via `model:` frontmatter) |\n| `TodoWrite` tool | yes | no equivalent |\n\n## Regenerating\n\n```bash\nmake generate HARNESS=gemini                            # all plugins\nmake generate HARNESS=gemini PLUGIN=javascript-typescript   # one plugin\nmake clean-generated HARNESS=gemini                     # remove output\n```\n\n## See also\n\n- [`AGENTS.md`](AGENTS.md) — canonical context (cross-harness conventions)\n- [`docs/harnesses.md`](docs/harnesses.md) — full capability matrix\n- [`docs/round-trip-results.md`](docs/round-trip-results.md) — Gemini round-trip verification recipe\n","plugins/ui-design/skills/mobile-ios-design/references/swiftui-components.md":"# SwiftUI Component Library\n\n## Lists and Collections\n\n### Basic List\n\n```swift\nstruct ItemListView: View {\n    @State private var items: [Item] = []\n\n    var body: some View {\n        List {\n            ForEach(items) { item in\n                ItemRow(item: item)\n            }\n            .onDelete(perform: deleteItems)\n            .onMove(perform: moveItems)\n        }\n        .listStyle(.insetGrouped)\n        .refreshable {\n            await loadItems()\n        }\n    }\n\n    private func deleteItems(at offsets: IndexSet) {\n        items.remove(atOffsets: offsets)\n    }\n\n    private func moveItems(from source: IndexSet, to destination: Int) {\n        items.move(fromOffsets: source, toOffset: destination)\n    }\n}\n```\n\n### Sectioned List\n\n```swift\nstruct SectionedListView: View {\n    let groupedItems: [String: [Item]]\n\n    var body: some View {\n        List {\n            ForEach(groupedItems.keys.sorted(), id: \\.self) { key in\n                Section(header: Text(key)) {\n                    ForEach(groupedItems[key] ?? []) { item in\n                        ItemRow(item: item)\n                    }\n                }\n            }\n        }\n        .listStyle(.sidebar)\n    }\n}\n```\n\n### Search Integration\n\n```swift\nstruct SearchableListView: View {\n    @State private var searchText = \"\"\n    @State private var items: [Item] = []\n\n    var filteredItems: [Item] {\n        if searchText.isEmpty {\n            return items\n        }\n        return items.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n    }\n\n    var body: some View {\n        NavigationStack {\n            List(filteredItems) { item in\n                ItemRow(item: item)\n            }\n            .searchable(text: $searchText, prompt: \"Search items\")\n            .searchSuggestions {\n                ForEach(searchSuggestions, id: \\.self) { suggestion in\n                    Text(suggestion)\n                        .searchCompletion(suggestion)\n                }\n            }\n            .navigationTitle(\"Items\")\n        }\n    }\n}\n```\n\n## Forms and Input\n\n### Settings Form\n\n```swift\nstruct SettingsView: View {\n    @AppStorage(\"notifications\") private var notificationsEnabled = true\n    @AppStorage(\"soundEnabled\") private var soundEnabled = true\n    @State private var selectedTheme = Theme.system\n    @State private var username = \"\"\n\n    var body: some View {\n        Form {\n            Section(\"Account\") {\n                TextField(\"Username\", text: $username)\n                    .textContentType(.username)\n                    .autocorrectionDisabled()\n            }\n\n            Section(\"Preferences\") {\n                Toggle(\"Enable Notifications\", isOn: $notificationsEnabled)\n                Toggle(\"Sound Effects\", isOn: $soundEnabled)\n\n                Picker(\"Theme\", selection: $selectedTheme) {\n                    ForEach(Theme.allCases) { theme in\n                        Text(theme.rawValue).tag(theme)\n                    }\n                }\n            }\n\n            Section(\"About\") {\n                LabeledContent(\"Version\", value: \"1.0.0\")\n\n                Link(destination: URL(string: \"https://example.com/privacy\")!) {\n                    Text(\"Privacy Policy\")\n                }\n            }\n        }\n        .navigationTitle(\"Settings\")\n    }\n}\n```\n\n### Custom Input Fields\n\n```swift\nstruct ValidatedTextField: View {\n    let title: String\n    @Binding var text: String\n    let validation: (String) -> Bool\n\n    @State private var isValid = true\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(title)\n                .font(.caption)\n                .foregroundStyle(.secondary)\n\n            TextField(title, text: $text)\n                .textFieldStyle(.roundedBorder)\n                .focused($isFocused)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 8)\n                        .stroke(borderColor, lineWidth: 1)\n                )\n                .onChange(of: text) { _, newValue in\n                    isValid = validation(newValue)\n                }\n\n            if !isValid && !text.isEmpty {\n                Text(\"Invalid input\")\n                    .font(.caption)\n                    .foregroundStyle(.red)\n            }\n        }\n    }\n\n    private var borderColor: Color {\n        if isFocused {\n            return isValid ? .blue : .red\n        }\n        return .clear\n    }\n}\n```\n\n## Buttons and Actions\n\n### Button Styles\n\n```swift\n// Primary filled button\nButton(\"Continue\") {\n    // action\n}\n.buttonStyle(.borderedProminent)\n.controlSize(.large)\n\n// Secondary button\nButton(\"Cancel\") {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Destructive button\nButton(\"Delete\", role: .destructive) {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Custom button style\nstruct ScaleButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n    }\n}\n```\n\n### Menu and Context Menu\n\n```swift\n// Menu button\nMenu {\n    Button(\"Edit\", systemImage: \"pencil\") { }\n    Button(\"Duplicate\", systemImage: \"doc.on.doc\") { }\n    Divider()\n    Button(\"Delete\", systemImage: \"trash\", role: .destructive) { }\n} label: {\n    Image(systemName: \"ellipsis.circle\")\n}\n\n// Context menu on any view\nText(\"Long press me\")\n    .contextMenu {\n        Button(\"Copy\", systemImage: \"doc.on.doc\") { }\n        Button(\"Share\", systemImage: \"square.and.arrow.up\") { }\n    } preview: {\n        ItemPreviewView()\n    }\n```\n\n## Sheets and Modals\n\n### Sheet Presentation\n\n```swift\nstruct ParentView: View {\n    @State private var showSettings = false\n    @State private var selectedItem: Item?\n\n    var body: some View {\n        VStack {\n            Button(\"Settings\") {\n                showSettings = true\n            }\n        }\n        .sheet(isPresented: $showSettings) {\n            SettingsSheet()\n                .presentationDetents([.medium, .large])\n                .presentationDragIndicator(.visible)\n        }\n        .sheet(item: $selectedItem) { item in\n            ItemDetailSheet(item: item)\n                .presentationDetents([.height(300), .large])\n                .presentationCornerRadius(24)\n        }\n    }\n}\n\nstruct SettingsSheet: View {\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        NavigationStack {\n            SettingsContent()\n                .toolbar {\n                    ToolbarItem(placement: .confirmationAction) {\n                        Button(\"Done\") {\n                            dismiss()\n                        }\n                    }\n                }\n        }\n    }\n}\n```\n\n### Confirmation Dialog\n\n```swift\nstruct DeleteConfirmationView: View {\n    @State private var showConfirmation = false\n\n    var body: some View {\n        Button(\"Delete Account\", role: .destructive) {\n            showConfirmation = true\n        }\n        .confirmationDialog(\n            \"Delete Account\",\n            isPresented: $showConfirmation,\n            titleVisibility: .visible\n        ) {\n            Button(\"Delete\", role: .destructive) {\n                deleteAccount()\n            }\n            Button(\"Cancel\", role: .cancel) { }\n        } message: {\n            Text(\"This action cannot be undone.\")\n        }\n    }\n}\n```\n\n## Loading and Progress\n\n### Progress Indicators\n\n```swift\n// Indeterminate spinner\nProgressView()\n    .progressViewStyle(.circular)\n\n// Determinate progress\nProgressView(value: downloadProgress, total: 1.0) {\n    Text(\"Downloading...\")\n} currentValueLabel: {\n    Text(\"\\(Int(downloadProgress * 100))%\")\n}\n\n// Custom loading view\nstruct LoadingOverlay: View {\n    let message: String\n\n    var body: some View {\n        ZStack {\n            Color.black.opacity(0.4)\n                .ignoresSafeArea()\n\n            VStack(spacing: 16) {\n                ProgressView()\n                    .scaleEffect(1.5)\n                    .tint(.white)\n\n                Text(message)\n                    .font(.subheadline)\n                    .foregroundStyle(.white)\n            }\n            .padding(24)\n            .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))\n        }\n    }\n}\n```\n\n### Skeleton Loading\n\n```swift\nstruct SkeletonRow: View {\n    @State private var isAnimating = false\n\n    var body: some View {\n        HStack(spacing: 12) {\n            Circle()\n                .fill(.gray.opacity(0.3))\n                .frame(width: 44, height: 44)\n\n            VStack(alignment: .leading, spacing: 8) {\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.3))\n                    .frame(height: 14)\n                    .frame(maxWidth: 200)\n\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.2))\n                    .frame(height: 12)\n                    .frame(maxWidth: 150)\n            }\n        }\n        .opacity(isAnimating ? 0.5 : 1.0)\n        .animation(.easeInOut(duration: 0.8).repeatForever(), value: isAnimating)\n        .onAppear { isAnimating = true }\n    }\n}\n```\n\n## Async Content Loading\n\n### AsyncImage\n\n```swift\nAsyncImage(url: imageURL) { phase in\n    switch phase {\n    case .empty:\n        ProgressView()\n    case .success(let image):\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fill)\n    case .failure:\n        Image(systemName: \"photo\")\n            .foregroundStyle(.secondary)\n    @unknown default:\n        EmptyView()\n    }\n}\n.frame(width: 100, height: 100)\n.clipShape(RoundedRectangle(cornerRadius: 8))\n```\n\n### Task-Based Loading\n\n```swift\nstruct AsyncContentView: View {\n    @State private var items: [Item] = []\n    @State private var isLoading = true\n    @State private var error: Error?\n\n    var body: some View {\n        Group {\n            if isLoading {\n                ProgressView(\"Loading...\")\n            } else if let error {\n                ContentUnavailableView(\n                    \"Failed to Load\",\n                    systemImage: \"exclamationmark.triangle\",\n                    description: Text(error.localizedDescription)\n                )\n            } else if items.isEmpty {\n                ContentUnavailableView(\n                    \"No Items\",\n                    systemImage: \"tray\",\n                    description: Text(\"Add your first item to get started.\")\n                )\n            } else {\n                List(items) { item in\n                    ItemRow(item: item)\n                }\n            }\n        }\n        .task {\n            await loadItems()\n        }\n    }\n\n    private func loadItems() async {\n        do {\n            items = try await api.fetchItems()\n            isLoading = false\n        } catch {\n            self.error = error\n            isLoading = false\n        }\n    }\n}\n```\n\n## Animations\n\n### Implicit Animations\n\n```swift\nstruct AnimatedCard: View {\n    @State private var isExpanded = false\n\n    var body: some View {\n        VStack {\n            Text(\"Tap to expand\")\n\n            if isExpanded {\n                Text(\"Additional content here\")\n                    .transition(.move(edge: .top).combined(with: .opacity))\n            }\n        }\n        .padding()\n        .frame(maxWidth: .infinity)\n        .background(.blue.opacity(0.1), in: RoundedRectangle(cornerRadius: 12))\n        .onTapGesture {\n            withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {\n                isExpanded.toggle()\n            }\n        }\n    }\n}\n```\n\n### Custom Transitions\n\n```swift\nextension AnyTransition {\n    static var slideAndFade: AnyTransition {\n        .asymmetric(\n            insertion: .move(edge: .trailing).combined(with: .opacity),\n            removal: .move(edge: .leading).combined(with: .opacity)\n        )\n    }\n\n    static var scaleAndFade: AnyTransition {\n        .scale(scale: 0.8).combined(with: .opacity)\n    }\n}\n```\n\n### Phase Animator (iOS 17+)\n\n```swift\nstruct PulsingButton: View {\n    var body: some View {\n        Button(\"Tap Me\") { }\n            .buttonStyle(.borderedProminent)\n            .phaseAnimator([false, true]) { content, phase in\n                content\n                    .scaleEffect(phase ? 1.05 : 1.0)\n            } animation: { _ in\n                .easeInOut(duration: 0.5)\n            }\n    }\n}\n```\n\n## Gestures\n\n### Drag Gesture\n\n```swift\nstruct DraggableCard: View {\n    @State private var offset = CGSize.zero\n    @State private var isDragging = false\n\n    var body: some View {\n        RoundedRectangle(cornerRadius: 16)\n            .fill(.blue)\n            .frame(width: 200, height: 150)\n            .offset(offset)\n            .scaleEffect(isDragging ? 1.05 : 1.0)\n            .gesture(\n                DragGesture()\n                    .onChanged { value in\n                        offset = value.translation\n                        isDragging = true\n                    }\n                    .onEnded { _ in\n                        withAnimation(.spring()) {\n                            offset = .zero\n                            isDragging = false\n                        }\n                    }\n            )\n    }\n}\n```\n\n### Simultaneous Gestures\n\n```swift\nstruct ZoomableImage: View {\n    @State private var scale: CGFloat = 1.0\n    @State private var lastScale: CGFloat = 1.0\n\n    var body: some View {\n        Image(\"photo\")\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n            .scaleEffect(scale)\n            .gesture(\n                MagnificationGesture()\n                    .onChanged { value in\n                        scale = lastScale * value\n                    }\n                    .onEnded { _ in\n                        lastScale = scale\n                    }\n            )\n            .gesture(\n                TapGesture(count: 2)\n                    .onEnded {\n                        withAnimation {\n                            scale = 1.0\n                            lastScale = 1.0\n                        }\n                    }\n            )\n    }\n}\n```\n"},"files":{"AGENTS.md":"# claude-agents — multi-harness agentic plugin marketplace\n\nProduction-ready agentic-workflow building blocks: **94 plugins** (90 local + 4 external), **203 agents**, **175 skills**, **109 commands**. Native source-of-truth for Claude Code; also consumed by OpenAI Codex CLI, Cursor, OpenCode, and Gemini CLI from a single Markdown source.\n\nThis file is the canonical context file. Codex / Cursor / OpenCode read it directly. Claude Code reads it via `CLAUDE.md`, a symlink to this file. Gemini CLI reads it via `gemini-extension.json` (`contextFileName`) / `.gemini/settings.json`.\n\n> **Read this file like a table of contents.** Detail lives in `docs/`. Authoring conventions live in `docs/authoring.md`. Per-harness setup and capability deltas live in [`docs/harnesses.md`](docs/harnesses.md). Gemini-specific setup is in `GEMINI.md` (also auto-loaded by Gemini CLI). This file should never grow beyond ~150 lines (per OpenAI's [harness-engineering](https://openai.com/index/harness-engineering/) practice).\n\n## Map\n\n- **[ARCHITECTURE.md](ARCHITECTURE.md)** — top-level architectural overview (adapter framework, source-of-truth invariant, capability matrix summary)\n- **[docs/architecture.md](docs/architecture.md)** — detailed design principles\n- **[docs/plugins.md](docs/plugins.md)** — full plugin catalog (94 plugins by category)\n- **[docs/agents.md](docs/agents.md)** — agent reference (203 agents, model tiers)\n- **[docs/agent-skills.md](docs/agent-skills.md)** — skill reference (progressive disclosure model)\n- **[docs/usage.md](docs/usage.md)** — commands, workflows, examples\n- **[docs/authoring.md](docs/authoring.md)** — portable-content style guide (read before adding plugins)\n- **[docs/harnesses.md](docs/harnesses.md)** — per-harness capability matrix\n- **[docs/plugin-eval.md](docs/plugin-eval.md)** — three-layer quality evaluation framework\n- **[docs/round-trip-results.md](docs/round-trip-results.md)** — real-CLI verification recipes\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** — how to contribute\n\n## Working in this repo\n\n- Python tooling: **uv** (package manager), **ruff** (lint/format), **ty** (type check). Do not use pip / mypy / black.\n- Plugins live under `plugins/<name>/` with auto-discovery — see `docs/authoring.md` for frontmatter shapes.\n- Plugin names: lowercase, hyphen-separated. Never use `__` (it's the adapter namespace separator).\n- Never commit secrets. Never run destructive git (force-push, `reset --hard`, branch -D) without explicit ask.\n\n## Quality gates (run these before pushing)\n\n```bash\nmake validate STRICT=1     # structural validation across all harness outputs\nmake garden                # drift detection (dead links, stale artifacts, oversize skills)\nmake test                  # full pytest suite (plugin-eval + tools/tests/)\nmake smoke-test            # real-CLI subprocess tests against generated artifacts\n```\n\nCI (`.github/workflows/validate.yml`) runs all four on every PR plus installs OpenCode + Gemini CLI for live verification.\n\n## Regenerating per-harness artifacts\n\n```bash\nmake generate HARNESS=codex      # .codex/skills, .codex/agents, .codex/plugins/<p>/, .agents/plugins/marketplace.json\nmake generate HARNESS=cursor     # .cursor-plugin/{marketplace,plugin}.json, .cursor/rules/\nmake generate HARNESS=opencode   # .opencode/{skills,agents,commands,plugins}/, opencode.json\nmake generate HARNESS=gemini     # skills/, agents/, commands/ at extension root\nmake generate-all                # all four\n```\n\nGenerated artifacts are **committed** so each harness installs natively from a clone / GitHub URL (native-install commands in [`docs/harnesses.md`](docs/harnesses.md)). Run `make generate-all` before committing source changes — CI fails on drift. Source-of-truth lives only under `plugins/`; never hand-edit generated files.\n\n## Skills (cross-harness)\n\n175 skills under `plugins/*/skills/<n>/SKILL.md` — discoverable by every harness:\n\n- **Claude Code**: auto-discovery via Anthropic's SKILL.md spec\n- **Codex CLI**: mirrored to `.codex/skills/<plugin>__<skill>/` (8 KB body cap; detail in `references/details.md`)\n- **OpenCode**: mirrored to `.opencode/skills/<plugin>-<skill>/` using hyphenated names for global install\n- **Cursor**: reads `.claude/skills/` directly (no re-emit)\n- **Gemini CLI**: native skills at `skills/<plugin>__<skill>/SKILL.md`\n\nTop-level `skills/` is Gemini output; do not use it for OpenCode installs.\n\n## Subagents (cross-harness)\n\n203 subagents under `plugins/*/agents/<name>.md`. Per-harness transpilation:\n\n- **Codex**: `.codex/agents/<plugin>__<agent>.toml` (drop `tools:`, map model alias to the GPT-5.x family, infer `sandbox_mode`)\n- **OpenCode**: `.opencode/agents/<plugin>__<agent>.md` with `mode: subagent` + `permission:` block (locked agents — those with source `tools: []` — get deny-everything except base `skill`/`task`)\n- **Gemini**: `agents/<plugin>__<agent>.md` (April 2026 subagent spec)\n- **Cursor**: reads `.claude/agents/` directly\n\n## Why this file is short\n\nPer OpenAI's harness-engineering practice: this file is a **map**, not an encyclopedia. Procedural detail lives in skills (loaded on demand by agents). Reference material lives in `docs/` (loaded when an agent navigates). A single bloated AGENTS.md crowds out the task, rots quickly, and is hard to verify mechanically. Keep it lean; push detail elsewhere.\n","GEMINI.md":"# Gemini CLI — setup guide\n\n> The canonical context file is [`AGENTS.md`](AGENTS.md) at the repo root. Gemini CLI reads it via `.gemini/settings.json` (`context.fileName`). This guide covers Gemini-specific setup only.\n\n## Install\n\n```bash\ngemini extensions install https://github.com/wshobson/agents\ncd ~/.gemini/extensions/claude-code-workflows\nmake generate HARNESS=gemini\n# restart Gemini CLI\n```\n\n## What you get\n\n- **156 skills** at `skills/<plugin>__<skill>/SKILL.md` — described in `AGENTS.md`. Describe a task to activate.\n- **192 subagents** at `agents/<plugin>__<agent>.md` — invoke with `@<agent>`.\n- **102 slash commands** at `/<plugin>:<command>` — use `/help` to list.\n\n## Companion Memory Extension\n\nPensyve is maintained upstream as a separate Gemini CLI extension with MCP-backed\nmemory and context injection:\n\n```bash\ngemini extensions install https://github.com/major7apps/pensyve\n```\n\n## Gemini-specific differences\n\n| Capability | Claude Code | Gemini CLI |\n|---|---|---|\n| Plugin installation | `/plugin install` | `gemini extensions install <url>` |\n| Context file | reads `CLAUDE.md` (a symlink to `AGENTS.md`) | reads via `.gemini/settings.json` redirect to AGENTS.md |\n| Per-agent tool allowlist | `tools:` (always) | `tools:` (honored — remapped to Gemini-native names) |\n| Skill / agent discovery | native | native (skills/, agents/ at extension root) |\n| Model assignment | per-agent | session-level (override via `model:` frontmatter) |\n| `TodoWrite` tool | yes | no equivalent |\n\n## Regenerating\n\n```bash\nmake generate HARNESS=gemini                            # all plugins\nmake generate HARNESS=gemini PLUGIN=javascript-typescript   # one plugin\nmake clean-generated HARNESS=gemini                     # remove output\n```\n\n## See also\n\n- [`AGENTS.md`](AGENTS.md) — canonical context (cross-harness conventions)\n- [`docs/harnesses.md`](docs/harnesses.md) — full capability matrix\n- [`docs/round-trip-results.md`](docs/round-trip-results.md) — Gemini round-trip verification recipe\n","plugins/ui-design/skills/mobile-ios-design/references/swiftui-components.md":"# SwiftUI Component Library\n\n## Lists and Collections\n\n### Basic List\n\n```swift\nstruct ItemListView: View {\n    @State private var items: [Item] = []\n\n    var body: some View {\n        List {\n            ForEach(items) { item in\n                ItemRow(item: item)\n            }\n            .onDelete(perform: deleteItems)\n            .onMove(perform: moveItems)\n        }\n        .listStyle(.insetGrouped)\n        .refreshable {\n            await loadItems()\n        }\n    }\n\n    private func deleteItems(at offsets: IndexSet) {\n        items.remove(atOffsets: offsets)\n    }\n\n    private func moveItems(from source: IndexSet, to destination: Int) {\n        items.move(fromOffsets: source, toOffset: destination)\n    }\n}\n```\n\n### Sectioned List\n\n```swift\nstruct SectionedListView: View {\n    let groupedItems: [String: [Item]]\n\n    var body: some View {\n        List {\n            ForEach(groupedItems.keys.sorted(), id: \\.self) { key in\n                Section(header: Text(key)) {\n                    ForEach(groupedItems[key] ?? []) { item in\n                        ItemRow(item: item)\n                    }\n                }\n            }\n        }\n        .listStyle(.sidebar)\n    }\n}\n```\n\n### Search Integration\n\n```swift\nstruct SearchableListView: View {\n    @State private var searchText = \"\"\n    @State private var items: [Item] = []\n\n    var filteredItems: [Item] {\n        if searchText.isEmpty {\n            return items\n        }\n        return items.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n    }\n\n    var body: some View {\n        NavigationStack {\n            List(filteredItems) { item in\n                ItemRow(item: item)\n            }\n            .searchable(text: $searchText, prompt: \"Search items\")\n            .searchSuggestions {\n                ForEach(searchSuggestions, id: \\.self) { suggestion in\n                    Text(suggestion)\n                        .searchCompletion(suggestion)\n                }\n            }\n            .navigationTitle(\"Items\")\n        }\n    }\n}\n```\n\n## Forms and Input\n\n### Settings Form\n\n```swift\nstruct SettingsView: View {\n    @AppStorage(\"notifications\") private var notificationsEnabled = true\n    @AppStorage(\"soundEnabled\") private var soundEnabled = true\n    @State private var selectedTheme = Theme.system\n    @State private var username = \"\"\n\n    var body: some View {\n        Form {\n            Section(\"Account\") {\n                TextField(\"Username\", text: $username)\n                    .textContentType(.username)\n                    .autocorrectionDisabled()\n            }\n\n            Section(\"Preferences\") {\n                Toggle(\"Enable Notifications\", isOn: $notificationsEnabled)\n                Toggle(\"Sound Effects\", isOn: $soundEnabled)\n\n                Picker(\"Theme\", selection: $selectedTheme) {\n                    ForEach(Theme.allCases) { theme in\n                        Text(theme.rawValue).tag(theme)\n                    }\n                }\n            }\n\n            Section(\"About\") {\n                LabeledContent(\"Version\", value: \"1.0.0\")\n\n                Link(destination: URL(string: \"https://example.com/privacy\")!) {\n                    Text(\"Privacy Policy\")\n                }\n            }\n        }\n        .navigationTitle(\"Settings\")\n    }\n}\n```\n\n### Custom Input Fields\n\n```swift\nstruct ValidatedTextField: View {\n    let title: String\n    @Binding var text: String\n    let validation: (String) -> Bool\n\n    @State private var isValid = true\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(title)\n                .font(.caption)\n                .foregroundStyle(.secondary)\n\n            TextField(title, text: $text)\n                .textFieldStyle(.roundedBorder)\n                .focused($isFocused)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 8)\n                        .stroke(borderColor, lineWidth: 1)\n                )\n                .onChange(of: text) { _, newValue in\n                    isValid = validation(newValue)\n                }\n\n            if !isValid && !text.isEmpty {\n                Text(\"Invalid input\")\n                    .font(.caption)\n                    .foregroundStyle(.red)\n            }\n        }\n    }\n\n    private var borderColor: Color {\n        if isFocused {\n            return isValid ? .blue : .red\n        }\n        return .clear\n    }\n}\n```\n\n## Buttons and Actions\n\n### Button Styles\n\n```swift\n// Primary filled button\nButton(\"Continue\") {\n    // action\n}\n.buttonStyle(.borderedProminent)\n.controlSize(.large)\n\n// Secondary button\nButton(\"Cancel\") {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Destructive button\nButton(\"Delete\", role: .destructive) {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Custom button style\nstruct ScaleButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n    }\n}\n```\n\n### Menu and Context Menu\n\n```swift\n// Menu button\nMenu {\n    Button(\"Edit\", systemImage: \"pencil\") { }\n    Button(\"Duplicate\", systemImage: \"doc.on.doc\") { }\n    Divider()\n    Button(\"Delete\", systemImage: \"trash\", role: .destructive) { }\n} label: {\n    Image(systemName: \"ellipsis.circle\")\n}\n\n// Context menu on any view\nText(\"Long press me\")\n    .contextMenu {\n        Button(\"Copy\", systemImage: \"doc.on.doc\") { }\n        Button(\"Share\", systemImage: \"square.and.arrow.up\") { }\n    } preview: {\n        ItemPreviewView()\n    }\n```\n\n## Sheets and Modals\n\n### Sheet Presentation\n\n```swift\nstruct ParentView: View {\n    @State private var showSettings = false\n    @State private var selectedItem: Item?\n\n    var body: some View {\n        VStack {\n            Button(\"Settings\") {\n                showSettings = true\n            }\n        }\n        .sheet(isPresented: $showSettings) {\n            SettingsSheet()\n                .presentationDetents([.medium, .large])\n                .presentationDragIndicator(.visible)\n        }\n        .sheet(item: $selectedItem) { item in\n            ItemDetailSheet(item: item)\n                .presentationDetents([.height(300), .large])\n                .presentationCornerRadius(24)\n        }\n    }\n}\n\nstruct SettingsSheet: View {\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        NavigationStack {\n            SettingsContent()\n                .toolbar {\n                    ToolbarItem(placement: .confirmationAction) {\n                        Button(\"Done\") {\n                            dismiss()\n                        }\n                    }\n                }\n        }\n    }\n}\n```\n\n### Confirmation Dialog\n\n```swift\nstruct DeleteConfirmationView: View {\n    @State private var showConfirmation = false\n\n    var body: some View {\n        Button(\"Delete Account\", role: .destructive) {\n            showConfirmation = true\n        }\n        .confirmationDialog(\n            \"Delete Account\",\n            isPresented: $showConfirmation,\n            titleVisibility: .visible\n        ) {\n            Button(\"Delete\", role: .destructive) {\n                deleteAccount()\n            }\n            Button(\"Cancel\", role: .cancel) { }\n        } message: {\n            Text(\"This action cannot be undone.\")\n        }\n    }\n}\n```\n\n## Loading and Progress\n\n### Progress Indicators\n\n```swift\n// Indeterminate spinner\nProgressView()\n    .progressViewStyle(.circular)\n\n// Determinate progress\nProgressView(value: downloadProgress, total: 1.0) {\n    Text(\"Downloading...\")\n} currentValueLabel: {\n    Text(\"\\(Int(downloadProgress * 100))%\")\n}\n\n// Custom loading view\nstruct LoadingOverlay: View {\n    let message: String\n\n    var body: some View {\n        ZStack {\n            Color.black.opacity(0.4)\n                .ignoresSafeArea()\n\n            VStack(spacing: 16) {\n                ProgressView()\n                    .scaleEffect(1.5)\n                    .tint(.white)\n\n                Text(message)\n                    .font(.subheadline)\n                    .foregroundStyle(.white)\n            }\n            .padding(24)\n            .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))\n        }\n    }\n}\n```\n\n### Skeleton Loading\n\n```swift\nstruct SkeletonRow: View {\n    @State private var isAnimating = false\n\n    var body: some View {\n        HStack(spacing: 12) {\n            Circle()\n                .fill(.gray.opacity(0.3))\n                .frame(width: 44, height: 44)\n\n            VStack(alignment: .leading, spacing: 8) {\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.3))\n                    .frame(height: 14)\n                    .frame(maxWidth: 200)\n\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.2))\n                    .frame(height: 12)\n                    .frame(maxWidth: 150)\n            }\n        }\n        .opacity(isAnimating ? 0.5 : 1.0)\n        .animation(.easeInOut(duration: 0.8).repeatForever(), value: isAnimating)\n        .onAppear { isAnimating = true }\n    }\n}\n```\n\n## Async Content Loading\n\n### AsyncImage\n\n```swift\nAsyncImage(url: imageURL) { phase in\n    switch phase {\n    case .empty:\n        ProgressView()\n    case .success(let image):\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fill)\n    case .failure:\n        Image(systemName: \"photo\")\n            .foregroundStyle(.secondary)\n    @unknown default:\n        EmptyView()\n    }\n}\n.frame(width: 100, height: 100)\n.clipShape(RoundedRectangle(cornerRadius: 8))\n```\n\n### Task-Based Loading\n\n```swift\nstruct AsyncContentView: View {\n    @State private var items: [Item] = []\n    @State private var isLoading = true\n    @State private var error: Error?\n\n    var body: some View {\n        Group {\n            if isLoading {\n                ProgressView(\"Loading...\")\n            } else if let error {\n                ContentUnavailableView(\n                    \"Failed to Load\",\n                    systemImage: \"exclamationmark.triangle\",\n                    description: Text(error.localizedDescription)\n                )\n            } else if items.isEmpty {\n                ContentUnavailableView(\n                    \"No Items\",\n                    systemImage: \"tray\",\n                    description: Text(\"Add your first item to get started.\")\n                )\n            } else {\n                List(items) { item in\n                    ItemRow(item: item)\n                }\n            }\n        }\n        .task {\n            await loadItems()\n        }\n    }\n\n    private func loadItems() async {\n        do {\n            items = try await api.fetchItems()\n            isLoading = false\n        } catch {\n            self.error = error\n            isLoading = false\n        }\n    }\n}\n```\n\n## Animations\n\n### Implicit Animations\n\n```swift\nstruct AnimatedCard: View {\n    @State private var isExpanded = false\n\n    var body: some View {\n        VStack {\n            Text(\"Tap to expand\")\n\n            if isExpanded {\n                Text(\"Additional content here\")\n                    .transition(.move(edge: .top).combined(with: .opacity))\n            }\n        }\n        .padding()\n        .frame(maxWidth: .infinity)\n        .background(.blue.opacity(0.1), in: RoundedRectangle(cornerRadius: 12))\n        .onTapGesture {\n            withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {\n                isExpanded.toggle()\n            }\n        }\n    }\n}\n```\n\n### Custom Transitions\n\n```swift\nextension AnyTransition {\n    static var slideAndFade: AnyTransition {\n        .asymmetric(\n            insertion: .move(edge: .trailing).combined(with: .opacity),\n            removal: .move(edge: .leading).combined(with: .opacity)\n        )\n    }\n\n    static var scaleAndFade: AnyTransition {\n        .scale(scale: 0.8).combined(with: .opacity)\n    }\n}\n```\n\n### Phase Animator (iOS 17+)\n\n```swift\nstruct PulsingButton: View {\n    var body: some View {\n        Button(\"Tap Me\") { }\n            .buttonStyle(.borderedProminent)\n            .phaseAnimator([false, true]) { content, phase in\n                content\n                    .scaleEffect(phase ? 1.05 : 1.0)\n            } animation: { _ in\n                .easeInOut(duration: 0.5)\n            }\n    }\n}\n```\n\n## Gestures\n\n### Drag Gesture\n\n```swift\nstruct DraggableCard: View {\n    @State private var offset = CGSize.zero\n    @State private var isDragging = false\n\n    var body: some View {\n        RoundedRectangle(cornerRadius: 16)\n            .fill(.blue)\n            .frame(width: 200, height: 150)\n            .offset(offset)\n            .scaleEffect(isDragging ? 1.05 : 1.0)\n            .gesture(\n                DragGesture()\n                    .onChanged { value in\n                        offset = value.translation\n                        isDragging = true\n                    }\n                    .onEnded { _ in\n                        withAnimation(.spring()) {\n                            offset = .zero\n                            isDragging = false\n                        }\n                    }\n            )\n    }\n}\n```\n\n### Simultaneous Gestures\n\n```swift\nstruct ZoomableImage: View {\n    @State private var scale: CGFloat = 1.0\n    @State private var lastScale: CGFloat = 1.0\n\n    var body: some View {\n        Image(\"photo\")\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n            .scaleEffect(scale)\n            .gesture(\n                MagnificationGesture()\n                    .onChanged { value in\n                        scale = lastScale * value\n                    }\n                    .onEnded { _ in\n                        lastScale = scale\n                    }\n            )\n            .gesture(\n                TapGesture(count: 2)\n                    .onEnded {\n                        withAnimation {\n                            scale = 1.0\n                            lastScale = 1.0\n                        }\n                    }\n            )\n    }\n}\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# claude-agents — multi-harness agentic plugin marketplace\n\nProduction-ready agentic-workflow building blocks: **94 plugins** (90 local + 4 external), **203 agents**, **175 skills**, **109 commands**. Native source-of-truth for Claude Code; also consumed by OpenAI Codex CLI, Cursor, OpenCode, and Gemini CLI from a single Markdown source.\n\nThis file is the canonical context file. Codex / Cursor / OpenCode read it directly. Claude Code reads it via `CLAUDE.md`, a symlink to this file. Gemini CLI reads it via `gemini-extension.json` (`contextFileName`) / `.gemini/settings.json`.\n\n> **Read this file like a table of contents.** Detail lives in `docs/`. Authoring conventions live in `docs/authoring.md`. Per-harness setup and capability deltas live in [`docs/harnesses.md`](docs/harnesses.md). Gemini-specific setup is in `GEMINI.md` (also auto-loaded by Gemini CLI). This file should never grow beyond ~150 lines (per OpenAI's [harness-engineering](https://openai.com/index/harness-engineering/) practice).\n\n## Map\n\n- **[ARCHITECTURE.md](ARCHITECTURE.md)** — top-level architectural overview (adapter framework, source-of-truth invariant, capability matrix summary)\n- **[docs/architecture.md](docs/architecture.md)** — detailed design principles\n- **[docs/plugins.md](docs/plugins.md)** — full plugin catalog (94 plugins by category)\n- **[docs/agents.md](docs/agents.md)** — agent reference (203 agents, model tiers)\n- **[docs/agent-skills.md](docs/agent-skills.md)** — skill reference (progressive disclosure model)\n- **[docs/usage.md](docs/usage.md)** — commands, workflows, examples\n- **[docs/authoring.md](docs/authoring.md)** — portable-content style guide (read before adding plugins)\n- **[docs/harnesses.md](docs/harnesses.md)** — per-harness capability matrix\n- **[docs/plugin-eval.md](docs/plugin-eval.md)** — three-layer quality evaluation framework\n- **[docs/round-trip-results.md](docs/round-trip-results.md)** — real-CLI verification recipes\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** — how to contribute\n\n## Working in this repo\n\n- Python tooling: **uv** (package manager), **ruff** (lint/format), **ty** (type check). Do not use pip / mypy / black.\n- Plugins live under `plugins/<name>/` with auto-discovery — see `docs/authoring.md` for frontmatter shapes.\n- Plugin names: lowercase, hyphen-separated. Never use `__` (it's the adapter namespace separator).\n- Never commit secrets. Never run destructive git (force-push, `reset --hard`, branch -D) without explicit ask.\n\n## Quality gates (run these before pushing)\n\n```bash\nmake validate STRICT=1     # structural validation across all harness outputs\nmake garden                # drift detection (dead links, stale artifacts, oversize skills)\nmake test                  # full pytest suite (plugin-eval + tools/tests/)\nmake smoke-test            # real-CLI subprocess tests against generated artifacts\n```\n\nCI (`.github/workflows/validate.yml`) runs all four on every PR plus installs OpenCode + Gemini CLI for live verification.\n\n## Regenerating per-harness artifacts\n\n```bash\nmake generate HARNESS=codex      # .codex/skills, .codex/agents, .codex/plugins/<p>/, .agents/plugins/marketplace.json\nmake generate HARNESS=cursor     # .cursor-plugin/{marketplace,plugin}.json, .cursor/rules/\nmake generate HARNESS=opencode   # .opencode/{skills,agents,commands,plugins}/, opencode.json\nmake generate HARNESS=gemini     # skills/, agents/, commands/ at extension root\nmake generate-all                # all four\n```\n\nGenerated artifacts are **committed** so each harness installs natively from a clone / GitHub URL (native-install commands in [`docs/harnesses.md`](docs/harnesses.md)). Run `make generate-all` before committing source changes — CI fails on drift. Source-of-truth lives only under `plugins/`; never hand-edit generated files.\n\n## Skills (cross-harness)\n\n175 skills under `plugins/*/skills/<n>/SKILL.md` — discoverable by every harness:\n\n- **Claude Code**: auto-discovery via Anthropic's SKILL.md spec\n- **Codex CLI**: mirrored to `.codex/skills/<plugin>__<skill>/` (8 KB body cap; detail in `references/details.md`)\n- **OpenCode**: mirrored to `.opencode/skills/<plugin>-<skill>/` using hyphenated names for global install\n- **Cursor**: reads `.claude/skills/` directly (no re-emit)\n- **Gemini CLI**: native skills at `skills/<plugin>__<skill>/SKILL.md`\n\nTop-level `skills/` is Gemini output; do not use it for OpenCode installs.\n\n## Subagents (cross-harness)\n\n203 subagents under `plugins/*/agents/<name>.md`. Per-harness transpilation:\n\n- **Codex**: `.codex/agents/<plugin>__<agent>.toml` (drop `tools:`, map model alias to the GPT-5.x family, infer `sandbox_mode`)\n- **OpenCode**: `.opencode/agents/<plugin>__<agent>.md` with `mode: subagent` + `permission:` block (locked agents — those with source `tools: []` — get deny-everything except base `skill`/`task`)\n- **Gemini**: `agents/<plugin>__<agent>.md` (April 2026 subagent spec)\n- **Cursor**: reads `.claude/agents/` directly\n\n## Why this file is short\n\nPer OpenAI's harness-engineering practice: this file is a **map**, not an encyclopedia. Procedural detail lives in skills (loaded on demand by agents). Reference material lives in `docs/` (loaded when an agent navigates). A single bloated AGENTS.md crowds out the task, rots quickly, and is hard to verify mechanically. Keep it lean; push detail elsewhere.\n","category":"root","tokens":1337},{"name":"GEMINI.md","path":"GEMINI.md","title":"GEMINI.md","content":"# Gemini CLI — setup guide\n\n> The canonical context file is [`AGENTS.md`](AGENTS.md) at the repo root. Gemini CLI reads it via `.gemini/settings.json` (`context.fileName`). This guide covers Gemini-specific setup only.\n\n## Install\n\n```bash\ngemini extensions install https://github.com/wshobson/agents\ncd ~/.gemini/extensions/claude-code-workflows\nmake generate HARNESS=gemini\n# restart Gemini CLI\n```\n\n## What you get\n\n- **156 skills** at `skills/<plugin>__<skill>/SKILL.md` — described in `AGENTS.md`. Describe a task to activate.\n- **192 subagents** at `agents/<plugin>__<agent>.md` — invoke with `@<agent>`.\n- **102 slash commands** at `/<plugin>:<command>` — use `/help` to list.\n\n## Companion Memory Extension\n\nPensyve is maintained upstream as a separate Gemini CLI extension with MCP-backed\nmemory and context injection:\n\n```bash\ngemini extensions install https://github.com/major7apps/pensyve\n```\n\n## Gemini-specific differences\n\n| Capability | Claude Code | Gemini CLI |\n|---|---|---|\n| Plugin installation | `/plugin install` | `gemini extensions install <url>` |\n| Context file | reads `CLAUDE.md` (a symlink to `AGENTS.md`) | reads via `.gemini/settings.json` redirect to AGENTS.md |\n| Per-agent tool allowlist | `tools:` (always) | `tools:` (honored — remapped to Gemini-native names) |\n| Skill / agent discovery | native | native (skills/, agents/ at extension root) |\n| Model assignment | per-agent | session-level (override via `model:` frontmatter) |\n| `TodoWrite` tool | yes | no equivalent |\n\n## Regenerating\n\n```bash\nmake generate HARNESS=gemini                            # all plugins\nmake generate HARNESS=gemini PLUGIN=javascript-typescript   # one plugin\nmake clean-generated HARNESS=gemini                     # remove output\n```\n\n## See also\n\n- [`AGENTS.md`](AGENTS.md) — canonical context (cross-harness conventions)\n- [`docs/harnesses.md`](docs/harnesses.md) — full capability matrix\n- [`docs/round-trip-results.md`](docs/round-trip-results.md) — Gemini round-trip verification recipe\n","category":"root","tokens":504},{"name":"swiftui-components.md","path":"plugins/ui-design/skills/mobile-ios-design/references/swiftui-components.md","title":"swiftui-components.md","content":"# SwiftUI Component Library\n\n## Lists and Collections\n\n### Basic List\n\n```swift\nstruct ItemListView: View {\n    @State private var items: [Item] = []\n\n    var body: some View {\n        List {\n            ForEach(items) { item in\n                ItemRow(item: item)\n            }\n            .onDelete(perform: deleteItems)\n            .onMove(perform: moveItems)\n        }\n        .listStyle(.insetGrouped)\n        .refreshable {\n            await loadItems()\n        }\n    }\n\n    private func deleteItems(at offsets: IndexSet) {\n        items.remove(atOffsets: offsets)\n    }\n\n    private func moveItems(from source: IndexSet, to destination: Int) {\n        items.move(fromOffsets: source, toOffset: destination)\n    }\n}\n```\n\n### Sectioned List\n\n```swift\nstruct SectionedListView: View {\n    let groupedItems: [String: [Item]]\n\n    var body: some View {\n        List {\n            ForEach(groupedItems.keys.sorted(), id: \\.self) { key in\n                Section(header: Text(key)) {\n                    ForEach(groupedItems[key] ?? []) { item in\n                        ItemRow(item: item)\n                    }\n                }\n            }\n        }\n        .listStyle(.sidebar)\n    }\n}\n```\n\n### Search Integration\n\n```swift\nstruct SearchableListView: View {\n    @State private var searchText = \"\"\n    @State private var items: [Item] = []\n\n    var filteredItems: [Item] {\n        if searchText.isEmpty {\n            return items\n        }\n        return items.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n    }\n\n    var body: some View {\n        NavigationStack {\n            List(filteredItems) { item in\n                ItemRow(item: item)\n            }\n            .searchable(text: $searchText, prompt: \"Search items\")\n            .searchSuggestions {\n                ForEach(searchSuggestions, id: \\.self) { suggestion in\n                    Text(suggestion)\n                        .searchCompletion(suggestion)\n                }\n            }\n            .navigationTitle(\"Items\")\n        }\n    }\n}\n```\n\n## Forms and Input\n\n### Settings Form\n\n```swift\nstruct SettingsView: View {\n    @AppStorage(\"notifications\") private var notificationsEnabled = true\n    @AppStorage(\"soundEnabled\") private var soundEnabled = true\n    @State private var selectedTheme = Theme.system\n    @State private var username = \"\"\n\n    var body: some View {\n        Form {\n            Section(\"Account\") {\n                TextField(\"Username\", text: $username)\n                    .textContentType(.username)\n                    .autocorrectionDisabled()\n            }\n\n            Section(\"Preferences\") {\n                Toggle(\"Enable Notifications\", isOn: $notificationsEnabled)\n                Toggle(\"Sound Effects\", isOn: $soundEnabled)\n\n                Picker(\"Theme\", selection: $selectedTheme) {\n                    ForEach(Theme.allCases) { theme in\n                        Text(theme.rawValue).tag(theme)\n                    }\n                }\n            }\n\n            Section(\"About\") {\n                LabeledContent(\"Version\", value: \"1.0.0\")\n\n                Link(destination: URL(string: \"https://example.com/privacy\")!) {\n                    Text(\"Privacy Policy\")\n                }\n            }\n        }\n        .navigationTitle(\"Settings\")\n    }\n}\n```\n\n### Custom Input Fields\n\n```swift\nstruct ValidatedTextField: View {\n    let title: String\n    @Binding var text: String\n    let validation: (String) -> Bool\n\n    @State private var isValid = true\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(title)\n                .font(.caption)\n                .foregroundStyle(.secondary)\n\n            TextField(title, text: $text)\n                .textFieldStyle(.roundedBorder)\n                .focused($isFocused)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 8)\n                        .stroke(borderColor, lineWidth: 1)\n                )\n                .onChange(of: text) { _, newValue in\n                    isValid = validation(newValue)\n                }\n\n            if !isValid && !text.isEmpty {\n                Text(\"Invalid input\")\n                    .font(.caption)\n                    .foregroundStyle(.red)\n            }\n        }\n    }\n\n    private var borderColor: Color {\n        if isFocused {\n            return isValid ? .blue : .red\n        }\n        return .clear\n    }\n}\n```\n\n## Buttons and Actions\n\n### Button Styles\n\n```swift\n// Primary filled button\nButton(\"Continue\") {\n    // action\n}\n.buttonStyle(.borderedProminent)\n.controlSize(.large)\n\n// Secondary button\nButton(\"Cancel\") {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Destructive button\nButton(\"Delete\", role: .destructive) {\n    // action\n}\n.buttonStyle(.bordered)\n\n// Custom button style\nstruct ScaleButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n    }\n}\n```\n\n### Menu and Context Menu\n\n```swift\n// Menu button\nMenu {\n    Button(\"Edit\", systemImage: \"pencil\") { }\n    Button(\"Duplicate\", systemImage: \"doc.on.doc\") { }\n    Divider()\n    Button(\"Delete\", systemImage: \"trash\", role: .destructive) { }\n} label: {\n    Image(systemName: \"ellipsis.circle\")\n}\n\n// Context menu on any view\nText(\"Long press me\")\n    .contextMenu {\n        Button(\"Copy\", systemImage: \"doc.on.doc\") { }\n        Button(\"Share\", systemImage: \"square.and.arrow.up\") { }\n    } preview: {\n        ItemPreviewView()\n    }\n```\n\n## Sheets and Modals\n\n### Sheet Presentation\n\n```swift\nstruct ParentView: View {\n    @State private var showSettings = false\n    @State private var selectedItem: Item?\n\n    var body: some View {\n        VStack {\n            Button(\"Settings\") {\n                showSettings = true\n            }\n        }\n        .sheet(isPresented: $showSettings) {\n            SettingsSheet()\n                .presentationDetents([.medium, .large])\n                .presentationDragIndicator(.visible)\n        }\n        .sheet(item: $selectedItem) { item in\n            ItemDetailSheet(item: item)\n                .presentationDetents([.height(300), .large])\n                .presentationCornerRadius(24)\n        }\n    }\n}\n\nstruct SettingsSheet: View {\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        NavigationStack {\n            SettingsContent()\n                .toolbar {\n                    ToolbarItem(placement: .confirmationAction) {\n                        Button(\"Done\") {\n                            dismiss()\n                        }\n                    }\n                }\n        }\n    }\n}\n```\n\n### Confirmation Dialog\n\n```swift\nstruct DeleteConfirmationView: View {\n    @State private var showConfirmation = false\n\n    var body: some View {\n        Button(\"Delete Account\", role: .destructive) {\n            showConfirmation = true\n        }\n        .confirmationDialog(\n            \"Delete Account\",\n            isPresented: $showConfirmation,\n            titleVisibility: .visible\n        ) {\n            Button(\"Delete\", role: .destructive) {\n                deleteAccount()\n            }\n            Button(\"Cancel\", role: .cancel) { }\n        } message: {\n            Text(\"This action cannot be undone.\")\n        }\n    }\n}\n```\n\n## Loading and Progress\n\n### Progress Indicators\n\n```swift\n// Indeterminate spinner\nProgressView()\n    .progressViewStyle(.circular)\n\n// Determinate progress\nProgressView(value: downloadProgress, total: 1.0) {\n    Text(\"Downloading...\")\n} currentValueLabel: {\n    Text(\"\\(Int(downloadProgress * 100))%\")\n}\n\n// Custom loading view\nstruct LoadingOverlay: View {\n    let message: String\n\n    var body: some View {\n        ZStack {\n            Color.black.opacity(0.4)\n                .ignoresSafeArea()\n\n            VStack(spacing: 16) {\n                ProgressView()\n                    .scaleEffect(1.5)\n                    .tint(.white)\n\n                Text(message)\n                    .font(.subheadline)\n                    .foregroundStyle(.white)\n            }\n            .padding(24)\n            .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))\n        }\n    }\n}\n```\n\n### Skeleton Loading\n\n```swift\nstruct SkeletonRow: View {\n    @State private var isAnimating = false\n\n    var body: some View {\n        HStack(spacing: 12) {\n            Circle()\n                .fill(.gray.opacity(0.3))\n                .frame(width: 44, height: 44)\n\n            VStack(alignment: .leading, spacing: 8) {\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.3))\n                    .frame(height: 14)\n                    .frame(maxWidth: 200)\n\n                RoundedRectangle(cornerRadius: 4)\n                    .fill(.gray.opacity(0.2))\n                    .frame(height: 12)\n                    .frame(maxWidth: 150)\n            }\n        }\n        .opacity(isAnimating ? 0.5 : 1.0)\n        .animation(.easeInOut(duration: 0.8).repeatForever(), value: isAnimating)\n        .onAppear { isAnimating = true }\n    }\n}\n```\n\n## Async Content Loading\n\n### AsyncImage\n\n```swift\nAsyncImage(url: imageURL) { phase in\n    switch phase {\n    case .empty:\n        ProgressView()\n    case .success(let image):\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fill)\n    case .failure:\n        Image(systemName: \"photo\")\n            .foregroundStyle(.secondary)\n    @unknown default:\n        EmptyView()\n    }\n}\n.frame(width: 100, height: 100)\n.clipShape(RoundedRectangle(cornerRadius: 8))\n```\n\n### Task-Based Loading\n\n```swift\nstruct AsyncContentView: View {\n    @State private var items: [Item] = []\n    @State private var isLoading = true\n    @State private var error: Error?\n\n    var body: some View {\n        Group {\n            if isLoading {\n                ProgressView(\"Loading...\")\n            } else if let error {\n                ContentUnavailableView(\n                    \"Failed to Load\",\n                    systemImage: \"exclamationmark.triangle\",\n                    description: Text(error.localizedDescription)\n                )\n            } else if items.isEmpty {\n                ContentUnavailableView(\n                    \"No Items\",\n                    systemImage: \"tray\",\n                    description: Text(\"Add your first item to get started.\")\n                )\n            } else {\n                List(items) { item in\n                    ItemRow(item: item)\n                }\n            }\n        }\n        .task {\n            await loadItems()\n        }\n    }\n\n    private func loadItems() async {\n        do {\n            items = try await api.fetchItems()\n            isLoading = false\n        } catch {\n            self.error = error\n            isLoading = false\n        }\n    }\n}\n```\n\n## Animations\n\n### Implicit Animations\n\n```swift\nstruct AnimatedCard: View {\n    @State private var isExpanded = false\n\n    var body: some View {\n        VStack {\n            Text(\"Tap to expand\")\n\n            if isExpanded {\n                Text(\"Additional content here\")\n                    .transition(.move(edge: .top).combined(with: .opacity))\n            }\n        }\n        .padding()\n        .frame(maxWidth: .infinity)\n        .background(.blue.opacity(0.1), in: RoundedRectangle(cornerRadius: 12))\n        .onTapGesture {\n            withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {\n                isExpanded.toggle()\n            }\n        }\n    }\n}\n```\n\n### Custom Transitions\n\n```swift\nextension AnyTransition {\n    static var slideAndFade: AnyTransition {\n        .asymmetric(\n            insertion: .move(edge: .trailing).combined(with: .opacity),\n            removal: .move(edge: .leading).combined(with: .opacity)\n        )\n    }\n\n    static var scaleAndFade: AnyTransition {\n        .scale(scale: 0.8).combined(with: .opacity)\n    }\n}\n```\n\n### Phase Animator (iOS 17+)\n\n```swift\nstruct PulsingButton: View {\n    var body: some View {\n        Button(\"Tap Me\") { }\n            .buttonStyle(.borderedProminent)\n            .phaseAnimator([false, true]) { content, phase in\n                content\n                    .scaleEffect(phase ? 1.05 : 1.0)\n            } animation: { _ in\n                .easeInOut(duration: 0.5)\n            }\n    }\n}\n```\n\n## Gestures\n\n### Drag Gesture\n\n```swift\nstruct DraggableCard: View {\n    @State private var offset = CGSize.zero\n    @State private var isDragging = false\n\n    var body: some View {\n        RoundedRectangle(cornerRadius: 16)\n            .fill(.blue)\n            .frame(width: 200, height: 150)\n            .offset(offset)\n            .scaleEffect(isDragging ? 1.05 : 1.0)\n            .gesture(\n                DragGesture()\n                    .onChanged { value in\n                        offset = value.translation\n                        isDragging = true\n                    }\n                    .onEnded { _ in\n                        withAnimation(.spring()) {\n                            offset = .zero\n                            isDragging = false\n                        }\n                    }\n            )\n    }\n}\n```\n\n### Simultaneous Gestures\n\n```swift\nstruct ZoomableImage: View {\n    @State private var scale: CGFloat = 1.0\n    @State private var lastScale: CGFloat = 1.0\n\n    var body: some View {\n        Image(\"photo\")\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n            .scaleEffect(scale)\n            .gesture(\n                MagnificationGesture()\n                    .onChanged { value in\n                        scale = lastScale * value\n                    }\n                    .onEnded { _ in\n                        lastScale = scale\n                    }\n            )\n            .gesture(\n                TapGesture(count: 2)\n                    .onEnded {\n                        withAnimation {\n                            scale = 1.0\n                            lastScale = 1.0\n                        }\n                    }\n            )\n    }\n}\n```\n","category":"plugins","tokens":3529}]}