@ketecode/loom 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +516 -2
- package/agents/accessibility-reviewer/AGENT.md +70 -0
- package/agents/accessibility-reviewer/evals/triggers.json +15 -0
- package/agents/agent-evaluator/AGENT.md +74 -0
- package/agents/agent-evaluator/evals/triggers.json +15 -0
- package/agents/architect/AGENT.md +82 -0
- package/agents/architect/evals/triggers.json +17 -0
- package/agents/billing-reviewer/AGENT.md +68 -0
- package/agents/billing-reviewer/evals/triggers.json +16 -0
- package/agents/code-reviewer/AGENT.md +51 -0
- package/agents/code-reviewer/evals/triggers.json +14 -0
- package/agents/code-simplifier/AGENT.md +74 -0
- package/agents/code-simplifier/evals/triggers.json +15 -0
- package/agents/cpp-build-resolver/AGENT.md +73 -0
- package/agents/cpp-build-resolver/evals/triggers.json +16 -0
- package/agents/cpp-reviewer/AGENT.md +59 -0
- package/agents/cpp-reviewer/evals/triggers.json +16 -0
- package/agents/csharp-build-resolver/AGENT.md +84 -0
- package/agents/csharp-build-resolver/evals/triggers.json +17 -0
- package/agents/csharp-reviewer/AGENT.md +88 -0
- package/agents/csharp-reviewer/evals/triggers.json +17 -0
- package/agents/dart-build-resolver/AGENT.md +80 -0
- package/agents/dart-build-resolver/evals/triggers.json +17 -0
- package/agents/dart-reviewer/AGENT.md +66 -0
- package/agents/dart-reviewer/evals/triggers.json +17 -0
- package/agents/database-reviewer/AGENT.md +72 -0
- package/agents/database-reviewer/evals/triggers.json +16 -0
- package/agents/django-reviewer/AGENT.md +61 -0
- package/agents/django-reviewer/evals/triggers.json +15 -0
- package/agents/doc-updater/AGENT.md +92 -0
- package/agents/doc-updater/evals/triggers.json +14 -0
- package/agents/e2e-runner/AGENT.md +88 -0
- package/agents/e2e-runner/evals/triggers.json +16 -0
- package/agents/fastapi-reviewer/AGENT.md +63 -0
- package/agents/fastapi-reviewer/evals/triggers.json +15 -0
- package/agents/flutter-reviewer/AGENT.md +70 -0
- package/agents/flutter-reviewer/evals/triggers.json +15 -0
- package/agents/fsharp-build-resolver/AGENT.md +81 -0
- package/agents/fsharp-build-resolver/evals/triggers.json +15 -0
- package/agents/fsharp-reviewer/AGENT.md +77 -0
- package/agents/fsharp-reviewer/evals/triggers.json +15 -0
- package/agents/go-build-resolver/AGENT.md +85 -0
- package/agents/go-build-resolver/evals/triggers.json +16 -0
- package/agents/go-reviewer/AGENT.md +66 -0
- package/agents/go-reviewer/evals/triggers.json +16 -0
- package/agents/healthcare-reviewer/AGENT.md +65 -0
- package/agents/healthcare-reviewer/evals/triggers.json +16 -0
- package/agents/i18n-reviewer/AGENT.md +64 -0
- package/agents/i18n-reviewer/evals/triggers.json +16 -0
- package/agents/infra-reviewer/AGENT.md +93 -0
- package/agents/infra-reviewer/evals/triggers.json +15 -0
- package/agents/java-build-resolver/AGENT.md +82 -0
- package/agents/java-build-resolver/evals/triggers.json +16 -0
- package/agents/java-reviewer/AGENT.md +64 -0
- package/agents/java-reviewer/evals/triggers.json +16 -0
- package/agents/kotlin-build-resolver/AGENT.md +89 -0
- package/agents/kotlin-build-resolver/evals/triggers.json +16 -0
- package/agents/kotlin-reviewer/AGENT.md +75 -0
- package/agents/kotlin-reviewer/evals/triggers.json +16 -0
- package/agents/mle-reviewer/AGENT.md +68 -0
- package/agents/mle-reviewer/evals/triggers.json +16 -0
- package/agents/network-config-reviewer/AGENT.md +68 -0
- package/agents/network-config-reviewer/evals/triggers.json +17 -0
- package/agents/payments-reviewer/AGENT.md +64 -0
- package/agents/payments-reviewer/evals/triggers.json +16 -0
- package/agents/performance-optimizer/AGENT.md +86 -0
- package/agents/performance-optimizer/evals/triggers.json +16 -0
- package/agents/perl-build-resolver/AGENT.md +84 -0
- package/agents/perl-build-resolver/evals/triggers.json +15 -0
- package/agents/perl-reviewer/AGENT.md +77 -0
- package/agents/perl-reviewer/evals/triggers.json +15 -0
- package/agents/php-build-resolver/AGENT.md +88 -0
- package/agents/php-build-resolver/evals/triggers.json +16 -0
- package/agents/php-reviewer/AGENT.md +61 -0
- package/agents/php-reviewer/evals/triggers.json +15 -0
- package/agents/planner/AGENT.md +75 -0
- package/agents/planner/evals/triggers.json +17 -0
- package/agents/pr-test-analyzer/AGENT.md +82 -0
- package/agents/pr-test-analyzer/evals/triggers.json +16 -0
- package/agents/python-build-resolver/AGENT.md +87 -0
- package/agents/python-build-resolver/evals/triggers.json +16 -0
- package/agents/python-reviewer/AGENT.md +63 -0
- package/agents/python-reviewer/evals/triggers.json +15 -0
- package/agents/rag-reviewer/AGENT.md +71 -0
- package/agents/rag-reviewer/evals/triggers.json +16 -0
- package/agents/react-reviewer/AGENT.md +70 -0
- package/agents/react-reviewer/evals/triggers.json +16 -0
- package/agents/refactor-cleaner/AGENT.md +82 -0
- package/agents/refactor-cleaner/evals/triggers.json +16 -0
- package/agents/ruby-build-resolver/AGENT.md +93 -0
- package/agents/ruby-build-resolver/evals/triggers.json +16 -0
- package/agents/ruby-reviewer/AGENT.md +62 -0
- package/agents/ruby-reviewer/evals/triggers.json +16 -0
- package/agents/rust-build-resolver/AGENT.md +98 -0
- package/agents/rust-build-resolver/evals/triggers.json +16 -0
- package/agents/rust-reviewer/AGENT.md +78 -0
- package/agents/rust-reviewer/evals/triggers.json +15 -0
- package/agents/security-reviewer/AGENT.md +84 -0
- package/agents/security-reviewer/evals/triggers.json +16 -0
- package/agents/silent-failure-hunter/AGENT.md +66 -0
- package/agents/silent-failure-hunter/evals/triggers.json +16 -0
- package/agents/solidity-reviewer/AGENT.md +70 -0
- package/agents/solidity-reviewer/evals/triggers.json +16 -0
- package/agents/swift-build-resolver/AGENT.md +85 -0
- package/agents/swift-build-resolver/evals/triggers.json +15 -0
- package/agents/swift-reviewer/AGENT.md +75 -0
- package/agents/swift-reviewer/evals/triggers.json +15 -0
- package/agents/typescript-build-resolver/AGENT.md +76 -0
- package/agents/typescript-build-resolver/evals/triggers.json +15 -0
- package/agents/typescript-reviewer/AGENT.md +76 -0
- package/agents/typescript-reviewer/evals/triggers.json +15 -0
- package/agents/vue-reviewer/AGENT.md +75 -0
- package/agents/vue-reviewer/evals/triggers.json +15 -0
- package/bin/kete-loom.js +683 -0
- package/commands/aside.md +19 -0
- package/commands/build-fix.md +26 -0
- package/commands/campaign.md +19 -0
- package/commands/changelog.md +36 -0
- package/commands/checkpoint.md +26 -0
- package/commands/codemap.md +25 -0
- package/commands/commit.md +26 -0
- package/commands/deps-audit.md +32 -0
- package/commands/epic-decompose.md +21 -0
- package/commands/explain.md +37 -0
- package/commands/feature-dev.md +22 -0
- package/commands/hookify.md +23 -0
- package/commands/i18n-check.md +39 -0
- package/commands/issue-sync.md +22 -0
- package/commands/jira.md +19 -0
- package/commands/learn.md +23 -0
- package/commands/loom.md +21 -0
- package/commands/loop.md +20 -0
- package/commands/model-route.md +27 -0
- package/commands/onboard.md +35 -0
- package/commands/orchestrate.md +21 -0
- package/commands/plan-feature.md +36 -0
- package/commands/postmortem.md +42 -0
- package/commands/pr-describe.md +39 -0
- package/commands/pr.md +22 -0
- package/commands/prd.md +41 -0
- package/commands/project-init.md +21 -0
- package/commands/refactor-clean.md +24 -0
- package/commands/resume-session.md +22 -0
- package/commands/review.md +35 -0
- package/commands/save-session.md +37 -0
- package/commands/security-scan.md +32 -0
- package/commands/sessions.md +21 -0
- package/commands/skill-create.md +21 -0
- package/commands/tdd.md +36 -0
- package/commands/test-coverage.md +24 -0
- package/commands/threat-model.md +36 -0
- package/commands/update-docs.md +25 -0
- package/commands/verify.md +21 -0
- package/hooks/block-dangerous-commands/HOOK.md +30 -0
- package/hooks/block-dangerous-commands/cases.json +50 -0
- package/hooks/block-dangerous-commands/hook.js +75 -0
- package/hooks/block-no-verify/HOOK.md +25 -0
- package/hooks/block-no-verify/cases.json +30 -0
- package/hooks/block-no-verify/hook.js +43 -0
- package/hooks/format-on-edit/HOOK.md +24 -0
- package/hooks/format-on-edit/hook.js +89 -0
- package/hooks/guard-secrets/HOOK.md +19 -0
- package/hooks/guard-secrets/cases.json +110 -0
- package/hooks/guard-secrets/hook.js +44 -0
- package/hooks/load-session-context/HOOK.md +20 -0
- package/hooks/load-session-context/hook.js +56 -0
- package/hooks/protect-files/HOOK.md +24 -0
- package/hooks/protect-files/cases.json +27 -0
- package/hooks/protect-files/hook.js +39 -0
- package/hooks/run.js +132 -0
- package/mcp/atlassian.json +11 -0
- package/mcp/chrome-devtools.json +18 -0
- package/mcp/cloudflare.json +11 -0
- package/mcp/context7.json +11 -0
- package/mcp/fetch.json +17 -0
- package/mcp/figma.json +11 -0
- package/mcp/git.json +17 -0
- package/mcp/github.json +11 -0
- package/mcp/linear.json +11 -0
- package/mcp/memory.json +18 -0
- package/mcp/notion.json +11 -0
- package/mcp/playwright.json +18 -0
- package/mcp/sentry.json +11 -0
- package/mcp/stripe.json +11 -0
- package/mcp/supabase.json +11 -0
- package/mcp/time.json +17 -0
- package/package.json +59 -4
- package/profiles.json +165 -0
- package/rules/common.md +21 -0
- package/rules/cpp.md +29 -0
- package/rules/csharp.md +25 -0
- package/rules/dart.md +29 -0
- package/rules/fsharp.md +28 -0
- package/rules/git.md +26 -0
- package/rules/go.md +29 -0
- package/rules/java.md +29 -0
- package/rules/kotlin.md +27 -0
- package/rules/perl.md +26 -0
- package/rules/php.md +28 -0
- package/rules/python.md +30 -0
- package/rules/ruby.md +27 -0
- package/rules/rust.md +26 -0
- package/rules/security.md +29 -0
- package/rules/swift.md +28 -0
- package/rules/testing.md +25 -0
- package/rules/typescript.md +29 -0
- package/schemas/skill-frontmatter.schema.json +52 -0
- package/scripts/lib/audit.js +96 -0
- package/scripts/lib/frontmatter.js +65 -0
- package/scripts/lib/hooks-install.js +292 -0
- package/scripts/lib/i18n.js +51 -0
- package/scripts/lib/inventory.js +85 -0
- package/scripts/lib/lint.js +158 -0
- package/scripts/lib/mcp-install.js +284 -0
- package/scripts/lib/profiles.js +128 -0
- package/scripts/lib/render.js +86 -0
- package/scripts/lib/rules-install.js +118 -0
- package/scripts/lib/skills.js +86 -0
- package/scripts/lib/targets.js +113 -0
- package/skills/accounting-api-integration/SKILL.md +81 -0
- package/skills/accounting-api-integration/evals/triggers.json +17 -0
- package/skills/accounting-api-integration/references/quickbooks.md +48 -0
- package/skills/accounting-api-integration/references/xero.md +45 -0
- package/skills/agent-harness-design/SKILL.md +125 -0
- package/skills/agent-harness-design/evals/triggers.json +21 -0
- package/skills/ai-media-generation/SKILL.md +89 -0
- package/skills/ai-media-generation/evals/triggers.json +16 -0
- package/skills/ai-media-generation/references/job-pipeline.md +76 -0
- package/skills/angular-patterns/SKILL.md +122 -0
- package/skills/angular-patterns/evals/triggers.json +16 -0
- package/skills/api-design/SKILL.md +96 -0
- package/skills/api-design/evals/triggers.json +18 -0
- package/skills/api-design/references/conventions.md +133 -0
- package/skills/architecture-decision-records/SKILL.md +98 -0
- package/skills/architecture-decision-records/evals/triggers.json +17 -0
- package/skills/article-writing/SKILL.md +108 -0
- package/skills/article-writing/evals/triggers.json +16 -0
- package/skills/aspnet-core-patterns/SKILL.md +149 -0
- package/skills/aspnet-core-patterns/evals/triggers.json +16 -0
- package/skills/auth-patterns/SKILL.md +104 -0
- package/skills/auth-patterns/evals/triggers.json +17 -0
- package/skills/auth-patterns/references/oauth-oidc.md +64 -0
- package/skills/autonomous-loops/SKILL.md +99 -0
- package/skills/autonomous-loops/evals/triggers.json +14 -0
- package/skills/aws-patterns/SKILL.md +91 -0
- package/skills/aws-patterns/evals/triggers.json +16 -0
- package/skills/azure-patterns/SKILL.md +86 -0
- package/skills/azure-patterns/evals/triggers.json +16 -0
- package/skills/benchmark-methodology/SKILL.md +88 -0
- package/skills/benchmark-methodology/evals/triggers.json +16 -0
- package/skills/benchmark-methodology/references/language-harnesses.md +130 -0
- package/skills/bgp-diagnostics/SKILL.md +84 -0
- package/skills/bgp-diagnostics/evals/triggers.json +18 -0
- package/skills/bgp-diagnostics/references/bgp-reference.md +81 -0
- package/skills/bioinformatics-workflows/SKILL.md +108 -0
- package/skills/bioinformatics-workflows/evals/triggers.json +16 -0
- package/skills/bioinformatics-workflows/references/commands.md +71 -0
- package/skills/bioinformatics-workflows/references/formats.md +24 -0
- package/skills/bioinformatics-workflows/references/workflow-managers.md +107 -0
- package/skills/brand-voice/SKILL.md +112 -0
- package/skills/brand-voice/evals/triggers.json +16 -0
- package/skills/browser-qa/SKILL.md +99 -0
- package/skills/browser-qa/evals/triggers.json +16 -0
- package/skills/bun-runtime/SKILL.md +93 -0
- package/skills/bun-runtime/evals/triggers.json +16 -0
- package/skills/cisco-ios-config/SKILL.md +101 -0
- package/skills/cisco-ios-config/evals/triggers.json +18 -0
- package/skills/cisco-ios-config/references/verification.md +63 -0
- package/skills/clickhouse-patterns/SKILL.md +101 -0
- package/skills/clickhouse-patterns/evals/triggers.json +18 -0
- package/skills/clickhouse-patterns/references/engines-and-views.md +138 -0
- package/skills/clinical-decision-support/SKILL.md +90 -0
- package/skills/clinical-decision-support/evals/triggers.json +16 -0
- package/skills/clinical-decision-support/references/cds-hooks.md +76 -0
- package/skills/clinical-decision-support/references/regulatory-boundaries.md +37 -0
- package/skills/code-tour/SKILL.md +97 -0
- package/skills/code-tour/evals/triggers.json +14 -0
- package/skills/codebase-onboarding/SKILL.md +112 -0
- package/skills/codebase-onboarding/evals/triggers.json +14 -0
- package/skills/cold-outreach-sequences/SKILL.md +90 -0
- package/skills/cold-outreach-sequences/evals/triggers.json +16 -0
- package/skills/content-repurposing/SKILL.md +82 -0
- package/skills/content-repurposing/evals/triggers.json +16 -0
- package/skills/context-budget/SKILL.md +84 -0
- package/skills/context-budget/evals/triggers.json +14 -0
- package/skills/conventional-commits/SKILL.md +144 -0
- package/skills/conventional-commits/evals/triggers.json +16 -0
- package/skills/cpp-patterns/SKILL.md +129 -0
- package/skills/cpp-patterns/evals/triggers.json +16 -0
- package/skills/cpp-patterns/references/cmake-and-tooling.md +88 -0
- package/skills/cpp-patterns/references/undefined-behaviour.md +75 -0
- package/skills/cpp-testing/SKILL.md +131 -0
- package/skills/cpp-testing/evals/triggers.json +16 -0
- package/skills/csharp-patterns/SKILL.md +110 -0
- package/skills/csharp-patterns/evals/triggers.json +17 -0
- package/skills/csharp-patterns/references/analyzers.md +59 -0
- package/skills/csharp-testing/SKILL.md +85 -0
- package/skills/csharp-testing/evals/triggers.json +17 -0
- package/skills/csharp-testing/references/integration-tests.md +77 -0
- package/skills/css-architecture/SKILL.md +143 -0
- package/skills/css-architecture/evals/triggers.json +16 -0
- package/skills/customs-trade-compliance/SKILL.md +95 -0
- package/skills/customs-trade-compliance/evals/triggers.json +16 -0
- package/skills/customs-trade-compliance/references/incoterms-2020.md +42 -0
- package/skills/dart-patterns/SKILL.md +157 -0
- package/skills/dart-patterns/evals/triggers.json +18 -0
- package/skills/dart-testing/SKILL.md +164 -0
- package/skills/dart-testing/evals/triggers.json +18 -0
- package/skills/data-analysis-notebooks/SKILL.md +113 -0
- package/skills/data-analysis-notebooks/evals/triggers.json +16 -0
- package/skills/data-analysis-notebooks/references/dataframe-pitfalls.md +49 -0
- package/skills/data-analysis-notebooks/references/statistics-pitfalls.md +24 -0
- package/skills/data-pipelines/SKILL.md +90 -0
- package/skills/data-pipelines/evals/triggers.json +16 -0
- package/skills/data-pipelines/references/orchestrators.md +64 -0
- package/skills/database-migrations/SKILL.md +102 -0
- package/skills/database-migrations/evals/triggers.json +16 -0
- package/skills/database-migrations/references/recipes.md +98 -0
- package/skills/database-migrations/references/tools.md +21 -0
- package/skills/dbt-patterns/SKILL.md +96 -0
- package/skills/dbt-patterns/evals/triggers.json +16 -0
- package/skills/dbt-patterns/references/yaml-examples.md +114 -0
- package/skills/deep-research/SKILL.md +107 -0
- package/skills/deep-research/evals/triggers.json +14 -0
- package/skills/dependency-audit/SKILL.md +103 -0
- package/skills/dependency-audit/evals/triggers.json +16 -0
- package/skills/dependency-audit/references/tools.md +124 -0
- package/skills/deployment-strategies/SKILL.md +108 -0
- package/skills/deployment-strategies/evals/triggers.json +16 -0
- package/skills/django-patterns/SKILL.md +123 -0
- package/skills/django-patterns/evals/triggers.json +16 -0
- package/skills/docker-patterns/SKILL.md +132 -0
- package/skills/docker-patterns/evals/triggers.json +16 -0
- package/skills/docker-patterns/references/dockerfiles.md +105 -0
- package/skills/e2e-testing-playwright/SKILL.md +94 -0
- package/skills/e2e-testing-playwright/evals/triggers.json +16 -0
- package/skills/e2e-testing-playwright/references/config-and-ci.md +129 -0
- package/skills/ef-core-patterns/SKILL.md +117 -0
- package/skills/ef-core-patterns/evals/triggers.json +16 -0
- package/skills/email-deliverability/SKILL.md +99 -0
- package/skills/email-deliverability/evals/triggers.json +17 -0
- package/skills/email-deliverability/references/dns-records.md +85 -0
- package/skills/error-handling-patterns/SKILL.md +112 -0
- package/skills/error-handling-patterns/evals/triggers.json +18 -0
- package/skills/error-handling-patterns/references/language-idioms.md +49 -0
- package/skills/estimation-and-scoping/SKILL.md +97 -0
- package/skills/estimation-and-scoping/evals/triggers.json +14 -0
- package/skills/evm-dapp-integration/SKILL.md +73 -0
- package/skills/evm-dapp-integration/evals/triggers.json +16 -0
- package/skills/fastapi-patterns/SKILL.md +127 -0
- package/skills/fastapi-patterns/evals/triggers.json +16 -0
- package/skills/ffmpeg-media-processing/SKILL.md +94 -0
- package/skills/ffmpeg-media-processing/evals/triggers.json +16 -0
- package/skills/ffmpeg-media-processing/references/recipes.md +112 -0
- package/skills/fhir-integration/SKILL.md +81 -0
- package/skills/fhir-integration/evals/triggers.json +16 -0
- package/skills/fhir-integration/references/search-and-bundles.md +73 -0
- package/skills/fhir-integration/references/smart-on-fhir.md +50 -0
- package/skills/flask-patterns/SKILL.md +132 -0
- package/skills/flask-patterns/evals/triggers.json +16 -0
- package/skills/flutter-patterns/SKILL.md +99 -0
- package/skills/flutter-patterns/evals/triggers.json +16 -0
- package/skills/flutterwave-integration/SKILL.md +75 -0
- package/skills/flutterwave-integration/evals/triggers.json +16 -0
- package/skills/flutterwave-integration/references/api.md +73 -0
- package/skills/flutterwave-integration/references/webhooks.md +49 -0
- package/skills/fsharp-patterns/SKILL.md +133 -0
- package/skills/fsharp-patterns/evals/triggers.json +16 -0
- package/skills/fsharp-testing/SKILL.md +136 -0
- package/skills/fsharp-testing/evals/triggers.json +16 -0
- package/skills/gcp-patterns/SKILL.md +86 -0
- package/skills/gcp-patterns/evals/triggers.json +16 -0
- package/skills/git-workflow/SKILL.md +114 -0
- package/skills/git-workflow/evals/triggers.json +16 -0
- package/skills/github-actions-ci/SKILL.md +117 -0
- package/skills/github-actions-ci/evals/triggers.json +16 -0
- package/skills/github-api-automation/SKILL.md +101 -0
- package/skills/github-api-automation/evals/triggers.json +17 -0
- package/skills/go-patterns/SKILL.md +118 -0
- package/skills/go-patterns/evals/triggers.json +17 -0
- package/skills/go-patterns/references/concurrency.md +162 -0
- package/skills/go-testing/SKILL.md +154 -0
- package/skills/go-testing/evals/triggers.json +17 -0
- package/skills/hexagonal-architecture/SKILL.md +89 -0
- package/skills/hexagonal-architecture/evals/triggers.json +17 -0
- package/skills/hipaa-compliance/SKILL.md +91 -0
- package/skills/hipaa-compliance/evals/triggers.json +18 -0
- package/skills/hipaa-compliance/references/safeguards.md +68 -0
- package/skills/hl7v2-integration/SKILL.md +69 -0
- package/skills/hl7v2-integration/evals/triggers.json +16 -0
- package/skills/hl7v2-integration/references/messages-and-ack.md +78 -0
- package/skills/homelab-network-setup/SKILL.md +125 -0
- package/skills/homelab-network-setup/evals/triggers.json +18 -0
- package/skills/homelab-network-setup/references/firewall-and-dns.md +45 -0
- package/skills/homelab-network-setup/references/wireguard.md +72 -0
- package/skills/hubtel-integration/SKILL.md +77 -0
- package/skills/hubtel-integration/evals/triggers.json +16 -0
- package/skills/hubtel-integration/references/api.md +92 -0
- package/skills/i18n-localization/SKILL.md +114 -0
- package/skills/i18n-localization/evals/triggers.json +16 -0
- package/skills/i18n-localization/references/libraries.md +52 -0
- package/skills/incident-postmortem/SKILL.md +118 -0
- package/skills/incident-postmortem/evals/triggers.json +14 -0
- package/skills/inventory-demand-planning/SKILL.md +89 -0
- package/skills/inventory-demand-planning/evals/triggers.json +16 -0
- package/skills/inventory-demand-planning/references/formulas.md +62 -0
- package/skills/investor-materials/SKILL.md +127 -0
- package/skills/investor-materials/evals/triggers.json +16 -0
- package/skills/investor-materials/references/templates.md +74 -0
- package/skills/invoicing-and-tax/SKILL.md +84 -0
- package/skills/invoicing-and-tax/evals/triggers.json +17 -0
- package/skills/invoicing-and-tax/references/e-invoicing.md +24 -0
- package/skills/invoicing-and-tax/references/invoice-fields.md +38 -0
- package/skills/invoicing-and-tax/references/rounding.md +26 -0
- package/skills/java-patterns/SKILL.md +141 -0
- package/skills/java-patterns/evals/triggers.json +17 -0
- package/skills/java-testing/SKILL.md +142 -0
- package/skills/java-testing/evals/triggers.json +16 -0
- package/skills/jetpack-compose-patterns/SKILL.md +88 -0
- package/skills/jetpack-compose-patterns/evals/triggers.json +16 -0
- package/skills/jira-integration/SKILL.md +92 -0
- package/skills/jira-integration/evals/triggers.json +17 -0
- package/skills/jpa-hibernate/SKILL.md +92 -0
- package/skills/jpa-hibernate/evals/triggers.json +18 -0
- package/skills/jpa-hibernate/references/mapping.md +99 -0
- package/skills/kotlin-multiplatform/SKILL.md +97 -0
- package/skills/kotlin-multiplatform/evals/triggers.json +16 -0
- package/skills/kotlin-patterns/SKILL.md +128 -0
- package/skills/kotlin-patterns/evals/triggers.json +16 -0
- package/skills/kotlin-testing/SKILL.md +117 -0
- package/skills/kotlin-testing/evals/triggers.json +16 -0
- package/skills/ktor-patterns/SKILL.md +135 -0
- package/skills/ktor-patterns/evals/triggers.json +18 -0
- package/skills/kubernetes-patterns/SKILL.md +106 -0
- package/skills/kubernetes-patterns/evals/triggers.json +16 -0
- package/skills/kubernetes-patterns/references/manifests.md +115 -0
- package/skills/laravel-patterns/SKILL.md +136 -0
- package/skills/laravel-patterns/evals/triggers.json +16 -0
- package/skills/linear-integration/SKILL.md +98 -0
- package/skills/linear-integration/evals/triggers.json +17 -0
- package/skills/literature-review/SKILL.md +109 -0
- package/skills/literature-review/evals/triggers.json +16 -0
- package/skills/literature-review/references/appraisal.md +41 -0
- package/skills/literature-review/references/search-sources.md +39 -0
- package/skills/llm-app-patterns/SKILL.md +97 -0
- package/skills/llm-app-patterns/evals/triggers.json +17 -0
- package/skills/llm-evals/SKILL.md +98 -0
- package/skills/llm-evals/evals/triggers.json +16 -0
- package/skills/llm-evals/references/graders.md +66 -0
- package/skills/logistics-exception-management/SKILL.md +90 -0
- package/skills/logistics-exception-management/evals/triggers.json +16 -0
- package/skills/manim-animations/SKILL.md +106 -0
- package/skills/manim-animations/evals/triggers.json +16 -0
- package/skills/market-research/SKILL.md +117 -0
- package/skills/market-research/evals/triggers.json +16 -0
- package/skills/marketing-campaign/SKILL.md +111 -0
- package/skills/marketing-campaign/evals/triggers.json +16 -0
- package/skills/mcp-server-patterns/SKILL.md +127 -0
- package/skills/mcp-server-patterns/evals/triggers.json +18 -0
- package/skills/mcp-server-patterns/references/protocol-notes.md +68 -0
- package/skills/metrics-dashboards/SKILL.md +113 -0
- package/skills/metrics-dashboards/evals/triggers.json +16 -0
- package/skills/ml-experiment-workflow/SKILL.md +125 -0
- package/skills/ml-experiment-workflow/evals/triggers.json +16 -0
- package/skills/mongodb-patterns/SKILL.md +115 -0
- package/skills/mongodb-patterns/evals/triggers.json +18 -0
- package/skills/mongodb-patterns/references/indexes-and-aggregation.md +94 -0
- package/skills/mpesa-daraja-integration/SKILL.md +70 -0
- package/skills/mpesa-daraja-integration/evals/triggers.json +16 -0
- package/skills/mpesa-daraja-integration/references/api.md +103 -0
- package/skills/mpesa-daraja-integration/references/callbacks.md +74 -0
- package/skills/mtn-momo-integration/SKILL.md +66 -0
- package/skills/mtn-momo-integration/evals/triggers.json +16 -0
- package/skills/mtn-momo-integration/references/api.md +153 -0
- package/skills/mtn-momo-integration/references/callbacks.md +92 -0
- package/skills/mtn-momo-integration/references/sandbox-and-go-live.md +51 -0
- package/skills/mtn-momo-integration/scripts/sandbox-setup.js +79 -0
- package/skills/mysql-patterns/SKILL.md +96 -0
- package/skills/mysql-patterns/evals/triggers.json +16 -0
- package/skills/mysql-patterns/references/explain.md +34 -0
- package/skills/mysql-patterns/references/online-ddl.md +41 -0
- package/skills/nestjs-patterns/SKILL.md +114 -0
- package/skills/nestjs-patterns/evals/triggers.json +16 -0
- package/skills/nestjs-patterns/references/testing.md +68 -0
- package/skills/network-automation/SKILL.md +107 -0
- package/skills/network-automation/evals/triggers.json +18 -0
- package/skills/network-automation/references/tool-snippets.md +153 -0
- package/skills/network-troubleshooting/SKILL.md +152 -0
- package/skills/network-troubleshooting/evals/triggers.json +18 -0
- package/skills/network-troubleshooting/references/commands.md +33 -0
- package/skills/nextjs-patterns/SKILL.md +150 -0
- package/skills/nextjs-patterns/evals/triggers.json +18 -0
- package/skills/nextjs-patterns/references/caching.md +87 -0
- package/skills/node-backend-patterns/SKILL.md +103 -0
- package/skills/node-backend-patterns/evals/triggers.json +16 -0
- package/skills/node-backend-patterns/references/templates.md +122 -0
- package/skills/nuxt-patterns/SKILL.md +103 -0
- package/skills/nuxt-patterns/evals/triggers.json +16 -0
- package/skills/parallel-agent-orchestration/SKILL.md +106 -0
- package/skills/parallel-agent-orchestration/evals/triggers.json +14 -0
- package/skills/paystack-integration/SKILL.md +59 -0
- package/skills/paystack-integration/evals/triggers.json +14 -0
- package/skills/paystack-integration/references/api.md +73 -0
- package/skills/paystack-integration/references/mobile-money.md +50 -0
- package/skills/paystack-integration/references/webhooks.md +80 -0
- package/skills/pci-dss-scope/SKILL.md +103 -0
- package/skills/pci-dss-scope/evals/triggers.json +18 -0
- package/skills/pci-dss-scope/references/saq-and-data.md +36 -0
- package/skills/perl-patterns/SKILL.md +135 -0
- package/skills/perl-patterns/evals/triggers.json +18 -0
- package/skills/perl-testing/SKILL.md +126 -0
- package/skills/perl-testing/evals/triggers.json +17 -0
- package/skills/php-patterns/SKILL.md +116 -0
- package/skills/php-patterns/evals/triggers.json +16 -0
- package/skills/php-testing/SKILL.md +123 -0
- package/skills/php-testing/evals/triggers.json +16 -0
- package/skills/postgres-patterns/SKILL.md +103 -0
- package/skills/postgres-patterns/evals/triggers.json +17 -0
- package/skills/postgres-patterns/references/explain.md +59 -0
- package/skills/postgres-patterns/references/locks.md +53 -0
- package/skills/prd-writing/SKILL.md +124 -0
- package/skills/prd-writing/evals/triggers.json +15 -0
- package/skills/prisma-patterns/SKILL.md +83 -0
- package/skills/prisma-patterns/evals/triggers.json +18 -0
- package/skills/prisma-patterns/references/prisma-8.md +46 -0
- package/skills/prisma-patterns/references/workflow.md +50 -0
- package/skills/privacy-compliance/SKILL.md +121 -0
- package/skills/privacy-compliance/evals/triggers.json +18 -0
- package/skills/privacy-compliance/references/jurisdictions.md +51 -0
- package/skills/production-scheduling/SKILL.md +98 -0
- package/skills/production-scheduling/evals/triggers.json +16 -0
- package/skills/prompt-engineering/SKILL.md +92 -0
- package/skills/prompt-engineering/evals/triggers.json +16 -0
- package/skills/python-patterns/SKILL.md +107 -0
- package/skills/python-patterns/evals/triggers.json +16 -0
- package/skills/python-patterns/references/tooling.md +101 -0
- package/skills/python-testing/SKILL.md +126 -0
- package/skills/python-testing/evals/triggers.json +16 -0
- package/skills/python-testing/references/mocking.md +71 -0
- package/skills/pytorch-patterns/SKILL.md +139 -0
- package/skills/pytorch-patterns/evals/triggers.json +16 -0
- package/skills/pytorch-patterns/references/debugging.md +39 -0
- package/skills/quarkus-patterns/SKILL.md +93 -0
- package/skills/quarkus-patterns/evals/triggers.json +18 -0
- package/skills/rag-pipeline-patterns/SKILL.md +117 -0
- package/skills/rag-pipeline-patterns/evals/triggers.json +18 -0
- package/skills/rag-pipeline-patterns/references/evaluation.md +37 -0
- package/skills/rag-pipeline-patterns/references/retrieval-recipes.md +78 -0
- package/skills/rails-patterns/SKILL.md +132 -0
- package/skills/rails-patterns/evals/triggers.json +16 -0
- package/skills/react-native-patterns/SKILL.md +101 -0
- package/skills/react-native-patterns/evals/triggers.json +16 -0
- package/skills/react-patterns/SKILL.md +140 -0
- package/skills/react-patterns/evals/triggers.json +18 -0
- package/skills/react-performance/SKILL.md +107 -0
- package/skills/react-performance/evals/triggers.json +18 -0
- package/skills/redis-patterns/SKILL.md +109 -0
- package/skills/redis-patterns/evals/triggers.json +18 -0
- package/skills/redis-patterns/references/operations.md +61 -0
- package/skills/refactoring-safely/SKILL.md +107 -0
- package/skills/refactoring-safely/evals/triggers.json +17 -0
- package/skills/release-management/SKILL.md +141 -0
- package/skills/release-management/evals/triggers.json +16 -0
- package/skills/remotion-video/SKILL.md +91 -0
- package/skills/remotion-video/evals/triggers.json +16 -0
- package/skills/remotion-video/references/lambda.md +66 -0
- package/skills/returns-reverse-logistics/SKILL.md +97 -0
- package/skills/returns-reverse-logistics/evals/triggers.json +16 -0
- package/skills/ruby-patterns/SKILL.md +127 -0
- package/skills/ruby-patterns/evals/triggers.json +18 -0
- package/skills/ruby-testing/SKILL.md +113 -0
- package/skills/ruby-testing/evals/triggers.json +18 -0
- package/skills/runbook-writing/SKILL.md +101 -0
- package/skills/runbook-writing/evals/triggers.json +14 -0
- package/skills/rust-patterns/SKILL.md +118 -0
- package/skills/rust-patterns/evals/triggers.json +16 -0
- package/skills/rust-patterns/references/cargo-and-clippy.md +105 -0
- package/skills/rust-patterns/references/errors-and-traits.md +97 -0
- package/skills/rust-testing/SKILL.md +148 -0
- package/skills/rust-testing/evals/triggers.json +16 -0
- package/skills/rust-testing/references/fakes-proptest-async.md +113 -0
- package/skills/search-first/SKILL.md +73 -0
- package/skills/search-first/evals/triggers.json +14 -0
- package/skills/security-review/SKILL.md +58 -0
- package/skills/security-review/evals/triggers.json +14 -0
- package/skills/security-review/references/checklist.md +54 -0
- package/skills/security-review/scripts/scan-secrets.js +79 -0
- package/skills/seo-audit/SKILL.md +111 -0
- package/skills/seo-audit/evals/triggers.json +16 -0
- package/skills/seo-audit/references/checks.md +65 -0
- package/skills/session-handoff/SKILL.md +101 -0
- package/skills/session-handoff/evals/triggers.json +14 -0
- package/skills/skill-authoring/SKILL.md +87 -0
- package/skills/skill-authoring/evals/triggers.json +14 -0
- package/skills/skill-authoring/references/frontmatter.md +40 -0
- package/skills/slack-app-integration/SKILL.md +108 -0
- package/skills/slack-app-integration/evals/triggers.json +17 -0
- package/skills/smart-contract-security/SKILL.md +110 -0
- package/skills/smart-contract-security/evals/triggers.json +16 -0
- package/skills/smart-contract-security/references/vulnerability-checklist.md +79 -0
- package/skills/social-media-publishing/SKILL.md +84 -0
- package/skills/social-media-publishing/evals/triggers.json +17 -0
- package/skills/social-media-publishing/references/platform-apis.md +116 -0
- package/skills/solidity-patterns/SKILL.md +95 -0
- package/skills/solidity-patterns/evals/triggers.json +16 -0
- package/skills/spring-boot-patterns/SKILL.md +128 -0
- package/skills/spring-boot-patterns/evals/triggers.json +18 -0
- package/skills/spring-boot-patterns/references/testing.md +72 -0
- package/skills/stripe-integration/SKILL.md +69 -0
- package/skills/stripe-integration/evals/triggers.json +16 -0
- package/skills/stripe-integration/references/api.md +71 -0
- package/skills/stripe-integration/references/billing.md +58 -0
- package/skills/stripe-integration/references/webhooks.md +76 -0
- package/skills/subscription-billing-ops/SKILL.md +118 -0
- package/skills/subscription-billing-ops/evals/triggers.json +17 -0
- package/skills/subscription-billing-ops/references/proration.md +47 -0
- package/skills/subscription-billing-ops/references/revenue-metrics.md +38 -0
- package/skills/svelte-patterns/SKILL.md +122 -0
- package/skills/svelte-patterns/evals/triggers.json +16 -0
- package/skills/swift-patterns/SKILL.md +113 -0
- package/skills/swift-patterns/evals/triggers.json +16 -0
- package/skills/swift-patterns/references/concurrency-migration.md +40 -0
- package/skills/swift-testing/SKILL.md +121 -0
- package/skills/swift-testing/evals/triggers.json +16 -0
- package/skills/swift-testing/references/swift-testing-cheatsheet.md +81 -0
- package/skills/swiftui-patterns/SKILL.md +102 -0
- package/skills/swiftui-patterns/evals/triggers.json +16 -0
- package/skills/tdd-workflow/SKILL.md +67 -0
- package/skills/tdd-workflow/evals/triggers.json +14 -0
- package/skills/technical-writing/SKILL.md +95 -0
- package/skills/technical-writing/evals/triggers.json +14 -0
- package/skills/terraform-patterns/SKILL.md +122 -0
- package/skills/terraform-patterns/evals/triggers.json +16 -0
- package/skills/terraform-patterns/references/backends.md +67 -0
- package/skills/test-coverage-improvement/SKILL.md +107 -0
- package/skills/test-coverage-improvement/evals/triggers.json +16 -0
- package/skills/threat-modeling/SKILL.md +113 -0
- package/skills/threat-modeling/evals/triggers.json +16 -0
- package/skills/typescript-patterns/SKILL.md +122 -0
- package/skills/typescript-patterns/evals/triggers.json +16 -0
- package/skills/typescript-testing/SKILL.md +101 -0
- package/skills/typescript-testing/evals/triggers.json +16 -0
- package/skills/verification-loop/SKILL.md +70 -0
- package/skills/verification-loop/evals/triggers.json +14 -0
- package/skills/verification-loop/scripts/detect-checks.js +102 -0
- package/skills/vite-patterns/SKILL.md +117 -0
- package/skills/vite-patterns/evals/triggers.json +16 -0
- package/skills/vue-patterns/SKILL.md +104 -0
- package/skills/vue-patterns/evals/triggers.json +16 -0
- package/skills/web-accessibility/SKILL.md +119 -0
- package/skills/web-accessibility/evals/triggers.json +16 -0
- package/skills/web-performance/SKILL.md +107 -0
- package/skills/web-performance/evals/triggers.json +16 -0
- package/skills/web-scraping/SKILL.md +80 -0
- package/skills/web-scraping/evals/triggers.json +16 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-patterns
|
|
3
|
+
description: Flutter app architecture from the widget tree up, covering small const widgets and composition over inheritance, choosing between Riverpod, Bloc and Provider for state, declarative routing with go_router or Navigator, FutureBuilder and StreamBuilder async UI with loading and error states, rebuild and jank performance, and Dart-to-native platform channels or Pigeon. Use when building or restructuring Flutter screens and widgets, picking or applying a state management approach, wiring navigation and deep links, chasing dropped frames, or calling iOS or Android code from a Flutter app.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Needs the Flutter SDK (flutter analyze, flutter test); device runs need Xcode for iOS or the Android SDK.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: coding
|
|
9
|
+
kete-loom/risk: runs-commands
|
|
10
|
+
kete-loom/requires: "flutter"
|
|
11
|
+
kete-loom/owner: "Kete-org"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Flutter patterns
|
|
15
|
+
|
|
16
|
+
Build Flutter screens from small widgets, keep state where one decision rule puts it, and keep frames under budget. Plain Dart language features, isolates and package layout belong to `dart-patterns`; widget and golden tests belong to `dart-testing`. To review someone else's Flutter change, use the `flutter-reviewer` agent.
|
|
17
|
+
|
|
18
|
+
## 0. Before you write
|
|
19
|
+
|
|
20
|
+
1. Read `pubspec.yaml`: Flutter/Dart SDK constraints, and which of `flutter_riverpod`, `flutter_bloc`, `provider`, `go_router`, `auto_route` are already used.
|
|
21
|
+
2. Read `analysis_options.yaml`. Keep `flutter_lints` (or the project's stricter set) on; `use_build_context_synchronously` catches real async bugs. `prefer_const_constructors` is no longer in `flutter_lints` (removed in 5.0), so enable it explicitly if the team wants const enforced.
|
|
22
|
+
3. Follow the existing folder layout (feature-first `lib/features/<name>/` or layer-first). Don't reorganise unasked.
|
|
23
|
+
4. Run `flutter analyze` and `flutter test` once and record the result.
|
|
24
|
+
|
|
25
|
+
## 1. Widget composition
|
|
26
|
+
|
|
27
|
+
- Split a widget when a part rebuilds for different reasons, is reused, or the `build` method passes ~80 lines. Extract a **widget class**, not a helper method returning `Widget`: classes get their own element, can be `const` and rebuild independently.
|
|
28
|
+
- Mark constructors and instances `const` wherever possible; const subtrees are skipped on rebuild.
|
|
29
|
+
- `StatelessWidget` by default. Use `StatefulWidget` only for state owned by this widget: controllers, animation, focus, local form input.
|
|
30
|
+
- Every `TextEditingController`, `AnimationController`, `ScrollController`, `FocusNode`, `StreamSubscription` and `Timer` created in a `State` is disposed or cancelled in `dispose()`. Create them in `initState`, never in `build`.
|
|
31
|
+
- Pass data down through constructors; pass events up through callbacks. Don't reach into a child's state with a `GlobalKey` unless a framework API (forms, navigators) needs it.
|
|
32
|
+
- Keys: use `ValueKey(item.id)` on list items that can reorder, insert or delete, so state follows the item.
|
|
33
|
+
|
|
34
|
+
## 2. State management
|
|
35
|
+
|
|
36
|
+
Use what the project already uses. For a new project or a new kind of state, apply this rule:
|
|
37
|
+
|
|
38
|
+
| State | Put it in |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Ephemeral UI (tab index, expanded, text field) | `State` of the widget |
|
|
41
|
+
| Shared app or server state, new or small-to-mid project | Riverpod (`Notifier` / `AsyncNotifier`, optionally with `riverpod_generator`) |
|
|
42
|
+
| Event-driven flows where the team wants explicit events and transitions | Bloc / Cubit (`flutter_bloc`) |
|
|
43
|
+
| Existing Provider app | Keep `provider` with `ChangeNotifier`; don't mix in a second library |
|
|
44
|
+
|
|
45
|
+
Rules that apply to all three:
|
|
46
|
+
|
|
47
|
+
- Keep business logic out of widgets: a notifier, cubit or repository owns it, and is unit-testable without a widget.
|
|
48
|
+
- State objects are immutable values (`copyWith`, or `freezed` if already used); never mutate a list in place and re-emit the same instance.
|
|
49
|
+
- Watch the narrowest thing: `ref.watch(provider.select((s) => s.count))`, `context.select`, or `BlocSelector`, so unrelated changes don't rebuild the screen.
|
|
50
|
+
- Use `ref.read` / `context.read` in callbacks, `watch` only in `build`.
|
|
51
|
+
- Model async state explicitly: loading, data, error (`AsyncValue` in Riverpod, sealed state classes in Bloc).
|
|
52
|
+
|
|
53
|
+
## 3. Navigation
|
|
54
|
+
|
|
55
|
+
- Apps with deep links, web, or nested tabs: `go_router` with a single route table, path parameters for IDs, and `redirect` for auth guards.
|
|
56
|
+
- Small apps with no deep links: `Navigator.push` with `MaterialPageRoute` is fine.
|
|
57
|
+
- Pass IDs in routes, not whole objects, so deep links and restoration work.
|
|
58
|
+
- Return results with `Navigator.pop(context, value)` / `context.pop(value)` and await the push.
|
|
59
|
+
- Handle the Android back button with `PopScope` where leaving would lose data.
|
|
60
|
+
|
|
61
|
+
## 4. Async UI
|
|
62
|
+
|
|
63
|
+
- Never start a future inside `build`. `FutureBuilder(future: fetch())` refetches on every rebuild. Create the future in `initState` (or a provider) and pass the stored future.
|
|
64
|
+
- Always render all three states: loading, error with a retry, and data (including empty).
|
|
65
|
+
- After every `await` in a `State` method, check `if (!mounted) return;` before using `context` or calling `setState`. For a `BuildContext` passed in as a parameter, check `context.mounted`.
|
|
66
|
+
- Capture `Navigator.of(context)` or `ScaffoldMessenger.of(context)` before the `await` when you need them after it.
|
|
67
|
+
- Cancel stream subscriptions in `dispose()`; prefer `StreamBuilder` or a provider so the framework does it.
|
|
68
|
+
|
|
69
|
+
## 5. Performance
|
|
70
|
+
|
|
71
|
+
Measure in **profile mode** (`flutter run --profile`) on a real device, with the DevTools performance view and "track widget rebuilds". Debug mode is not representative.
|
|
72
|
+
|
|
73
|
+
- Long or unbounded lists: `ListView.builder` / `SliverList` with `itemExtent` or `prototypeItem` when rows are fixed height. Never `Column` + `map` inside a `SingleChildScrollView` for long data.
|
|
74
|
+
- Avoid `shrinkWrap: true` on long lists inside other scrollables; use slivers in a `CustomScrollView`.
|
|
75
|
+
- Keep `build` cheap: no parsing, sorting, I/O or `DateFormat` construction in it. Move heavy CPU work to `compute()` / `Isolate.run`.
|
|
76
|
+
- `Opacity`, `ClipRRect`, `ShaderMask` and `BackdropFilter` over large areas are expensive; prefer `FadeTransition`, pre-clipped images or a colour with alpha.
|
|
77
|
+
- Wrap independently animating regions in `RepaintBoundary` only when the DevTools repaint view shows it helps.
|
|
78
|
+
- Size images: `cacheWidth` / `cacheHeight` on `Image` for thumbnails.
|
|
79
|
+
|
|
80
|
+
## 6. Platform channels
|
|
81
|
+
|
|
82
|
+
1. Search pub.dev for a maintained plugin (federated, with iOS and Android support) first.
|
|
83
|
+
2. For your own native calls, prefer **Pigeon** to generate type-safe Dart, Kotlin and Swift code over hand-written `MethodChannel` string maps.
|
|
84
|
+
3. With `MethodChannel`: one channel name per feature (`com.example.app/battery`), handle `PlatformException` and `MissingPluginException` in Dart, and return errors from native with an error code, never a crash.
|
|
85
|
+
4. Platform handlers run on the platform's main thread; move long native work to a background thread and reply when done.
|
|
86
|
+
5. Native code changes need a full rebuild, not a hot reload. Test on both platforms.
|
|
87
|
+
|
|
88
|
+
**Stop and ask the user** before adding a new state management or routing package, raising the SDK constraint, or changing native project settings (signing, min SDK, deployment target).
|
|
89
|
+
|
|
90
|
+
## Never
|
|
91
|
+
|
|
92
|
+
- Never call `setState` or use `context` after an `await` without a mounted check.
|
|
93
|
+
- Never create controllers, futures or streams inside `build`.
|
|
94
|
+
- Never use a helper method where a const widget class would do.
|
|
95
|
+
- Never ignore an analyzer warning by adding `// ignore:` without saying why.
|
|
96
|
+
|
|
97
|
+
## Report back
|
|
98
|
+
|
|
99
|
+
End with: widgets and files changed, state approach used and why, `flutter analyze` and `flutter test` results, and the platforms and mode (debug, profile, release) you ran it in.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Should this Flutter app use Riverpod or Bloc for the cart and auth state?",
|
|
4
|
+
"Set up go_router with a login redirect and a product/:id route",
|
|
5
|
+
"My FutureBuilder keeps refetching every time the parent rebuilds, how do I structure this screen?",
|
|
6
|
+
"Call the Android battery API from my Flutter app using Pigeon",
|
|
7
|
+
"Break this 400-line Flutter build method into smaller widgets"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Review this Flutter pull request for setState after await and leaked controllers",
|
|
11
|
+
"Convert these Dart enums and nullable fields into a sealed class hierarchy",
|
|
12
|
+
"Write widget tests and golden tests for the checkout screen",
|
|
13
|
+
"My React Native FlashList is janky on older Android phones",
|
|
14
|
+
"Build a Jetpack Compose list screen backed by a ViewModel"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutterwave-integration
|
|
3
|
+
description: Builds and reviews Flutterwave v3 payment integrations across African markets, covering Flutterwave Standard hosted checkout, direct charges for mobile money (Ghana, Kenya M-Pesa, Uganda, Francophone) and cards, server-side verification by transaction id, verif-hash webhook checks and fulfilment that runs once per tx_ref. Use when adding Flutterwave payments to an app, handling a Flutterwave webhook or redirect, charging a mobile money wallet through Flutterwave, or debugging why a Flutterwave order was marked paid wrongly or twice.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Live calls need network access to api.flutterwave.com. Examples are TypeScript (Node 18+); the rules apply to any backend language.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: integrations
|
|
9
|
+
kete-loom/risk: network
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Flutterwave integration
|
|
14
|
+
|
|
15
|
+
Build a Flutterwave flow where the browser can't fake a payment, every `tx_ref` gives value at most once, and pending mobile money charges still settle. This skill targets the v3 API (`https://api.flutterwave.com/v3`). If the account uses a newer API version, stop and confirm the endpoints and webhook signing scheme in the dashboard and docs before writing code.
|
|
16
|
+
|
|
17
|
+
## Ground rules
|
|
18
|
+
|
|
19
|
+
1. **The secret key stays on the server.** `FLW_SECRET_KEY` (`FLWSECK-...`) and the encryption key are read from the environment. Only the public key (`FLWPUBK-...`) may reach a browser or app.
|
|
20
|
+
2. **The server sets the price.** Compute `amount` and `currency` from your own order record. Never accept them from the client.
|
|
21
|
+
3. **One `tx_ref` per attempt, stored before you call Flutterwave**, with a unique constraint. Flutterwave requires `tx_ref` to be unique per transaction. A customer retry gets a new `tx_ref` on the same order.
|
|
22
|
+
4. **Only a verify call gives value.** The redirect query string and the webhook body are hints. Call `GET /v3/transactions/{id}/verify` and require: `status === "successful"`, `tx_ref` equals yours, `currency` equals yours, and `amount >= expected` (if higher, give value and refund the difference, as Flutterwave's docs advise).
|
|
23
|
+
5. **Fulfilment is idempotent.** Mark paid with a guarded update (`UPDATE payments SET status='paid' WHERE tx_ref = $1 AND status = 'pending'`) and fulfil only if a row changed. Webhooks retry, users refresh, and both paths may run at once.
|
|
24
|
+
6. **Amounts are decimals in major units** in v3 (GHS 25.50 is `25.5`). Store integer minor units in your database and convert at the API boundary; never do float arithmetic on money.
|
|
25
|
+
7. **Test keys first** (`FLWSECK_TEST-...`) until success, failure, cancel and duplicate-webhook paths all work.
|
|
26
|
+
|
|
27
|
+
## Choose the flow
|
|
28
|
+
|
|
29
|
+
| Need | Use |
|
|
30
|
+
|---|---|
|
|
31
|
+
| Fastest safe integration, cards + mobile money + bank, no PCI scope | Flutterwave Standard: `POST /v3/payments` returns `data.link`, redirect there |
|
|
32
|
+
| Your own UI for mobile money | Direct charge: `POST /v3/charges?type=<method>` (see `references/api.md`) |
|
|
33
|
+
| Your own card form | Direct card charge needs payload encryption with the account encryption key and puts you in PCI DSS scope. Stop and ask the user before building it; prefer Standard or the inline modal |
|
|
34
|
+
|
|
35
|
+
## Flow (Standard)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
client ──(order id)──▶ server: create payment row (tx_ref, expected amount/currency, pending)
|
|
39
|
+
server ──POST /v3/payments──▶ Flutterwave: data.link
|
|
40
|
+
client ──redirect──▶ hosted page; customer pays
|
|
41
|
+
Flutterwave ──redirect_url?status=..&tx_ref=..&transaction_id=..──▶ server: verify by id, show result
|
|
42
|
+
Flutterwave ──webhook charge.completed (verif-hash header)──▶ server: check hash, 200, queue verify + fulfil once
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Implementation steps
|
|
46
|
+
|
|
47
|
+
1. **Create the payment** with `POST /v3/payments`, `Authorization: Bearer <secret key>`, body `tx_ref`, `amount`, `currency`, `redirect_url`, `customer.email` (required), optional `customer.name`, `customer.phonenumber`, `payment_options`, `meta` (put your order id here). Shapes in `references/api.md`.
|
|
48
|
+
2. **Redirect handler**: read `transaction_id` from the query, call verify by id, apply ground rule 4, then show the stored status. A `status=cancelled` or `failed` redirect has nothing to verify; show the failure and offer a retry with a new `tx_ref`.
|
|
49
|
+
3. **Webhook endpoint**: follow `references/webhooks.md`. Compare the `verif-hash` header to your stored secret hash in constant time, return `200` fast, then verify by `data.id` before fulfilling.
|
|
50
|
+
4. **Mobile money direct charges**: follow the per-country notes in `references/api.md`. A `"success"` response with "Charge initiated" means the prompt was sent, not that money moved. Ghana returns `meta.authorization.mode: "redirect"`; send the customer to `meta.authorization.redirect`.
|
|
51
|
+
5. **Reconcile pending payments**: a background job verifies payments still `pending` after a few minutes (verify by id if you have it; otherwise look up by `tx_ref` using the endpoint the current docs give) and times out stale ones.
|
|
52
|
+
6. **Persist** per payment: `tx_ref`, order id, expected amount (minor units) and currency, Flutterwave transaction `id`, `flw_ref`, status, payment type, failure reason, timestamps.
|
|
53
|
+
|
|
54
|
+
## Reviewing an existing integration
|
|
55
|
+
|
|
56
|
+
Check every ground rule, then look for:
|
|
57
|
+
|
|
58
|
+
- Fulfilment in the redirect handler based on `status=successful` in the query string, without a verify call.
|
|
59
|
+
- Verify result trusted without comparing `amount`, `currency` and `tx_ref` to the stored order.
|
|
60
|
+
- Webhook with no `verif-hash` check, or a check with `==` against a hard-coded value in source.
|
|
61
|
+
- Webhook doing the verify and fulfilment inline before responding, so slow calls cause retries.
|
|
62
|
+
- `tx_ref` reused across attempts, or generated on the client.
|
|
63
|
+
- Amounts multiplied by 100 (Paystack habit) or computed with floats.
|
|
64
|
+
- Secret key in a `NEXT_PUBLIC_`/`VITE_` variable or committed to the repo.
|
|
65
|
+
- No handling for `failed`, `cancelled` or a charge that stays `pending`.
|
|
66
|
+
|
|
67
|
+
## Stop and ask the user
|
|
68
|
+
|
|
69
|
+
- Before building a raw card form (encryption plus PCI scope).
|
|
70
|
+
- Which countries and currencies they sell in; payment methods differ per country.
|
|
71
|
+
- Whether refunds or payouts (transfers) are in scope; they are separate APIs with their own approvals.
|
|
72
|
+
|
|
73
|
+
## Before you finish
|
|
74
|
+
|
|
75
|
+
Flutterwave's docs at developer.flutterwave.com are the final word on field names and versions; confirm them when writing new code. Then test in test mode: a successful payment, a declined card, a cancelled checkout, a mobile money charge that times out, the same webhook delivered twice (fulfils once), a webhook with a wrong `verif-hash` (rejected), and a tampered redirect URL (`status=successful` with a failed transaction id must not fulfil).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Add Flutterwave checkout to our Nigerian e-commerce site",
|
|
4
|
+
"How do I check the verif-hash header on my Flutterwave webhook endpoint?",
|
|
5
|
+
"Charge a Kenyan customer's M-Pesa wallet through Flutterwave from our backend",
|
|
6
|
+
"Our Flutterwave redirect marks orders paid even when the transaction failed, can you fix it?",
|
|
7
|
+
"Accept Ghana mobile money with Flutterwave direct charge instead of the hosted page"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Add Paystack checkout to our Next.js shop so Ghanaian customers can pay",
|
|
11
|
+
"Send an STK push with the Safaricom Daraja API directly",
|
|
12
|
+
"Set up Stripe subscriptions with a customer portal",
|
|
13
|
+
"Review our payment code for float money arithmetic and unverified webhooks",
|
|
14
|
+
"Integrate Hubtel online checkout for a Ghanaian school fees portal"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Flutterwave v3 API shapes used by this skill
|
|
2
|
+
|
|
3
|
+
Base URL: `https://api.flutterwave.com/v3`. Server calls send `Authorization: Bearer <FLW_SECRET_KEY>` and `Content-Type: application/json`. A top-level `"status": "success"` means the API call worked, not that money moved.
|
|
4
|
+
|
|
5
|
+
Confirm fields against developer.flutterwave.com before shipping; this is a working summary.
|
|
6
|
+
|
|
7
|
+
## Create a Standard payment link
|
|
8
|
+
|
|
9
|
+
`POST /payments`
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"tx_ref": "ord_8f2c1a_a1",
|
|
14
|
+
"amount": "25.50",
|
|
15
|
+
"currency": "GHS",
|
|
16
|
+
"redirect_url": "https://app.example.com/pay/flutterwave/return",
|
|
17
|
+
"customer": { "email": "customer@example.com", "name": "Ama Mensah", "phonenumber": "233241234567" },
|
|
18
|
+
"payment_options": "card, mobilemoneyghana",
|
|
19
|
+
"meta": { "order_id": "8f2c1a" },
|
|
20
|
+
"customizations": { "title": "Example Store" }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Response: `data.link`. Redirect the customer there. `currency` defaults to NGN when omitted, so always send it. `payment_options` only applies when "Disable preferred payment methods" is off in the dashboard.
|
|
25
|
+
|
|
26
|
+
Redirect back to `redirect_url`:
|
|
27
|
+
- success: `?status=successful&tx_ref=...&transaction_id=...`
|
|
28
|
+
- cancelled or failed: `?status=cancelled&tx_ref=...` (no transaction to verify)
|
|
29
|
+
|
|
30
|
+
## Verify a transaction
|
|
31
|
+
|
|
32
|
+
`GET /transactions/{id}/verify`, where `id` is `transaction_id` from the redirect or `data.id` from the webhook.
|
|
33
|
+
|
|
34
|
+
| Field in `data` | Expect |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `status` | `"successful"` (others include `failed`, `pending`) |
|
|
37
|
+
| `tx_ref` | the reference you stored |
|
|
38
|
+
| `currency` | the currency you stored |
|
|
39
|
+
| `amount` | `>=` the expected amount; refund any excess |
|
|
40
|
+
| `id`, `flw_ref` | store both |
|
|
41
|
+
|
|
42
|
+
## Direct charges (mobile money)
|
|
43
|
+
|
|
44
|
+
`POST /charges?type=<type>` with `tx_ref`, `amount`, `currency`, `email`, `phone_number`, and any country-specific fields.
|
|
45
|
+
|
|
46
|
+
| Market | `type` | Currency | Notes |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| Ghana | `mobile_money_ghana` | GHS | Also send `network`. The docs list both `MTN`/`VODAFONE`/`TIGO` and `MTN`/`Telecel`/`AIRTEL`; confirm accepted values in the current reference. Response has `meta.authorization.mode: "redirect"`; send the customer to `meta.authorization.redirect` |
|
|
49
|
+
| Kenya | `mpesa` | KES | Customer approves an STK prompt |
|
|
50
|
+
| Uganda | `mobile_money_uganda` | UGX | Confirm in the reference |
|
|
51
|
+
| Francophone West/Central Africa | `mobile_money_franco` | XOF / XAF | Confirm in the reference, including `country` |
|
|
52
|
+
|
|
53
|
+
Every direct charge ends the same way: wait for the `charge.completed` webhook (or poll), then verify by id before giving value.
|
|
54
|
+
|
|
55
|
+
## Minimal server client (TypeScript)
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const FLW = 'https://api.flutterwave.com/v3';
|
|
59
|
+
const headers = { Authorization: `Bearer ${process.env.FLW_SECRET_KEY}`, 'Content-Type': 'application/json' };
|
|
60
|
+
|
|
61
|
+
export async function verifyFlutterwave(id: string, expected: { txRef: string; currency: string; amountMinor: number }) {
|
|
62
|
+
const res = await fetch(`${FLW}/transactions/${encodeURIComponent(id)}/verify`, { headers });
|
|
63
|
+
if (!res.ok) throw new Error(`verify failed: ${res.status}`);
|
|
64
|
+
const { data } = await res.json();
|
|
65
|
+
const paidMinor = Math.round(Number(data.amount) * 100); // shortcut: assumes 2-decimal currencies; UGX/XOF/XAF have 0
|
|
66
|
+
return (
|
|
67
|
+
data.status === 'successful' &&
|
|
68
|
+
data.tx_ref === expected.txRef &&
|
|
69
|
+
data.currency === expected.currency &&
|
|
70
|
+
paidMinor >= expected.amountMinor
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Flutterwave webhooks (v3)
|
|
2
|
+
|
|
3
|
+
## What Flutterwave sends
|
|
4
|
+
|
|
5
|
+
- `POST` with JSON such as `{ "event": "charge.completed", "data": { "id": 123, "tx_ref": "...", "status": "successful", "amount": 25.5, "currency": "GHS", ... } }`.
|
|
6
|
+
- Header `verif-hash`: the **secret hash** you set in the dashboard webhook settings. It is a shared secret echoed back, not an HMAC of the body, so it proves the sender knows the secret but not that the body is untouched. That is why every webhook is followed by a verify call.
|
|
7
|
+
- Anything other than `200` (including `3xx`) is a failure. With retries enabled in the dashboard, Flutterwave retries 3 times, 30 minutes apart. Requests time out after 60 seconds. Response body and headers are ignored.
|
|
8
|
+
|
|
9
|
+
If the dashboard or docs for your account show an HMAC signature header instead of, or as well as, `verif-hash`, verify that signature on the raw body and keep the verify call.
|
|
10
|
+
|
|
11
|
+
## Handler rules
|
|
12
|
+
|
|
13
|
+
1. Store the secret hash in the environment (`FLW_WEBHOOK_HASH`), not in code.
|
|
14
|
+
2. Read the `verif-hash` header. Missing or not equal (constant-time compare) → respond `401` and stop.
|
|
15
|
+
3. Respond `200` immediately, then enqueue `data.id` for processing.
|
|
16
|
+
4. The processor calls `GET /v3/transactions/{id}/verify`, applies the checks in `api.md`, and marks the payment paid with a guarded update keyed on `tx_ref`. Unknown `tx_ref` → log and drop; never create an order from a webhook.
|
|
17
|
+
5. Log event type, `id` and `tx_ref`. Never log card data, the secret key or the hash.
|
|
18
|
+
|
|
19
|
+
## Express (TypeScript)
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import crypto from 'node:crypto';
|
|
23
|
+
import express from 'express';
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
const HASH = process.env.FLW_WEBHOOK_HASH!;
|
|
27
|
+
|
|
28
|
+
function sameSecret(a: string, b: string) {
|
|
29
|
+
const x = Buffer.from(a), y = Buffer.from(b);
|
|
30
|
+
return x.length === y.length && crypto.timingSafeEqual(x, y);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
app.post('/webhooks/flutterwave', express.json(), (req, res) => {
|
|
34
|
+
if (!sameSecret(req.header('verif-hash') ?? '', HASH)) return res.sendStatus(401);
|
|
35
|
+
res.sendStatus(200); // acknowledge first
|
|
36
|
+
if (req.body?.event === 'charge.completed' && req.body.data?.id) {
|
|
37
|
+
queue.add('flw-verify', { id: String(req.body.data.id) }); // verify + fulfil once
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Guarded fulfilment (SQL)
|
|
43
|
+
|
|
44
|
+
```sql
|
|
45
|
+
UPDATE payments
|
|
46
|
+
SET status = 'paid', flw_id = $2, paid_at = now()
|
|
47
|
+
WHERE tx_ref = $1 AND status = 'pending'
|
|
48
|
+
RETURNING order_id; -- fulfil only when a row comes back
|
|
49
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fsharp-patterns
|
|
3
|
+
description: Idiomatic F# for writing and restructuring code - discriminated unions and records that make illegal states unrepresentable, Option and Result pipelines instead of exceptions and null, task/async/result computation expressions, compilation-order-aware module layout, and safe .NET interop. Use when writing new F# code, modelling a domain in F#, converting exception-heavy or C#-style F# to a functional style, or calling .NET libraries from F#.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Needs the .NET SDK (dotnet CLI). Nullable reference type guidance needs F# 9 or later.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: coding
|
|
9
|
+
kete-loom/risk: writes-files
|
|
10
|
+
kete-loom/requires: "dotnet"
|
|
11
|
+
kete-loom/owner: "Kete-org"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# F# patterns
|
|
15
|
+
|
|
16
|
+
Write F# that the compiler can check: types carry the rules, functions are total, errors are values, and side effects live at the edges.
|
|
17
|
+
|
|
18
|
+
## 0. Before you write
|
|
19
|
+
|
|
20
|
+
1. Open the `.fsproj` and read the `<Compile Include=...>` list. F# compiles files **in that order**; a file can only use what is above it.
|
|
21
|
+
2. Check the target framework and `LangVersion`, and whether `<Nullable>enable</Nullable>` is set (F# 9+).
|
|
22
|
+
3. Check `<PackageReference>` (or `paket.references`) for FsToolkit.ErrorHandling, FSharpPlus or similar. Use what the project already has; don't add a library for a few helpers.
|
|
23
|
+
4. Match the existing style: Fantomas config (`.editorconfig`), module vs namespace usage, naming. If the project is formatted with Fantomas, run `dotnet fantomas <files>` after editing.
|
|
24
|
+
|
|
25
|
+
## 1. Model the domain with types
|
|
26
|
+
|
|
27
|
+
- **Records** for data that always has all its fields. **Discriminated unions** for "one of these". Never a record with a `Kind: string` plus optional fields that only apply to some kinds.
|
|
28
|
+
- **Single-case unions with a private constructor** for validated primitives, with a `create` that returns `Result`:
|
|
29
|
+
|
|
30
|
+
```fsharp
|
|
31
|
+
type EmailAddress = private EmailAddress of string
|
|
32
|
+
|
|
33
|
+
module EmailAddress =
|
|
34
|
+
let create (s: string) =
|
|
35
|
+
if System.String.IsNullOrWhiteSpace s || not (s.Contains '@')
|
|
36
|
+
then Error "invalid email"
|
|
37
|
+
else Ok (EmailAddress (s.Trim()))
|
|
38
|
+
let value (EmailAddress s) = s
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- Encode states as cases, not flags: `type Order = Draft of DraftOrder | Paid of PaidOrder | Shipped of ShippedOrder`, so a function that ships only accepts `PaidOrder`.
|
|
42
|
+
- Use `[<Measure>]` units for money and quantities that must not mix (`decimal<GHS>`). Use `decimal`, never `float`, for money.
|
|
43
|
+
- Use `option` for "may be absent". Use `| null` (F# 9) only at interop boundaries, never to model missing domain data.
|
|
44
|
+
- Add `[<RequireQualifiedAccess>]` to unions whose case names would clash (`Status.Active`).
|
|
45
|
+
|
|
46
|
+
## 2. Keep functions total
|
|
47
|
+
|
|
48
|
+
- No partial functions on input you don't control: avoid `List.head`, `List.find`, `Seq.head`, `Map.find`, `Array.get`/`arr.[i]`, `Option.get`, `.Value` and `int "abc"`. Use `List.tryHead`, `List.tryFind`, `Map.tryFind`, `Array.tryItem`, `Int32.TryParse`.
|
|
49
|
+
- Every `match` must be complete. Treat warning FS0025 (incomplete matches) as an error: `<WarningsAsErrors>$(WarningsAsErrors);FS0025</WarningsAsErrors>` (append, so existing entries survive).
|
|
50
|
+
- Avoid a trailing `| _ ->` on your own unions: it hides new cases. List the cases so adding one breaks the build where it matters.
|
|
51
|
+
- Prefer immutable values and `List`/`Map`/`Set`. Use `mutable` or `ref` only inside a function for a local loop or measured hot path, never shared across threads or captured in a closure that outlives it.
|
|
52
|
+
|
|
53
|
+
## 3. Errors as values
|
|
54
|
+
|
|
55
|
+
Decision rule:
|
|
56
|
+
|
|
57
|
+
| Situation | Use |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Value may be missing, no reason needed | `Option` |
|
|
60
|
+
| Expected failure the caller must handle (validation, not found, conflict) | `Result<'T, DomainError>` with a union error type |
|
|
61
|
+
| Bug or broken invariant (should never happen) | exception (`invalidOp`, `failwith`) |
|
|
62
|
+
| .NET call that throws on expected failure (I/O, parsing, HTTP) | catch at the boundary, convert to `Result` |
|
|
63
|
+
|
|
64
|
+
- Make the error type a union (`type OrderError = NotFound of OrderId | OutOfStock of Sku | PaymentDeclined of string`), not `string`, once callers branch on it.
|
|
65
|
+
- Compose with `Result.bind`, `Result.map`, `Result.mapError`, `Option.bind`, `Option.map`, `Option.defaultValue`.
|
|
66
|
+
- Convert at the edges: `Option.ofObj` / `Option.ofNullable` for .NET nulls, `Result.requireSome` (FsToolkit) or a small helper for Option to Result.
|
|
67
|
+
- Wrap throwing calls once, at the boundary, and catch the specific exception:
|
|
68
|
+
|
|
69
|
+
```fsharp
|
|
70
|
+
let readConfig path =
|
|
71
|
+
try Ok (System.IO.File.ReadAllText path)
|
|
72
|
+
with
|
|
73
|
+
| :? System.IO.FileNotFoundException -> Error (ConfigMissing path)
|
|
74
|
+
| :? System.UnauthorizedAccessException -> Error (ConfigDenied path)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Never catch `exn` and turn it into `Ok` or a default; never use `try ... with _ -> ()`.
|
|
78
|
+
|
|
79
|
+
## 4. Computation expressions
|
|
80
|
+
|
|
81
|
+
- `task { }` for code that talks to .NET (ASP.NET Core, EF Core, HttpClient). It is hot (starts immediately) and has less overhead.
|
|
82
|
+
- `async { }` for cold, composable workflows and F#-first libraries; pass `CancellationToken` via `Async.CancellationToken` or explicit parameters. Convert with `Async.AwaitTask` and `Async.StartAsTask` at the boundary only.
|
|
83
|
+
- Don't mix: pick one per layer. Never block with `.Result`, `.Wait()` or `Async.RunSynchronously` except in `main` or a script.
|
|
84
|
+
- `result { }`, `option { }`, `asyncResult { }`, `taskResult { }` and `validation { }` come from FsToolkit.ErrorHandling (not FSharp.Core; a FsToolkit.ErrorHandling.TaskResult package also exists, so check which one the project references before using `taskResult`). Use them if the project references it; otherwise use `Result.bind` pipelines, or ask before adding the package.
|
|
85
|
+
- `let!` binds, `do!` awaits unit, `return!` returns a wrapped value, `and!` runs applicatively (accumulates errors in `validation { }`).
|
|
86
|
+
- In `task { }`, `use` disposes when the task finishes; don't return a disposable resource out of the block.
|
|
87
|
+
|
|
88
|
+
```fsharp
|
|
89
|
+
// deps.FindCustomer and deps.Save return Task<Result<_, OrderError>>
|
|
90
|
+
let placeOrder (deps: Deps) (req: PlaceOrderRequest) : Task<Result<OrderId, OrderError>> =
|
|
91
|
+
taskResult {
|
|
92
|
+
let! customer = deps.FindCustomer req.CustomerId
|
|
93
|
+
let! lines = validateLines req.Lines |> Result.mapError InvalidLines
|
|
94
|
+
let order = Order.create customer lines
|
|
95
|
+
do! deps.Save order
|
|
96
|
+
return order.Id
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 5. Modules and file order
|
|
101
|
+
|
|
102
|
+
- Order files bottom-up: domain types, then pure domain logic, then infrastructure (DB, HTTP), then composition root / `Program.fs` last.
|
|
103
|
+
- One concept per file: a type and its module of functions with the same name (`type Order` + `module Order`), types first.
|
|
104
|
+
- Use `namespace` at the top of library files and modules inside; a top-level `module X` is fine for small apps and scripts.
|
|
105
|
+
- Mutually recursive types go in one `type ... and ...` group or a `namespace rec`; use `rec` sparingly, not to dodge file order.
|
|
106
|
+
- Keep side effects out of domain modules. Pass dependencies as function parameters or a small record of functions; reach for interfaces only when interop needs them.
|
|
107
|
+
- Mark helpers `private` and keep public surface small. Add `.fsi` signature files only if the project already uses them.
|
|
108
|
+
|
|
109
|
+
## 6. .NET interop
|
|
110
|
+
|
|
111
|
+
- Treat every value from a C#/.NET API as possibly null: wrap with `Option.ofObj`, `Option.ofNullable`, or `match x with null -> ... | v -> ...`. With `<Nullable>enable</Nullable>`, annotate interop types as `string | null` and let the compiler warn.
|
|
112
|
+
- `TryParse`-style out parameters return a tuple: `match Int32.TryParse s with | true, n -> Some n | _ -> None`.
|
|
113
|
+
- Expose C#-friendly APIs from library code consumed by C#: classes with members, `Task<T>` not `Async<T>`, `seq`/`IReadOnlyList` not F# `list`, no curried public members, `[<CompiledName>]` where useful.
|
|
114
|
+
- Records and unions get structural equality; .NET classes don't. Don't use classes as `Map` keys unless they implement equality.
|
|
115
|
+
- `[<CLIMutable>]` on records only when a serializer or ORM needs a parameterless constructor. For JSON with unions, use the project's serializer setup (for example FSharp.SystemTextJson) rather than hand-written converters.
|
|
116
|
+
- Implement `IDisposable` with `interface IDisposable with member _.Dispose() = ...` and bind with `use`, not `let`.
|
|
117
|
+
|
|
118
|
+
## Never
|
|
119
|
+
|
|
120
|
+
- Never use exceptions for control flow inside a `Result` pipeline.
|
|
121
|
+
- Never `ignore` a `Result`, `Task` or `Async` (`|> ignore` on a `Task<Result<_,_>>` drops the error).
|
|
122
|
+
- Never add `| _ ->` just to silence FS0025.
|
|
123
|
+
- Never reorder `.fsproj` files without checking every file that depended on the old order still compiles.
|
|
124
|
+
|
|
125
|
+
## Stop and ask the user
|
|
126
|
+
|
|
127
|
+
- Before adding a NuGet package (FsToolkit.ErrorHandling, FSharpPlus, a JSON library).
|
|
128
|
+
- Before changing a public signature that C# or other assemblies consume.
|
|
129
|
+
- When the domain rule is unclear (which states are valid, which errors callers need to distinguish).
|
|
130
|
+
|
|
131
|
+
## Report back
|
|
132
|
+
|
|
133
|
+
List the files changed and where they sit in the compile order, any new types or error cases, and the build or test command you ran with its result.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Model our order lifecycle in F# so you can't ship an order that hasn't been paid",
|
|
4
|
+
"This F# module throws exceptions everywhere, rewrite it to return Result instead",
|
|
5
|
+
"Write an F# function that loads a customer from EF Core inside a task computation expression and returns Result",
|
|
6
|
+
"How should I organise the modules and file order in my new F# web API project?",
|
|
7
|
+
"Wrap this C# library that returns nulls so my F# code only sees Option values"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Write Expecto property tests with FsCheck for my F# parser",
|
|
11
|
+
"Review this F# pull request for incomplete pattern matches and mutable state",
|
|
12
|
+
"My F# project fails to build with FS0039 after I added a new file, fix it",
|
|
13
|
+
"Refactor this C# service to use records and pattern matching",
|
|
14
|
+
"Set up an EF Core migration for the new Orders table in our C# API"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fsharp-testing
|
|
3
|
+
description: Writes and fixes F# tests with Expecto or xUnit, FsCheck property-based tests for invariants, Unquote quotation assertions, and correct testing of task and async code without blocking. Use when adding tests to an F# project, turning example-based F# tests into properties, choosing between Expecto and xUnit for F#, or debugging flaky or hanging F# async tests.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Needs the .NET SDK (dotnet CLI) and the project's test packages.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: testing
|
|
9
|
+
kete-loom/risk: runs-commands
|
|
10
|
+
kete-loom/requires: "dotnet"
|
|
11
|
+
kete-loom/owner: "Kete-org"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# F# testing
|
|
15
|
+
|
|
16
|
+
Test behaviour through public functions, prefer properties for invariants, and keep async tests asynchronous.
|
|
17
|
+
|
|
18
|
+
## 0. Find the setup first
|
|
19
|
+
|
|
20
|
+
1. Find the test project (`*.Tests.fsproj`) and read its `<PackageReference>`s (or `paket.references`): Expecto, xUnit (`xunit` or `xunit.v3`), NUnit, FsCheck, `FsCheck.Xunit` / `FsCheck.Xunit.v3`, `Expecto.FsCheck`, Unquote.
|
|
21
|
+
2. **Use the framework already there.** Don't add Expecto to an xUnit project or the reverse. If there is no test project, ask the user which one; default to Expecto for F#-only code and xUnit when the solution already uses it for C#.
|
|
22
|
+
3. Run the suite once: `dotnet test` (or `dotnet run --project tests/X.Tests` for Expecto projects without the test adapter). If it already fails, **stop and tell the user** before changing anything.
|
|
23
|
+
4. Remember file order: a new test file must be added to `<Compile Include=...>` in the test `.fsproj`, after the helpers it uses and before `Main.fs` / `Program.fs`.
|
|
24
|
+
|
|
25
|
+
## 1. Expecto
|
|
26
|
+
|
|
27
|
+
```fsharp
|
|
28
|
+
module OrderTests
|
|
29
|
+
|
|
30
|
+
open Expecto
|
|
31
|
+
|
|
32
|
+
[<Tests>]
|
|
33
|
+
let tests =
|
|
34
|
+
testList "Order" [
|
|
35
|
+
testCase "total sums line amounts" <| fun () ->
|
|
36
|
+
let order = Order.create [ line 2 10m; line 1 5m ]
|
|
37
|
+
Expect.equal (Order.total order) 25m "total"
|
|
38
|
+
|
|
39
|
+
testCase "empty order is rejected" <| fun () ->
|
|
40
|
+
Expect.isError (Order.tryCreate []) "empty order"
|
|
41
|
+
|
|
42
|
+
testTask "saves and reloads" {
|
|
43
|
+
let! saved = repo.Save order
|
|
44
|
+
let! loaded = repo.Load saved.Id
|
|
45
|
+
Expect.equal loaded (Some saved) "round trip"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
testCaseAsync "async workflow" <| async {
|
|
49
|
+
let! r = Workflow.run input
|
|
50
|
+
Expect.isOk r "workflow"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `Expect.*` arguments are **actual, expected, message**. Swapping them gives misleading failure output.
|
|
56
|
+
- Entry point: `[<EntryPoint>] let main argv = runTestsInAssemblyWithCLIArgs [] argv`, with `[<Tests>]` on each test value.
|
|
57
|
+
- For `dotnet test` and IDE discovery, reference `YoloDev.Expecto.TestSdk` and `Microsoft.NET.Test.Sdk`.
|
|
58
|
+
- Never commit `ftestCase` / `ftestList` (focused) or `ptestCase` (pending) by accident; focused tests silently skip the rest of the suite.
|
|
59
|
+
- Use `testSequenced` for tests that share a resource (a database, a port); Expecto runs tests in parallel by default.
|
|
60
|
+
|
|
61
|
+
## 2. xUnit
|
|
62
|
+
|
|
63
|
+
```fsharp
|
|
64
|
+
module OrderTests
|
|
65
|
+
|
|
66
|
+
open Xunit
|
|
67
|
+
open Swensen.Unquote
|
|
68
|
+
|
|
69
|
+
[<Fact>]
|
|
70
|
+
let ``total sums line amounts`` () =
|
|
71
|
+
let order = Order.create [ line 2 10m; line 1 5m ]
|
|
72
|
+
test <@ Order.total order = 25m @>
|
|
73
|
+
|
|
74
|
+
[<Theory>]
|
|
75
|
+
[<InlineData("", false)>]
|
|
76
|
+
[<InlineData("a@b.co", true)>]
|
|
77
|
+
let ``email validation`` (input: string, ok: bool) =
|
|
78
|
+
test <@ Result.isOk (EmailAddress.create input) = ok @>
|
|
79
|
+
|
|
80
|
+
[<Fact>]
|
|
81
|
+
let ``saves and reloads`` () = task {
|
|
82
|
+
let! saved = repo.Save order
|
|
83
|
+
let! loaded = repo.Load saved.Id
|
|
84
|
+
loaded =! Some saved
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- Use backtick names that read as sentences. Put tests in a module, not a class, unless you need fixture constructors or `IDisposable` cleanup.
|
|
89
|
+
- Async tests return `Task` via `task { }`. Never call `.Result`, `.Wait()` or `Async.RunSynchronously` in a test; it can deadlock and hides the real exception.
|
|
90
|
+
- Use `Assert.ThrowsAsync<T>` (or Unquote `raises`) for expected exceptions, not `try ... with`.
|
|
91
|
+
|
|
92
|
+
## 3. Unquote
|
|
93
|
+
|
|
94
|
+
- `test <@ expr @>` fails with a step-by-step reduction of the expression, which beats `Assert.True`.
|
|
95
|
+
- Operators: `actual =! expected`, `actual <>! other`, `x >! y`, `raises<ArgumentException> <@ f bad @>`.
|
|
96
|
+
- Works inside Expecto and xUnit. Keep each quotation to one claim; split compound `&&` checks into separate tests.
|
|
97
|
+
|
|
98
|
+
## 4. FsCheck property tests
|
|
99
|
+
|
|
100
|
+
Write a property when a rule holds for all inputs: round trips (`parse (format x) = x`), invariants (total is never negative), equivalence to a simple model, idempotence (`normalise (normalise x) = normalise x`).
|
|
101
|
+
|
|
102
|
+
```fsharp
|
|
103
|
+
// xUnit
|
|
104
|
+
[<Property>]
|
|
105
|
+
let ``reverse twice is identity`` (xs: int list) =
|
|
106
|
+
List.rev (List.rev xs) = xs
|
|
107
|
+
|
|
108
|
+
// Expecto (Expecto.FsCheck)
|
|
109
|
+
testProperty "format then parse round trips" <| fun (m: Money) ->
|
|
110
|
+
Money.parse (Money.format m) = Ok m
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Custom data: build a `Gen` and turn it into an `Arbitrary` with `Arb.fromGen`. Use `Gen.choose (lo, hi)`, `Gen.elements`, `Gen.map`, `Gen.filter` sparingly (heavy filters make FsCheck give up).
|
|
114
|
+
- Register generators as a type with static members returning `Arbitrary<'T>`, then `[<Property(Arbitrary = [| typeof<Generators> |])>]` in xUnit, or the `arbitrary` field of `FsCheckConfig` in Expecto.
|
|
115
|
+
- For validated types (private single-case unions), generate raw values that pass `create`, so properties exercise the real constructor.
|
|
116
|
+
- Use `==>` for preconditions only when they rarely fail; otherwise write a generator.
|
|
117
|
+
- When a property fails, FsCheck prints the shrunk counterexample and a seed. Copy the counterexample into a regular example test so it stays covered, then fix the code.
|
|
118
|
+
- FsCheck 2 and 3 differ (for example, 3 uses `ArbMap.defaults` where 2 used `Arb.Default`). Check the referenced version before copying examples.
|
|
119
|
+
|
|
120
|
+
## 5. Testing task and async code
|
|
121
|
+
|
|
122
|
+
- Test the async function itself; don't wrap it in a synchronous helper.
|
|
123
|
+
- Pass a `CancellationToken` in and assert cancellation behaviour with a pre-cancelled token.
|
|
124
|
+
- Replace real time and I/O with function parameters (`now: unit -> DateTimeOffset`, `send: Email -> Task<Result<unit, SendError>>`). Pass fakes as plain lambdas; you rarely need a mocking library in F#.
|
|
125
|
+
- For `Result`-returning workflows, assert on the exact error case (`Error (OutOfStock sku)`), not just `isError`.
|
|
126
|
+
- No real network or wall-clock sleeps. If a test needs a delay, the design needs an injected clock.
|
|
127
|
+
|
|
128
|
+
## Never
|
|
129
|
+
|
|
130
|
+
- Never delete, skip or weaken an existing test to make the suite pass without the user's agreement.
|
|
131
|
+
- Never leave focused (`ftest*`) or `Skip =` tests in a commit.
|
|
132
|
+
- Never assert on `ToString()` or `sprintf "%A"` output of records and unions; compare values, they have structural equality.
|
|
133
|
+
|
|
134
|
+
## Report back
|
|
135
|
+
|
|
136
|
+
List tests added (names and file), the command you ran (`dotnet test --filter ...` or the Expecto run), final pass/fail counts, any FsCheck seed that found a bug, and what is still untested.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Add Expecto tests for the F# invoice module, including the task-based repository calls",
|
|
4
|
+
"Turn these F# example tests into FsCheck properties so we cover more inputs",
|
|
5
|
+
"My xUnit test in F# hangs when I call Async.RunSynchronously, how should I test this async workflow?",
|
|
6
|
+
"Write Unquote assertions for the F# parser instead of Assert.Equal",
|
|
7
|
+
"Should our new F# library use Expecto or xUnit for its test project?"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Model the payment states in F# with discriminated unions",
|
|
11
|
+
"Review my F# changes for partial functions and incomplete matches",
|
|
12
|
+
"The F# test project won't compile because of a value restriction error",
|
|
13
|
+
"Write xUnit tests with Moq for my C# OrderService",
|
|
14
|
+
"Raise test coverage across the repo to 80 percent"
|
|
15
|
+
]
|
|
16
|
+
}
|